diff --git a/AUTHORS.md b/AUTHORS.md index 5320a24625..0740818763 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -148,6 +148,7 @@ Programmers Scott Howard scrawl Sebastian Wick (swick) + Sergey Fukanchik Sergey Shambir ShadowRadiance Siimacore diff --git a/CHANGELOG.md b/CHANGELOG.md index c11d92900b..b707851deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.45.0 ------ + Bug #1875: Actors in inactive cells don't heal from resting Bug #1990: Sunrise/sunset not set correct Bug #2131: Lustidrike's spell misses the player every time Bug #2222: Fatigue's effect on selling price is backwards @@ -19,13 +20,16 @@ Bug #3049: Drain and Fortify effects are not properly applied on health, magicka and fatigue Bug #3059: Unable to hit with marksman weapons when too close to an enemy Bug #3072: Fatal error on AddItem that has a script containing Equip + Bug #3219: NPC and creature initial position tracing down limit is too small Bug #3249: Fixed revert function not updating views properly + Bug #3288: TrueType fonts are handled incorrectly Bug #3374: Touch spells not hitting kwama foragers Bug #3486: [Mod] NPC Commands does not work Bug #3533: GetSpellEffects should detect effects with zero duration Bug #3591: Angled hit distance too low Bug #3629: DB assassin attack never triggers creature spawning Bug #3681: OpenMW-CS: Clicking Scripts in Preferences spawns many color pickers + Bug #3762: AddSoulGem and RemoveSpell redundant count arguments break script execution Bug #3788: GetPCInJail and GetPCTraveling do not work as in vanilla Bug #3836: Script fails to compile when command argument contains "\n" Bug #3876: Landscape texture painting is misaligned @@ -45,6 +49,7 @@ Bug #4230: AiTravel package issues break some Tribunal quests Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality Bug #4251: Stationary NPCs do not return to their position after combat + Bug #4260: Keyboard navigation makes persuasion exploitable Bug #4271: Scamp flickers when attacking Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+ Bug #4286: Scripted animations can be interrupted @@ -54,6 +59,7 @@ Bug #4307: World cleanup should remove dead bodies only if death animation is finished Bug #4311: OpenMW does not handle RootCollisionNode correctly Bug #4327: Missing animations during spell/weapon stance switching + Bug #4333: Keyboard navigation in containers is not intuitive Bug #4358: Running animation is interrupted when magic mode is toggled Bug #4368: Settings window ok button doesn't have key focus by default Bug #4378: On-self absorb spells restore stats @@ -75,6 +81,7 @@ Bug #4460: Script function "Equip" doesn't bypass beast restrictions Bug #4461: "Open" spell from non-player caster isn't a crime Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages + Bug #4467: Content selector: cyrillic characters are decoded incorrectly in plugin descriptions Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal Bug #4470: Non-bipedal creatures with Weapon & Shield flag have inconsistent behaviour Bug #4474: No fallback when getVampireHead fails @@ -90,8 +97,11 @@ Bug #4503: Cast and ExplodeSpell commands increase alteration skill Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute Bug #4519: Knockdown does not discard movement in the 1st-person mode + Bug #4527: Sun renders on water shader in some situations where it shouldn't Bug #4531: Movement does not reset idle animations + Bug #4532: Underwater sfx isn't tied to 3rd person camera Bug #4539: Paper Doll is affected by GUI scaling + Bug #4543: Picking cursed items through inventory (menumode) makes it disappear Bug #4545: Creatures flee from werewolves Bug #4551: Replace 0 sound range with default range separately Bug #4553: Forcegreeting on non-actor opens a dialogue window which cannot be closed @@ -105,18 +115,41 @@ Bug #4575: Weird result of attack animation blending with movement animations Bug #4576: Reset of idle animations when attack can not be started Bug #4591: Attack strength should be 0 if player did not hold the attack button + Bug #4593: Editor: Instance dragging is broken Bug #4597: <> operator causes a compile error Bug #4604: Picking up gold from the ground only makes 1 grabbed Bug #4607: Scaling for animated collision shapes is applied twice Bug #4608: Falling damage is applied twice + Bug #4611: Instant magic effects have 0 duration in custom spell cost calculations unlike vanilla Bug #4614: Crash due to division by zero when FlipController has no textures Bug #4615: Flicker effects for light sources are handled incorrectly Bug #4617: First person sneaking offset is not applied while the character is in air Bug #4618: Sneaking is possible while the character is flying Bug #4622: Recharging enchanted items with Soul Gems does not award experience if it fails Bug #4628: NPC record reputation, disposition and faction rank should have unsigned char type + Bug #4633: Sneaking stance affects speed even if the actor is not able to crouch + Bug #4641: GetPCJumping is handled incorrectly + Bug #4644: %Name should be available for all actors, not just for NPCs + Bug #4646: Weapon force-equipment messes up ongoing attack animations + Bug #4648: Hud thinks that throwing weapons have condition + Bug #4649: Levelup fully restores health + Bug #4653: Length of non-ASCII strings is handled incorrectly in ESM reader + Bug #4654: Editor: UpdateVisitor does not initialize skeletons for animated objects + Bug #4656: Combat AI: back up behaviour is incorrect + Bug #4668: Editor: Light source color is displayed as an integer + Bug #4669: ToggleCollision should trace the player down after collision being enabled + Bug #4671: knownEffect functions should use modified Alchemy skill + Bug #4672: Pitch factor is handled incorrectly for crossbow animations + Bug #4674: Journal can be opened when settings window is open + Bug #4677: Crash in ESM reader when NPC record has DNAM record without DODT one + Bug #4678: Crash in ESP parser when SCVR has no variable names + Bug #4685: Missing sound causes an exception inside Say command + Feature #912: Editor: Add missing icons to UniversalId tables + Feature #1221: Editor: Creature/NPC rendering + Feature #1617: Editor: Enchantment effect record verifier Feature #1645: Casting effects from objects Feature #2606: Editor: Implemented (optional) case sensitive global search + Feature #2847: Content selector: allow to copy the path to a file by using the context menu Feature #3083: Play animation when NPC is casting spell via script Feature #3103: Provide option for disposition to get increased by successful trade Feature #3276: Editor: Search - Show number of (remaining) search results and indicate a search without any results @@ -125,6 +158,7 @@ Feature #4012: Editor: Write a log file if OpenCS crashes Feature #4222: 360° screenshots Feature #4256: Implement ToggleBorders (TB) console command + Feature #4285: Support soundgen calls for activators Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4345: Add equivalents for the command line commands to Launcher Feature #4404: Editor: All EnumDelegate fields should have their items sorted alphabetically @@ -142,12 +176,18 @@ Feature #4625: Weapon priority: use weighted mean for melee damage rating Feature #4626: Weapon priority: account for weapon speed Feature #4632: AI priority: utilize vanilla AI GMSTs for priority rating + Feature #4636: Use sTo GMST in spellmaking menu + Feature #4642: Batching potion creation + Feature #4682: Use the collision box from basic creature mesh if the X one have no collisions Task #2490: Don't open command prompt window on Release-mode builds automatically Task #4545: Enable is_pod string test Task #4605: Optimize skinning Task #4606: Support Rapture3D's OpenAL driver Task #4613: Incomplete type errors when compiling with g++ on OSX 10.9 Task #4621: Optimize combat AI + Task #4643: Revise editor record verifying functionality + Task #4645: Use constants instead of widely used magic numbers + Task #4652: Move call to enemiesNearby() from InputManager::rest() to World::canRest() 0.44.0 ------ diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 2c4fe6312e..8ee4b7fd99 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -346,7 +346,7 @@ if [ -z $SKIP_DOWNLOAD ]; then # OpenAL download "OpenAL-Soft 1.17.2" \ - "http://kcat.strangesoft.net/openal-binaries/openal-soft-1.17.2-bin.zip" \ + "http://openal-soft.org/openal-binaries/openal-soft-1.17.2-bin.zip" \ "OpenAL-Soft-1.17.2.zip" # OSG diff --git a/CI/build_googletest.sh b/CI/build_googletest.sh index cd61379b59..ee89ebda6e 100755 --- a/CI/build_googletest.sh +++ b/CI/build_googletest.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -git clone https://github.com/google/googletest.git +git clone -b release-1.8.1 https://github.com/google/googletest.git cd googletest mkdir build cd build diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 2473daf955..900fbb05cd 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include "convertcrec.hpp" #include "convertcntc.hpp" #include "convertscri.hpp" @@ -288,12 +290,12 @@ namespace ESSImport notepos[1] += 31.f; notepos[0] += 0.5; notepos[1] += 0.5; - notepos[0] = 8192 * notepos[0] / 32.f; - notepos[1] = 8192 * notepos[1] / 32.f; + notepos[0] = Constants::CellSizeInUnits * notepos[0] / 32.f; + notepos[1] = Constants::CellSizeInUnits * notepos[1] / 32.f; if (cell.isExterior()) { - notepos[0] += 8192 * cell.mData.mX; - notepos[1] += 8192 * cell.mData.mY; + notepos[0] += Constants::CellSizeInUnits * cell.mData.mX; + notepos[1] += Constants::CellSizeInUnits * cell.mData.mY; } // TODO: what encoding is this in? std::string note = esm.getHNString("MPNT"); diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 1772e0e2d0..2694ea5707 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -526,7 +526,10 @@ public: class ConvertGAME : public Converter { public: - ConvertGAME() : mHasGame(false) {} + ConvertGAME() + : mHasGame(false) + { + } virtual void read(ESM::ESMReader &esm) { diff --git a/apps/essimporter/convertplayer.cpp b/apps/essimporter/convertplayer.cpp index 4a4a9a573e..5e2da2b034 100644 --- a/apps/essimporter/convertplayer.cpp +++ b/apps/essimporter/convertplayer.cpp @@ -1,5 +1,6 @@ #include "convertplayer.hpp" +#include #include namespace ESSImport @@ -78,9 +79,8 @@ namespace ESSImport if (pcdt.mHasENAM) { - const int cellSize = 8192; - out.mLastKnownExteriorPosition[0] = (pcdt.mENAM.mCellX + 0.5f) * cellSize; - out.mLastKnownExteriorPosition[1] = (pcdt.mENAM.mCellY + 0.5f) * cellSize; + out.mLastKnownExteriorPosition[0] = (pcdt.mENAM.mCellX + 0.5f) * Constants::CellSizeInUnits; + out.mLastKnownExteriorPosition[1] = (pcdt.mENAM.mCellY + 0.5f) * Constants::CellSizeInUnits; out.mLastKnownExteriorPosition[2] = 0.0f; } } diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 4538d4e63c..a54c133340 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -25,6 +25,8 @@ #include #include +#include + #include #include "importercontext.hpp" @@ -413,9 +415,8 @@ namespace ESSImport if (context.mPlayer.mCellId.mPaged) { // exterior cell -> determine cell coordinates based on position - const int cellSize = 8192; - int cellX = static_cast(std::floor(context.mPlayer.mObject.mPosition.pos[0]/cellSize)); - int cellY = static_cast(std::floor(context.mPlayer.mObject.mPosition.pos[1] / cellSize)); + 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; } diff --git a/apps/essimporter/importgame.hpp b/apps/essimporter/importgame.hpp index fca7d72a0a..d8051a527a 100644 --- a/apps/essimporter/importgame.hpp +++ b/apps/essimporter/importgame.hpp @@ -14,13 +14,13 @@ namespace ESSImport { struct GMDT { - char mCellName[64]; - int mFogColour; - float mFogDensity; - int mCurrentWeather, mNextWeather; - int mWeatherTransition; // 0-100 transition between weathers, top 3 bytes may be garbage - float mTimeOfNextTransition; // weather changes when gamehour == timeOfNextTransition - int mMasserPhase, mSecundaPhase; // 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/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 102463085b..41f546af46 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -72,6 +72,7 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); + loadSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game"); loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); loadSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game"); loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); @@ -131,6 +132,7 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); + saveSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game"); saveSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game"); saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 7b703a9242..6f2389de3f 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -36,6 +36,8 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: ui.setupUi (this); setObjectName ("DataFilesPage"); mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); + const QString encoding = mGameSettings.value("encoding", "win1252"); + mSelector->setEncoding(encoding); mProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this); @@ -357,4 +359,4 @@ void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles) QStringList cellNamesList = QStringList::fromSet(cellNameLoader.getCellNames(selectedFiles)); std::sort(cellNamesList.begin(), cellNamesList.end()); emit signalLoadedCellsChanged(cellNamesList); -} \ No newline at end of file +} diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d1ebcde428..b0bd95eb95 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -19,6 +19,7 @@ opencs_hdrs_noqt (model/doc opencs_units (model/world idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel landtexturetableproxymodel + actoradapter ) @@ -42,7 +43,7 @@ opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck - mergestages gmstcheck topicinfocheck journalcheck + mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck ) opencs_hdrs_noqt (model/tools @@ -88,7 +89,7 @@ opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget previewwidget editmode instancemode instanceselectionmode instancemovemode orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller - cellwater terraintexturemode + cellwater terraintexturemode actor ) opencs_units_noqt (view/render diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index e733d9924b..450b434e66 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -22,7 +22,7 @@ CS::Editor::Editor (int argc, char **argv) : mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), mPid(""), mLock(), mMerge (mDocumentManager), - mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) + mIpcServerName ("org.openmw.OpenCS"), mServer(nullptr), mClientSocket(nullptr) { std::pair > config = readConfig(); @@ -108,8 +108,9 @@ std::pair > CS::Editor::readConfi mCfgMgr.readConfiguration(variables, desc, quiet); - mDocumentManager.setEncoding ( - ToUTF8::calculateEncoding (variables["encoding"].as().toStdString())); + const std::string encoding = variables["encoding"].as().toStdString(); + mDocumentManager.setEncoding (ToUTF8::calculateEncoding (encoding)); + mFileDialog.setEncoding (QString::fromUtf8(encoding.c_str())); mDocumentManager.setResourceDir (mResources = variables["resources"].as().toStdString()); @@ -338,7 +339,7 @@ bool CS::Editor::makeIPCServer() } mServer->close(); - mServer = NULL; + mServer = nullptr; return false; } diff --git a/apps/opencs/model/doc/messages.cpp b/apps/opencs/model/doc/messages.cpp index 76bbb6f227..b70d44eda3 100644 --- a/apps/opencs/model/doc/messages.cpp +++ b/apps/opencs/model/doc/messages.cpp @@ -35,11 +35,6 @@ void CSMDoc::Messages::add (const CSMWorld::UniversalId& id, const std::string& mMessages.push_back (Message (id, message, hint, severity)); } -void CSMDoc::Messages::push_back (const std::pair& data) -{ - add (data.first, data.second); -} - CSMDoc::Messages::Iterator CSMDoc::Messages::begin() const { return mMessages.begin(); diff --git a/apps/opencs/model/doc/messages.hpp b/apps/opencs/model/doc/messages.hpp index 4041e1a678..671ded82a0 100644 --- a/apps/opencs/model/doc/messages.hpp +++ b/apps/opencs/model/doc/messages.hpp @@ -56,9 +56,6 @@ namespace CSMDoc const std::string& hint = "", Message::Severity severity = Message::Severity_Default); - /// \deprecated Use add instead. - void push_back (const std::pair& data); - Iterator begin() const; Iterator end() const; diff --git a/apps/opencs/model/doc/operationholder.cpp b/apps/opencs/model/doc/operationholder.cpp index ccbed6c8ba..0fd2bef957 100644 --- a/apps/opencs/model/doc/operationholder.cpp +++ b/apps/opencs/model/doc/operationholder.cpp @@ -3,7 +3,7 @@ #include "operation.hpp" CSMDoc::OperationHolder::OperationHolder (Operation *operation) - : mOperation(NULL) + : mOperation(nullptr) , mRunning (false) { if (operation) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index a704fb825d..b6a55ea862 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -305,6 +305,7 @@ void CSMPrefs::State::declare() 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"); diff --git a/apps/opencs/model/tools/birthsigncheck.cpp b/apps/opencs/model/tools/birthsigncheck.cpp index fc29893076..f91fc22f60 100644 --- a/apps/opencs/model/tools/birthsigncheck.cpp +++ b/apps/opencs/model/tools/birthsigncheck.cpp @@ -1,16 +1,15 @@ #include "birthsigncheck.hpp" -#include -#include - -#include +#include #include "../prefs/state.hpp" #include "../world/universalid.hpp" -CSMTools::BirthsignCheckStage::BirthsignCheckStage (const CSMWorld::IdCollection& birthsigns) -: mBirthsigns (birthsigns) +CSMTools::BirthsignCheckStage::BirthsignCheckStage (const CSMWorld::IdCollection& birthsigns, + const CSMWorld::Resources &textures) +: mBirthsigns(birthsigns), + mTextures(textures) { mIgnoreBaseRecords = false; } @@ -34,17 +33,20 @@ void CSMTools::BirthsignCheckStage::perform (int stage, CSMDoc::Messages& messag CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Birthsign, birthsign.mId); - // test for empty name, description and texture if (birthsign.mName.empty()) - messages.push_back (std::make_pair (id, birthsign.mId + " has an empty name")); + messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); if (birthsign.mDescription.empty()) - messages.push_back (std::make_pair (id, birthsign.mId + " has an empty description")); + messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); if (birthsign.mTexture.empty()) - messages.push_back (std::make_pair (id, birthsign.mId + " is missing a texture")); - - /// \todo test if the texture exists + messages.add(id, "Image is missing", "", CSMDoc::Message::Severity_Error); + else if (mTextures.searchId(birthsign.mTexture) == -1) + { + 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); + } /// \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 a8a7a2c14a..9001c524cd 100644 --- a/apps/opencs/model/tools/birthsigncheck.hpp +++ b/apps/opencs/model/tools/birthsigncheck.hpp @@ -4,6 +4,7 @@ #include #include "../world/idcollection.hpp" +#include "../world/resources.hpp" #include "../doc/stage.hpp" @@ -12,12 +13,14 @@ namespace CSMTools /// \brief VerifyStage: make sure that birthsign records are internally consistent class BirthsignCheckStage : public CSMDoc::Stage { - const CSMWorld::IdCollection& mBirthsigns; + const CSMWorld::IdCollection &mBirthsigns; + const CSMWorld::Resources &mTextures; bool mIgnoreBaseRecords; public: - BirthsignCheckStage (const CSMWorld::IdCollection& birthsigns); + BirthsignCheckStage (const CSMWorld::IdCollection &birthsigns, + const CSMWorld::Resources &textures); virtual int setup(); ///< \return number of steps diff --git a/apps/opencs/model/tools/bodypartcheck.cpp b/apps/opencs/model/tools/bodypartcheck.cpp index b5bd78f6c7..16dd9891e9 100644 --- a/apps/opencs/model/tools/bodypartcheck.cpp +++ b/apps/opencs/model/tools/bodypartcheck.cpp @@ -34,25 +34,23 @@ void CSMTools::BodyPartCheckStage::perform (int stage, CSMDoc::Messages &message // Check BYDT if (bodyPart.mData.mPart > 14 ) - messages.push_back(std::make_pair( id, bodyPart.mId + " has out of range part value." )); + messages.add(id, "Invalid part", "", CSMDoc::Message::Severity_Error); if (bodyPart.mData.mFlags > 3 ) - messages.push_back(std::make_pair( id, bodyPart.mId + " has out of range flags value." )); + messages.add(id, "Invalid flags", "", CSMDoc::Message::Severity_Error); if (bodyPart.mData.mType > 2 ) - messages.push_back(std::make_pair( id, bodyPart.mId + " has out of range type value." )); + messages.add(id, "Invalid type", "", CSMDoc::Message::Severity_Error); // Check MODL - if ( bodyPart.mModel.empty() ) - messages.push_back(std::make_pair( id, bodyPart.mId + " has no model." )); + messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); else if ( mMeshes.searchId( bodyPart.mModel ) == -1 ) - messages.push_back(std::make_pair( id, bodyPart.mId + " has invalid model." )); + messages.add(id, "Model '" + bodyPart.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); // Check FNAM - if ( bodyPart.mRace.empty() ) - messages.push_back(std::make_pair( id, bodyPart.mId + " has no race." )); + messages.add(id, "Race is missing", "", CSMDoc::Message::Severity_Error); else if ( mRaces.searchId( bodyPart.mRace ) == -1 ) - messages.push_back(std::make_pair( id, bodyPart.mId + " has invalid race." )); + messages.add(id, "Race '" + bodyPart.mRace + " does not exist", "", CSMDoc::Message::Severity_Error); } diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp index 89923a3984..a82121597c 100644 --- a/apps/opencs/model/tools/classcheck.cpp +++ b/apps/opencs/model/tools/classcheck.cpp @@ -1,6 +1,5 @@ #include "classcheck.hpp" -#include #include #include @@ -37,26 +36,22 @@ void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages) // A class should have a name if (class_.mName.empty()) - messages.push_back (std::make_pair (id, class_.mId + " doesn't have a name")); + messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); // A playable class should have a description if (class_.mData.mIsPlayable != 0 && class_.mDescription.empty()) - messages.push_back (std::make_pair (id, class_.mId + " doesn't have a description and it's playable")); + 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) { - std::ostringstream stream; - - stream << "Attribute #" << i << " of " << class_.mId << " is not set"; - - messages.push_back (std::make_pair (id, stream.str())); + 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) { - messages.push_back (std::make_pair (id, "Class lists same attribute twice")); + messages.add(id, "Same attribute is listed twice", "", CSMDoc::Message::Severity_Error); } // test for non-unique skill @@ -66,10 +61,9 @@ void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages) for (int i2=0; i2<2; ++i2) ++skills[class_.mData.mSkills[i][i2]]; - for (std::map::const_iterator iter (skills.begin()); iter!=skills.end(); ++iter) - if (iter->second>1) + for (auto &skill : skills) + if (skill.second>1) { - messages.push_back (std::make_pair (id, - ESM::Skill::indexToId (iter->first) + " is listed more than once")); + messages.add(id, "Skill " + ESM::Skill::indexToId (skill.first) + " is listed more than once", "", CSMDoc::Message::Severity_Error); } } diff --git a/apps/opencs/model/tools/enchantmentcheck.cpp b/apps/opencs/model/tools/enchantmentcheck.cpp new file mode 100644 index 0000000000..28f2b32cb9 --- /dev/null +++ b/apps/opencs/model/tools/enchantmentcheck.cpp @@ -0,0 +1,82 @@ +#include "enchantmentcheck.hpp" + +#include "../prefs/state.hpp" + +#include "../world/universalid.hpp" + +CSMTools::EnchantmentCheckStage::EnchantmentCheckStage (const CSMWorld::IdCollection& enchantments) + : mEnchantments (enchantments) +{ + mIgnoreBaseRecords = false; +} + +int CSMTools::EnchantmentCheckStage::setup() +{ + mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue(); + + return mEnchantments.getSize(); +} + +void CSMTools::EnchantmentCheckStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = mEnchantments.getRecord (stage); + + // Skip "Base" records (setting!) and "Deleted" records + if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) + return; + + const ESM::Enchantment& enchantment = record.get(); + + 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); + + if (enchantment.mData.mCost < 0) + messages.add(id, "Cost is negative", "", CSMDoc::Message::Severity_Error); + + if (enchantment.mData.mCharge < 0) + messages.add(id, "Charge is negative", "", CSMDoc::Message::Severity_Error); + + if (enchantment.mData.mCost > enchantment.mData.mCharge) + messages.add(id, "Cost is higher than charge", "", CSMDoc::Message::Severity_Error); + + if (enchantment.mEffects.mList.empty()) + { + messages.add(id, "Enchantment doesn't have any magic effects", "", CSMDoc::Message::Severity_Warning); + } + else + { + std::vector::const_iterator effect = enchantment.mEffects.mList.begin(); + + for (size_t i = 1; i <= enchantment.mEffects.mList.size(); i++) + { + const std::string number = std::to_string(i); + // At the time of writing this effects, attributes and skills are hardcoded + if (effect->mEffectID < 0 || effect->mEffectID > 142) + { + messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error); + ++effect; + continue; + } + + if (effect->mSkill < -1 || effect->mSkill > 26) + 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); + if (effect->mRange < 0 || effect->mRange > 2) + messages.add(id, "Effect #" + number + " range is invalid", "", CSMDoc::Message::Severity_Error); + if (effect->mArea < 0) + messages.add(id, "Effect #" + number + " area is negative", "", CSMDoc::Message::Severity_Error); + 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); + if (effect->mMagnMax < 0) + 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); + ++effect; + } + } +} diff --git a/apps/opencs/model/tools/enchantmentcheck.hpp b/apps/opencs/model/tools/enchantmentcheck.hpp new file mode 100644 index 0000000000..3bd85326fb --- /dev/null +++ b/apps/opencs/model/tools/enchantmentcheck.hpp @@ -0,0 +1,31 @@ +#ifndef CSM_TOOLS_ENCHANTMENTCHECK_H +#define CSM_TOOLS_ENCHANTMENTCHECK_H + +#include + +#include "../world/idcollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief Make sure that enchantment records are correct + class EnchantmentCheckStage : public CSMDoc::Stage + { + const CSMWorld::IdCollection& mEnchantments; + bool mIgnoreBaseRecords; + + public: + + EnchantmentCheckStage (const CSMWorld::IdCollection& enchantments); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this tage will be appended to \a messages. + + }; +} + +#endif diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index 39073db5f0..8a198e9535 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -1,9 +1,7 @@ #include "factioncheck.hpp" -#include #include -#include #include #include "../prefs/state.hpp" @@ -37,12 +35,12 @@ void CSMTools::FactionCheckStage::perform (int stage, CSMDoc::Messages& messages // test for empty name if (faction.mName.empty()) - messages.push_back (std::make_pair (id, faction.mId + " has an empty name")); + 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) { - messages.push_back (std::make_pair (id , "Faction lists same attribute twice")); + messages.add(id, "Same attribute is listed twice", "", CSMDoc::Message::Severity_Error); } // test for non-unique skill @@ -52,11 +50,10 @@ void CSMTools::FactionCheckStage::perform (int stage, CSMDoc::Messages& messages if (faction.mData.mSkills[i]!=-1) ++skills[faction.mData.mSkills[i]]; - for (std::map::const_iterator iter (skills.begin()); iter!=skills.end(); ++iter) - if (iter->second>1) + for (auto &skill : skills) + if (skill.second>1) { - messages.push_back (std::make_pair (id, - ESM::Skill::indexToId (iter->first) + " is listed more than once")); + messages.add(id, "Skill " + ESM::Skill::indexToId (skill.first) + " 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/journalcheck.cpp b/apps/opencs/model/tools/journalcheck.cpp index 4a7ab7d660..ae83abfa01 100644 --- a/apps/opencs/model/tools/journalcheck.cpp +++ b/apps/opencs/model/tools/journalcheck.cpp @@ -1,7 +1,6 @@ #include "journalcheck.hpp" #include -#include #include "../prefs/state.hpp" @@ -57,34 +56,27 @@ void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages) if (journalInfo.mResponse.empty()) { CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); - - messages.add(id, "Journal Info: missing description", "", CSMDoc::Message::Severity_Warning); + messages.add(id, "Missing journal entry text", "", CSMDoc::Message::Severity_Warning); } std::pair::iterator, bool> result = questIndices.insert(journalInfo.mData.mJournalIndex); // Duplicate index - if (result.second == false) + if (!result.second) { CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); - - std::ostringstream stream; - stream << "Journal: duplicated quest index " << journalInfo.mData.mJournalIndex; - - messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + messages.add(id, "Duplicated quest index " + std::to_string(journalInfo.mData.mJournalIndex), "", CSMDoc::Message::Severity_Error); } } if (totalInfoCount == 0) { CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); - - messages.add(id, "Journal: no defined Journal Infos", "", CSMDoc::Message::Severity_Warning); + messages.add(id, "No related journal entry", "", CSMDoc::Message::Severity_Warning); } else if (statusNamedCount > 1) { CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); - - messages.add(id, "Journal: multiple infos with quest status \"Named\"", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Multiple entries with quest status 'Named'", "", CSMDoc::Message::Severity_Error); } } diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp index 531bd9e1d6..f55fb14eea 100644 --- a/apps/opencs/model/tools/magiceffectcheck.cpp +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -4,79 +4,26 @@ #include "../prefs/state.hpp" -#include "../world/resources.hpp" -#include "../world/data.hpp" - -namespace -{ - void addMessageIfNotEmpty(CSMDoc::Messages &messages, const CSMWorld::UniversalId &id, const std::string& text) - { - if (!text.empty()) - { - messages.push_back(std::make_pair(id, text)); - } - } -} - -bool CSMTools::MagicEffectCheckStage::isTextureExists(const std::string &texture, bool isIcon) const -{ - const CSMWorld::Resources &textures = isIcon ? mIcons : mTextures; - bool exists = false; - - if (textures.searchId(texture) != -1) - { - exists = true; - } - else - { - std::string ddsTexture = texture; - if (Misc::ResourceHelpers::changeExtensionToDds(ddsTexture) && textures.searchId(ddsTexture) != -1) - { - exists = true; - } - } - - return exists; -} - -std::string CSMTools::MagicEffectCheckStage::checkReferenceable(const std::string &id, +std::string CSMTools::MagicEffectCheckStage::checkObject(const std::string &id, const CSMWorld::UniversalId &type, const std::string &column) const { - std::string error; - if (!id.empty()) - { - CSMWorld::RefIdData::LocalIndex index = mReferenceables.getDataSet().searchId(id); - if (index.first == -1) - { - error = "No such " + column + " '" + id + "'"; - } - else if (index.second != type.getType()) - { - error = column + " is not of type " + type.getTypeName(); - } - } - return error; -} - -std::string CSMTools::MagicEffectCheckStage::checkSound(const std::string &id, const std::string &column) const -{ - std::string error; - if (!id.empty() && mSounds.searchId(id) == -1) - { - error = "No such " + column + " '" + id + "'"; - } - return error; + 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"); + return std::string(); } CSMTools::MagicEffectCheckStage::MagicEffectCheckStage(const CSMWorld::IdCollection &effects, const CSMWorld::IdCollection &sounds, - const CSMWorld::RefIdCollection &referenceables, + const CSMWorld::RefIdCollection &objects, const CSMWorld::Resources &icons, const CSMWorld::Resources &textures) : mMagicEffects(effects), mSounds(sounds), - mReferenceables(referenceables), + mObjects(objects), mIcons(icons), mTextures(textures) { @@ -100,46 +47,75 @@ void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messa ESM::MagicEffect effect = record.get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId); - + + if (effect.mDescription.empty()) + { + messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); + } + if (effect.mData.mBaseCost < 0.0f) { - messages.push_back(std::make_pair(id, "Base Cost is negative")); + messages.add(id, "Base cost is negative", "", CSMDoc::Message::Severity_Error); } if (effect.mIcon.empty()) { - messages.push_back(std::make_pair(id, "Icon is not specified")); + messages.add(id, "Icon is missing", "", CSMDoc::Message::Severity_Error); } - else if (!isTextureExists(effect.mIcon, true)) + else { - messages.push_back(std::make_pair(id, "No such Icon '" + effect.mIcon + "'")); + if (mIcons.searchId(effect.mIcon) == -1) + { + std::string ddsIcon = effect.mIcon; + if (!(Misc::ResourceHelpers::changeExtensionToDds(ddsIcon) && mIcons.searchId(ddsIcon) != -1)) + messages.add(id, "Icon '" + effect.mIcon + "' does not exist", "", CSMDoc::Message::Severity_Error); + } } - if (!effect.mParticle.empty() && !isTextureExists(effect.mParticle, false)) + if (!effect.mParticle.empty()) { - messages.push_back(std::make_pair(id, "No such Particle '" + effect.mParticle + "'")); + if (mTextures.searchId(effect.mParticle) == -1) + { + 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); + } } - addMessageIfNotEmpty(messages, - id, - checkReferenceable(effect.mCasting, CSMWorld::UniversalId::Type_Static, "Casting Object")); - addMessageIfNotEmpty(messages, - id, - checkReferenceable(effect.mHit, CSMWorld::UniversalId::Type_Static, "Hit Object")); - addMessageIfNotEmpty(messages, - id, - checkReferenceable(effect.mArea, CSMWorld::UniversalId::Type_Static, "Area Object")); - addMessageIfNotEmpty(messages, - id, - checkReferenceable(effect.mBolt, CSMWorld::UniversalId::Type_Weapon, "Bolt Object")); - - addMessageIfNotEmpty(messages, id, checkSound(effect.mCastSound, "Casting Sound")); - addMessageIfNotEmpty(messages, id, checkSound(effect.mHitSound, "Hit Sound")); - addMessageIfNotEmpty(messages, id, checkSound(effect.mAreaSound, "Area Sound")); - addMessageIfNotEmpty(messages, id, checkSound(effect.mBoltSound, "Bolt Sound")); - - if (effect.mDescription.empty()) + if (!effect.mCasting.empty()) { - messages.push_back(std::make_pair(id, "Description is empty")); + const std::string error = checkObject(effect.mCasting, CSMWorld::UniversalId::Type_Static, "Casting object"); + if (!error.empty()) + messages.add(id, error, "", CSMDoc::Message::Severity_Error); } + + if (!effect.mHit.empty()) + { + const std::string error = checkObject(effect.mHit, CSMWorld::UniversalId::Type_Static, "Hit object"); + if (!error.empty()) + messages.add(id, error, "", CSMDoc::Message::Severity_Error); + } + + if (!effect.mArea.empty()) + { + const std::string error = checkObject(effect.mArea, CSMWorld::UniversalId::Type_Static, "Area object"); + if (!error.empty()) + messages.add(id, error, "", CSMDoc::Message::Severity_Error); + } + + if (!effect.mBolt.empty()) + { + const std::string error = checkObject(effect.mBolt, CSMWorld::UniversalId::Type_Weapon, "Bolt object"); + if (!error.empty()) + messages.add(id, error, "", CSMDoc::Message::Severity_Error); + } + + if (!effect.mCastSound.empty() && mSounds.searchId(effect.mCastSound) == -1) + messages.add(id, "Casting sound '" + effect.mCastSound + "' 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); + if (!effect.mAreaSound.empty() && mSounds.searchId(effect.mAreaSound) == -1) + messages.add(id, "Area sound '" + effect.mAreaSound + "' 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); } diff --git a/apps/opencs/model/tools/magiceffectcheck.hpp b/apps/opencs/model/tools/magiceffectcheck.hpp index 28a4062832..a52723b0fe 100644 --- a/apps/opencs/model/tools/magiceffectcheck.hpp +++ b/apps/opencs/model/tools/magiceffectcheck.hpp @@ -6,14 +6,10 @@ #include "../world/idcollection.hpp" #include "../world/refidcollection.hpp" +#include "../world/resources.hpp" #include "../doc/stage.hpp" -namespace CSMWorld -{ - class Resources; -} - namespace CSMTools { /// \brief VerifyStage: make sure that magic effect records are internally consistent @@ -21,23 +17,18 @@ namespace CSMTools { const CSMWorld::IdCollection &mMagicEffects; const CSMWorld::IdCollection &mSounds; - const CSMWorld::RefIdCollection &mReferenceables; + const CSMWorld::RefIdCollection &mObjects; const CSMWorld::Resources &mIcons; const CSMWorld::Resources &mTextures; bool mIgnoreBaseRecords; private: - bool isTextureExists(const std::string &texture, bool isIcon) const; - - std::string checkReferenceable(const std::string &id, - const CSMWorld::UniversalId &type, - const std::string &column) const; - std::string checkSound(const std::string &id, const std::string &column) const; + std::string checkObject(const std::string &id, const CSMWorld::UniversalId &type, const std::string &column) const; public: MagicEffectCheckStage(const CSMWorld::IdCollection &effects, const CSMWorld::IdCollection &sounds, - const CSMWorld::RefIdCollection &referenceables, + const CSMWorld::RefIdCollection &objects, const CSMWorld::Resources &icons, const CSMWorld::Resources &textures); diff --git a/apps/opencs/model/tools/pathgridcheck.cpp b/apps/opencs/model/tools/pathgridcheck.cpp index c25845885e..febb79c641 100644 --- a/apps/opencs/model/tools/pathgridcheck.cpp +++ b/apps/opencs/model/tools/pathgridcheck.cpp @@ -37,9 +37,9 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message // check the number of pathgrid points if (pathgrid.mData.mS2 < static_cast(pathgrid.mPoints.size())) - messages.add (id, pathgrid.mId + " has 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, pathgrid.mId + " has 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; @@ -56,9 +56,8 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message if (pointList[pathgrid.mEdges[i].mV0].mOtherIndex[j] == pathgrid.mEdges[i].mV1) { std::ostringstream ss; - ss << "has a duplicate edge between points" << pathgrid.mEdges[i].mV0 - << " and " << pathgrid.mEdges[i].mV1; - messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); + ss << "Duplicate edge between points #" << pathgrid.mEdges[i].mV0 << " and #" << pathgrid.mEdges[i].mV1; + messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Error); break; } } @@ -70,8 +69,8 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message else { std::ostringstream ss; - ss << " has an edge connecting a non-existent point " << pathgrid.mEdges[i].mV0; - messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); + ss << "An edge is connected to a non-existent point #" << pathgrid.mEdges[i].mV0; + messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Error); } } @@ -93,18 +92,15 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message if (!foundReverse) { std::ostringstream ss; - ss << " has a missing edge between points " << i << " and " << pointList[i].mOtherIndex[j]; - messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); + ss << "Missing edge between points #" << i << " and #" << pointList[i].mOtherIndex[j]; + messages.add (id, ss.str(), "", CSMDoc::Message::Severity_Error); } } // check duplicate points // FIXME: how to do this efficiently? - for (unsigned int j = 0; j < pathgrid.mPoints.size(); ++j) + for (unsigned int j = 0; j != i; ++j) { - if (j == i) - continue; - 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) @@ -113,11 +109,9 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message if (it == duplList.end()) { std::ostringstream ss; - ss << " has a duplicated point (" << i - << ") x=" << pathgrid.mPoints[i].mX - << ", y=" << pathgrid.mPoints[i].mY - << ", z=" << pathgrid.mPoints[i].mZ; - messages.add (id, pathgrid.mId + 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; @@ -132,11 +126,11 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message if (pointList[i].mConnectionNum == 0) { std::ostringstream ss; - ss << " has an orphaned point (" << i - << ") x=" << pathgrid.mPoints[i].mX - << ", y=" << pathgrid.mPoints[i].mY - << ", z=" << pathgrid.mPoints[i].mZ; - messages.add (id, pathgrid.mId + 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/racecheck.cpp b/apps/opencs/model/tools/racecheck.cpp index 38abfef18b..6585a31ccb 100644 --- a/apps/opencs/model/tools/racecheck.cpp +++ b/apps/opencs/model/tools/racecheck.cpp @@ -1,9 +1,5 @@ #include "racecheck.hpp" -#include - -#include - #include "../prefs/state.hpp" #include "../world/universalid.hpp" @@ -29,24 +25,24 @@ void CSMTools::RaceCheckStage::performPerRecord (int stage, CSMDoc::Messages& me // test for empty name and description if (race.mName.empty()) - messages.push_back (std::make_pair (id, race.mId + " has an empty name")); + messages.add(id, "Name is missing", "", (race.mData.mFlags & 0x1) ? CSMDoc::Message::Severity_Error : CSMDoc::Message::Severity_Warning); if (race.mDescription.empty()) - messages.push_back (std::make_pair (id, race.mId + " has an empty description")); + messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); // test for positive height if (race.mData.mHeight.mMale<=0) - messages.push_back (std::make_pair (id, "male " + race.mId + " has non-positive height")); + messages.add(id, "Male height is non-positive", "", CSMDoc::Message::Severity_Error); if (race.mData.mHeight.mFemale<=0) - messages.push_back (std::make_pair (id, "female " + race.mId + " has non-positive height")); + messages.add(id, "Female height is non-positive", "", CSMDoc::Message::Severity_Error); // test for non-negative weight if (race.mData.mWeight.mMale<0) - messages.push_back (std::make_pair (id, "male " + race.mId + " has negative weight")); + messages.add(id, "Male weight is negative", "", CSMDoc::Message::Severity_Error); if (race.mData.mWeight.mFemale<0) - messages.push_back (std::make_pair (id, "female " + race.mId + " has negative weight")); + messages.add(id, "Female weight is negative", "", CSMDoc::Message::Severity_Error); /// \todo check data members that can't be edited in the table view } @@ -56,7 +52,7 @@ void CSMTools::RaceCheckStage::performFinal (CSMDoc::Messages& messages) CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Races); if (!mPlayable) - messages.push_back (std::make_pair (id, "No playable race")); + messages.add(id, "No playable race", "", CSMDoc::Message::Severity_SeriousError); } CSMTools::RaceCheckStage::RaceCheckStage (const CSMWorld::IdCollection& races) diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index 3e8dc11887..fdbab7fd09 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -1,6 +1,7 @@ #include "referenceablecheck.hpp" #include +#include #include "../prefs/state.hpp" @@ -11,13 +12,18 @@ CSMTools::ReferenceableCheckStage::ReferenceableCheckStage( const CSMWorld::RefIdData& referenceable, const CSMWorld::IdCollection& races, const CSMWorld::IdCollection& classes, const CSMWorld::IdCollection& faction, - const CSMWorld::IdCollection& scripts) - : - mReferencables(referenceable), + 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) { mIgnoreBaseRecords = false; @@ -270,9 +276,10 @@ void CSMTools::ReferenceableCheckStage::activatorCheck( const ESM::Activator& activator = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Activator, activator.mId); - //Checking for model, IIRC all activators should have a model if (activator.mModel.empty()) - messages.push_back (std::make_pair (id, activator.mId + " has no model")); + messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); + else if (mModels.searchId(activator.mModel) == -1) + messages.add(id, "Model '" + activator.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); // Check that mentioned scripts exist scriptCheck(activator, messages, id.toString()); @@ -293,7 +300,7 @@ void CSMTools::ReferenceableCheckStage::potionCheck( CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Potion, potion.mId); inventoryItemCheck(potion, messages, id.toString()); - //IIRC potion can have empty effects list just fine. + /// \todo Check magic effects for validity // Check that mentioned scripts exist scriptCheck(potion, messages, id.toString()); @@ -338,13 +345,13 @@ void CSMTools::ReferenceableCheckStage::armorCheck( inventoryItemCheck(armor, messages, id.toString(), true); - //checking for armor class, armor should have poistive armor class, but 0 is considered legal + // Armor should have positive armor class, but 0 class is not an error if (armor.mData.mArmor < 0) - messages.push_back (std::make_pair (id, armor.mId + " has negative armor class")); + messages.add(id, "Armor class is negative", "", CSMDoc::Message::Severity_Error); - //checking for health. Only positive numbers are allowed, or 0 is illegal + // Armor durability must be a positive number if (armor.mData.mHealth <= 0) - messages.push_back (std::make_pair (id, armor.mId + " has non positive health")); + messages.add(id, "Durability is non-positive", "", CSMDoc::Message::Severity_Error); // Check that mentioned scripts exist scriptCheck(armor, messages, id.toString()); @@ -383,18 +390,19 @@ void CSMTools::ReferenceableCheckStage::containerCheck( const ESM::Container& container = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Container, container.mId); - //Checking for model, IIRC all containers should have a model + //checking for name + if (container.mName.empty()) + messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); + + //Checking for model if (container.mModel.empty()) - messages.push_back (std::make_pair (id, container.mId + " has no model")); + 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 - messages.push_back (std::make_pair (id, - container.mId + " has negative weight (capacity)")); - - //checking for name - if (container.mName.empty()) - messages.push_back (std::make_pair (id, container.mId + " has an empty name")); + messages.add(id, "Capacity is negative", "", CSMDoc::Message::Severity_Error); //checking contained items inventoryListCheck(container.mInventory.mList, messages, id.toString()); @@ -416,68 +424,81 @@ void CSMTools::ReferenceableCheckStage::creatureCheck ( const ESM::Creature& creature = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Creature, creature.mId); - if (creature.mModel.empty()) - messages.push_back (std::make_pair (id, creature.mId + " has no model")); - if (creature.mName.empty()) - messages.push_back (std::make_pair (id, creature.mId + " has an empty name")); + messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); + + if (creature.mModel.empty()) + messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); + else if (mModels.searchId(creature.mModel) == -1) + messages.add(id, "Model '" + creature.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); //stats checks - if (creature.mData.mLevel < 1) - messages.push_back (std::make_pair (id, creature.mId + " has non-positive level")); + if (creature.mData.mLevel <= 0) + messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mStrength < 0) - messages.push_back (std::make_pair (id, creature.mId + " has negative strength")); - + messages.add(id, "Strength is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mIntelligence < 0) - messages.push_back (std::make_pair (id, creature.mId + " has negative intelligence")); - + messages.add(id, "Intelligence is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mWillpower < 0) - messages.push_back (std::make_pair (id, creature.mId + " has negative willpower")); - + messages.add(id, "Willpower is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mAgility < 0) - messages.push_back (std::make_pair (id, creature.mId + " has negative agility")); - + messages.add(id, "Agility is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mSpeed < 0) - messages.push_back (std::make_pair (id, creature.mId + " has negative speed")); - + messages.add(id, "Speed is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mEndurance < 0) - messages.push_back (std::make_pair (id, creature.mId + " has negative endurance")); - + messages.add(id, "Endurance is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mPersonality < 0) - messages.push_back (std::make_pair (id, creature.mId + " has negative personality")); - + messages.add(id, "Personality is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mLuck < 0) - messages.push_back (std::make_pair (id, creature.mId + " has negative luck")); + messages.add(id, "Luck is negative", "", CSMDoc::Message::Severity_Warning); + + if (creature.mData.mCombat < 0) + messages.add(id, "Combat is negative", "", CSMDoc::Message::Severity_Warning); + if (creature.mData.mMagic < 0) + messages.add(id, "Magic is negative", "", CSMDoc::Message::Severity_Warning); + if (creature.mData.mStealth < 0) + messages.add(id, "Stealth is negative", "", CSMDoc::Message::Severity_Warning); if (creature.mData.mHealth < 0) - messages.push_back (std::make_pair (id, creature.mId + " has negative health")); + messages.add(id, "Health is negative", "", CSMDoc::Message::Severity_Error); + if (creature.mData.mMana < 0) + messages.add(id, "Magicka is negative", "", CSMDoc::Message::Severity_Error); + if (creature.mData.mFatigue < 0) + messages.add(id, "Fatigue is negative", "", CSMDoc::Message::Severity_Error); if (creature.mData.mSoul < 0) - messages.push_back (std::make_pair (id, creature.mId + " has negative soul value")); + messages.add(id, "Soul value is negative", "", CSMDoc::Message::Severity_Error); for (int i = 0; i < 6; ++i) { if (creature.mData.mAttack[i] < 0) - { - messages.push_back (std::make_pair (id, - creature.mId + " has negative attack strength")); - break; - } + 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); } - //TODO, find meaning of other values - if (creature.mData.mGold < 0) //It seems that this is for gold in merchant creatures - messages.push_back (std::make_pair (id, creature.mId + " has negative gold ")); + if (creature.mData.mGold < 0) + messages.add(id, "Gold count is negative", "", CSMDoc::Message::Severity_Error); if (creature.mScale == 0) - messages.push_back (std::make_pair (id, creature.mId + " has zero scale value")); + messages.add(id, "Scale is equal to zero", "", CSMDoc::Message::Severity_Error); + + if (!creature.mOriginal.empty()) + { + 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); + else if (index.second != CSMWorld::UniversalId::Type_Creature) + messages.add(id, "'" + creature.mOriginal + "' 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( @@ -495,10 +516,12 @@ void CSMTools::ReferenceableCheckStage::doorCheck( //usual, name or model if (door.mName.empty()) - messages.push_back (std::make_pair (id, door.mId + " has an empty name")); + messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); if (door.mModel.empty()) - messages.push_back (std::make_pair (id, door.mId + " has no model")); + messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); + else if (mModels.searchId(door.mModel) == -1) + messages.add(id, "Model '" + door.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); // Check that mentioned scripts exist scriptCheck(door, messages, id.toString()); @@ -572,7 +595,7 @@ void CSMTools::ReferenceableCheckStage::lightCheck( CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Light, light.mId); if (light.mData.mRadius < 0) - messages.push_back (std::make_pair (id, light.mId + " has negative light radius")); + messages.add(id, "Light radius is negative", "", CSMDoc::Message::Severity_Error); if (light.mData.mFlags & ESM::Light::Carry) inventoryItemCheck(light, messages, id.toString()); @@ -644,96 +667,75 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( return; short level(npc.mNpdt.mLevel); - char disposition(npc.mNpdt.mDisposition); - char reputation(npc.mNpdt.mReputation); - char rank(npc.mNpdt.mRank); - //Don't know what unknown is for int gold(npc.mNpdt.mGold); if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) //12 = autocalculated { if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0010 = autocalculated flag { - messages.push_back (std::make_pair (id, npc.mId + " mNpdtType or flags mismatch!")); //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; } - - level = npc.mNpdt.mLevel; - disposition = npc.mNpdt.mDisposition; - reputation = npc.mNpdt.mReputation; - rank = npc.mNpdt.mRank; - gold = npc.mNpdt.mGold; } else { - if (npc.mNpdt.mAgility == 0) - messages.push_back (std::make_pair (id, npc.mId + " agility has zero value")); - - if (npc.mNpdt.mEndurance == 0) - messages.push_back (std::make_pair (id, npc.mId + " endurance has zero value")); - - if (npc.mNpdt.mIntelligence == 0) - messages.push_back (std::make_pair (id, npc.mId + " intelligence has zero value")); - - if (npc.mNpdt.mLuck == 0) - messages.push_back (std::make_pair (id, npc.mId + " luck has zero value")); - - if (npc.mNpdt.mPersonality == 0) - messages.push_back (std::make_pair (id, npc.mId + " personality has zero value")); - if (npc.mNpdt.mStrength == 0) - messages.push_back (std::make_pair (id, npc.mId + " strength has zero value")); - - if (npc.mNpdt.mSpeed == 0) - messages.push_back (std::make_pair (id, npc.mId + " speed has zero value")); - + messages.add(id, "Strength is equal to zero", "", CSMDoc::Message::Severity_Warning); + if (npc.mNpdt.mIntelligence == 0) + messages.add(id, "Intelligence is equal to zero", "", CSMDoc::Message::Severity_Warning); if (npc.mNpdt.mWillpower == 0) - messages.push_back (std::make_pair (id, npc.mId + " willpower has zero value")); + messages.add(id, "Willpower is equal to zero", "", CSMDoc::Message::Severity_Warning); + if (npc.mNpdt.mAgility == 0) + messages.add(id, "Agility is equal to zero", "", CSMDoc::Message::Severity_Warning); + if (npc.mNpdt.mSpeed == 0) + messages.add(id, "Speed is equal to zero", "", CSMDoc::Message::Severity_Warning); + if (npc.mNpdt.mEndurance == 0) + messages.add(id, "Endurance is equal to zero", "", CSMDoc::Message::Severity_Warning); + if (npc.mNpdt.mPersonality == 0) + messages.add(id, "Personality is equal to zero", "", CSMDoc::Message::Severity_Warning); + if (npc.mNpdt.mLuck == 0) + messages.add(id, "Luck is equal to zero", "", CSMDoc::Message::Severity_Warning); } - if (level < 1) - messages.push_back (std::make_pair (id, npc.mId + " level is non positive")); + if (level <= 0) + messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); if (gold < 0) - messages.push_back (std::make_pair (id, npc.mId + " gold has negative value")); + messages.add(id, "Gold count is negative", "", CSMDoc::Message::Severity_Error); if (npc.mName.empty()) - messages.push_back (std::make_pair (id, npc.mId + " has any empty name")); + messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); if (npc.mClass.empty()) - messages.push_back (std::make_pair (id, npc.mId + " has an empty class")); + messages.add(id, "Class is missing", "", CSMDoc::Message::Severity_Error); else if (mClasses.searchId (npc.mClass) == -1) - messages.push_back (std::make_pair (id, npc.mId + " has invalid class")); + messages.add(id, "Class '" + npc.mClass + "' does not exist", "", CSMDoc::Message::Severity_Error); if (npc.mRace.empty()) - messages.push_back (std::make_pair (id, npc.mId + " has an empty race")); + messages.add(id, "Race is missing", "", CSMDoc::Message::Severity_Error); else if (mRaces.searchId (npc.mRace) == -1) - messages.push_back (std::make_pair (id, npc.mId + " has invalid race")); + messages.add(id, "Race '" + npc.mRace + "' does not exist", "", CSMDoc::Message::Severity_Error); - if (disposition < 0) - messages.push_back (std::make_pair (id, npc.mId + " has negative disposition")); - - if (reputation < 0) //It seems that no character in Morrowind.esm have negative reputation. I'm assuming that negative reputation is invalid - { - messages.push_back (std::make_pair (id, npc.mId + " has negative reputation")); - } - - if (!npc.mFaction.empty()) - { - if (rank < 0) - messages.push_back (std::make_pair (id, npc.mId + " has negative rank")); - - if (mFactions.searchId(npc.mFaction) == -1) - messages.push_back (std::make_pair (id, npc.mId + " has invalid faction")); - } + if (!npc.mFaction.empty() && mFactions.searchId(npc.mFaction) == -1) + messages.add(id, "Faction '" + npc.mFaction + "' does not exist", "", CSMDoc::Message::Severity_Error); if (npc.mHead.empty()) - messages.push_back (std::make_pair (id, npc.mId + " has no head")); + 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); + /// \todo Check gender, race and other body parts stuff validity for the specific NPC + } if (npc.mHair.empty()) - messages.push_back (std::make_pair (id, npc.mId + " has no hair")); - - //TODO: reputation, Disposition, rank, everything else + messages.add(id, "Hair is missing", "", CSMDoc::Message::Severity_Error); + else + { + if (mBodyParts.searchId(npc.mHair) == -1) + messages.add(id, "Hair body part '" + npc.mHair + "' 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()); @@ -793,28 +795,25 @@ void CSMTools::ReferenceableCheckStage::weaponCheck( weapon.mData.mType == ESM::Weapon::Bolt)) { if (weapon.mData.mSlash[0] > weapon.mData.mSlash[1]) - messages.push_back (std::make_pair (id, - weapon.mId + " has minimum slash damage higher than maximum")); + messages.add(id, "Minimum slash damage higher than maximum", "", CSMDoc::Message::Severity_Warning); if (weapon.mData.mThrust[0] > weapon.mData.mThrust[1]) - messages.push_back (std::make_pair (id, - weapon.mId + " has minimum thrust damage higher than maximum")); + messages.add(id, "Minimum thrust damage higher than maximum", "", CSMDoc::Message::Severity_Warning); } if (weapon.mData.mChop[0] > weapon.mData.mChop[1]) - messages.push_back (std::make_pair (id, - weapon.mId + " has minimum chop damage higher than maximum")); + 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)) { //checking of health - if (weapon.mData.mHealth <= 0) - messages.push_back (std::make_pair (id, weapon.mId + " has non-positive health")); + if (weapon.mData.mHealth == 0) + messages.add(id, "Durability is equal to zero", "", CSMDoc::Message::Severity_Warning); if (weapon.mData.mReach < 0) - messages.push_back (std::make_pair (id, weapon.mId + " has negative reach")); + messages.add(id, "Reach is negative", "", CSMDoc::Message::Severity_Error); } } @@ -877,7 +876,9 @@ void CSMTools::ReferenceableCheckStage::staticCheck ( CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Static, staticElement.mId); if (staticElement.mModel.empty()) - messages.push_back (std::make_pair (id, staticElement.mId + " has no model")); + messages.add(id, "Model is missing", "", CSMDoc::Message::Severity_Error); + else if (mModels.searchId(staticElement.mModel) == -1) + messages.add(id, "Model '" + staticElement.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); } //final check @@ -885,8 +886,7 @@ void CSMTools::ReferenceableCheckStage::staticCheck ( void CSMTools::ReferenceableCheckStage::finalCheck (CSMDoc::Messages& messages) { if (!mPlayerPresent) - messages.push_back (std::make_pair (CSMWorld::UniversalId::Type_Referenceables, - "There is no player record")); + messages.add(CSMWorld::UniversalId::Type_Referenceables, "Player record is missing", "", CSMDoc::Message::Severity_SeriousError); } void CSMTools::ReferenceableCheckStage::inventoryListCheck( @@ -900,8 +900,7 @@ void CSMTools::ReferenceableCheckStage::inventoryListCheck( CSMWorld::RefIdData::LocalIndex localIndex = mReferencables.searchId(itemName); if (localIndex.first == -1) - messages.push_back (std::make_pair (id, - id + " contains non-existing item (" + itemName + ")")); + messages.add(id, "Item '" + itemName + "' does not exist", "", CSMDoc::Message::Severity_Error); else { // Needs to accommodate containers, creatures, and NPCs @@ -922,8 +921,7 @@ void CSMTools::ReferenceableCheckStage::inventoryListCheck( case CSMWorld::UniversalId::Type_ItemLevelledList: break; default: - messages.push_back (std::make_pair(id, - id + " contains item of invalid type (" + itemName + ")")); + messages.add(id, "'" + itemName + "' is not an item", "", CSMDoc::Message::Severity_Error); } } } @@ -935,67 +933,82 @@ template void CSMTools::ReferenceableCheckStage::inventoryItemChe const Item& someItem, CSMDoc::Messages& messages, const std::string& someID, bool enchantable) { if (someItem.mName.empty()) - messages.push_back (std::make_pair (someID, someItem.mId + " has an empty name")); + messages.add(someID, "Name is missing", "", CSMDoc::Message::Severity_Error); //Checking for weight if (someItem.mData.mWeight < 0) - messages.push_back (std::make_pair (someID, someItem.mId + " has negative weight")); + messages.add(someID, "Weight is negative", "", CSMDoc::Message::Severity_Error); //Checking for value if (someItem.mData.mValue < 0) - messages.push_back (std::make_pair (someID, someItem.mId + " has negative value")); + messages.add(someID, "Value is negative", "", CSMDoc::Message::Severity_Error); //checking for model if (someItem.mModel.empty()) - messages.push_back (std::make_pair (someID, someItem.mId + " has no model")); + 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 if (someItem.mIcon.empty()) - messages.push_back (std::make_pair (someID, someItem.mId + " has no icon")); + messages.add(someID, "Icon is missing", "", CSMDoc::Message::Severity_Error); + else if (mIcons.searchId(someItem.mIcon) == -1) + { + std::string ddsIcon = someItem.mIcon; + if (!(Misc::ResourceHelpers::changeExtensionToDds(ddsIcon) && mIcons.searchId(ddsIcon) != -1)) + messages.add(someID, "Icon '" + someItem.mIcon + "' does not exist", "", CSMDoc::Message::Severity_Error); + } if (enchantable && someItem.mData.mEnchant < 0) - messages.push_back (std::make_pair (someID, someItem.mId + " has negative enchantment")); + messages.add(someID, "Enchantment points number is negative", "", CSMDoc::Message::Severity_Error); } template void CSMTools::ReferenceableCheckStage::inventoryItemCheck ( const Item& someItem, CSMDoc::Messages& messages, const std::string& someID) { if (someItem.mName.empty()) - messages.push_back (std::make_pair (someID, someItem.mId + " has an empty name")); + messages.add(someID, "Name is missing", "", CSMDoc::Message::Severity_Error); //Checking for weight if (someItem.mData.mWeight < 0) - messages.push_back (std::make_pair (someID, someItem.mId + " has negative weight")); + messages.add(someID, "Weight is negative", "", CSMDoc::Message::Severity_Error); //Checking for value if (someItem.mData.mValue < 0) - messages.push_back (std::make_pair (someID, someItem.mId + " has negative value")); + messages.add(someID, "Value is negative", "", CSMDoc::Message::Severity_Error); //checking for model if (someItem.mModel.empty()) - messages.push_back (std::make_pair (someID, someItem.mId + " has no model")); + 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 if (someItem.mIcon.empty()) - messages.push_back (std::make_pair (someID, someItem.mId + " has no icon")); + messages.add(someID, "Icon is missing", "", CSMDoc::Message::Severity_Error); + else if (mIcons.searchId(someItem.mIcon) == -1) + { + std::string ddsIcon = someItem.mIcon; + if (!(Misc::ResourceHelpers::changeExtensionToDds(ddsIcon) && mIcons.searchId(ddsIcon) != -1)) + messages.add(someID, "Icon '" + someItem.mIcon + "' does not exist", "", CSMDoc::Message::Severity_Error); + } } template void CSMTools::ReferenceableCheckStage::toolCheck ( const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID, bool canBeBroken) { if (someTool.mData.mQuality <= 0) - messages.push_back (std::make_pair (someID, someTool.mId + " has non-positive quality")); + messages.add(someID, "Quality is non-positive", "", CSMDoc::Message::Severity_Error); if (canBeBroken && someTool.mData.mUses<=0) - messages.push_back (std::make_pair (someID, - someTool.mId + " has non-positive uses count")); + messages.add(someID, "Number of uses is non-positive", "", CSMDoc::Message::Severity_Error); } template void CSMTools::ReferenceableCheckStage::toolCheck ( const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID) { if (someTool.mData.mQuality <= 0) - messages.push_back (std::make_pair (someID, someTool.mId + " has non-positive quality")); + messages.add(someID, "Quality is non-positive", "", CSMDoc::Message::Severity_Error); } template void CSMTools::ReferenceableCheckStage::listCheck ( @@ -1004,12 +1017,10 @@ template void CSMTools::ReferenceableCheckStage::listCheck ( for (unsigned i = 0; i < someList.mList.size(); ++i) { if (mReferencables.searchId(someList.mList[i].mId).first == -1) - messages.push_back (std::make_pair (someID, - someList.mId + " contains item without referencable")); + messages.add(someID, "Object '" + someList.mList[i].mId + "' does not exist", "", CSMDoc::Message::Severity_Error); if (someList.mList[i].mLevel < 1) - messages.push_back (std::make_pair (someID, - someList.mId + " contains item with non-positive level")); + messages.add(someID, "Level of item '" + someList.mList[i].mId + "' is non-positive", "", CSMDoc::Message::Severity_Error); } } @@ -1019,6 +1030,6 @@ template void CSMTools::ReferenceableCheckStage::scriptCheck ( if (!someTool.mScript.empty()) { if (mScripts.searchId(someTool.mScript) == -1) - messages.push_back (std::make_pair (someID, someTool.mId + " refers to an unknown script \""+someTool.mScript+"\"")); + messages.add(someID, "Script '" + someTool.mScript + "' does not exist", "", CSMDoc::Message::Severity_Error); } } diff --git a/apps/opencs/model/tools/referenceablecheck.hpp b/apps/opencs/model/tools/referenceablecheck.hpp index f9341bd9ce..e55e5fad97 100644 --- a/apps/opencs/model/tools/referenceablecheck.hpp +++ b/apps/opencs/model/tools/referenceablecheck.hpp @@ -5,6 +5,7 @@ #include "../doc/stage.hpp" #include "../world/data.hpp" #include "../world/refiddata.hpp" +#include "../world/resources.hpp" namespace CSMTools { @@ -16,7 +17,10 @@ namespace CSMTools const CSMWorld::IdCollection& races, const CSMWorld::IdCollection& classes, const CSMWorld::IdCollection& factions, - const CSMWorld::IdCollection& scripts); + const CSMWorld::IdCollection& scripts, + const CSMWorld::Resources& models, + const CSMWorld::Resources& icons, + const CSMWorld::IdCollection& bodyparts); virtual void perform(int stage, CSMDoc::Messages& messages); virtual int setup(); @@ -81,6 +85,9 @@ namespace CSMTools 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; }; diff --git a/apps/opencs/model/tools/referencecheck.cpp b/apps/opencs/model/tools/referencecheck.cpp index 447238be41..76bfeb3ba2 100644 --- a/apps/opencs/model/tools/referencecheck.cpp +++ b/apps/opencs/model/tools/referencecheck.cpp @@ -9,7 +9,7 @@ CSMTools::ReferenceCheckStage::ReferenceCheckStage( const CSMWorld::IdCollection& factions) : mReferences(references), - mReferencables(referencables), + mObjects(referencables), mDataSet(referencables.getDataSet()), mCells(cells), mFactions(factions) @@ -28,78 +28,59 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages &message const CSMWorld::CellRef& cellRef = record.get(); const CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Reference, cellRef.mId); - // Check for empty reference id - if (cellRef.mRefID.empty()) { - messages.push_back(std::make_pair(id, " is an empty instance (not based on an object)")); - } else { + // Check reference id + if (cellRef.mRefID.empty()) + messages.add(id, "Instance is not based on an object", "", CSMDoc::Message::Severity_Error); + else + { // Check for non existing referenced object - if (mReferencables.searchId(cellRef.mRefID) == -1) { - messages.push_back(std::make_pair(id, " is referencing non existing object " + cellRef.mRefID)); - } else { + if (mObjects.searchId(cellRef.mRefID) == -1) + messages.add(id, "Instance of a non-existent object '" + cellRef.mRefID + "'", "", 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); bool isLight = localIndex.second == CSMWorld::UniversalId::Type_Light; - if ((isLight && cellRef.mChargeFloat < -1) || (!isLight && cellRef.mChargeInt < -1)) { - std::string str = " has invalid charge "; - if (localIndex.second == CSMWorld::UniversalId::Type_Light) - str += std::to_string(cellRef.mChargeFloat); - else - str += std::to_string(cellRef.mChargeInt); - messages.push_back(std::make_pair(id, id.getId() + str)); - } + if ((isLight && cellRef.mChargeFloat < -1) || (!isLight && cellRef.mChargeInt < -1)) + messages.add(id, "Invalid charge", "", CSMDoc::Message::Severity_Error); } } // If object have owner, check if that owner reference is valid - if (!cellRef.mOwner.empty() && mReferencables.searchId(cellRef.mOwner) == -1) - messages.push_back(std::make_pair(id, " has non existing owner " + cellRef.mOwner)); + if (!cellRef.mOwner.empty() && mObjects.searchId(cellRef.mOwner) == -1) + messages.add(id, "Owner object '" + cellRef.mOwner + "' 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 (mReferencables.searchId(cellRef.mSoul) == -1) - messages.push_back(std::make_pair(id, " has non existing trapped soul " + cellRef.mSoul)); + if (mObjects.searchId(cellRef.mSoul) == -1) + messages.add(id, "Trapped soul object '" + cellRef.mOwner + "' does not exist", "", CSMDoc::Message::Severity_Error); - bool hasFaction = !cellRef.mFaction.empty(); - - // If object have faction, check if that faction reference is valid - if (hasFaction) - if (mFactions.searchId(cellRef.mFaction) == -1) - messages.push_back(std::make_pair(id, " has non existing faction " + cellRef.mFaction)); - - // Check item's faction rank - if (hasFaction && cellRef.mFactionRank < -1) - messages.push_back(std::make_pair(id, " has faction set but has invalid faction rank " + std::to_string(cellRef.mFactionRank))); - else if (!hasFaction && cellRef.mFactionRank != -2) - messages.push_back(std::make_pair(id, " has invalid faction rank " + std::to_string(cellRef.mFactionRank))); - - // If door have destination cell, check if that reference is valid - if (!cellRef.mDestCell.empty()) - if (mCells.searchId(cellRef.mDestCell) == -1) - messages.push_back(std::make_pair(id, " has non existing destination cell " + cellRef.mDestCell)); - - // Check if scale isn't negative - if (cellRef.mScale < 0) + if (cellRef.mFaction.empty()) { - std::string str = " has negative scale "; - str += std::to_string(cellRef.mScale); - messages.push_back(std::make_pair(id, id.getId() + str)); + if (cellRef.mFactionRank != -2) + messages.add(id, "Reference without a faction has a faction rank", "", CSMDoc::Message::Severity_Error); } + else + { + if (mFactions.searchId(cellRef.mFaction) == -1) + messages.add(id, "Faction '" + cellRef.mFaction + "' 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.mScale < 0) + messages.add(id, "Negative scale", "", CSMDoc::Message::Severity_Error); // Check if enchantement points aren't negative or are at full (-1) - if (cellRef.mEnchantmentCharge < 0 && cellRef.mEnchantmentCharge != -1) - { - std::string str = " has negative enchantment points "; - str += std::to_string(cellRef.mEnchantmentCharge); - messages.push_back(std::make_pair(id, id.getId() + str)); - } + if (cellRef.mEnchantmentCharge < -1) + messages.add(id, "Negative number of enchantment points", "", CSMDoc::Message::Severity_Error); // Check if gold value isn't negative if (cellRef.mGoldValue < 0) - { - std::string str = " has negative gold value "; - str += cellRef.mGoldValue; - messages.push_back(std::make_pair(id, id.getId() + str)); - } + messages.add(id, "Negative gold value", "", CSMDoc::Message::Severity_Error); } int CSMTools::ReferenceCheckStage::setup() diff --git a/apps/opencs/model/tools/referencecheck.hpp b/apps/opencs/model/tools/referencecheck.hpp index 5e25924f3a..7373903e66 100644 --- a/apps/opencs/model/tools/referencecheck.hpp +++ b/apps/opencs/model/tools/referencecheck.hpp @@ -19,7 +19,7 @@ namespace CSMTools private: const CSMWorld::RefCollection& mReferences; - const CSMWorld::RefIdCollection& mReferencables; + const CSMWorld::RefIdCollection& mObjects; const CSMWorld::RefIdData& mDataSet; const CSMWorld::IdCollection& mCells; const CSMWorld::IdCollection& mFactions; diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index f21253090f..0b6537d7fd 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -1,10 +1,5 @@ #include "regioncheck.hpp" -#include -#include - -#include - #include "../prefs/state.hpp" #include "../world/universalid.hpp" @@ -36,7 +31,7 @@ void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages) // test for empty name if (region.mName.empty()) - messages.add(id, region.mId + " has an empty name", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); /// \todo test that the ID in mSleeplist exists diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index d3c6221cd2..d3d9d1503d 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -30,10 +30,7 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, const Compi CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); - stream - << "script " << mFile - << ", line " << loc.mLine << ", column " << loc.mColumn - << " (" << loc.mLiteral << "): " << message; + stream << "line " << loc.mLine << ", column " << loc.mColumn << ": " << message << " (" << loc.mLiteral << ")"; std::ostringstream hintStream; @@ -47,7 +44,7 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, Type type) CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); std::ostringstream stream; - stream << "script " << mFile << ": " << message; + stream << message; mMessages->add (id, stream.str(), "", getSeverity (type)); } @@ -128,7 +125,7 @@ void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); std::ostringstream stream; - stream << "script " << mFile << ": " << error.what(); + stream << error.what(); messages.add (id, stream.str(), "", CSMDoc::Message::Severity_SeriousError); } diff --git a/apps/opencs/model/tools/skillcheck.cpp b/apps/opencs/model/tools/skillcheck.cpp index b34d18e2a9..c5b38dc1e0 100644 --- a/apps/opencs/model/tools/skillcheck.cpp +++ b/apps/opencs/model/tools/skillcheck.cpp @@ -1,9 +1,5 @@ #include "skillcheck.hpp" -#include - -#include - #include "../prefs/state.hpp" #include "../world/universalid.hpp" @@ -33,16 +29,12 @@ void CSMTools::SkillCheckStage::perform (int stage, CSMDoc::Messages& messages) 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) { - std::ostringstream stream; - - stream << "Use value #" << i << " of " << skill.mId << " is negative"; - - messages.push_back (std::make_pair (id, stream.str())); + messages.add(id, "Use value #" + std::to_string(i) + " is negative", "", CSMDoc::Message::Severity_Error); } - - if (skill.mDescription.empty()) - messages.push_back (std::make_pair (id, skill.mId + " has an empty description")); } diff --git a/apps/opencs/model/tools/soundcheck.cpp b/apps/opencs/model/tools/soundcheck.cpp index b84453b5cd..c0d893f1a1 100644 --- a/apps/opencs/model/tools/soundcheck.cpp +++ b/apps/opencs/model/tools/soundcheck.cpp @@ -1,15 +1,13 @@ #include "soundcheck.hpp" -#include - -#include - #include "../prefs/state.hpp" #include "../world/universalid.hpp" -CSMTools::SoundCheckStage::SoundCheckStage (const CSMWorld::IdCollection& sounds) -: mSounds (sounds) +CSMTools::SoundCheckStage::SoundCheckStage (const CSMWorld::IdCollection &sounds, + const CSMWorld::Resources &soundfiles) + : mSounds (sounds), + mSoundFiles (soundfiles) { mIgnoreBaseRecords = false; } @@ -34,7 +32,16 @@ void CSMTools::SoundCheckStage::perform (int stage, CSMDoc::Messages& messages) CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Sound, sound.mId); if (sound.mData.mMinRange>sound.mData.mMaxRange) - messages.push_back (std::make_pair (id, "Minimum range larger than maximum range")); + { + messages.add(id, "Minimum range is larger than maximum range", "", CSMDoc::Message::Severity_Warning); + } - /// \todo check, if the sound file exists + if (sound.mSound.empty()) + { + messages.add(id, "Sound file is missing", "", CSMDoc::Message::Severity_Error); + } + else if (mSoundFiles.searchId(sound.mSound) == -1) + { + messages.add(id, "Sound file '" + sound.mSound + "' does not exist", "", CSMDoc::Message::Severity_Error); + } } diff --git a/apps/opencs/model/tools/soundcheck.hpp b/apps/opencs/model/tools/soundcheck.hpp index d6fff52638..fc59257178 100644 --- a/apps/opencs/model/tools/soundcheck.hpp +++ b/apps/opencs/model/tools/soundcheck.hpp @@ -3,6 +3,7 @@ #include +#include "../world/resources.hpp" #include "../world/idcollection.hpp" #include "../doc/stage.hpp" @@ -13,11 +14,13 @@ namespace CSMTools class SoundCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mSounds; + const CSMWorld::Resources &mSoundFiles; bool mIgnoreBaseRecords; public: - SoundCheckStage (const CSMWorld::IdCollection& sounds); + SoundCheckStage (const CSMWorld::IdCollection& sounds, + const CSMWorld::Resources &soundfiles); virtual int setup(); ///< \return number of steps diff --git a/apps/opencs/model/tools/soundgencheck.cpp b/apps/opencs/model/tools/soundgencheck.cpp index 3692259cee..ec29e23fef 100644 --- a/apps/opencs/model/tools/soundgencheck.cpp +++ b/apps/opencs/model/tools/soundgencheck.cpp @@ -1,7 +1,5 @@ #include "soundgencheck.hpp" -#include - #include "../prefs/state.hpp" #include "../world/refiddata.hpp" @@ -9,10 +7,10 @@ CSMTools::SoundGenCheckStage::SoundGenCheckStage(const CSMWorld::IdCollection &soundGens, const CSMWorld::IdCollection &sounds, - const CSMWorld::RefIdCollection &referenceables) + const CSMWorld::RefIdCollection &objects) : mSoundGens(soundGens), mSounds(sounds), - mReferenceables(referenceables) + mObjects(objects) { mIgnoreBaseRecords = false; } @@ -37,23 +35,23 @@ void CSMTools::SoundGenCheckStage::perform(int stage, CSMDoc::Messages &messages if (!soundGen.mCreature.empty()) { - CSMWorld::RefIdData::LocalIndex creatureIndex = mReferenceables.getDataSet().searchId(soundGen.mCreature); + CSMWorld::RefIdData::LocalIndex creatureIndex = mObjects.getDataSet().searchId(soundGen.mCreature); if (creatureIndex.first == -1) { - messages.push_back(std::make_pair(id, "No such creature '" + soundGen.mCreature + "'")); + messages.add(id, "Creature '" + soundGen.mCreature + "' doesn't exist", "", CSMDoc::Message::Severity_Error); } else if (creatureIndex.second != CSMWorld::UniversalId::Type_Creature) { - messages.push_back(std::make_pair(id, "'" + soundGen.mCreature + "' is not a creature")); + messages.add(id, "'" + soundGen.mCreature + "' is not a creature", "", CSMDoc::Message::Severity_Error); } } if (soundGen.mSound.empty()) { - messages.push_back(std::make_pair(id, "Sound is not specified")); + messages.add(id, "Sound is missing", "", CSMDoc::Message::Severity_Error); } else if (mSounds.searchId(soundGen.mSound) == -1) { - messages.push_back(std::make_pair(id, "No such sound '" + soundGen.mSound + "'")); + messages.add(id, "Sound '" + soundGen.mSound + "' doesn't exist", "", CSMDoc::Message::Severity_Error); } } diff --git a/apps/opencs/model/tools/soundgencheck.hpp b/apps/opencs/model/tools/soundgencheck.hpp index 19388cb915..3c2a7f071d 100644 --- a/apps/opencs/model/tools/soundgencheck.hpp +++ b/apps/opencs/model/tools/soundgencheck.hpp @@ -12,13 +12,13 @@ namespace CSMTools { const CSMWorld::IdCollection &mSoundGens; const CSMWorld::IdCollection &mSounds; - const CSMWorld::RefIdCollection &mReferenceables; + const CSMWorld::RefIdCollection &mObjects; bool mIgnoreBaseRecords; public: SoundGenCheckStage(const CSMWorld::IdCollection &soundGens, const CSMWorld::IdCollection &sounds, - const CSMWorld::RefIdCollection &referenceables); + const CSMWorld::RefIdCollection &objects); virtual int setup(); ///< \return number of steps diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp index 3e59f0d9a3..dc9ce65c0a 100644 --- a/apps/opencs/model/tools/spellcheck.cpp +++ b/apps/opencs/model/tools/spellcheck.cpp @@ -34,13 +34,13 @@ void CSMTools::SpellCheckStage::perform (int stage, CSMDoc::Messages& messages) CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Spell, spell.mId); - // test for empty name and description + // test for empty name if (spell.mName.empty()) - messages.push_back (std::make_pair (id, spell.mId + " has an empty name")); + messages.add(id, "Name is missing", "", CSMDoc::Message::Severity_Error); // test for invalid cost values if (spell.mData.mCost<0) - messages.push_back (std::make_pair (id, spell.mId + " has a negative spell costs")); + 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/startscriptcheck.cpp b/apps/opencs/model/tools/startscriptcheck.cpp index b1d92380b3..deb7d384fc 100644 --- a/apps/opencs/model/tools/startscriptcheck.cpp +++ b/apps/opencs/model/tools/startscriptcheck.cpp @@ -25,8 +25,7 @@ void CSMTools::StartScriptCheckStage::perform(int stage, CSMDoc::Messages& messa CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_StartScript, scriptId); if (mScripts.searchId (Misc::StringUtils::lowerCase (scriptId))==-1) - messages.push_back ( - std::make_pair (id, "Start script " + scriptId + " does not exist")); + messages.add(id, "Start script " + scriptId + " does not exist", "", CSMDoc::Message::Severity_Error); } int CSMTools::StartScriptCheckStage::setup() diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 445db53af7..07a721e8e2 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -32,6 +32,7 @@ #include "gmstcheck.hpp" #include "topicinfocheck.hpp" #include "journalcheck.hpp" +#include "enchantmentcheck.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -74,15 +75,17 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mVerifierOperation->appendStage (new RaceCheckStage (mData.getRaces())); - mVerifierOperation->appendStage (new SoundCheckStage (mData.getSounds())); + mVerifierOperation->appendStage (new SoundCheckStage (mData.getSounds(), mData.getResources (CSMWorld::UniversalId::Type_SoundsRes))); mVerifierOperation->appendStage (new RegionCheckStage (mData.getRegions())); - mVerifierOperation->appendStage (new BirthsignCheckStage (mData.getBirthsigns())); + 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())); + 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())); @@ -126,6 +129,8 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mVerifierOperation->appendStage (new JournalCheckStage(mData.getJournals(), mData.getJournalInfos())); + mVerifierOperation->appendStage (new EnchantmentCheckStage(mData.getEnchantments())); + mVerifier.setOperation (mVerifierOperation); } diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp index ac1f596aee..fe9bc991d4 100644 --- a/apps/opencs/model/tools/topicinfocheck.cpp +++ b/apps/opencs/model/tools/topicinfocheck.cpp @@ -133,8 +133,7 @@ void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& message if (topicInfo.mData.mGender < -1 || topicInfo.mData.mGender > 1) { - std::ostringstream stream; - messages.add(id, "Gender: Value is invalid", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Gender is invalid", "", CSMDoc::Message::Severity_Error); } if (!topicInfo.mRace.empty()) @@ -166,23 +165,24 @@ void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& message bool CSMTools::TopicInfoCheckStage::verifyActor(const std::string& actor, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { - const std::string specifier = "Actor"; - CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(actor); if (index.first == -1) { - writeMissingIdError(specifier, actor, id, messages); + messages.add(id, "Actor '" + actor + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } else if (mReferencables.getRecord(index).isDeleted()) { - writeDeletedRecordError(specifier, actor, id, messages); + messages.add(id, "Deleted actor '" + actor + "' is being referenced", "", CSMDoc::Message::Severity_Error); return false; } else if (index.second != CSMWorld::UniversalId::Type_Npc && index.second != CSMWorld::UniversalId::Type_Creature) { - writeInvalidTypeError(specifier, actor, index.second, "NPC or Creature", id, messages); + 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)"; + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); return false; } @@ -192,11 +192,9 @@ bool CSMTools::TopicInfoCheckStage::verifyActor(const std::string& actor, const bool CSMTools::TopicInfoCheckStage::verifyCell(const std::string& cell, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { - const std::string specifier = "Cell"; - if (mCellNames.find(cell) == mCellNames.end()) { - writeMissingIdError(specifier, cell, id, messages); + messages.add(id, "Cell '" + cell + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } @@ -209,9 +207,8 @@ bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& faction if (rank < -1) { std::ostringstream stream; - stream << "Rank or PC Rank is set to " << rank << ", but should be set to -1 if no rank is required"; - - messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + stream << "Faction rank is set to " << rank << ", but it should be set to -1 if there are no rank requirements"; + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); return false; } @@ -229,8 +226,8 @@ bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& faction if (rank >= limit) { std::ostringstream stream; - stream << "Rank or PC Rank is set to " << rank << " which is more than the maximum of " << limit - 1 - << " for the " << factionName << " faction"; + stream << "Faction rank is set to " << rank << " which is more than the maximum of " << limit - 1 + << " for the '" << factionName << "' faction"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); return false; @@ -242,18 +239,16 @@ bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& faction bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { - const std::string specifier = "Item"; - CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(item); if (index.first == -1) { - writeMissingIdError(specifier, item, id, messages); + messages.add(id, ("Item '" + item + "' does not exist"), "", CSMDoc::Message::Severity_Error); return false; } else if (mReferencables.getRecord(index).isDeleted()) { - writeDeletedRecordError(specifier, item, id, messages); + messages.add(id, ("Deleted item '" + item + "' is being referenced"), "", CSMDoc::Message::Severity_Error); return false; } else @@ -276,8 +271,13 @@ bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CS break; default: - writeInvalidTypeError(specifier, item, index.second, "Potion, Armor, Book, etc.", id, messages); + { + 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)"; + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); return false; + } } } @@ -291,13 +291,13 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::Sele if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None) { - messages.add(id, "Invalid Info Condition: " + infoCondition.toString(), "", CSMDoc::Message::Severity_Error); + messages.add(id, "Invalid condition '" + infoCondition.toString() + "'", "", CSMDoc::Message::Severity_Error); return false; } else if (!infoCondition.variantTypeIsValid()) { std::ostringstream stream; - stream << "Info Condition: Value for \"" << infoCondition.toString() << "\" has a type of "; + stream << "Value of condition '" << infoCondition.toString() << "' has invalid "; switch (select.mValue.getType()) { @@ -307,26 +307,21 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::Sele 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; + default: stream << "unknown"; break; } + stream << " type"; messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); return false; } else if (infoCondition.conditionIsAlwaysTrue()) { - std::ostringstream stream; - stream << "Info Condition: " << infoCondition.toString() << " is always true"; - - messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + messages.add(id, "Condition '" + infoCondition.toString() + "' is always true", "", CSMDoc::Message::Severity_Warning); return false; } else if (infoCondition.conditionIsNeverTrue()) { - std::ostringstream stream; - stream << "Info Condition: " << infoCondition.toString() << " is never true"; - - messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + messages.add(id, "Condition '" + infoCondition.toString() + "' is never true", "", CSMDoc::Message::Severity_Warning); return false; } @@ -383,11 +378,9 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::Sele bool CSMTools::TopicInfoCheckStage::verifySound(const std::string& sound, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { - const std::string specifier = "Sound File"; - if (mSoundFiles.searchId(sound) == -1) { - writeMissingIdError(specifier, sound, id, messages); + messages.add(id, "Sound file '" + sound + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } @@ -402,47 +395,14 @@ bool CSMTools::TopicInfoCheckStage::verifyId(const std::string& name, const CSMW if (index == -1) { - writeMissingIdError(T::getRecordType(), name, id, messages); + messages.add(id, T::getRecordType() + " '" + name + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } else if (collection.getRecord(index).isDeleted()) { - writeDeletedRecordError(T::getRecordType(), name, id, messages); + messages.add(id, "Deleted " + T::getRecordType() + " record '" + name + "' is being referenced", "", CSMDoc::Message::Severity_Error); return false; } return true; } - -// Error functions - -void CSMTools::TopicInfoCheckStage::writeMissingIdError(const std::string& specifier, const std::string& missingId, - const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) -{ - std::ostringstream stream; - stream << specifier << ": ID or name \"" << missingId << "\" could not be found"; - - messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); -} - -void CSMTools::TopicInfoCheckStage::writeDeletedRecordError(const std::string& specifier, const std::string& recordId, - const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) -{ - std::ostringstream stream; - stream << specifier << ": Deleted record with ID \"" << recordId << "\" is being referenced"; - - messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); -} - -void CSMTools::TopicInfoCheckStage::writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, - CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, const CSMWorld::UniversalId& id, - CSMDoc::Messages& messages) -{ - CSMWorld::UniversalId tempId(invalidType, invalidId); - - std::ostringstream stream; - stream << specifier << ": invalid type of " << tempId.getTypeName() << " was found for referencable \"" - << invalidId << "\" (can be of type " << expectedType << ")"; - - messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); -} diff --git a/apps/opencs/model/tools/topicinfocheck.hpp b/apps/opencs/model/tools/topicinfocheck.hpp index dbd5fe1c57..9575181b0a 100644 --- a/apps/opencs/model/tools/topicinfocheck.hpp +++ b/apps/opencs/model/tools/topicinfocheck.hpp @@ -80,17 +80,6 @@ namespace CSMTools template bool verifyId(const std::string& name, const CSMWorld::IdCollection& collection, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); - - // Common error messages - void writeMissingIdError(const std::string& specifier, const std::string& missingId, - const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); - - void writeDeletedRecordError(const std::string& specifier, const std::string& recordId, - const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); - - void writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, - CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, - const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); }; } diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp new file mode 100644 index 0000000000..ea40a1f8dd --- /dev/null +++ b/apps/opencs/model/world/actoradapter.cpp @@ -0,0 +1,686 @@ +#include "actoradapter.hpp" + +#include +#include +#include +#include +#include +#include + +#include "data.hpp" + +namespace CSMWorld +{ + const std::string& ActorAdapter::RaceData::getId() const + { + return mId; + } + + bool ActorAdapter::RaceData::isBeast() const + { + return mIsBeast; + } + + bool ActorAdapter::RaceData::handlesPart(ESM::PartReferenceType type) const + { + switch (type) + { + case ESM::PRT_Skirt: + case ESM::PRT_Shield: + case ESM::PRT_RPauldron: + case ESM::PRT_LPauldron: + case ESM::PRT_Weapon: + return false; + default: + return true; + } + } + + const std::string& ActorAdapter::RaceData::getFemalePart(ESM::PartReferenceType index) const + { + return mFemaleParts[ESM::getMeshPart(index)]; + } + + const std::string& ActorAdapter::RaceData::getMalePart(ESM::PartReferenceType index) const + { + return mMaleParts[ESM::getMeshPart(index)]; + } + + bool ActorAdapter::RaceData::hasDependency(const std::string& id) const + { + return mDependencies.find(id) != mDependencies.end(); + } + + void ActorAdapter::RaceData::setFemalePart(ESM::BodyPart::MeshPart index, const std::string& partId) + { + mFemaleParts[index] = partId; + addOtherDependency(partId); + } + + void ActorAdapter::RaceData::setMalePart(ESM::BodyPart::MeshPart index, const std::string& partId) + { + mMaleParts[index] = partId; + addOtherDependency(partId); + } + + void ActorAdapter::RaceData::addOtherDependency(const std::string& id) + { + if (!id.empty()) mDependencies.emplace(id); + } + + void ActorAdapter::RaceData::reset_data(const std::string& id, bool isBeast) + { + mId = id; + mIsBeast = isBeast; + for (auto& str : mFemaleParts) + str.clear(); + for (auto& str : mMaleParts) + str.clear(); + mDependencies.clear(); + + // Mark self as a dependency + addOtherDependency(id); + } + + + const std::string& ActorAdapter::ActorData::getId() const + { + return mId; + } + + bool ActorAdapter::ActorData::isCreature() const + { + return mCreature; + } + + bool ActorAdapter::ActorData::isFemale() const + { + return mFemale; + } + + std::string ActorAdapter::ActorData::getSkeleton() const + { + if (mCreature || !mSkeletonOverride.empty()) + return "meshes\\" + mSkeletonOverride; + + bool firstPerson = false; + bool beast = mRaceData ? mRaceData->isBeast() : false; + bool werewolf = false; + + return SceneUtil::getActorSkeleton(firstPerson, mFemale, beast, werewolf); + } + + const std::string ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const + { + auto it = mParts.find(index); + if (it == mParts.end() && mRaceData && mRaceData->handlesPart(index)) + { + if (mFemale) + { + // Note: we should use male parts for females as fallback + const std::string femalePart = mRaceData->getFemalePart(index); + if (!femalePart.empty()) + return femalePart; + } + + return mRaceData->getMalePart(index); + } + + const std::string& partName = it->second.first; + return partName; + } + + bool ActorAdapter::ActorData::hasDependency(const std::string& id) const + { + return mDependencies.find(id) != mDependencies.end(); + } + + void ActorAdapter::ActorData::setPart(ESM::PartReferenceType index, const std::string& partId, int priority) + { + auto it = mParts.find(index); + if (it != mParts.end()) + { + if (it->second.second >= priority) + return; + } + + mParts[index] = std::make_pair(partId, priority); + addOtherDependency(partId); + } + + void ActorAdapter::ActorData::addOtherDependency(const std::string& 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) + { + mId = id; + mCreature = isCreature; + mFemale = isFemale; + mSkeletonOverride = skeleton; + mRaceData = raceData; + mParts.clear(); + mDependencies.clear(); + + // Mark self and race as a dependency + addOtherDependency(id); + if (raceData) addOtherDependency(raceData->getId()); + } + + + ActorAdapter::ActorAdapter(Data& data) + : mReferenceables(data.getReferenceables()) + , mRaces(data.getRaces()) + , mBodyParts(data.getBodyParts()) + { + // 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))); + + 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))); + + 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))); + } + + ActorAdapter::ActorDataPtr ActorAdapter::getActorData(const std::string& id) + { + // Return cached actor data if it exists + ActorDataPtr data = mCachedActors.get(id); + if (data) + { + return data; + } + + // Create the actor data + data.reset(new ActorData()); + setupActor(id, data); + mCachedActors.insert(id, data); + return data; + } + + void ActorAdapter::handleReferenceablesInserted(const QModelIndex& parent, int start, int end) + { + // Only rows added at the top level are pertinent. Others are caught by dataChanged handler. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + std::string refId = mReferenceables.getId(row); + markDirtyDependency(refId); + } + } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight) + { + int start = getHighestIndex(topLeft).row(); + int end = getHighestIndex(botRight).row(); + + // A change to record status (ex. Deleted) returns an invalid botRight + if (end == -1) + end = start; + + // Handle each record + for (int row = start; row <= end; ++row) + { + std::string refId = mReferenceables.getId(row); + markDirtyDependency(refId); + } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleReferenceablesAboutToBeRemoved(const QModelIndex& parent, int start, int end) + { + // Only rows at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + std::string refId = mReferenceables.getId(row); + markDirtyDependency(refId); + } + } + } + + void ActorAdapter::handleReferenceablesRemoved(const QModelIndex& parent, int start, int end) + { + // Changes specified in handleReferenceablesAboutToBeRemoved + updateDirty(); + } + + void ActorAdapter::handleRacesInserted(const QModelIndex& parent, int start, int end) + { + // Only rows added at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + std::string raceId = mReferenceables.getId(row); + markDirtyDependency(raceId); + } + } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleRaceChanged(const QModelIndex& topLeft, const QModelIndex& botRight) + { + int start = getHighestIndex(topLeft).row(); + int end = getHighestIndex(botRight).row(); + + // A change to record status (ex. Deleted) returns an invalid botRight + if (end == -1) + end = start; + + for (int row = start; row <= end; ++row) + { + std::string raceId = mRaces.getId(row); + markDirtyDependency(raceId); + } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleRacesAboutToBeRemoved(const QModelIndex& parent, int start, int end) + { + // Only changes at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + std::string raceId = mRaces.getId(row); + markDirtyDependency(raceId); + } + } + } + + void ActorAdapter::handleRacesRemoved(const QModelIndex& parent, int start, int end) + { + // Changes specified in handleRacesAboutToBeRemoved + updateDirty(); + } + + void ActorAdapter::handleBodyPartsInserted(const QModelIndex& parent, int start, int end) + { + // Only rows added at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + // Race specified by part may need update + auto& record = mBodyParts.getRecord(row); + if (!record.isDeleted()) + { + markDirtyDependency(record.get().mRace); + } + + std::string partId = mBodyParts.getId(row); + markDirtyDependency(partId); + } + } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight) + { + int start = getHighestIndex(topLeft).row(); + int end = getHighestIndex(botRight).row(); + + // A change to record status (ex. Deleted) returns an invalid botRight + if (end == -1) + end = start; + + for (int row = start; row <= end; ++row) + { + // Race specified by part may need update + auto& record = mBodyParts.getRecord(row); + if (!record.isDeleted()) + { + markDirtyDependency(record.get().mRace); + } + + // Update entries with a tracked dependency + std::string partId = mBodyParts.getId(row); + markDirtyDependency(partId); + } + + // Update affected + updateDirty(); + } + + void ActorAdapter::handleBodyPartsAboutToBeRemoved(const QModelIndex& parent, int start, int end) + { + // Only changes at the top are pertinent. + if (!parent.isValid()) + { + for (int row = start; row <= end; ++row) + { + std::string partId = mBodyParts.getId(row); + markDirtyDependency(partId); + } + } + } + + void ActorAdapter::handleBodyPartsRemoved(const QModelIndex& parent, int start, int end) + { + // Changes specified in handleBodyPartsAboutToBeRemoved + updateDirty(); + } + + QModelIndex ActorAdapter::getHighestIndex(QModelIndex index) const + { + while (index.parent().isValid()) + index = index.parent(); + 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) + { + // Return cached race data if it exists + RaceDataPtr data = mCachedRaces.get(id); + if (data) return data; + + // Create the race data + data.reset(new RaceData()); + setupRace(id, data); + mCachedRaces.insert(id, data); + return data; + } + + void ActorAdapter::setupActor(const std::string& id, ActorDataPtr data) + { + int index = mReferenceables.searchId(id); + if (index == -1) + { + // Record does not exist + data->reset_data(id); + emit actorChanged(id); + return; + } + + auto& record = mReferenceables.getRecord(index); + if (record.isDeleted()) + { + // Record is deleted and therefore not accessible + data->reset_data(id); + emit actorChanged(id); + return; + } + + const int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType); + int type = mReferenceables.getData(index, TypeColumn).toInt(); + if (type == UniversalId::Type_Creature) + { + // Valid creature record + setupCreature(id, data); + emit actorChanged(id); + } + else if (type == UniversalId::Type_Npc) + { + // Valid npc record + setupNpc(id, data); + emit actorChanged(id); + } + else + { + // Wrong record type + data->reset_data(id); + emit actorChanged(id); + } + } + + void ActorAdapter::setupRace(const std::string& id, RaceDataPtr data) + { + int index = mRaces.searchId(id); + if (index == -1) + { + // Record does not exist + data->reset_data(id); + return; + } + + auto& raceRecord = mRaces.getRecord(index); + if (raceRecord.isDeleted()) + { + // Record is deleted, so not accessible + data->reset_data(id); + return; + } + + auto& race = raceRecord.get(); + data->reset_data(id, race.mData.mFlags & ESM::Race::Beast); + + // 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()) + { + // Record is deleted, so not accessible. + continue; + } + + auto& part = partRecord.get(); + if (part.mRace == id && part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId)) + { + 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); + } + } + } + + void ActorAdapter::setupNpc(const std::string& id, ActorDataPtr data) + { + // Common setup, record is known to exist and is not deleted + int index = mReferenceables.searchId(id); + auto& npc = dynamic_cast&>(mReferenceables.getRecord(index)).get(); + + RaceDataPtr raceData = getRaceData(npc.mRace); + data->reset_data(id, "", false, !npc.isMale(), raceData); + + // Add inventory items + for (auto& item : npc.mInventory.mList) + { + if (item.mCount <= 0) continue; + std::string itemId = item.mItem.toString(); + addNpcItem(itemId, data); + } + + // Add head and hair + data->setPart(ESM::PRT_Head, npc.mHead, 0); + data->setPart(ESM::PRT_Hair, npc.mHair, 0); + } + + void ActorAdapter::addNpcItem(const std::string& itemId, ActorDataPtr data) + { + int index = mReferenceables.searchId(itemId); + if (index == -1) + { + // Item does not exist yet + data->addOtherDependency(itemId); + return; + } + + auto& record = mReferenceables.getRecord(index); + if (record.isDeleted()) + { + // Item cannot be accessed yet + data->addOtherDependency(itemId); + return; + } + + // Convenience function to add a parts list to actor data + auto addParts = [&](const std::vector& list, int priority) { + for (auto& part : list) + { + std::string partId; + auto partType = (ESM::PartReferenceType) part.mPart; + + if (data->isFemale()) + partId = part.mFemale; + if (partId.empty()) + partId = part.mMale; + + data->setPart(partType, partId, priority); + } + }; + + int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType); + int type = mReferenceables.getData(index, TypeColumn).toInt(); + if (type == UniversalId::Type_Armor) + { + auto& armor = dynamic_cast&>(record).get(); + addParts(armor.mParts.mParts, 1); + + // Changing parts could affect what is picked for rendering + data->addOtherDependency(itemId); + } + else if (type == UniversalId::Type_Clothing) + { + int priority = 0; + // TODO: reserve bodyparts for robes and skirts + auto& clothing = dynamic_cast&>(record).get(); + + if (clothing.mData.mType == ESM::Clothing::Robe) + { + auto reservedList = std::vector(); + + ESM::PartReference pr; + pr.mMale = ""; + pr.mFemale = ""; + + 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 + }; + size_t parts_size = sizeof(parts)/sizeof(parts[0]); + for(size_t p = 0;p < parts_size;++p) + { + pr.mPart = parts[p]; + reservedList.push_back(pr); + } + + priority = parts_size; + addParts(reservedList, priority); + } + else if (clothing.mData.mType == ESM::Clothing::Skirt) + { + auto reservedList = std::vector(); + + ESM::PartReference pr; + pr.mMale = ""; + pr.mFemale = ""; + + ESM::PartReferenceType parts[] = { + ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg + }; + size_t parts_size = sizeof(parts)/sizeof(parts[0]); + for(size_t p = 0;p < parts_size;++p) + { + pr.mPart = parts[p]; + reservedList.push_back(pr); + } + + priority = parts_size; + addParts(reservedList, priority); + } + + addParts(clothing.mParts.mParts, priority); + + // Changing parts could affect what is picked for rendering + data->addOtherDependency(itemId); + } + } + + void ActorAdapter::setupCreature(const std::string& id, ActorDataPtr data) + { + // Record is known to exist and is not deleted + int index = mReferenceables.searchId(id); + auto& creature = dynamic_cast&>(mReferenceables.getRecord(index)).get(); + + data->reset_data(id, creature.mModel, true); + } + + void ActorAdapter::markDirtyDependency(const std::string& dep) + { + for (auto raceIt : mCachedRaces) + { + if (raceIt->hasDependency(dep)) + mDirtyRaces.emplace(raceIt->getId()); + } + for (auto actorIt : mCachedActors) + { + if (actorIt->hasDependency(dep)) + mDirtyActors.emplace(actorIt->getId()); + } + } + + void ActorAdapter::updateDirty() + { + // Handle races before actors, since actors are dependent on race + for (auto& race : mDirtyRaces) + { + RaceDataPtr data = mCachedRaces.get(race); + if (data) + { + setupRace(race, data); + // Race was changed. Need to mark actor dependencies as dirty. + // Cannot use markDirtyDependency because that would invalidate + // the current iterator. + for (auto actorIt : mCachedActors) + { + if (actorIt->hasDependency(race)) + mDirtyActors.emplace(actorIt->getId()); + } + } + } + mDirtyRaces.clear(); + + for (auto& actor : mDirtyActors) + { + ActorDataPtr data = mCachedActors.get(actor); + if (data) + { + setupActor(actor, data); + } + } + mDirtyActors.clear(); + } +} diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp new file mode 100644 index 0000000000..1c9265be42 --- /dev/null +++ b/apps/opencs/model/world/actoradapter.hpp @@ -0,0 +1,174 @@ +#ifndef CSM_WOLRD_ACTORADAPTER_H +#define CSM_WOLRD_ACTORADAPTER_H + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "refidcollection.hpp" +#include "idcollection.hpp" + +namespace ESM +{ + struct Race; +} + +namespace CSMWorld +{ + class Data; + + /// Adapts multiple collections to provide the data needed to render + /// an npc or creature. + class ActorAdapter : public QObject + { + Q_OBJECT + public: + + /// A list indexed by ESM::PartReferenceType + using ActorPartList = std::map>; + /// A list indexed by ESM::BodyPart::MeshPart + using RacePartList = std::array; + /// Tracks unique strings + using StringSet = std::unordered_set; + + + /// Contains base race data shared between actors + class RaceData + { + public: + /// Retrieves the id of the race represented + const std::string& 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; + /// Retrieves the associated body part + const std::string& getMalePart(ESM::PartReferenceType index) const; + /// Checks if the race has a data dependency + bool hasDependency(const std::string& id) const; + + /// Sets the associated part if it's empty and marks a dependency + void setFemalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId); + /// Sets the associated part if it's empty and marks a dependency + void setMalePart(ESM::BodyPart::MeshPart partIndex, const std::string& partId); + /// Marks an additional dependency + void addOtherDependency(const std::string& id); + /// Clears parts and dependencies + void reset_data(const std::string& raceId, bool isBeast=false); + + private: + bool handles(ESM::PartReferenceType type) const; + std::string mId; + bool mIsBeast; + RacePartList mFemaleParts; + RacePartList mMaleParts; + StringSet mDependencies; + }; + using RaceDataPtr = std::shared_ptr; + + /// Contains all the data needed to render an actor. Tracks dependencies + /// so that pertinent data changes can be checked. + class ActorData + { + public: + /// Retrieves the id of the actor represented + const std::string& getId() const; + /// Checks if the actor is a creature + bool isCreature() const; + /// Checks if the actor is female + bool isFemale() const; + /// 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; + /// Checks if the actor has a data dependency + bool hasDependency(const std::string& id) const; + + /// Sets the actor part used and marks a dependency + void setPart(ESM::PartReferenceType partIndex, const std::string& partId, int priority); + /// Marks an additional dependency for the actor + void addOtherDependency(const std::string& 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); + + private: + std::string mId; + bool mCreature; + bool mFemale; + std::string mSkeletonOverride; + RaceDataPtr mRaceData; + ActorPartList mParts; + StringSet mDependencies; + }; + using ActorDataPtr = std::shared_ptr; + + + ActorAdapter(Data& data); + + /// Obtains the shared data for a given actor + ActorDataPtr getActorData(const std::string& refId); + + signals: + + void actorChanged(const std::string& refId); + + public slots: + + void handleReferenceablesInserted(const QModelIndex&, int, int); + void handleReferenceableChanged(const QModelIndex&, const QModelIndex&); + void handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int); + void handleReferenceablesRemoved(const QModelIndex&, int, int); + + void handleRacesInserted(const QModelIndex&, int, int); + void handleRaceChanged(const QModelIndex&, const QModelIndex&); + void handleRacesAboutToBeRemoved(const QModelIndex&, int, int); + void handleRacesRemoved(const QModelIndex&, int, int); + + void handleBodyPartsInserted(const QModelIndex&, int, int); + void handleBodyPartChanged(const QModelIndex&, const QModelIndex&); + void handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int); + 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); + + void setupActor(const std::string& id, ActorDataPtr data); + void setupRace(const std::string& id, RaceDataPtr data); + + void setupNpc(const std::string& id, ActorDataPtr data); + void addNpcItem(const std::string& itemId, ActorDataPtr data); + + void setupCreature(const std::string& id, ActorDataPtr data); + + void markDirtyDependency(const std::string& dependency); + void updateDirty(); + + RefIdCollection& mReferenceables; + IdCollection& mRaces; + IdCollection& mBodyParts; + + Misc::WeakCache mCachedActors; // Key: referenceable id + Misc::WeakCache mCachedRaces; // Key: race id + + StringSet mDirtyActors; // Actors that need updating + StringSet mDirtyRaces; // Races that need updating + }; +} + +#endif diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp index abb3bc82eb..dbe90b9061 100644 --- a/apps/opencs/model/world/cellcoordinates.cpp +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -5,6 +5,8 @@ #include #include +#include + CSMWorld::CellCoordinates::CellCoordinates() : mX (0), mY (0) {} CSMWorld::CellCoordinates::CellCoordinates (int x, int y) : mX (x), mY (y) {} @@ -61,9 +63,7 @@ std::pair CSMWorld::CellCoordinates::fromId ( std::pair CSMWorld::CellCoordinates::coordinatesToCellIndex (float x, float y) { - const int cellSize = 8192; - - return std::make_pair (std::floor (x/cellSize), std::floor (y/cellSize)); + return std::make_pair (std::floor (x / Constants::CellSizeInUnits), std::floor (y / Constants::CellSizeInUnits)); } bool CSMWorld::operator== (const CellCoordinates& left, const CellCoordinates& right) diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index 49e4bebe6a..b202a97d93 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -318,7 +318,7 @@ namespace CSMWorld QVariant BodyPartRaceColumn::get(const Record &record) const { - if (mMeshType != NULL && mMeshType->get(record) == ESM::BodyPart::MT_Skin) + if (mMeshType != nullptr && mMeshType->get(record) == ESM::BodyPart::MT_Skin) { return QString::fromUtf8(record.get().mRace.c_str()); } diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 79900c6c4c..4c70964791 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -244,7 +244,7 @@ void CSMWorld::CreateCommand::applyModifications() if (!mNestedValues.empty()) { CSMWorld::IdTree *tree = dynamic_cast(&mModel); - if (tree == NULL) + if (tree == nullptr) { throw std::logic_error("CSMWorld::CreateCommand: Attempt to add nested values to the non-nested model"); } @@ -445,16 +445,14 @@ void CSMWorld::UpdateCellCommand::redo() int cellColumn = mModel.searchColumnIndex (Columns::ColumnId_Cell); mIndex = mModel.index (mRow, cellColumn); - const int cellSize = 8192; - QModelIndex xIndex = mModel.index ( mRow, mModel.findColumnIndex (Columns::ColumnId_PositionXPos)); QModelIndex yIndex = mModel.index ( mRow, mModel.findColumnIndex (Columns::ColumnId_PositionYPos)); - int x = std::floor (mModel.data (xIndex).toFloat() / cellSize); - int y = std::floor (mModel.data (yIndex).toFloat() / cellSize); + int x = std::floor (mModel.data (xIndex).toFloat() / Constants::CellSizeInUnits); + int y = std::floor (mModel.data (yIndex).toFloat() / Constants::CellSizeInUnits); std::ostringstream stream; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 053754943e..f3f897a292 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -572,6 +572,8 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat UniversalId::Type_Video); addModel (new IdTable (&mMetaData), UniversalId::Type_MetaData); + mActorAdapter.reset(new ActorAdapter(*this)); + mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files } @@ -912,6 +914,16 @@ QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& return iter->second; } +const CSMWorld::ActorAdapter* CSMWorld::Data::getActorAdapter() const +{ + return mActorAdapter.get(); +} + +CSMWorld::ActorAdapter* CSMWorld::Data::getActorAdapter() +{ + return mActorAdapter.get(); +} + void CSMWorld::Data::merge() { mGlobals.merge(); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 1b975f4308..e50780f507 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -36,6 +36,7 @@ #include "../doc/stage.hpp" +#include "actoradapter.hpp" #include "idcollection.hpp" #include "nestedidcollection.hpp" #include "universalid.hpp" @@ -110,6 +111,7 @@ namespace CSMWorld RefCollection mRefs; IdCollection mFilters; Collection mMetaData; + std::unique_ptr mActorAdapter; const Fallback::Map* mFallbackMap; std::vector mModels; std::map mModelIndex; @@ -287,6 +289,10 @@ namespace CSMWorld /// \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; + + ActorAdapter* getActorAdapter(); + void merge(); ///< Merge modified into base. diff --git a/apps/opencs/model/world/idcompletionmanager.cpp b/apps/opencs/model/world/idcompletionmanager.cpp index 9fa6e3addf..649a960387 100644 --- a/apps/opencs/model/world/idcompletionmanager.cpp +++ b/apps/opencs/model/world/idcompletionmanager.cpp @@ -92,7 +92,7 @@ void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data &data) { QAbstractItemModel *model = data.getTableModel(current->second); CSMWorld::IdTableBase *table = dynamic_cast(model); - if (table != NULL) + if (table != nullptr) { int idColumn = table->searchColumnIndex(CSMWorld::Columns::ColumnId_Id); if (idColumn != -1) diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 6d50e9edb0..534e5dd63b 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -18,7 +18,7 @@ namespace void CSMWorld::IdTableProxyModel::updateColumnMap() { - Q_ASSERT(mSourceModel != NULL); + Q_ASSERT(mSourceModel != nullptr); mColumnMap.clear(); if (mFilter) @@ -33,7 +33,7 @@ void CSMWorld::IdTableProxyModel::updateColumnMap() bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const { - Q_ASSERT(mSourceModel != NULL); + Q_ASSERT(mSourceModel != nullptr); // It is not possible to use filterAcceptsColumn() and check for // sourceModel()->headerData (sourceColumn, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags) @@ -51,14 +51,14 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelI CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) : QSortFilterProxyModel (parent), - mSourceModel(NULL) + mSourceModel(nullptr) { setSortCaseSensitivity (Qt::CaseInsensitive); } QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const { - Q_ASSERT(mSourceModel != NULL); + Q_ASSERT(mSourceModel != nullptr); return mapFromSource(mSourceModel->getModelIndex (id, column)); } @@ -113,7 +113,7 @@ bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex &left, const QModel QString CSMWorld::IdTableProxyModel::getRecordId(int sourceRow) const { - Q_ASSERT(mSourceModel != NULL); + Q_ASSERT(mSourceModel != nullptr); int idColumn = mSourceModel->findColumnIndex(Columns::ColumnId_Id); return mSourceModel->data(mSourceModel->index(sourceRow, idColumn)).toString(); diff --git a/apps/opencs/model/world/infotableproxymodel.cpp b/apps/opencs/model/world/infotableproxymodel.cpp index 4f0b2e7528..7c6a9c323e 100644 --- a/apps/opencs/model/world/infotableproxymodel.cpp +++ b/apps/opencs/model/world/infotableproxymodel.cpp @@ -28,7 +28,7 @@ void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel *sourceMod { IdTableProxyModel::setSourceModel(sourceModel); - if (mSourceModel != NULL) + if (mSourceModel != nullptr) { mInfoColumnIndex = mSourceModel->findColumnIndex(mInfoColumnId); mFirstRowCache.clear(); @@ -37,7 +37,7 @@ void CSMWorld::InfoTableProxyModel::setSourceModel(QAbstractItemModel *sourceMod bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - Q_ASSERT(mSourceModel != NULL); + Q_ASSERT(mSourceModel != nullptr); QModelIndex first = mSourceModel->index(getFirstInfoRow(left.row()), left.column()); QModelIndex second = mSourceModel->index(getFirstInfoRow(right.row()), right.column()); @@ -52,7 +52,7 @@ bool CSMWorld::InfoTableProxyModel::lessThan(const QModelIndex &left, const QMod int CSMWorld::InfoTableProxyModel::getFirstInfoRow(int currentRow) const { - Q_ASSERT(mSourceModel != NULL); + Q_ASSERT(mSourceModel != nullptr); int row = currentRow; int column = mInfoColumnIndex; diff --git a/apps/opencs/model/world/metadata.cpp b/apps/opencs/model/world/metadata.cpp index 960fdc9e4e..b2fa3487cd 100644 --- a/apps/opencs/model/world/metadata.cpp +++ b/apps/opencs/model/world/metadata.cpp @@ -14,8 +14,8 @@ void CSMWorld::MetaData::blank() void CSMWorld::MetaData::load (ESM::ESMReader& esm) { mFormat = esm.getHeader().mFormat; - mAuthor = esm.getHeader().mData.author.toString(); - mDescription = esm.getHeader().mData.desc.toString(); + mAuthor = esm.getHeader().mData.author; + mDescription = esm.getHeader().mData.desc; } void CSMWorld::MetaData::save (ESM::ESMWriter& esm) const diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 5ac9ecb188..1948a95cbf 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -453,13 +453,13 @@ void CSMWorld::ContainerRefIdAdapter::setData (const RefIdColumn *column, RefIdD CSMWorld::CreatureColumns::CreatureColumns (const ActorColumns& actorColumns) : ActorColumns (actorColumns), - mType(NULL), - mScale(NULL), - mOriginal(NULL), - mAttributes(NULL), - mAttacks(NULL), - mMisc(NULL), - mBloodType(NULL) + mType(nullptr), + mScale(nullptr), + mOriginal(nullptr), + mAttributes(nullptr), + mAttacks(nullptr), + mMisc(nullptr), + mBloodType(nullptr) {} CSMWorld::CreatureRefIdAdapter::CreatureRefIdAdapter (const CreatureColumns& columns) @@ -748,16 +748,16 @@ void CSMWorld::MiscRefIdAdapter::setData (const RefIdColumn *column, RefIdData& CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) : ActorColumns (actorColumns), - mRace(NULL), - mClass(NULL), - mFaction(NULL), - mHair(NULL), - mHead(NULL), - mAttributes(NULL), - mSkills(NULL), - mMisc(NULL), - mBloodType(NULL), - mGender(NULL) + 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) diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 6716c72408..272722f649 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -437,7 +437,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.push_back (RefIdColumn (Columns::ColumnId_Radius, ColumnBase::Display_Integer)); lightColumns.mRadius = &mColumns.back(); - mColumns.push_back (RefIdColumn (Columns::ColumnId_Colour, ColumnBase::Display_Integer)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_Colour, ColumnBase::Display_Colour)); lightColumns.mColor = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_Sound, ColumnBase::Display_Sound)); diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index 62dfe53a9e..1af9c5e9b1 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -3,7 +3,7 @@ #include CSMWorld::ResourcesManager::ResourcesManager() - : mVFS(NULL) + : mVFS(nullptr) { } diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 8d7a7761e4..486f3770aa 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -18,47 +18,43 @@ namespace static const TypeData sNoArg[] = { { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "-", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_TopicInfos, "Topic Infos", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_JournalInfos, "Journal Infos", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Enchantments, "Enchantments", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, - "Objects", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, - "Instances", 0 }, - { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, - "Region Map", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", 0 }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", 0 }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", 0 }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Music Files", 0 }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_SoundsRes, "Sound Files", 0 }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", 0 }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", 0 }, - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "LandTextures", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", 0 }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Meta Data Table", 0 }, - + { 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" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", ":./faction.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", ":./race.png" }, + { 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_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_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_Filters, "Filters", ":./filter.png" }, + { 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_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 }; @@ -81,7 +77,7 @@ namespace { 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", 0 }, + { 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_Potion, "Potion", ":./potion.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", ":./apparatus.png" }, @@ -93,9 +89,9 @@ namespace { 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_CreatureLevelledList, - "Creature Levelled List", ":./leveled-creature.png" }, + "Creature Levelled List", ":./levelled-creature.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList, - "Item Levelled List", ":./leveled-item.png" }, + "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, @@ -105,35 +101,35 @@ namespace { 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", 0 }, + { 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", 0 }, - { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", 0 }, + { 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_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_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_Video, "Video", ":resources-video"}, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", 0 }, + { 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_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_Land, "Land", ":./land-heightmap.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "LandTexture", ":./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", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Meta Data", 0 }, + { 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[] = { - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results", 0 }, - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_LoadErrorLog, "Load Error Log", 0 }, - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", 0 }, + { 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 }; } diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index b6f4aaec31..7693276c63 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -33,6 +33,11 @@ void CSVDoc::FileDialog::addFiles(const QString &path) mSelector->addFiles(path); } +void CSVDoc::FileDialog::setEncoding(const QString &encoding) +{ + mSelector->setEncoding(encoding); +} + void CSVDoc::FileDialog::clearFiles() { mSelector->clearFiles(); @@ -184,7 +189,7 @@ void CSVDoc::FileDialog::slotRejected() if(mFileWidget) { delete mFileWidget; - mFileWidget = NULL; + mFileWidget = nullptr; } close(); } @@ -195,7 +200,7 @@ void CSVDoc::FileDialog::slotNewFile() if(mFileWidget) { delete mFileWidget; - mFileWidget = NULL; + mFileWidget = nullptr; } disconnect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); close(); diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index ca6145b9c6..69acfac3d1 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -42,6 +42,7 @@ namespace CSVDoc void showDialog (ContentAction action); void addFiles (const QString &path); + void setEncoding (const QString &encoding); void clearFiles (); QString filename() const; diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index 8402bf79a6..44b81743f9 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -64,7 +64,7 @@ namespace CSVDoc void updateTitle(); - void updateSubViewIndices (SubView *view = NULL); + void updateSubViewIndices (SubView *view = nullptr); void universalIdChanged (const CSMWorld::UniversalId& universalId); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 3ebefd69a6..ed4dcff76f 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -47,58 +47,40 @@ void CSVDoc::View::setupFileMenu() { QMenu *file = menuBar()->addMenu (tr ("File")); - QAction *newGame = new QAction (tr ("New Game"), this); + QAction* newGame = createMenuEntry("New Game", ":./menu-new-game.png", file, "document-file-newgame"); connect (newGame, SIGNAL (triggered()), this, SIGNAL (newGameRequest())); - setupShortcut("document-file-newgame", newGame); - file->addAction (newGame); - - QAction *newAddon = new QAction (tr ("New Addon"), this); + QAction* newAddon = createMenuEntry("New Addon", ":./menu-new-addon.png", file, "document-file-newaddon"); connect (newAddon, SIGNAL (triggered()), this, SIGNAL (newAddonRequest())); - setupShortcut("document-file-newaddon", newAddon); - file->addAction (newAddon); - QAction *open = new QAction (tr ("Open"), this); + QAction* open = createMenuEntry("Open", ":./menu-open.png", file, "document-file-open"); connect (open, SIGNAL (triggered()), this, SIGNAL (loadDocumentRequest())); - setupShortcut("document-file-open", open); - file->addAction (open); - mSave = new QAction (tr ("Save"), this); - connect (mSave, SIGNAL (triggered()), this, SLOT (save())); - setupShortcut("document-file-save", mSave); - file->addAction (mSave); + QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save"); + connect (save, SIGNAL (triggered()), this, SLOT (save())); + mSave = save; - mVerify = new QAction (tr ("Verify"), this); - connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); - setupShortcut("document-file-verify", mVerify); - file->addAction (mVerify); + QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify"); + connect (verify, SIGNAL (triggered()), this, SLOT (verify())); + mVerify = verify; - mMerge = new QAction (tr ("Merge"), this); - connect (mMerge, SIGNAL (triggered()), this, SLOT (merge())); - setupShortcut("document-file-merge", mMerge); - file->addAction (mMerge); + QAction* merge = createMenuEntry("Merge", ":./menu-merge.png", file, "document-file-merge"); + connect (merge, SIGNAL (triggered()), this, SLOT (merge())); + mMerge = merge; - QAction *loadErrors = new QAction (tr ("Open Load Error Log"), this); + QAction* loadErrors = createMenuEntry("Error Log", ":./error-log.png", file, "document-file-errorlog"); connect (loadErrors, SIGNAL (triggered()), this, SLOT (loadErrorLog())); - setupShortcut("document-file-errorlog", loadErrors); - file->addAction (loadErrors); - QAction *meta = new QAction (tr ("Meta Data"), this); + QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata"); connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView())); - setupShortcut("document-file-metadata", meta); - file->addAction (meta); - QAction *close = new QAction (tr ("Close Document"), this); + QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close"); connect (close, SIGNAL (triggered()), this, SLOT (close())); - setupShortcut("document-file-close", close); - file->addAction(close); - QAction *exit = new QAction (tr ("Exit Application"), this); + QAction* exit = createMenuEntry("Exit", ":./menu-exit.png", file, "document-file-exit"); connect (exit, SIGNAL (triggered()), this, SLOT (exit())); - connect (this, SIGNAL(exitApplicationRequest(CSVDoc::View *)), &mViewManager, SLOT(exitApplication(CSVDoc::View *))); - setupShortcut("document-file-exit", exit); - file->addAction(exit); + connect (this, SIGNAL(exitApplicationRequest(CSVDoc::View *)), &mViewManager, SLOT(exitApplication(CSVDoc::View *))); } namespace @@ -130,251 +112,174 @@ void CSVDoc::View::setupEditMenu() mUndo = mDocument->getUndoStack().createUndoAction (this, tr("Undo")); setupShortcut("document-edit-undo", mUndo); connect(mUndo, SIGNAL (changed ()), this, SLOT (undoActionChanged ())); + mUndo->setIcon(QIcon(QString::fromStdString(":./menu-undo.png"))); edit->addAction (mUndo); mRedo = mDocument->getUndoStack().createRedoAction (this, tr("Redo")); connect(mRedo, SIGNAL (changed ()), this, SLOT (redoActionChanged ())); setupShortcut("document-edit-redo", mRedo); + mRedo->setIcon(QIcon(QString::fromStdString(":./menu-redo.png"))); edit->addAction (mRedo); - QAction *userSettings = new QAction (tr ("Preferences"), this); + QAction* userSettings = createMenuEntry("Preferences", ":./menu-preferences.png", edit, "document-edit-preferences"); connect (userSettings, SIGNAL (triggered()), this, SIGNAL (editSettingsRequest())); - setupShortcut("document-edit-preferences", userSettings); - edit->addAction (userSettings); - QAction *search = new QAction (tr ("Search"), this); + QAction* search = createMenuEntry(CSMWorld::UniversalId::Type_Search, edit, "document-edit-search"); connect (search, SIGNAL (triggered()), this, SLOT (addSearchSubView())); - setupShortcut("document-edit-search", search); - edit->addAction (search); } void CSVDoc::View::setupViewMenu() { QMenu *view = menuBar()->addMenu (tr ("View")); - QAction *newWindow = new QAction (tr ("New View"), this); + QAction *newWindow = createMenuEntry("New View", ":./menu-new-window.png", view, "document-view-newview"); connect (newWindow, SIGNAL (triggered()), this, SLOT (newView())); - setupShortcut("document-view-newview", newWindow); - view->addAction (newWindow); - mShowStatusBar = new QAction (tr ("Toggle Status Bar"), this); - mShowStatusBar->setCheckable (true); + mShowStatusBar = createMenuEntry("Toggle Status Bar", ":./menu-status-bar.png", view, "document-view-statusbar"); connect (mShowStatusBar, SIGNAL (toggled (bool)), this, SLOT (toggleShowStatusBar (bool))); - setupShortcut("document-view-statusbar", mShowStatusBar); - + mShowStatusBar->setCheckable (true); mShowStatusBar->setChecked (CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); view->addAction (mShowStatusBar); - QAction *filters = new QAction (tr ("Filters"), this); + QAction *filters = createMenuEntry(CSMWorld::UniversalId::Type_Filters, view, "document-mechanics-filters"); connect (filters, SIGNAL (triggered()), this, SLOT (addFiltersSubView())); - setupShortcut("document-view-filters", filters); - view->addAction (filters); } void CSVDoc::View::setupWorldMenu() { QMenu *world = menuBar()->addMenu (tr ("World")); - QAction *regions = new QAction (tr ("Regions"), this); + QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions"); connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView())); - setupShortcut("document-world-regions", regions); - world->addAction (regions); - QAction *cells = new QAction (tr ("Cells"), this); + QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells"); connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView())); - setupShortcut("document-world-cells", cells); - world->addAction (cells); - QAction *referenceables = new QAction (tr ("Objects"), this); + QAction* referenceables = createMenuEntry(CSMWorld::UniversalId::Type_Referenceables, world, "document-world-referencables"); connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView())); - setupShortcut("document-world-referencables", referenceables); - world->addAction (referenceables); - QAction *references = new QAction (tr ("Instances"), this); + QAction* references = createMenuEntry(CSMWorld::UniversalId::Type_References, world, "document-world-references"); connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView())); - setupShortcut("document-world-references", references); - world->addAction (references); - QAction *lands = new QAction (tr ("Lands"), this); + QAction *lands = createMenuEntry(CSMWorld::UniversalId::Type_Lands, world, "document-world-lands"); connect (lands, SIGNAL (triggered()), this, SLOT (addLandsSubView())); - setupShortcut("document-world-lands", lands); - world->addAction (lands); - QAction *landTextures = new QAction (tr ("Land Textures"), this); + QAction *landTextures = createMenuEntry(CSMWorld::UniversalId::Type_LandTextures, world, "document-world-landtextures"); connect (landTextures, SIGNAL (triggered()), this, SLOT (addLandTexturesSubView())); - setupShortcut("document-world-landtextures", landTextures); - world->addAction (landTextures); - QAction *grid = new QAction (tr ("Pathgrid"), this); + QAction *grid = createMenuEntry(CSMWorld::UniversalId::Type_Pathgrids, world, "document-world-pathgrid"); connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView())); - setupShortcut("document-world-pathgrid", grid); - world->addAction (grid); world->addSeparator(); // items that don't represent single record lists follow here - QAction *regionMap = new QAction (tr ("Region Map"), this); + QAction *regionMap = createMenuEntry(CSMWorld::UniversalId::Type_RegionMap, world, "document-world-regionmap"); connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView())); - setupShortcut("document-world-regionmap", regionMap); - world->addAction (regionMap); } void CSVDoc::View::setupMechanicsMenu() { QMenu *mechanics = menuBar()->addMenu (tr ("Mechanics")); - QAction *globals = new QAction (tr ("Globals"), this); + QAction* globals = createMenuEntry(CSMWorld::UniversalId::Type_Globals, mechanics, "document-mechanics-globals"); connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView())); - setupShortcut("document-mechanics-globals", globals); - mechanics->addAction (globals); - QAction *gmsts = new QAction (tr ("Game Settings"), this); + QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings"); connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); - setupShortcut("document-mechanics-gamesettings", gmsts); - mechanics->addAction (gmsts); - QAction *scripts = new QAction (tr ("Scripts"), this); + QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts"); connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); - setupShortcut("document-mechanics-scripts", scripts); - mechanics->addAction (scripts); - QAction *spells = new QAction (tr ("Spells"), this); + QAction* spells = createMenuEntry(CSMWorld::UniversalId::Type_Spells, mechanics, "document-mechanics-spells"); connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView())); - setupShortcut("document-mechanics-spells", spells); - mechanics->addAction (spells); - QAction *enchantments = new QAction (tr ("Enchantments"), this); + QAction* enchantments = createMenuEntry(CSMWorld::UniversalId::Type_Enchantments, mechanics, "document-mechanics-enchantments"); connect (enchantments, SIGNAL (triggered()), this, SLOT (addEnchantmentsSubView())); - setupShortcut("document-mechanics-enchantments", enchantments); - mechanics->addAction (enchantments); - QAction *effects = new QAction (tr ("Magic Effects"), this); - connect (effects, SIGNAL (triggered()), this, SLOT (addMagicEffectsSubView())); - setupShortcut("document-mechanics-magiceffects", effects); - mechanics->addAction (effects); + QAction* magicEffects = createMenuEntry(CSMWorld::UniversalId::Type_MagicEffects, mechanics, "document-mechanics-magiceffects"); + connect (magicEffects, SIGNAL (triggered()), this, SLOT (addMagicEffectsSubView())); - QAction *startScripts = new QAction (tr ("Start Scripts"), this); + QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts"); connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView())); - setupShortcut("document-mechanics-startscripts", startScripts); - mechanics->addAction (startScripts); } void CSVDoc::View::setupCharacterMenu() { QMenu *characters = menuBar()->addMenu (tr ("Characters")); - QAction *skills = new QAction (tr ("Skills"), this); + QAction* skills = createMenuEntry(CSMWorld::UniversalId::Type_Skills, characters, "document-character-skills"); connect (skills, SIGNAL (triggered()), this, SLOT (addSkillsSubView())); - setupShortcut("document-character-skills", skills); - characters->addAction (skills); - QAction *classes = new QAction (tr ("Classes"), this); + QAction* classes = createMenuEntry(CSMWorld::UniversalId::Type_Classes, characters, "document-character-classes"); connect (classes, SIGNAL (triggered()), this, SLOT (addClassesSubView())); - setupShortcut("document-character-classes", classes); - characters->addAction (classes); - QAction *factions = new QAction (tr ("Factions"), this); + QAction* factions = createMenuEntry(CSMWorld::UniversalId::Type_Faction, characters, "document-character-factions"); connect (factions, SIGNAL (triggered()), this, SLOT (addFactionsSubView())); - setupShortcut("document-character-factions", factions); - characters->addAction (factions); - QAction *races = new QAction (tr ("Races"), this); + QAction* races = createMenuEntry(CSMWorld::UniversalId::Type_Races, characters, "document-character-races"); connect (races, SIGNAL (triggered()), this, SLOT (addRacesSubView())); - setupShortcut("document-character-races", races); - characters->addAction (races); - QAction *birthsigns = new QAction (tr ("Birthsigns"), this); + QAction* birthsigns = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns"); connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView())); - setupShortcut("document-character-birthsigns", birthsigns); - characters->addAction (birthsigns); - QAction *topics = new QAction (tr ("Topics"), this); + QAction* topics = createMenuEntry(CSMWorld::UniversalId::Type_Topics, characters, "document-character-topics"); connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView())); - setupShortcut("document-character-topics", topics); - characters->addAction (topics); - QAction *journals = new QAction (tr ("Journals"), this); + QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals"); connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); - setupShortcut("document-character-journals", journals); - characters->addAction (journals); - QAction *topicInfos = new QAction (tr ("Topic Infos"), this); + QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos"); connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView())); - setupShortcut("document-character-topicinfos", topicInfos); - characters->addAction (topicInfos); - QAction *journalInfos = new QAction (tr ("Journal Infos"), this); + QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos"); connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView())); - setupShortcut("document-character-journalinfos", journalInfos); - characters->addAction (journalInfos); - QAction *bodyParts = new QAction (tr ("Body Parts"), this); + QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts"); connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView())); - setupShortcut("document-character-bodyparts", bodyParts); - characters->addAction (bodyParts); } void CSVDoc::View::setupAssetsMenu() { QMenu *assets = menuBar()->addMenu (tr ("Assets")); - QAction *reload = new QAction (tr ("Reload"), this); + QAction* reload = createMenuEntry("Reload", ":./menu-reload.png", assets, "document-assets-reload"); connect (reload, SIGNAL (triggered()), &mDocument->getData(), SLOT (assetsChanged())); - setupShortcut("document-assets-reload", reload); - assets->addAction (reload); assets->addSeparator(); - QAction *sounds = new QAction (tr ("Sounds"), this); + QAction* sounds = createMenuEntry(CSMWorld::UniversalId::Type_Sounds, assets, "document-assets-sounds"); connect (sounds, SIGNAL (triggered()), this, SLOT (addSoundsSubView())); - setupShortcut("document-assets-sounds", sounds); - assets->addAction (sounds); - QAction *soundGens = new QAction (tr ("Sound Generators"), this); + QAction* soundGens = createMenuEntry(CSMWorld::UniversalId::Type_SoundGens, assets, "document-assets-soundgens"); connect (soundGens, SIGNAL (triggered()), this, SLOT (addSoundGensSubView())); - setupShortcut("document-assets-soundgens", soundGens); - assets->addAction (soundGens); assets->addSeparator(); // resources follow here - QAction *meshes = new QAction (tr ("Meshes"), this); + QAction* meshes = createMenuEntry(CSMWorld::UniversalId::Type_Meshes, assets, "document-assets-meshes"); connect (meshes, SIGNAL (triggered()), this, SLOT (addMeshesSubView())); - setupShortcut("document-assets-meshes", meshes); - assets->addAction (meshes); - QAction *icons = new QAction (tr ("Icons"), this); + QAction* icons = createMenuEntry(CSMWorld::UniversalId::Type_Icons, assets, "document-assets-icons"); connect (icons, SIGNAL (triggered()), this, SLOT (addIconsSubView())); - setupShortcut("document-assets-icons", icons); - assets->addAction (icons); - QAction *musics = new QAction (tr ("Music"), this); + QAction* musics = createMenuEntry(CSMWorld::UniversalId::Type_Musics, assets, "document-assets-musics"); connect (musics, SIGNAL (triggered()), this, SLOT (addMusicsSubView())); - setupShortcut("document-assets-music", musics); - assets->addAction (musics); - QAction *soundsRes = new QAction (tr ("Sound Files"), this); - connect (soundsRes, SIGNAL (triggered()), this, SLOT (addSoundsResSubView())); - setupShortcut("document-assets-soundres", soundsRes); - assets->addAction (soundsRes); + QAction* soundFiles = createMenuEntry(CSMWorld::UniversalId::Type_SoundsRes, assets, "document-assets-soundres"); + connect (soundFiles, SIGNAL (triggered()), this, SLOT (addSoundsResSubView())); - QAction *textures = new QAction (tr ("Textures"), this); + QAction* textures = createMenuEntry(CSMWorld::UniversalId::Type_Textures, assets, "document-assets-textures"); connect (textures, SIGNAL (triggered()), this, SLOT (addTexturesSubView())); - setupShortcut("document-assets-textures", textures); - assets->addAction (textures); - QAction *videos = new QAction (tr ("Videos"), this); + QAction* videos = createMenuEntry(CSMWorld::UniversalId::Type_Videos, assets, "document-assets-videos"); connect (videos, SIGNAL (triggered()), this, SLOT (addVideosSubView())); - setupShortcut("document-assets-videos", videos); - assets->addAction (videos); } void CSVDoc::View::setupDebugMenu() { QMenu *debug = menuBar()->addMenu (tr ("Debug")); - QAction *profiles = new QAction (tr ("Debug Profiles"), this); + QAction* profiles = createMenuEntry(CSMWorld::UniversalId::Type_DebugProfiles, debug, "document-debug-profiles"); connect (profiles, SIGNAL (triggered()), this, SLOT (addDebugProfilesSubView())); - debug->addAction (profiles); debug->addSeparator(); @@ -387,18 +292,41 @@ void CSVDoc::View::setupDebugMenu() QAction *runDebug = debug->addMenu (mGlobalDebugProfileMenu); runDebug->setText (tr ("Run OpenMW")); - setupShortcut("document-debug-run", runDebug); + runDebug->setIcon(QIcon(QString::fromStdString(":./run-openmw.png"))); - mStopDebug = new QAction (tr ("Shutdown OpenMW"), this); - connect (mStopDebug, SIGNAL (triggered()), this, SLOT (stop())); - setupShortcut("document-debug-shutdown", mStopDebug); - debug->addAction (mStopDebug); + QAction* stopDebug = createMenuEntry("Stop OpenMW", ":./stop-openmw.png", debug, "document-debug-shutdown"); + connect (stopDebug, SIGNAL (triggered()), this, SLOT (stop())); + mStopDebug = stopDebug; - QAction *runLog = new QAction (tr ("Open Run Log"), this); + QAction* runLog = createMenuEntry(CSMWorld::UniversalId::Type_RunLog, debug, "document-debug-runlog"); connect (runLog, SIGNAL (triggered()), this, SLOT (addRunLogSubView())); - setupShortcut("document-debug-runlog", runLog); - debug->addAction (runLog); +} + +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); + setupShortcut(shortcutName, entry); + const std::string iconName = CSMWorld::UniversalId (type).getIcon(); + if (!iconName.empty() && iconName != ":placeholder") + entry->setIcon(QIcon(QString::fromStdString(iconName))); + + menu->addAction (entry); + + return entry; +} + +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); + setupShortcut(shortcutName, entry); + if (!iconName.empty() && iconName != ":placeholder") + entry->setIcon(QIcon(QString::fromStdString(iconName))); + + menu->addAction (entry); + + return entry; } void CSVDoc::View::setupUi() @@ -472,7 +400,7 @@ void CSVDoc::View::updateSubViewIndices(SubView *view) else { delete subView->titleBarWidget(); - subView->setTitleBarWidget (NULL); + subView->setTitleBarWidget (nullptr); } } } @@ -501,7 +429,7 @@ void CSVDoc::View::updateActions() CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews) : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), - mViewTotal (totalViews), mScroll(NULL), mScrollbarOnly(false) + mViewTotal (totalViews), mScroll(nullptr), mScrollbarOnly(false) { CSMPrefs::Category& windows = CSMPrefs::State::get()["Windows"]; @@ -635,7 +563,7 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin return; } - SubView *view = NULL; + SubView *view = nullptr; if(isReferenceable) { view = mSubViewFactory.makeSubView (CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Referenceable, id.getId()), *mDocument); @@ -703,7 +631,7 @@ void CSVDoc::View::moveScrollBarToEnd(int min, int max) void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) { if (*setting=="Windows/hide-subview") - updateSubViewIndices (NULL); + updateSubViewIndices (nullptr); else if (*setting=="Windows/mainwindow-scrollbar") { if (setting->toString()!="Grow Only") @@ -731,7 +659,7 @@ void CSVDoc::View::settingChanged (const CSMPrefs::Setting *setting) mScroll->takeWidget(); setCentralWidget (&mSubViewWindow); mScroll->deleteLater(); - mScroll = NULL; + mScroll = nullptr; } } } diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 5418b7720a..7a9a48b0f0 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -66,6 +66,9 @@ namespace CSVDoc void closeEvent (QCloseEvent *event); + 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 setupFileMenu(); void setupEditMenu(); @@ -146,7 +149,7 @@ namespace CSVDoc void updateTitle(); // called when subviews are added or removed - void updateSubViewIndices (SubView *view = NULL); + void updateSubViewIndices (SubView *view = nullptr); private slots: diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp new file mode 100644 index 0000000000..d6077a65a5 --- /dev/null +++ b/apps/opencs/view/render/actor.cpp @@ -0,0 +1,128 @@ +#include "actor.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../../model/world/data.hpp" + +namespace CSVRender +{ + const std::string Actor::MeshPrefix = "meshes\\"; + + Actor::Actor(const std::string& 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&))); + } + + osg::Group* Actor::getBaseNode() + { + return mBaseNode; + } + + void Actor::update() + { + mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); + + // Load skeleton + std::string skeletonModel = mActorData->getSkeleton(); + skeletonModel = Misc::ResourceHelpers::correctActorModelPath(skeletonModel, mData.getResourceSystem()->getVFS()); + loadSkeleton(skeletonModel); + + if (!mActorData->isCreature()) + { + // Get rid of the extra attachments + SceneUtil::CleanObjectRootVisitor cleanVisitor; + mSkeleton->accept(cleanVisitor); + cleanVisitor.remove(); + + // Attach parts to skeleton + loadBodyParts(); + } + else + { + SceneUtil::RemoveTriBipVisitor removeTriBipVisitor; + mSkeleton->accept(removeTriBipVisitor); + removeTriBipVisitor.remove(); + } + + // Post setup + mSkeleton->markDirty(); + mSkeleton->setActive(SceneUtil::Skeleton::Active); + } + + void Actor::handleActorChanged(const std::string& refId) + { + if (mId == refId) + { + update(); + } + } + + void Actor::loadSkeleton(const std::string& model) + { + auto sceneMgr = mData.getResourceSystem()->getSceneManager(); + + osg::ref_ptr temp = sceneMgr->getInstance(model); + mSkeleton = dynamic_cast(temp.get()); + if (!mSkeleton) + { + mSkeleton = new SceneUtil::Skeleton(); + mSkeleton->addChild(temp); + } + mBaseNode->addChild(mSkeleton); + + // Map bone names to bones + 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)); + } + } + + void Actor::attachBodyPart(ESM::PartReferenceType type, const std::string& mesh) + { + auto sceneMgr = mData.getResourceSystem()->getSceneManager(); + + // Attach to skeleton + std::string boneName = ESM::getBoneName(type); + auto node = mNodeMap.find(boneName); + if (!mesh.empty() && node != mNodeMap.end()) + { + auto instance = sceneMgr->getInstance(mesh); + SceneUtil::attach(instance, mSkeleton, boneName, node->second); + } + } + + std::string Actor::getBodyPartMesh(const std::string& bodyPartId) + { + const auto& bodyParts = mData.getBodyParts(); + + int index = bodyParts.searchId(bodyPartId); + if (index != -1 && !bodyParts.getRecord(index).isDeleted()) + return MeshPrefix + bodyParts.getRecord(index).get().mModel; + else + return ""; + } +} diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp new file mode 100644 index 0000000000..2f19454f78 --- /dev/null +++ b/apps/opencs/view/render/actor.hpp @@ -0,0 +1,71 @@ +#ifndef OPENCS_VIEW_RENDER_ACTOR_H +#define OPENCS_VIEW_RENDER_ACTOR_H + +#include + +#include + +#include + +#include +#include + +#include "../../model/world/actoradapter.hpp" + +namespace osg +{ + class Group; +} + +namespace CSMWorld +{ + class Data; +} + +namespace SceneUtil +{ + class Skeleton; +} + +namespace CSVRender +{ + /// Handles loading an npc or creature + class Actor : public QObject + { + Q_OBJECT + public: + /// Creates an actor. + /// \param id The referenceable id + /// \param type The record type + /// \param data The data store + Actor(const std::string& id, CSMWorld::Data& data); + + /// Retrieves the base node that meshes are attached to + osg::Group* getBaseNode(); + + /// (Re)creates the npc or creature renderable + void update(); + + private slots: + void handleActorChanged(const std::string& 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); + + static const std::string MeshPrefix; + + std::string mId; + CSMWorld::Data& mData; + CSMWorld::ActorAdapter::ActorDataPtr mActorData; + + osg::ref_ptr mBaseNode; + SceneUtil::Skeleton* mSkeleton; + SceneUtil::NodeMapVisitor::NodeMap mNodeMap; + }; +} + +#endif diff --git a/apps/opencs/view/render/cameracontroller.cpp b/apps/opencs/view/render/cameracontroller.cpp index 37f439fd3b..524a798213 100644 --- a/apps/opencs/view/render/cameracontroller.cpp +++ b/apps/opencs/view/render/cameracontroller.cpp @@ -38,7 +38,7 @@ namespace CSVRender , mCameraSensitivity(1/650.f) , mSecondaryMoveMult(50) , mWheelMoveMult(8) - , mCamera(NULL) + , mCamera(nullptr) { } @@ -81,7 +81,7 @@ namespace CSVRender bool wasActive = mActive; mCamera = camera; - mActive = (mCamera != NULL); + mActive = (mCamera != nullptr); if (mActive != wasActive) { diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 552a54ac27..a0c408df0f 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -47,6 +47,7 @@ namespace CSVRender virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { + traverse(node, nv); CellNodeContainer* container = static_cast(node->getUserData()); container->getCell()->updateLand(); } diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp index b8c89c83d5..b6fee15459 100644 --- a/apps/opencs/view/render/cellarrow.cpp +++ b/apps/opencs/view/render/cellarrow.cpp @@ -10,6 +10,8 @@ #include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcutmanager.hpp" +#include + #include "mask.hpp" CSVRender::CellArrowTag::CellArrowTag (CellArrow *arrow) @@ -57,7 +59,7 @@ QString CSVRender::CellArrowTag::getToolTip (bool hideBasics) const void CSVRender::CellArrow::adjustTransform() { // position - const int cellSize = 8192; + const int cellSize = Constants::CellSizeInUnits; const int offset = cellSize / 2 + 800; int x = mCoordinates.getX()*cellSize + cellSize/2; diff --git a/apps/opencs/view/render/cellmarker.cpp b/apps/opencs/view/render/cellmarker.cpp index d0521a7b75..3de96ab023 100644 --- a/apps/opencs/view/render/cellmarker.cpp +++ b/apps/opencs/view/render/cellmarker.cpp @@ -5,6 +5,8 @@ #include #include +#include + CSVRender::CellMarkerTag::CellMarkerTag(CellMarker *marker) : TagBase(Mask_CellMarker), mMarker(marker) {} @@ -49,7 +51,7 @@ void CSVRender::CellMarker::buildMarker() void CSVRender::CellMarker::positionMarker() { - const int cellSize = 8192; + const int cellSize = Constants::CellSizeInUnits; const int markerHeight = 0; // Move marker to center of cell. diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 4b14e29bfd..1cf8a56983 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -29,15 +29,13 @@ int CSVRender::InstanceMode::getSubModeFromId (const std::string& id) const osg::Vec3f CSVRender::InstanceMode::quatToEuler(const osg::Quat& rot) const { - const float Pi = 3.14159265f; - float x, y, z; float test = 2 * (rot.w() * rot.y() + rot.x() * rot.z()); if (std::abs(test) >= 1.f) { x = atan2(rot.x(), rot.w()); - y = (test > 0) ? (Pi / 2) : (-Pi / 2); + y = (test > 0) ? (osg::PI / 2) : (-osg::PI / 2); z = 0; } else diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 8301f4e9ed..0266257959 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -1,6 +1,8 @@ #include "object.hpp" #include +#include +#include #include #include @@ -29,6 +31,7 @@ #include #include +#include "actor.hpp" #include "mask.hpp" @@ -78,64 +81,62 @@ void CSVRender::Object::update() { clear(); - std::string model; - int error = 0; // 1 referenceable does not exist, 2 referenceable does not specify a mesh - const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); + const int TypeIndex = referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType); + const int ModelIndex = referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model); int index = referenceables.searchId (mReferenceableId); - const ESM::Light* light = NULL; - - if (index==-1) - error = 1; - else - { - /// \todo check for Deleted state (error 1) - - model = referenceables.getData (index, - referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model)). - toString().toUtf8().constData(); - - int recordType = - referenceables.getData (index, - referenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType)).toInt(); - if (recordType == CSMWorld::UniversalId::Type_Light) - { - light = &dynamic_cast& >(referenceables.getRecord(index)).get(); - if (model.empty()) - model = "marker_light.nif"; - } - - if (recordType == CSMWorld::UniversalId::Type_CreatureLevelledList) - { - if (model.empty()) - model = "marker_creature.nif"; - } - - if (model.empty()) - error = 2; - } + const ESM::Light* light = nullptr; mBaseNode->removeChildren(0, mBaseNode->getNumChildren()); - if (error) + if (index == -1) { mBaseNode->addChild(createErrorCube()); + return; } - else + + /// \todo check for Deleted state (error 1) + + int recordType = referenceables.getData(index, TypeIndex).toInt(); + std::string model = referenceables.getData(index, ModelIndex).toString().toUtf8().constData(); + + if (recordType == CSMWorld::UniversalId::Type_Light) { - try + light = &dynamic_cast& >(referenceables.getRecord(index)).get(); + if (model.empty()) + model = "marker_light.nif"; + } + + if (recordType == CSMWorld::UniversalId::Type_CreatureLevelledList) + { + if (model.empty()) + model = "marker_creature.nif"; + } + + try + { + if (recordType == CSMWorld::UniversalId::Type_Npc || recordType == CSMWorld::UniversalId::Type_Creature) + { + if (!mActor) mActor.reset(new Actor(mReferenceableId, mData)); + mActor->update(); + mBaseNode->addChild(mActor->getBaseNode()); + } + else if (!model.empty()) { std::string path = "meshes\\" + model; - mResourceSystem->getSceneManager()->getInstance(path, mBaseNode); } - catch (std::exception& e) + else { - // TODO: use error marker mesh - Log(Debug::Error) << e.what(); + throw std::runtime_error(mReferenceableId + " has no model"); } } + catch (std::exception& e) + { + // TODO: use error marker mesh + Log(Debug::Error) << e.what(); + } if (light) { @@ -313,20 +314,18 @@ osg::ref_ptr CSVRender::Object::makeMoveOrScaleMarker (int axis) osg::ref_ptr CSVRender::Object::makeRotateMarker (int axis) { - const float Pi = 3.14159265f; - 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 * Pi / SegmentDistance))); + const size_t SegmentCount = std::min(64, std::max(24, (int)(OuterRadius * 2 * osg::PI / SegmentDistance))); const size_t VerticesPerSegment = 4; const size_t IndicesPerSegment = 24; const size_t VertexCount = SegmentCount * VerticesPerSegment; const size_t IndexCount = SegmentCount * IndicesPerSegment; - const float Angle = 2 * Pi / SegmentCount; + const float Angle = 2 * osg::PI / SegmentCount; const unsigned short IndexPattern[IndicesPerSegment] = { diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 3e54093d3e..10a46fc103 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -1,6 +1,7 @@ #ifndef OPENCS_VIEW_OBJECT_H #define OPENCS_VIEW_OBJECT_H +#include #include #include @@ -41,6 +42,7 @@ namespace CSMWorld namespace CSVRender { + class Actor; class Object; // 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 @@ -98,6 +100,7 @@ namespace CSVRender osg::ref_ptr mMarker[3]; int mSubMode; float mMarkerTransparency; + std::unique_ptr mActor; /// Not implemented Object (const Object&); diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 1d1a7cd17d..ff69656a29 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -8,6 +8,8 @@ #include +#include + #include "../../model/prefs/shortcut.hpp" #include "../../model/world/tablemimedata.hpp" @@ -506,13 +508,11 @@ void CSVRender::PagedWorldspaceWidget::moveCellSelection (int x, int y) void CSVRender::PagedWorldspaceWidget::addCellToSceneFromCamera (int offsetX, int offsetY) { - const int CellSize = 8192; - osg::Vec3f eye, center, up; getCamera()->getViewMatrixAsLookAt(eye, center, up); - int cellX = (int)std::floor(center.x() / CellSize) + offsetX; - int cellY = (int)std::floor(center.y() / CellSize) + offsetY; + int cellX = (int)std::floor(center.x() / Constants::CellSizeInUnits) + offsetX; + int cellY = (int)std::floor(center.y() / Constants::CellSizeInUnits) + offsetY; CSMWorld::CellCoordinates cellCoordinates(cellX, cellY); @@ -527,7 +527,7 @@ void CSVRender::PagedWorldspaceWidget::addCellToSceneFromCamera (int offsetX, in CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc::Document& document) : WorldspaceWidget (document, parent), mDocument (document), mWorldspace ("std::default"), - mControlElements(NULL), mDisplayCellCoord(true) + mControlElements(nullptr), mDisplayCellCoord(true) { QAbstractItemModel *cells = document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells); @@ -738,22 +738,18 @@ void CSVRender::PagedWorldspaceWidget::selectAllWithSameParentId (int elementMas std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point) const { - const int cellSize = 8192; - CSMWorld::CellCoordinates cellCoordinates ( - static_cast (std::floor (point.x()/cellSize)), - static_cast (std::floor (point.y()/cellSize))); + static_cast (std::floor (point.x() / Constants::CellSizeInUnits)), + static_cast (std::floor (point.y() / Constants::CellSizeInUnits))); return cellCoordinates.getId (mWorldspace); } CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const osg::Vec3d& point) const { - const int cellSize = 8192; - CSMWorld::CellCoordinates coords( - static_cast (std::floor (point.x()/cellSize)), - static_cast (std::floor (point.y()/cellSize))); + 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()) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 0d1780d574..6cc64f6537 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -185,7 +185,7 @@ SceneWidget::SceneWidget(std::shared_ptr resourceSyste bool retrieveInput) : RenderWidget(parent, f) , mResourceSystem(resourceSystem) - , mLighting(NULL) + , mLighting(nullptr) , mHasDefaultAmbient(false) , mPrevMouseX(0) , mPrevMouseY(0) @@ -425,21 +425,21 @@ void SceneWidget::selectNavigationMode (const std::string& mode) { if (mode=="1st") { - mCurrentCamControl->setCamera(NULL); + mCurrentCamControl->setCamera(nullptr); mCurrentCamControl = mFreeCamControl; mFreeCamControl->setCamera(getCamera()); mFreeCamControl->fixUpAxis(CameraController::WorldUp); } else if (mode=="free") { - mCurrentCamControl->setCamera(NULL); + mCurrentCamControl->setCamera(nullptr); mCurrentCamControl = mFreeCamControl; mFreeCamControl->setCamera(getCamera()); mFreeCamControl->unfixUpAxis(); } else if (mode=="orbit") { - mCurrentCamControl->setCamera(NULL); + mCurrentCamControl->setCamera(nullptr); mCurrentCamControl = mOrbitCamControl; mOrbitCamControl->setCamera(getCamera()); mOrbitCamControl->reset(); diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 51c9dd0093..e0edae7743 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -18,7 +18,7 @@ namespace CSVRender // 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)); if (index == -1) - return NULL; + 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); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 5b825fab1f..7e405266ee 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -648,11 +648,6 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) mDragX = event->posF().x(); mDragY = height() - event->posF().y(); #endif - - if (mDragMode == InteractionType_PrimaryEdit) - { - editMode.drag (event->pos(), mDragX, mDragY, mDragFactor); // note: terraintexturemode only uses pos - } } } else diff --git a/apps/opencs/view/widget/colorpickerpopup.cpp b/apps/opencs/view/widget/colorpickerpopup.cpp index 47aab89817..a38728ef3b 100644 --- a/apps/opencs/view/widget/colorpickerpopup.cpp +++ b/apps/opencs/view/widget/colorpickerpopup.cpp @@ -45,7 +45,7 @@ void CSVWidget::ColorPickerPopup::showPicker(const QPoint &position, const QColo void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event) { QPushButton *button = qobject_cast(parentWidget()); - if (button != NULL) + if (button != nullptr) { QStyleOptionButton option; option.init(button); diff --git a/apps/opencs/view/widget/completerpopup.cpp b/apps/opencs/view/widget/completerpopup.cpp index 5777325c83..be509bcb93 100644 --- a/apps/opencs/view/widget/completerpopup.cpp +++ b/apps/opencs/view/widget/completerpopup.cpp @@ -11,7 +11,7 @@ CSVWidget::CompleterPopup::CompleterPopup(QWidget *parent) int CSVWidget::CompleterPopup::sizeHintForRow(int row) const { - if (model() == NULL) + if (model() == nullptr) { return -1; } diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index 2a710a9400..a42e7ead4e 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -25,7 +25,7 @@ std::string CSVWorld::CellCreator::getId() const void CSVWorld::CellCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const { CSMWorld::IdTree *model = dynamic_cast(getData().getTableModel(getCollectionId())); - Q_ASSERT(model != NULL); + Q_ASSERT(model != nullptr); int parentIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Cell); int index = model->findNestedColumnIndex(parentIndex, CSMWorld::Columns::ColumnId_Interior); command.addNestedValue(parentIndex, index, mType->currentIndex() == 0); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 7b198056c4..b32e2c7a14 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -158,7 +158,7 @@ mNotEditableDelegate(table, parent) CSVWorld::CommandDelegate* CSVWorld::DialogueDelegateDispatcher::makeDelegate(CSMWorld::ColumnBase::Display display) { - CommandDelegate *delegate = NULL; + CommandDelegate *delegate = nullptr; std::map::const_iterator delegateIt(mDelegates.find(display)); if (delegateIt == mDelegates.end()) { @@ -251,11 +251,11 @@ QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor(CSMWorld::ColumnBase:: variant = index.data(Qt::DisplayRole); if (!variant.isValid()) { - return NULL; + return nullptr; } } - QWidget* editor = NULL; + QWidget* editor = nullptr; if (! (mTable->flags (index) & Qt::ItemIsEditable)) { return mNotEditableDelegate.createEditor(qobject_cast(mParent), @@ -325,7 +325,7 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di mWidget(widget), mIdType(CSMWorld::TableMimeData::convertEnums(display)) { - Q_ASSERT(mWidget != NULL); + Q_ASSERT(mWidget != nullptr); Q_ASSERT(CSMWorld::ColumnBase::isId(display)); Q_ASSERT(mIdType != CSMWorld::UniversalId::Type_None); @@ -339,7 +339,7 @@ CSVWorld::IdContextMenu::IdContextMenu(QWidget *widget, CSMWorld::ColumnBase::Di connect(mEditIdAction, SIGNAL(triggered()), this, SLOT(editIdRequest())); QLineEdit *lineEdit = qobject_cast(mWidget); - if (lineEdit != NULL) + if (lineEdit != nullptr) { mContextMenu = lineEdit->createStandardContextMenu(); } @@ -360,11 +360,11 @@ QString CSVWorld::IdContextMenu::getWidgetValue() const QLabel *label = qobject_cast(mWidget); QString value = ""; - if (lineEdit != NULL) + if (lineEdit != nullptr) { value = lineEdit->text(); } - else if (label != NULL) + else if (label != nullptr) { value = label->text(); } @@ -436,7 +436,7 @@ void CSVWorld::EditWidget::createEditorContextMenu(QWidget *editor, CSMWorld::ColumnBase::Display display, int currentRow) const { - Q_ASSERT(editor != NULL); + Q_ASSERT(editor != nullptr); if (CSMWorld::ColumnBase::isId(display) && CSMWorld::TableMimeData::convertEnums(display) != CSMWorld::UniversalId::Type_None) @@ -470,11 +470,11 @@ CSVWorld::EditWidget::EditWidget(QWidget *parent, int row, CSMWorld::IdTable* table, CSMWorld::CommandDispatcher& commandDispatcher, CSMDoc::Document& document, bool createAndDelete) : QScrollArea(parent), -mWidgetMapper(NULL), -mNestedTableMapper(NULL), -mDispatcher(NULL), -mNestedTableDispatcher(NULL), -mMainWidget(NULL), +mWidgetMapper(nullptr), +mNestedTableMapper(nullptr), +mDispatcher(nullptr), +mNestedTableDispatcher(nullptr), +mMainWidget(nullptr), mTable(table), mCommandDispatcher (commandDispatcher), mDocument (document) @@ -733,7 +733,7 @@ bool CSVWorld::SimpleDialogueSubView::isLocked() const CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mEditWidget(0), - mMainLayout(NULL), + mMainLayout(nullptr), mTable(dynamic_cast(document.getData().getTableModel(id))), mLocked(false), mDocument(document), diff --git a/apps/opencs/view/world/dragdroputils.cpp b/apps/opencs/view/world/dragdroputils.cpp index 7f3974e539..789d4f33dc 100644 --- a/apps/opencs/view/world/dragdroputils.cpp +++ b/apps/opencs/view/world/dragdroputils.cpp @@ -12,7 +12,7 @@ const CSMWorld::TableMimeData *CSVWorld::DragDropUtils::getTableMimeData(const Q bool CSVWorld::DragDropUtils::canAcceptData(const QDropEvent &event, CSMWorld::ColumnBase::Display type) { const CSMWorld::TableMimeData *data = getTableMimeData(event); - return data != NULL && data->holdsType(type); + return data != nullptr && data->holdsType(type); } CSMWorld::UniversalId CSVWorld::DragDropUtils::getAcceptedData(const QDropEvent &event, diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index 6d980e1712..d795bd5de1 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -83,7 +83,7 @@ void CSVWorld::DragRecordTable::dropEvent(QDropEvent *event) CSMWorld::ColumnBase::Display CSVWorld::DragRecordTable::getIndexDisplayType(const QModelIndex &index) const { - Q_ASSERT(model() != NULL); + Q_ASSERT(model() != nullptr); if (index.isValid()) { diff --git a/apps/opencs/view/world/dragrecordtable.hpp b/apps/opencs/view/world/dragrecordtable.hpp index 560864ba5a..9e29b61451 100644 --- a/apps/opencs/view/world/dragrecordtable.hpp +++ b/apps/opencs/view/world/dragrecordtable.hpp @@ -28,7 +28,7 @@ namespace CSVWorld bool mEditLock; public: - DragRecordTable(CSMDoc::Document& document, QWidget* parent = NULL); + DragRecordTable(CSMDoc::Document& document, QWidget* parent = nullptr); virtual std::vector getDraggedRecords() const = 0; diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index 2cf6222a6e..8947420242 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -95,7 +95,7 @@ void CSVWorld::ExtendedCommandConfigurator::setupGroupLayout() int divider = 1; do { - while (layout->itemAt(0) != NULL) + while (layout->itemAt(0) != nullptr) { layout->removeItem(layout->itemAt(0)); } diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 7f0f4ae460..4ff850b9f4 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -25,7 +25,7 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, { if (!index.data(Qt::EditRole).isValid() && !index.data(Qt::DisplayRole).isValid()) { - return NULL; + return nullptr; } // The completer for InfoCondVar needs to return a completer based on the first column diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 02bd93920c..1b72211e8e 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -23,9 +23,9 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, bool editable, bool fixedRows) : DragRecordTable(document, parent), - mAddNewRowAction(NULL), - mRemoveRowAction(NULL), - mEditIdAction(NULL), + mAddNewRowAction(nullptr), + mRemoveRowAction(nullptr), + mEditIdAction(nullptr), mModel(model) { mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); diff --git a/apps/opencs/view/world/nestedtable.hpp b/apps/opencs/view/world/nestedtable.hpp index 765060ea5b..b39c7e560a 100644 --- a/apps/opencs/view/world/nestedtable.hpp +++ b/apps/opencs/view/world/nestedtable.hpp @@ -38,7 +38,7 @@ namespace CSVWorld NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, - QWidget* parent = NULL, + QWidget* parent = nullptr, bool editable = true, bool fixedRows = false); diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index b14d708dae..b03cf8fdb9 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -27,7 +27,7 @@ #include "creator.hpp" CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mScene(NULL), mLayout(new QHBoxLayout), mDocument(document), mToolbar(NULL) +: SubView (id), mScene(nullptr), mLayout(new QHBoxLayout), mDocument(document), mToolbar(nullptr) { QVBoxLayout *layout = new QVBoxLayout; @@ -35,7 +35,7 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); - CSVRender::WorldspaceWidget* worldspaceWidget = NULL; + CSVRender::WorldspaceWidget* worldspaceWidget = nullptr; widgetType whatWidget; if (id.getId()==ESM::CellId::sDefaultWorldspace) @@ -189,9 +189,9 @@ void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalId >& universalIdData) { - CSVRender::PagedWorldspaceWidget* pagedNewWidget = NULL; - CSVRender::UnpagedWorldspaceWidget* unPagedNewWidget = NULL; - CSVWidget::SceneToolbar* toolbar = NULL; + CSVRender::PagedWorldspaceWidget* pagedNewWidget = nullptr; + CSVRender::UnpagedWorldspaceWidget* unPagedNewWidget = nullptr; + CSVWidget::SceneToolbar* toolbar = nullptr; CSVRender::WorldspaceWidget::DropType type = CSVRender::WorldspaceWidget::getDropType (universalIdData); diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index eab37e1bf4..8cdb2d2dbe 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -1,8 +1,6 @@ #include "util.hpp" #include -#include -#include #include #include @@ -131,7 +129,7 @@ void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemM // Color columns use a custom editor, so we need to fetch selected color from it. CSVWidget::ColorEditor *colorEditor = qobject_cast(editor); - if (colorEditor != NULL) + if (colorEditor != nullptr) { variant = colorEditor->colorInt(); } @@ -209,7 +207,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO case CSMWorld::ColumnBase::Display_Integer: { DialogueSpinBox *sb = new DialogueSpinBox(parent); - sb->setRange(INT_MIN, INT_MAX); + sb->setRange(std::numeric_limits::min(), std::numeric_limits::max()); return sb; } @@ -324,7 +322,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); - if (colorEditor != NULL) + if (colorEditor != nullptr) { colorEditor->setColor(variant.toInt()); return; diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c index d234a369dc..9a7b6bc501 100644 --- a/apps/openmw/android_main.c +++ b/apps/openmw/android_main.c @@ -20,14 +20,14 @@ void releaseArgv(); int Java_org_libsdl_app_SDLActivity_getMouseX(JNIEnv *env, jclass cls, jobject obj) { int ret = 0; - SDL_GetMouseState(&ret, NULL); + SDL_GetMouseState(&ret, nullptr); return ret; } int Java_org_libsdl_app_SDLActivity_getMouseY(JNIEnv *env, jclass cls, jobject obj) { int ret = 0; - SDL_GetMouseState(NULL, &ret); + SDL_GetMouseState(nullptr, &ret); return ret; } diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 2941ede1b2..f5b4171d4f 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -197,9 +197,9 @@ bool OMW::Engine::frame(float frametime) } OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) - : mWindow(NULL) + : mWindow(nullptr) , mEncoding(ToUTF8::WINDOWS_1252) - , mEncoder(NULL) + , mEncoder(nullptr) , mScreenCaptureOperation(nullptr) , mSkipMenu (false) , mUseSound (true) @@ -237,18 +237,18 @@ OMW::Engine::~Engine() mEnvironment.cleanup(); delete mScriptContext; - mScriptContext = NULL; + mScriptContext = nullptr; - mWorkQueue = NULL; + mWorkQueue = nullptr; mResourceSystem.reset(); - mViewer = NULL; + mViewer = nullptr; if (mWindow) { SDL_DestroyWindow(mWindow); - mWindow = NULL; + mWindow = nullptr; } SDL_Quit(); @@ -516,7 +516,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) MWGui::WindowManager* window = new MWGui::WindowManager(mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), mCfgMgr.getLogPath().string() + std::string("/"), myguiResources, mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, mFallbackMap, - Version::getOpenmwVersionDescription(mResDir.string())); + Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string()); mEnvironment.setWindowManager (window); // Create sound system diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index fe3fc5721b..8137bad959 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -90,6 +90,8 @@ namespace MWBase virtual void setPlayerClass (const ESM::Class& class_) = 0; ///< Set player class to custom class. + virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep) = 0; + virtual void rest(bool sleep) = 0; ///< If the player is sleeping or waiting, this should be called every hour. /// @param sleep is the player sleeping or waiting? diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 39eed55379..36beb25fc8 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -219,6 +219,7 @@ namespace MWBase 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; @@ -289,6 +290,8 @@ namespace MWBase /// 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? @@ -350,7 +353,8 @@ namespace MWBase virtual const MWGui::TextColours& getTextColours() = 0; - virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text) = 0; + virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat) = 0; + virtual bool injectKeyRelease(MyGUI::KeyCode key) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 72c4c3a2ab..027d1fd102 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -297,9 +297,11 @@ namespace MWBase ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. - virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors=false) = 0; + 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. + virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; + virtual bool toggleCollisionMode() = 0; ///< Toggle collision mode for player. If disabled player object should ignore /// collisions and gravity. @@ -438,12 +440,16 @@ namespace MWBase virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; - virtual int canRest() = 0; - ///< check if the player is allowed to rest \n - /// 0 - yes \n - /// 1 - only waiting \n - /// 2 - player is underwater \n - /// 3 - enemies are nearby (not implemented) + enum RestPermitted + { + Rest_Allowed = 0, + Rest_OnlyWaiting = 1, + Rest_PlayerIsUnderwater = 2, + Rest_EnemiesAreNearby = 3 + }; + + /// 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; @@ -567,6 +573,8 @@ namespace MWBase virtual bool isPlayerInJail() const = 0; + virtual void rest() = 0; + virtual void setPlayerTraveling(bool traveling) = 0; virtual bool isPlayerTraveling() const = 0; diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 8df262f240..e0e2013912 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -1,6 +1,7 @@ #include "activator.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -134,4 +135,60 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } + + std::string Activator::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const + { + std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + std::string creatureId; + + for (const ESM::Creature &iter : store.get()) + { + if (!iter.mModel.empty() && Misc::StringUtils::ciEqual(model, "meshes\\" + iter.mModel)) + { + creatureId = !iter.mOriginal.empty() ? iter.mOriginal : iter.mId; + break; + } + } + + if (creatureId.empty()) + return std::string(); + + int type = getSndGenTypeFromName(name); + std::vector sounds; + + for (auto sound = store.get().begin(); sound != store.get().end(); ++sound) + if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature))) + sounds.push_back(&*sound); + + if (!sounds.empty()) + return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; + + if (type == ESM::SoundGenerator::Land) + return "Body Fall Large"; + + return std::string(); + } + + int Activator::getSndGenTypeFromName(const std::string &name) + { + if (name == "left") + return ESM::SoundGenerator::LeftFoot; + if (name == "right") + return ESM::SoundGenerator::RightFoot; + if (name == "swimleft") + return ESM::SoundGenerator::SwimLeft; + if (name == "swimright") + return ESM::SoundGenerator::SwimRight; + if (name == "moan") + return ESM::SoundGenerator::Moan; + if (name == "roar") + return ESM::SoundGenerator::Roar; + if (name == "scream") + return ESM::SoundGenerator::Scream; + if (name == "land") + return ESM::SoundGenerator::Land; + + throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); + } } diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 3f333f4cbe..b92dc75cbd 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -10,6 +10,8 @@ namespace MWClass virtual MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const; + static int getSndGenTypeFromName(const std::string &name); + public: virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; @@ -44,6 +46,8 @@ namespace MWClass ///< Whether or not to use animated variant of model (default false) virtual bool isActivator() const; + + virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const; }; } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index efe460b00c..e1784d5937 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -74,7 +74,7 @@ namespace MWClass if (ref->mBase->mFlags & ESM::Container::Respawn) { MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); - ptr.getRefData().setCustomData(NULL); + ptr.getRefData().setCustomData(nullptr); } } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 6999558ae9..788e5cd689 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -298,7 +298,7 @@ namespace MWClass bool healthdmg = true; if (!weapon.isEmpty()) { - const unsigned char *attack = NULL; + const unsigned char *attack = nullptr; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; else if(type == ESM::Weapon::AT_Slash) @@ -688,9 +688,9 @@ namespace MWClass MWBase::World *world = MWBase::Environment::get().getWorld(); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) - return 2; + return ESM::SoundGenerator::SwimLeft; if(world->isOnGround(ptr)) - return 0; + return ESM::SoundGenerator::LeftFoot; return -1; } if(name == "right") @@ -698,23 +698,23 @@ namespace MWClass MWBase::World *world = MWBase::Environment::get().getWorld(); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) - return 3; + return ESM::SoundGenerator::SwimRight; if(world->isOnGround(ptr)) - return 1; + return ESM::SoundGenerator::RightFoot; return -1; } if(name == "swimleft") - return 2; + return ESM::SoundGenerator::SwimLeft; if(name == "swimright") - return 3; + return ESM::SoundGenerator::SwimRight; if(name == "moan") - return 4; + return ESM::SoundGenerator::Moan; if(name == "roar") - return 5; + return ESM::SoundGenerator::Roar; if(name == "scream") - return 6; + return ESM::SoundGenerator::Scream; if(name == "land") - return 7; + return ESM::SoundGenerator::Land; throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } @@ -828,7 +828,7 @@ namespace MWClass ptr.getRefData().setCount(1); MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); - ptr.getRefData().setCustomData(NULL); + ptr.getRefData().setCustomData(nullptr); // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index d738974dd7..a261180298 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -213,7 +213,7 @@ namespace MWClass 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/(3.14159265f * 0.5f); + float offset = doorRot/(osg::PI * 0.5f); action->setSoundOffset(offset); action->setSound(openSound); } @@ -221,7 +221,7 @@ namespace MWClass { MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, openSound, 0.5f); - float offset = 1.0f - doorRot/(3.14159265f * 0.5f); + float offset = 1.0f - doorRot/(osg::PI * 0.5f); action->setSoundOffset(std::max(offset, 0.0f)); action->setSound(closeSound); } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index c133b6a266..70339564c6 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -14,8 +14,6 @@ #include "../mwworld/actioneat.hpp" #include "../mwworld/nullaction.hpp" -#include "../mwmechanics/npcstats.hpp" - #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" @@ -134,8 +132,7 @@ namespace MWClass } MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats (player); - int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); + int alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy); static const float fWortChanceValue = MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->mValue.getFloat(); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 27a9f3f2b0..ea0abd6f64 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -41,7 +41,7 @@ namespace MWClass { MWWorld::LiveCellRef *ref = ptr.get(); - assert (ref->mBase != NULL); + assert (ref->mBase != nullptr); // TODO: add option somewhere to enable collision for placeable objects if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0) @@ -161,7 +161,12 @@ namespace MWClass std::string text; if (Settings::Manager::getBool("show effect duration","Game")) - text += "\n#{sDuration}: " + MWGui::ToolTips::toString(ptr.getClass().getRemainingUsageTime(ptr)); + { + // -1 is infinite light source, so duration makes no sense here. Other negative values are treated as 0. + float remainingTime = ptr.getClass().getRemainingUsageTime(ptr); + if (remainingTime != -1.0f) + text += "\n#{sDuration}: " + MWGui::ToolTips::toString(std::max(0.f, remainingTime)); + } text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c247ee543e..d6dafd2a25 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -609,7 +610,7 @@ namespace MWClass float damage = 0.0f; if(!weapon.isEmpty()) { - const unsigned char *attack = NULL; + const unsigned char *attack = nullptr; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; else if(type == ESM::Weapon::AT_Slash) @@ -938,8 +939,8 @@ namespace MWClass const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); - bool sneaking = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); - bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run); + bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr) && stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool running = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr) && stats.getStance(MWMechanics::CreatureStats::Stance_Run); float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()* (gmst.fMaxWalkSpeed->mValue.getFloat() - gmst.fMinWalkSpeed->mValue.getFloat()); @@ -948,7 +949,7 @@ namespace MWClass if(sneaking) walkSpeed *= gmst.fSneakSpeedMultiplier->mValue.getFloat(); - float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() * + float runSpeed = walkSpeed*(0.01f * getSkill(ptr, ESM::Skill::Athletics) * gmst.fAthleticsRunBonus->mValue.getFloat() + gmst.fBaseRunMultiplier->mValue.getFloat()); float moveSpeed; @@ -964,17 +965,17 @@ namespace MWClass flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } - else if(world->isSwimming(ptr)) + else if (world->isSwimming(ptr)) { float swimSpeed = walkSpeed; if(running) swimSpeed = runSpeed; swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); - swimSpeed *= gmst.fSwimRunBase->mValue.getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()* + swimSpeed *= gmst.fSwimRunBase->mValue.getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics)* gmst.fSwimRunAthleticsMult->mValue.getFloat(); moveSpeed = swimSpeed; } - else if(running && !sneaking) + else if (running) moveSpeed = runSpeed; else moveSpeed = walkSpeed; @@ -1003,7 +1004,7 @@ namespace MWClass gmst.fJumpEncumbranceMultiplier->mValue.getFloat() * (1.0f - Npc::getNormalizedEncumbrance(ptr)); - float a = static_cast(npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified()); + float a = static_cast(getSkill(ptr, ESM::Skill::Acrobatics)); float b = 0.0f; if(a > 50.0f) { @@ -1020,7 +1021,7 @@ namespace MWClass if(stats.getStance(MWMechanics::CreatureStats::Stance_Run)) x *= gmst.fJumpRunMultiplier->mValue.getFloat(); x *= npcdata->mNpcStats.getFatigueTerm(); - x -= -627.2f;/*gravity constant*/ + x -= -Constants::GravityConst * Constants::UnitsPerMeter; x /= 3.0f; return x; @@ -1128,7 +1129,7 @@ namespace MWClass float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); - int unarmoredSkill = stats.getSkill(ESM::Skill::Unarmored).getModified(); + int unarmoredSkill = getSkill(ptr, ESM::Skill::Unarmored); float ratings[MWWorld::InventoryStore::Slots]; for(int i = 0;i < MWWorld::InventoryStore::Slots;i++) @@ -1241,15 +1242,9 @@ namespace MWClass return ""; } + // Morrowind ignores land soundgen for NPCs if(name == "land") - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); - if (world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) - return "DefaultLandWater"; - - return "DefaultLand"; - } + return ""; if(name == "swimleft") return "Swim Left"; if(name == "swimright") @@ -1381,7 +1376,7 @@ namespace MWClass ptr.getRefData().setCount(1); MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); - ptr.getRefData().setCustomData(NULL); + ptr.getRefData().setCustomData(nullptr); // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 466ae47162..78678f4617 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -1,6 +1,7 @@ #include "weapon.hpp" #include +#include #include #include "../mwbase/environment.hpp" @@ -65,7 +66,7 @@ namespace MWClass { const MWWorld::LiveCellRef *ref = ptr.get(); - return (ref->mBase->mData.mType < 11); // thrown weapons and arrows/bolts don't have health, only quantity + return (ref->mBase->mData.mType < ESM::Weapon::MarksmanThrown); // thrown weapons and arrows/bolts don't have health, only quantity } int Weapon::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const @@ -318,21 +319,26 @@ namespace MWClass } } - if (ref->mBase->mData.mType < 11) // thrown weapons and arrows/bolts don't have health, only quantity + if (hasItemHealth(ptr)) { int remainingHealth = getItemHealth(ptr); text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/" + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); } - // add reach and attack speed for melee weapon - if (ref->mBase->mData.mType < 9 && Settings::Manager::getBool("show melee info", "Game")) + const bool verbose = Settings::Manager::getBool("show melee info", "Game"); + // add reach for melee weapon + if (ref->mBase->mData.mType < ESM::Weapon::MarksmanBow && verbose) { - // 64 game units = 1 yard = 3 ft, display value in feet + // display value in feet const float combatDistance = store.get().find("fCombatDistance")->mValue.getFloat() * ref->mBase->mData.mReach; - text += MWGui::ToolTips::getWeightString(combatDistance*3/64, "#{sRange}"); + text += MWGui::ToolTips::getWeightString(combatDistance / Constants::UnitsPerFoot, "#{sRange}"); text += " #{sFeet}"; + } + // add attack speed for any weapon excepts arrows and bolts + if (ref->mBase->mData.mType < ESM::Weapon::Arrow && verbose) + { text += MWGui::ToolTips::getPercentString(ref->mBase->mData.mSpeed, "#{sAttributeSpeed}"); } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index dcc33d1c32..17f69d69b7 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -572,7 +572,7 @@ namespace MWDialogue const MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); Filter filter(actor, 0, creatureStats.hasTalkedToPlayer()); const ESM::DialInfo *info = filter.search(*dial, false); - if(info != NULL) + if(info != nullptr) { MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); if(winMgr->getSubtitlesEnabled()) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 15020898fb..042ccb019a 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -621,7 +621,7 @@ const ESM::DialInfo* MWDialogue::Filter::search (const ESM::Dialogue& dialogue, std::vector suitableInfos = list (dialogue, fallbackToInfoRefusal, false); if (suitableInfos.empty()) - return NULL; + return nullptr; else return suitableInfos[0]; } diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 9f74d0733d..5eab6d5cae 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -30,7 +30,7 @@ namespace MWDialogue { if (actor.isEmpty()) { - MWScript::InterpreterContext interpreterContext(NULL,MWWorld::Ptr()); + MWScript::InterpreterContext interpreterContext(nullptr, MWWorld::Ptr()); mText = Interpreter::fixDefinesDialog(iter->mResponse, interpreterContext); } else diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 80284e9b2a..fa8a96185f 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -25,9 +25,12 @@ namespace MWGui { + const float AlchemyWindow::sCountChangeInitialPause = 0.5f; + const float AlchemyWindow::sCountChangeInterval = 0.1f; + AlchemyWindow::AlchemyWindow() : WindowBase("openmw_alchemy_window.layout") - , mSortModel(NULL) + , mSortModel(nullptr) , mAlchemy(new MWMechanics::Alchemy()) , mApparatus (4) , mIngredients (4) @@ -43,9 +46,21 @@ namespace MWGui getWidget(mApparatus[2], "Apparatus3"); getWidget(mApparatus[3], "Apparatus4"); getWidget(mEffectsBox, "CreatedEffects"); + getWidget(mBrewCountEdit, "BrewCount"); + getWidget(mIncreaseButton, "IncreaseButton"); + getWidget(mDecreaseButton, "DecreaseButton"); getWidget(mNameEdit, "NameEdit"); getWidget(mItemView, "ItemView"); + mBrewCountEdit->eventValueChanged += MyGUI::newDelegate(this, &AlchemyWindow::onCountValueChanged); + mBrewCountEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept); + mBrewCountEdit->setMinValue(1); + mBrewCountEdit->setValue(1); + + mIncreaseButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &AlchemyWindow::onIncreaseButtonPressed); + mIncreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &AlchemyWindow::onCountButtonReleased); + mDecreaseButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &AlchemyWindow::onDecreaseButtonPressed); + mDecreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &AlchemyWindow::onCountButtonReleased); mItemView->eventItemClicked += MyGUI::newDelegate(this, &AlchemyWindow::onSelectedItem); @@ -65,6 +80,9 @@ namespace MWGui void AlchemyWindow::onAccept(MyGUI::EditBox* sender) { onCreateButtonClicked(sender); + + // To do not spam onAccept() again and again + MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void AlchemyWindow::onCancelButtonClicked(MyGUI::Widget* _sender) @@ -74,7 +92,15 @@ namespace MWGui void AlchemyWindow::onCreateButtonClicked(MyGUI::Widget* _sender) { - MWMechanics::Alchemy::Result result = mAlchemy->create (mNameEdit->getCaption ()); + mAlchemy->setPotionName(mNameEdit->getCaption()); + int count = mAlchemy->countPotionsToBrew(); + count = std::min(count, mBrewCountEdit->getValue()); + createPotions(count); + } + + void AlchemyWindow::createPotions(int count) + { + MWMechanics::Alchemy::Result result = mAlchemy->create(mNameEdit->getCaption(), count); MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); switch (result) @@ -89,8 +115,11 @@ namespace MWGui winMgr->messageBox("#{sNotifyMessage6a}"); break; case MWMechanics::Alchemy::Result_Success: - winMgr->messageBox("#{sPotionSuccess}"); winMgr->playSound("potion success"); + if (count == 1) + winMgr->messageBox("#{sPotionSuccess}"); + else + winMgr->messageBox("#{sPotionSuccess} "+mNameEdit->getCaption()+" ("+std::to_string(count)+")"); break; case MWMechanics::Alchemy::Result_NoEffects: case MWMechanics::Alchemy::Result_RandomFailure: @@ -123,6 +152,7 @@ namespace MWGui mItemView->resetScrollBars(); mNameEdit->setCaption(""); + mBrewCountEdit->setValue(1); int index = 0; for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy->beginTools()); @@ -247,4 +277,61 @@ namespace MWGui update(); } + + void AlchemyWindow::addRepeatController(MyGUI::Widget *widget) + { + MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(Controllers::ControllerRepeatEvent::getClassTypeName()); + Controllers::ControllerRepeatEvent* controller = item->castType(); + controller->eventRepeatClick += MyGUI::newDelegate(this, &AlchemyWindow::onRepeatClick); + controller->setRepeat(sCountChangeInitialPause, sCountChangeInterval); + MyGUI::ControllerManager::getInstance().addItem(widget, controller); + } + + void AlchemyWindow::onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) + { + addRepeatController(_sender); + onIncreaseButtonTriggered(); + } + + void AlchemyWindow::onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) + { + addRepeatController(_sender); + onDecreaseButtonTriggered(); + } + + void AlchemyWindow::onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller) + { + if (widget == mIncreaseButton) + onIncreaseButtonTriggered(); + else if (widget == mDecreaseButton) + onDecreaseButtonTriggered(); + } + + void AlchemyWindow::onCountButtonReleased(MyGUI::Widget *_sender, int _left, int _top, MyGUI::MouseButton _id) + { + MyGUI::ControllerManager::getInstance().removeItem(_sender); + } + + void AlchemyWindow::onCountValueChanged(int value) + { + mBrewCountEdit->setValue(std::abs(value)); + } + + void AlchemyWindow::onIncreaseButtonTriggered() + { + int currentCount = mBrewCountEdit->getValue(); + + // prevent overflows + if (currentCount == std::numeric_limits::max()) + return; + + mBrewCountEdit->setValue(currentCount+1); + } + + void AlchemyWindow::onDecreaseButtonTriggered() + { + int currentCount = mBrewCountEdit->getValue(); + if (currentCount > 1) + mBrewCountEdit->setValue(currentCount-1); + } } diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index d1e54241a1..aa23fbaa50 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -3,8 +3,13 @@ #include +#include + #include "../mwmechanics/alchemy.hpp" +#include + +#include "controllers.hpp" #include "windowbase.hpp" namespace MWMechanics @@ -28,6 +33,10 @@ namespace MWGui void onResChange(int, int) { center(); } private: + + static const float sCountChangeInitialPause; // in seconds + static const float sCountChangeInterval; // in seconds + std::string mSuggestedPotionName; ItemView* mItemView; @@ -38,17 +47,32 @@ namespace MWGui MyGUI::Widget* mEffectsBox; + MyGUI::Button* mIncreaseButton; + MyGUI::Button* mDecreaseButton; MyGUI::EditBox* mNameEdit; + Gui::NumericEditBox* mBrewCountEdit; void onCancelButtonClicked(MyGUI::Widget* _sender); void onCreateButtonClicked(MyGUI::Widget* _sender); void onIngredientSelected(MyGUI::Widget* _sender); void onAccept(MyGUI::EditBox*); + 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 onCountButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); + void onCountValueChanged(int value); + void onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller); + + void addRepeatController(MyGUI::Widget* widget); + + void onIncreaseButtonTriggered(); + void onDecreaseButtonTriggered(); void onSelectedItem(int index); void removeIngredient(MyGUI::Widget* ingredient); + void createPotions(int count); + void update(); std::unique_ptr mAlchemy; diff --git a/apps/openmw/mwgui/backgroundimage.cpp b/apps/openmw/mwgui/backgroundimage.cpp index 98828a0411..55c825ebbd 100644 --- a/apps/openmw/mwgui/backgroundimage.cpp +++ b/apps/openmw/mwgui/backgroundimage.cpp @@ -10,7 +10,7 @@ void BackgroundImage::setBackgroundImage (const std::string& image, bool fixedRa if (mChild) { MyGUI::Gui::getInstance().destroyWidget(mChild); - mChild = NULL; + mChild = nullptr; } if (!stretch) { diff --git a/apps/openmw/mwgui/backgroundimage.hpp b/apps/openmw/mwgui/backgroundimage.hpp index 8c963b7620..3db5bab16a 100644 --- a/apps/openmw/mwgui/backgroundimage.hpp +++ b/apps/openmw/mwgui/backgroundimage.hpp @@ -14,7 +14,7 @@ namespace MWGui MYGUI_RTTI_DERIVED(BackgroundImage) public: - BackgroundImage() : mChild(NULL), mAspect(0) {} + BackgroundImage() : mChild(nullptr), mAspect(0) {} /** * @param fixedRatio Use a fixed ratio of 4:3, regardless of the image dimensions diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 6f16cf0769..80e92f15ae 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -1,6 +1,5 @@ #include "bookpage.hpp" -#include "MyGUI_FontManager.h" #include "MyGUI_RenderItem.h" #include "MyGUI_RenderManager.h" #include "MyGUI_TextureUtility.h" @@ -8,6 +7,9 @@ #include +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + namespace MWGui { struct TypesetBookImpl; @@ -108,7 +110,7 @@ struct TypesetBookImpl : TypesetBook Contents::iterator i = mContents.insert (mContents.end (), Content (text.first, text.second)); if (i->empty()) - return Range (Utf8Point (NULL), Utf8Point (NULL)); + return Range (Utf8Point (nullptr), Utf8Point (nullptr)); Utf8Point begin = &i->front (); Utf8Point end = &i->front () + i->size (); @@ -146,7 +148,7 @@ struct TypesetBookImpl : TypesetBook template void visitRuns (int top, int bottom, Visitor const & visitor) const { - visitRuns (top, bottom, NULL, visitor); + visitRuns (top, bottom, nullptr, visitor); } /// hit test with a margin for error. only hits on interactive text fragments are reported. @@ -174,7 +176,7 @@ struct TypesetBookImpl : TypesetBook return hit; } } - return NULL; + return nullptr; } StyleImpl * hitTest (int left, int top) const @@ -211,7 +213,7 @@ struct TypesetBookImpl : TypesetBook for (Styles::iterator i = mStyles.begin (); i != mStyles.end (); ++i) if (&*i == style) return i->mFont; - return NULL; + return nullptr; } struct Typesetter; @@ -251,8 +253,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter Typesetter (size_t width, size_t height) : mPageWidth (width), mPageHeight(height), - mSection (NULL), mLine (NULL), mRun (NULL), - mCurrentContent (NULL), + mSection (nullptr), mLine (nullptr), mRun (nullptr), + mCurrentContent (nullptr), mCurrentAlignment (AlignLeft) { mBook = std::make_shared (); @@ -262,18 +264,24 @@ struct TypesetBookImpl::Typesetter : BookTypesetter { } - Style * createStyle (char const * fontName, const Colour& fontColour) + Style * createStyle (const std::string& fontName, const Colour& fontColour, bool useBookFont) { - if (strcmp(fontName, "") == 0) - return createStyle(MyGUI::FontManager::getInstance().getDefaultFont().c_str(), fontColour); + 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 (fontName, fontColour, fontColour, fontColour, 0)) + if (i->match (fullFontName.c_str(), fontColour, fontColour, fontColour, 0)) return &*i; - MyGUI::IFont* font = MyGUI::FontManager::getInstance().getByName(fontName); + MyGUI::IFont* font = MyGUI::FontManager::getInstance().getByName(fullFontName); if (!font) - throw std::runtime_error(std::string("can't find font ") + fontName); + throw std::runtime_error(std::string("can't find font ") + fullFontName); StyleImpl & style = *mBook->mStyles.insert (mBook->mStyles.end (), StyleImpl ()); style.mFont = font; @@ -334,7 +342,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter void write (Style * style, size_t begin, size_t end) { - assert (mCurrentContent != NULL); + assert (mCurrentContent != nullptr); assert (end <= mCurrentContent->size ()); assert (begin <= mCurrentContent->size ()); @@ -350,8 +358,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter add_partial_text(); - mRun = NULL; - mLine = NULL; + mRun = nullptr; + mLine = nullptr; } void sectionBreak (int margin) @@ -360,9 +368,9 @@ struct TypesetBookImpl::Typesetter : BookTypesetter if (mBook->mSections.size () > 0) { - mRun = NULL; - mLine = NULL; - mSection = NULL; + 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); @@ -373,7 +381,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter { add_partial_text(); - if (mSection != NULL) + if (mSection != nullptr) mSectionAlignment.back () = sectionAlignment; mCurrentAlignment = sectionAlignment; } @@ -483,7 +491,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter { add_partial_text(); stream.consume (); - mLine = NULL, mRun = NULL; + mLine = nullptr, mRun = nullptr; continue; } @@ -497,9 +505,9 @@ struct TypesetBookImpl::Typesetter : BookTypesetter while (!stream.eof () && !ucsLineBreak (stream.peek ()) && ucsBreakingSpace (stream.peek ())) { - MyGUI::GlyphInfo* gi = style->mFont->getGlyphInfo (stream.peek ()); - if (gi) - space_width += static_cast(gi->advance + gi->bearingX); + MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); + if (info.charFound) + space_width += static_cast(info.advance + info.bearingX); stream.consume (); } @@ -507,9 +515,9 @@ struct TypesetBookImpl::Typesetter : BookTypesetter while (!stream.eof () && !ucsLineBreak (stream.peek ()) && !ucsBreakingSpace (stream.peek ())) { - MyGUI::GlyphInfo* gi = style->mFont->getGlyphInfo (stream.peek ()); - if (gi) - word_width += static_cast(gi->advance + gi->bearingX); + MWGui::GlyphInfo info = GlyphInfo(style->mFont, stream.peek()); + if (info.charFound) + word_width += static_cast(info.advance + info.bearingX); stream.consume (); } @@ -530,6 +538,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter if (mPartialWhitespace.empty() && mPartialWord.empty()) return; + int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); int space_width = 0; int word_width = 0; @@ -542,16 +551,15 @@ struct TypesetBookImpl::Typesetter : BookTypesetter if (left + space_width + word_width > mPageWidth) { - mLine = NULL, mRun = NULL, left = 0; + mLine = nullptr, mRun = nullptr, left = 0; } else { for (PartialTextConstIterator i = mPartialWhitespace.begin (); i != mPartialWhitespace.end (); ++i) { int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; - int line_height = i->mStyle->mFont->getDefaultHeight (); - append_run ( i->mStyle, i->mBegin, i->mEnd, 0, left + i->mWidth, top + line_height); + append_run ( i->mStyle, i->mBegin, i->mEnd, 0, left + i->mWidth, top + fontHeight); left = mLine->mRect.right; } @@ -560,9 +568,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter for (PartialTextConstIterator i = mPartialWord.begin (); i != mPartialWord.end (); ++i) { int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; - int line_height = i->mStyle->mFont->getDefaultHeight (); - append_run (i->mStyle, i->mBegin, i->mEnd, i->mEnd - i->mBegin, left + i->mWidth, top + line_height); + append_run (i->mStyle, i->mBegin, i->mEnd, i->mEnd - i->mBegin, left + i->mWidth, top + fontHeight); left = mLine->mRect.right; } @@ -573,7 +580,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter void append_run (StyleImpl * style, Utf8Stream::Point begin, Utf8Stream::Point end, int pc, int right, int bottom) { - if (mSection == NULL) + if (mSection == nullptr) { mBook->mSections.push_back (Section ()); mSection = &mBook->mSections.back (); @@ -581,7 +588,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter mSectionAlignment.push_back (mCurrentAlignment); } - if (mLine == NULL) + if (mLine == nullptr) { mSection->mLines.push_back (Line ()); mLine = &mSection->mLines.back (); @@ -606,7 +613,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter if (mLine->mRect.bottom < bottom) mLine->mRect.bottom = bottom; - if (mRun == NULL || mRun->mStyle != style || mRun->mRange.second != begin) + if (mRun == nullptr || mRun->mStyle != style || mRun->mRange.second != begin) { int left = mRun ? mRun->mRight : mLine->mRect.left; @@ -756,32 +763,32 @@ namespace void emitGlyph (wchar_t ch) { - MyGUI::GlyphInfo* gi = mFont->getGlyphInfo (ch); + MWGui::GlyphInfo info = GlyphInfo(mFont, ch); - if (!gi) + if (!info.charFound) return; MyGUI::FloatRect vr; - vr.left = mCursor.left + gi->bearingX; - vr.top = mCursor.top + gi->bearingY; - vr.right = vr.left + gi->width; - vr.bottom = vr.top + gi->height; + 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 = gi->uvRect; + MyGUI::FloatRect tr = info.uvRect; if (mRenderXform.clip (vr, tr)) quad (vr, tr); - mCursor.left += gi->bearingX + gi->advance; + mCursor.left += static_cast(info.bearingX + info.advance); } void emitSpace (wchar_t ch) { - MyGUI::GlyphInfo* gi = mFont->getGlyphInfo (ch); + MWGui::GlyphInfo info = GlyphInfo(mFont, ch); - if (gi) - mCursor.left += gi->bearingX + gi->advance; + if (info.charFound) + mCursor.left += static_cast(info.bearingX + info.advance); } private: @@ -836,17 +843,17 @@ protected: TextFormat (MyGUI::IFont* id, PageDisplay * display) : mFont (id), mCountVertex (0), - mTexture (NULL), - mRenderItem (NULL), + mTexture (nullptr), + mRenderItem (nullptr), mDisplay (display) { } void createDrawItem (MyGUI::ILayerNode* node) { - assert (mRenderItem == NULL); + assert (mRenderItem == nullptr); - if (mTexture != NULL) + if (mTexture != nullptr) { mRenderItem = node->addToRenderItem(mTexture, false, false); mRenderItem->addDrawItem(this, mCountVertex); @@ -855,12 +862,12 @@ protected: void destroyDrawItem (MyGUI::ILayerNode* node) { - assert (mTexture != NULL ? mRenderItem != NULL : mRenderItem == NULL); + assert (mTexture != nullptr ? mRenderItem != nullptr : mRenderItem == nullptr); - if (mTexture != NULL) + if (mTexture != nullptr) { mRenderItem->removeDrawItem (this); - mRenderItem = NULL; + mRenderItem = nullptr; } } @@ -913,9 +920,9 @@ public: resetPage (); mViewTop = 0; mViewBottom = 0; - mFocusItem = NULL; + mFocusItem = nullptr; mItemActive = false; - mNode = NULL; + mNode = nullptr; } void dirtyFocusItem () @@ -1046,7 +1053,7 @@ public: for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) { - if (mNode != NULL) + if (mNode != nullptr) i->second->destroyDrawItem (mNode); i->second.reset(); } @@ -1082,7 +1089,7 @@ public: else if (mBook && isPageDifferent (newPage)) { - if (mNode != NULL) + if (mNode != nullptr) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate(i->second->mRenderItem); @@ -1130,7 +1137,7 @@ public: { newBook->visitRuns (0, 0x7FFFFFFF, CreateActiveFormat (this)); - if (mNode != NULL) + if (mNode != nullptr) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) i->second->createDrawItem (mNode); } @@ -1231,7 +1238,7 @@ public: { _checkMargin(); - if (mNode != NULL) + if (mNode != nullptr) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate (i->second->mRenderItem); } @@ -1240,7 +1247,7 @@ public: { _checkMargin (); - if (mNode != NULL) + if (mNode != nullptr) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate (i->second->mRenderItem); @@ -1251,7 +1258,7 @@ public: for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) i->second->destroyDrawItem (mNode); - mNode = NULL; + mNode = nullptr; } }; @@ -1262,7 +1269,7 @@ MYGUI_RTTI_DERIVED(BookPage) public: BookPageImpl() - : mPageDisplay(NULL) + : mPageDisplay(nullptr) { } diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp index 66d1834c78..4ea59414df 100644 --- a/apps/openmw/mwgui/bookpage.hpp +++ b/apps/openmw/mwgui/bookpage.hpp @@ -3,10 +3,17 @@ #include "MyGUI_Colour.h" #include "MyGUI_Widget.h" +#include "MyGUI_FontManager.h" #include #include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + namespace MWGui { /// A formatted and paginated document to be used with @@ -28,6 +35,48 @@ namespace MWGui virtual std::pair getSize () const = 0; }; + struct GlyphInfo + { + char codePoint; + float width; + float height; + float advance; + float bearingX; + float bearingY; + bool charFound; + MyGUI::FloatRect uvRect; + + GlyphInfo(MyGUI::IFont* font, MyGUI::Char ch) + { + static const int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); + + MyGUI::GlyphInfo* gi = font->getGlyphInfo(ch); + if (gi) + { + 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; + uvRect = gi->uvRect; + charFound = true; + } + else + { + codePoint = 0; + bearingX = 0; + bearingY = 0; + width = 0; + height = 0; + advance = 0; + charFound = false; + } + } + }; + /// A factory class for creating a typeset book instance. struct BookTypesetter { @@ -56,7 +105,7 @@ namespace MWGui static Ptr create (int pageWidth, int pageHeight); /// Create a simple text style consisting of a font and a text color. - virtual Style* createStyle (char const * Font, const Colour& Colour) = 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 diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 6ed7a44915..45abe889ed 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -399,12 +399,12 @@ namespace MWGui CreateClassDialog::CreateClassDialog() : WindowModal("openmw_chargen_create_class.layout") - , mSpecDialog(NULL) - , mAttribDialog(NULL) - , mSkillDialog(NULL) - , mDescDialog(NULL) - , mAffectedAttribute(NULL) - , mAffectedSkill(NULL) + , mSpecDialog(nullptr) + , mAttribDialog(nullptr) + , mSkillDialog(nullptr) + , mDescDialog(nullptr) + , mAffectedAttribute(nullptr) + , mAffectedSkill(nullptr) { // Centre dialog center(); diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index c6ef2890d1..b2639e938a 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -37,8 +37,8 @@ namespace MWGui CompanionWindow::CompanionWindow(DragAndDrop *dragAndDrop, MessageBoxManager* manager) : WindowBase("openmw_companion_window.layout") - , mSortModel(NULL) - , mModel(NULL) + , mSortModel(nullptr) + , mModel(nullptr) , mSelectedItem(-1) , mDragAndDrop(dragAndDrop) , mMessageBoxManager(manager) @@ -89,7 +89,7 @@ void CompanionWindow::onItemSelected(int index) dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::dragItem); } else - dragItem (NULL, count); + dragItem (nullptr, count); } void CompanionWindow::dragItem(MyGUI::Widget* sender, int count) @@ -179,9 +179,9 @@ void CompanionWindow::onReferenceUnavailable() void CompanionWindow::resetReference() { ReferenceInterface::resetReference(); - mItemView->setModel(NULL); - mModel = NULL; - mSortModel = NULL; + mItemView->setModel(nullptr); + mModel = nullptr; + mSortModel = nullptr; } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index b367c6f492..dc22e4193b 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -152,12 +152,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } - void Console::setFont(const std::string &fntName) - { - mHistory->setFontName(fntName); - mCommandLine->setFontName(fntName); - } - void Console::print(const std::string &msg, const std::string& color) { mHistory->addText(color + MyGUI::TextIterator::toTagsString(msg)); diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index bbff34c8da..883bc8967d 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -41,8 +41,6 @@ namespace MWGui virtual void onOpen(); - void setFont(const std::string &fntName); - void onResChange(int width, int height); // Print a message to the console, in specified color. diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 703ba309e5..2f9643f740 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -31,8 +31,8 @@ namespace MWGui ContainerWindow::ContainerWindow(DragAndDrop* dragAndDrop) : WindowBase("openmw_container_window.layout") , mDragAndDrop(dragAndDrop) - , mSortModel(NULL) - , mModel(NULL) + , mSortModel(nullptr) + , mModel(nullptr) , mSelectedItem(-1) { getWidget(mDisposeCorpseButton, "DisposeCorpseButton"); @@ -83,7 +83,7 @@ namespace MWGui dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::dragItem); } else - dragItem (NULL, count); + dragItem (nullptr, count); } void ContainerWindow::dragItem(MyGUI::Widget* sender, int count) @@ -151,9 +151,9 @@ namespace MWGui void ContainerWindow::resetReference() { ReferenceInterface::resetReference(); - mItemView->setModel(NULL); - mModel = NULL; - mSortModel = NULL; + mItemView->setModel(nullptr); + mModel = nullptr; + mSortModel = nullptr; } void ContainerWindow::onClose() @@ -171,9 +171,11 @@ namespace MWGui void ContainerWindow::onTakeAllButtonClicked(MyGUI::Widget* _sender) { - if(mDragAndDrop != NULL && mDragAndDrop->mIsOnDragAndDrop) + if(mDragAndDrop != nullptr && mDragAndDrop->mIsOnDragAndDrop) return; + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); + // transfer everything into the player's inventory ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); mModel->update(); @@ -217,8 +219,10 @@ namespace MWGui void ContainerWindow::onDisposeCorpseButtonClicked(MyGUI::Widget *sender) { - if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop) + if(mDragAndDrop == nullptr || !mDragAndDrop->mIsOnDragAndDrop) { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); + onTakeAllButtonClicked(mTakeButton); if (mPtr.getClass().isPersistent(mPtr)) diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp index cf058caac3..baf3a43abd 100644 --- a/apps/openmw/mwgui/countdialog.cpp +++ b/apps/openmw/mwgui/countdialog.cpp @@ -63,7 +63,7 @@ namespace MWGui void CountDialog::onOkButtonClicked(MyGUI::Widget* _sender) { - eventOkClicked(NULL, mSlider->getScrollPosition()+1); + eventOkClicked(nullptr, mSlider->getScrollPosition()+1); setVisible(false); } @@ -72,9 +72,11 @@ namespace MWGui // Enter key void CountDialog::onEnterKeyPressed(MyGUI::EditBox* _sender) { - eventOkClicked(NULL, mSlider->getScrollPosition()+1); - + eventOkClicked(nullptr, mSlider->getScrollPosition()+1); setVisible(false); + + // To do not spam onEnterKeyPressed() again and again + MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void CountDialog::onEditValueChanged(int value) diff --git a/apps/openmw/mwgui/cursor.cpp b/apps/openmw/mwgui/cursor.cpp index 82e0b9699b..ed8a76eb87 100644 --- a/apps/openmw/mwgui/cursor.cpp +++ b/apps/openmw/mwgui/cursor.cpp @@ -10,7 +10,7 @@ namespace MWGui ResourceImageSetPointerFix::ResourceImageSetPointerFix() - : mImageSet(NULL) + : mImageSet(nullptr) , mRotation(0) { } @@ -47,7 +47,7 @@ namespace MWGui void ResourceImageSetPointerFix::setImage(MyGUI::ImageBox* _image) { - if (mImageSet != NULL) + if (mImageSet != nullptr) _image->setItemResourceInfo(mImageSet->getIndexInfo(0, 0)); } diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 6aceccaff5..3ac8839e27 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -137,7 +137,7 @@ namespace MWGui if (mTitle != "") { const MyGUI::Colour& headerColour = MWBase::Environment::get().getWindowManager()->getTextColours().header; - BookTypesetter::Style* title = typesetter->createStyle("", headerColour); + BookTypesetter::Style* title = typesetter->createStyle("", headerColour, false); typesetter->write(title, to_utf8_span(mTitle.c_str())); typesetter->sectionBreak(); } @@ -182,7 +182,7 @@ namespace MWGui { const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); - BookTypesetter::Style* style = typesetter->createStyle("", textColours.normal); + BookTypesetter::Style* style = typesetter->createStyle("", textColours.normal, false); size_t formatted = 0; // points to the first character that is not laid out yet for (std::map::iterator it = hyperLinks.begin(); it != hyperLinks.end(); ++it) { @@ -223,7 +223,7 @@ namespace MWGui { const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); - BookTypesetter::Style* style = typesetter->createStyle("", textColours.normal); + BookTypesetter::Style* style = typesetter->createStyle("", textColours.normal, false); if (topicId) @@ -239,7 +239,7 @@ namespace MWGui 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); + BookTypesetter::Style* title = typesetter->createStyle("", textColour, false); typesetter->sectionBreak(9); typesetter->write(title, to_utf8_span(mText.c_str())); } @@ -567,7 +567,7 @@ namespace MWGui (*it)->write(typesetter, &mKeywordSearch, mTopicLinks); - BookTypesetter::Style* body = typesetter->createStyle("", MyGUI::Colour::White); + BookTypesetter::Style* body = typesetter->createStyle("", MyGUI::Colour::White, false); typesetter->sectionBreak(9); // choices diff --git a/apps/openmw/mwgui/draganddrop.cpp b/apps/openmw/mwgui/draganddrop.cpp index d81b2ed006..daf9f66367 100644 --- a/apps/openmw/mwgui/draganddrop.cpp +++ b/apps/openmw/mwgui/draganddrop.cpp @@ -20,10 +20,10 @@ namespace MWGui DragAndDrop::DragAndDrop() : mIsOnDragAndDrop(false) - , mDraggedWidget(NULL) - , mSourceModel(NULL) - , mSourceView(NULL) - , mSourceSortModel(NULL) + , mDraggedWidget(nullptr) + , mSourceModel(nullptr) + , mSourceView(nullptr) + , mSourceSortModel(nullptr) , mDraggedCount(0) { } diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 8fbfa65c68..98980e3397 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -28,7 +28,7 @@ namespace MWGui EnchantingDialog::EnchantingDialog() : WindowBase("openmw_enchanting_dialog.layout") , EffectEditorBase(EffectEditorBase::Enchanting) - , mItemSelectionDialog(NULL) + , mItemSelectionDialog(nullptr) { getWidget(mName, "NameEdit"); getWidget(mCancelButton, "CancelButton"); @@ -288,6 +288,9 @@ namespace MWGui void EnchantingDialog::onAccept(MyGUI::EditBox *sender) { onBuyButtonClicked(sender); + + // To do not spam onAccept() again and again + MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void EnchantingDialog::onBuyButtonClicked(MyGUI::Widget* sender) diff --git a/apps/openmw/mwgui/exposedwindow.cpp b/apps/openmw/mwgui/exposedwindow.cpp index 150a8c893a..1a0484e729 100644 --- a/apps/openmw/mwgui/exposedwindow.cpp +++ b/apps/openmw/mwgui/exposedwindow.cpp @@ -14,7 +14,7 @@ namespace MWGui if (widgets.empty()) { MYGUI_ASSERT( ! _throw, "widget name '" << _name << "' not found in skin of layout '" << getName() << "'"); - return NULL; + return nullptr; } else { diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 663bd73380..78359608ec 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -27,7 +27,7 @@ namespace MWGui BookTextParser::BookTextParser(const std::string & text) : mIndex(0), mText(text), mIgnoreNewlineTags(true), mIgnoreLineEndings(true), mClosingTag(false) { - MWScript::InterpreterContext interpreterContext(NULL, MWWorld::Ptr()); // empty arguments, because there is no locals or actor + MWScript::InterpreterContext interpreterContext(nullptr, MWWorld::Ptr()); // empty arguments, because there is no locals or actor mText = Interpreter::fixDefinesBook(mText, interpreterContext); boost::algorithm::replace_all(mText, "\r", ""); @@ -377,7 +377,7 @@ namespace MWGui if (attr.find("face") != attr.end()) { std::string face = attr.at("face"); - mTextStyle.mFont = face; + mTextStyle.mFont = "Journalbook "+face; } if (attr.find("size") != attr.end()) { @@ -415,7 +415,7 @@ namespace MWGui : GraphicElement(parent, pag, blockStyle), mTextStyle(textStyle) { - MyGUI::EditBox* box = parent->createWidget("NormalText", + 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); @@ -432,15 +432,6 @@ namespace MWGui mEditBox = box; } - int TextElement::currentFontHeight() const - { - std::string fontName(mTextStyle.mFont == "Default" ? MyGUI::FontManager::getInstance().getDefaultFont() : mTextStyle.mFont); - MyGUI::IFont* font = MyGUI::FontManager::getInstance().getByName(fontName); - if (!font) - return 0; - return font->getDefaultHeight(); - } - int TextElement::getHeight() { return mEditBox->getTextSize().height; @@ -449,7 +440,7 @@ namespace MWGui int TextElement::pageSplit() { // split lines - const int lineHeight = currentFontHeight(); + const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); unsigned int lastLine = (mPaginator.getStartTop() + mPaginator.getPageHeight() - mPaginator.getCurrentTop()); if (lineHeight > 0) lastLine /= lineHeight; diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index d525baacec..b16a4e57d3 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace MWGui { namespace Formatting @@ -12,7 +14,7 @@ namespace MWGui { TextStyle() : mColour(0,0,0) - , mFont("Default") + , mFont("Journalbook Magic Cards") , mTextSize(16) { } @@ -152,7 +154,7 @@ namespace MWGui private: int currentFontHeight() const; TextStyle mTextStyle; - MyGUI::EditBox * mEditBox; + Gui::EditBox * mEditBox; }; class ImageElement : public GraphicElement diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 91467d490e..6076c1c10d 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -70,20 +70,20 @@ namespace MWGui 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(NULL) - , mMagicka(NULL) - , mStamina(NULL) - , mDrowning(NULL) - , mWeapImage(NULL) - , mSpellImage(NULL) - , mWeapStatus(NULL) - , mSpellStatus(NULL) - , mEffectBox(NULL) - , mMinimap(NULL) - , mCrosshair(NULL) - , mCellNameBox(NULL) - , mDrowningFrame(NULL) - , mDrowningFlash(NULL) + , mHealth(nullptr) + , mMagicka(nullptr) + , mStamina(nullptr) + , mDrowning(nullptr) + , mWeapImage(nullptr) + , mSpellImage(nullptr) + , mWeapStatus(nullptr) + , mSpellStatus(nullptr) + , mEffectBox(nullptr) + , mMinimap(nullptr) + , mCrosshair(nullptr) + , mCellNameBox(nullptr) + , mDrowningFrame(nullptr) + , mDrowningFlash(nullptr) , mHealthManaStaminaBaseLeft(0) , mWeapBoxBaseLeft(0) , mSpellBoxBaseLeft(0) @@ -247,7 +247,7 @@ namespace MWGui float mouseY = cursorPosition.top / float(viewSize.height); WorldItemModel drop (mouseX, mouseY); - mDragAndDrop->drop(&drop, NULL); + mDragAndDrop->drop(&drop, nullptr); MWBase::Environment::get().getWindowManager()->changePointer("arrow"); } diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 0d37bbff3b..1877ef97d8 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -60,8 +60,8 @@ namespace MWGui : WindowPinnableBase("openmw_inventory_window.layout") , mDragAndDrop(dragAndDrop) , mSelectedItem(-1) - , mSortModel(NULL) - , mTradeModel(NULL) + , mSortModel(nullptr) + , mTradeModel(nullptr) , mGuiMode(GM_Inventory) , mLastXSize(0) , mLastYSize(0) @@ -157,9 +157,9 @@ namespace MWGui void InventoryWindow::clear() { mPtr = MWWorld::Ptr(); - mTradeModel = NULL; - mSortModel = NULL; - mItemView->setModel(NULL); + mTradeModel = nullptr; + mSortModel = nullptr; + mItemView->setModel(nullptr); } void InventoryWindow::setGuiMode(GuiMode mode) @@ -296,9 +296,9 @@ namespace MWGui { mSelectedItem = index; if (mTrading) - sellItem (NULL, count); + sellItem (nullptr, count); else - dragItem (NULL, count); + dragItem (nullptr, count); } } diff --git a/apps/openmw/mwgui/itemchargeview.cpp b/apps/openmw/mwgui/itemchargeview.cpp index 3001b9ef2f..10c36c73f8 100644 --- a/apps/openmw/mwgui/itemchargeview.cpp +++ b/apps/openmw/mwgui/itemchargeview.cpp @@ -21,7 +21,7 @@ namespace MWGui { ItemChargeView::ItemChargeView() - : mScrollView(NULL), + : mScrollView(nullptr), mDisplayMode(DisplayMode_Health) { } @@ -36,7 +36,7 @@ namespace MWGui Base::initialiseOverride(); assignWidget(mScrollView, "ScrollView"); - if (mScrollView == NULL) + if (mScrollView == nullptr) throw std::runtime_error("Item charge view needs a scroll view"); mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index e3f38a54c1..c7c10e8e4b 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -31,7 +31,7 @@ namespace MWGui ItemStack::ItemStack() : mType(Type_Normal) , mFlags(0) - , mCreator(NULL) + , mCreator(nullptr) , mCount(0) { } @@ -106,7 +106,7 @@ namespace MWGui ProxyItemModel::ProxyItemModel() - : mSourceModel(NULL) + : mSourceModel(nullptr) { } @@ -167,7 +167,7 @@ namespace MWGui if (mSourceModel) { delete mSourceModel; - mSourceModel = NULL; + mSourceModel = nullptr; } mSourceModel = sourceModel; diff --git a/apps/openmw/mwgui/itemselection.cpp b/apps/openmw/mwgui/itemselection.cpp index effd11d557..23f1398eaa 100644 --- a/apps/openmw/mwgui/itemselection.cpp +++ b/apps/openmw/mwgui/itemselection.cpp @@ -12,8 +12,8 @@ namespace MWGui ItemSelectionDialog::ItemSelectionDialog(const std::string &label) : WindowModal("openmw_itemselection_dialog.layout") - , mSortModel(NULL) - , mModel(NULL) + , mSortModel(nullptr) + , mModel(nullptr) { getWidget(mItemView, "ItemView"); mItemView->eventItemClicked += MyGUI::newDelegate(this, &ItemSelectionDialog::onSelectedItem); diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index 2cdfcada4d..94dcc77c5e 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -18,8 +18,8 @@ namespace MWGui { ItemView::ItemView() - : mModel(NULL) - , mScrollView(NULL) + : mModel(nullptr) + , mScrollView(nullptr) { } @@ -44,7 +44,7 @@ void ItemView::initialiseOverride() Base::initialiseOverride(); assignWidget(mScrollView, "ScrollView"); - if (mScrollView == NULL) + if (mScrollView == nullptr) throw std::runtime_error("Item view needs a scroll view"); mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index 6b5be0314b..223bced4fa 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -32,10 +32,10 @@ namespace MWGui { ItemWidget::ItemWidget() - : mItem(NULL) - , mItemShadow(NULL) - , mFrame(NULL) - , mText(NULL) + : mItem(nullptr) + , mItemShadow(nullptr) + , mFrame(nullptr) + , mText(nullptr) { } diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index 1075239fa8..4302740f67 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -157,7 +157,7 @@ MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text) typedef TypesetBook::Ptr book; JournalBooks::JournalBooks (JournalViewModel::Ptr model, ToUTF8::FromType encoding) : - mModel (model), mEncoding(encoding) + mModel (model), mEncoding(encoding), mIndexPagesCount(0) { } @@ -229,10 +229,13 @@ book JournalBooks::createTopicIndexBook () BookTypesetter::Ptr JournalBooks::createLatinJournalIndex () { - BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 250); + 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); @@ -260,12 +263,23 @@ BookTypesetter::Ptr JournalBooks::createLatinJournalIndex () BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex () { - BookTypesetter::Ptr typesetter = BookTypesetter::create (92, 250); + 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) + { + sectionBreak = 15; + mIndexPagesCount = 2; + } + unsigned char ch[2] = {0xd0, 0x90}; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8 for (int i = 0; i < 32; ++i) @@ -287,7 +301,7 @@ BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex () if (i == 26 || i == 28) continue; - if (i == 15) + if (i % sectionBreak == 0) typesetter->sectionBreak (); typesetter->write (style, to_utf8_span (buffer)); diff --git a/apps/openmw/mwgui/journalbooks.hpp b/apps/openmw/mwgui/journalbooks.hpp index aa36eecdff..05eda6e222 100644 --- a/apps/openmw/mwgui/journalbooks.hpp +++ b/apps/openmw/mwgui/journalbooks.hpp @@ -25,6 +25,7 @@ namespace MWGui Book createTopicIndexBook (); ToUTF8::FromType mEncoding; + int mIndexPagesCount; private: BookTypesetter::Ptr createTypesetter (); diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index 63b48eab12..3621656bc3 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -39,7 +39,7 @@ struct JournalViewModelImpl : JournalViewModel static Utf8Span toUtf8Span (std::string const & str) { if (str.size () == 0) - return Utf8Span (Utf8Point (NULL), Utf8Point (NULL)); + return Utf8Span (Utf8Point (nullptr), Utf8Point (nullptr)); Utf8Point point = reinterpret_cast (str.c_str ()); diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 0776706d8c..a2f6ea1423 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -43,6 +43,7 @@ namespace 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"; struct JournalWindowImpl : MWGui::JournalBooks, MWGui::JournalWindow @@ -148,6 +149,7 @@ namespace callback = std::bind(&JournalWindowImpl::notifyIndexLinkClicked, this, std::placeholders::_1); getPage (LeftTopicIndex)->adviseLinkClicked (callback); + getPage (CenterTopicIndex)->adviseLinkClicked (callback); getPage (RightTopicIndex)->adviseLinkClicked (callback); } @@ -312,6 +314,7 @@ namespace setVisible (TopicsList, false); setVisible (QuestsList, mQuestMode); setVisible (LeftTopicIndex, !mQuestMode); + setVisible (CenterTopicIndex, !mQuestMode); setVisible (RightTopicIndex, !mQuestMode); setVisible (ShowAllBTN, mQuestMode && !mAllQuests); setVisible (ShowActiveBTN, mQuestMode && mAllQuests); @@ -465,8 +468,17 @@ namespace if (!mTopicIndexBook) mTopicIndexBook = createTopicIndexBook (); - getPage (LeftTopicIndex)->showPage (mTopicIndexBook, 0); - getPage (RightTopicIndex)->showPage (mTopicIndexBook, 1); + if (mIndexPagesCount == 3) + { + 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); + } } void notifyJournal(MyGUI::Widget* _sender) @@ -480,6 +492,7 @@ namespace void notifyIndexLinkClicked (MWGui::TypesetBook::InteractiveId index) { setVisible (LeftTopicIndex, false); + setVisible (CenterTopicIndex, false); setVisible (RightTopicIndex, false); setVisible (TopicsList, true); @@ -502,6 +515,7 @@ namespace mQuestMode = false; mTopicsMode = false; setVisible (LeftTopicIndex, true); + setVisible (CenterTopicIndex, true); setVisible (RightTopicIndex, true); setVisible (TopicsList, false); setVisible (QuestsList, false); @@ -540,6 +554,7 @@ namespace mQuestMode = true; setVisible (LeftTopicIndex, false); + setVisible (CenterTopicIndex, true); setVisible (RightTopicIndex, false); setVisible (TopicsList, false); setVisible (QuestsList, true); diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index cde8a17ccd..52ff0b2d93 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" @@ -49,7 +51,14 @@ KeyboardNavigation::KeyboardNavigation() KeyboardNavigation::~KeyboardNavigation() { - MyGUI::WidgetManager::getInstance().unregisterUnlinker(this); + try + { + MyGUI::WidgetManager::getInstance().unregisterUnlinker(this); + } + catch(const MyGUI::Exception& e) + { + Log(Debug::Error) << "Error in the destructor: " << e.what(); + } } void KeyboardNavigation::saveFocus(int mode) @@ -172,7 +181,7 @@ enum Direction D_Prev }; -bool KeyboardNavigation::injectKeyPress(MyGUI::KeyCode key, unsigned int text) +bool KeyboardNavigation::injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat) { if (!mEnabled) return false; @@ -192,7 +201,14 @@ bool KeyboardNavigation::injectKeyPress(MyGUI::KeyCode key, unsigned int text) 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; } diff --git a/apps/openmw/mwgui/keyboardnavigation.hpp b/apps/openmw/mwgui/keyboardnavigation.hpp index 7caf25690d..2a094a2df2 100644 --- a/apps/openmw/mwgui/keyboardnavigation.hpp +++ b/apps/openmw/mwgui/keyboardnavigation.hpp @@ -14,7 +14,7 @@ namespace MWGui ~KeyboardNavigation(); /// @return Was the key handled by this class? - bool injectKeyPress(MyGUI::KeyCode key, unsigned int text); + bool injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat); void saveFocus(int mode); void restoreFocus(int mode); diff --git a/apps/openmw/mwgui/layout.hpp b/apps/openmw/mwgui/layout.hpp index 0e7cf3faa7..ea51bf541e 100644 --- a/apps/openmw/mwgui/layout.hpp +++ b/apps/openmw/mwgui/layout.hpp @@ -5,6 +5,8 @@ #include #include +#include + namespace MWGui { /** The Layout class is an utility class used to load MyGUI layouts @@ -16,7 +18,17 @@ namespace MWGui Layout(const std::string & _layout, MyGUI::Widget* _parent = nullptr) : mMainWidget(nullptr) { initialise(_layout, _parent); } - virtual ~Layout() { shutdown(); } + virtual ~Layout() + { + try + { + shutdown(); + } + catch(const MyGUI::Exception& e) + { + Log(Debug::Error) << "Error in the destructor: " << e.what(); + } + } MyGUI::Widget* getWidget(const std::string& _name); diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index be3d477e1e..3bb3ee260d 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -140,7 +140,7 @@ namespace MWGui // Callback removes itself when done if (renderInfo.getCurrentCamera()) - renderInfo.getCurrentCamera()->setInitialDrawCallback(NULL); + renderInfo.getCurrentCamera()->setInitialDrawCallback(nullptr); } private: @@ -169,19 +169,18 @@ namespace MWGui // 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); - mShowWallpaper = visible && (MWBase::Environment::get().getStateManager()->getState() - == MWBase::StateManager::State_NoGame); + mVisible = visible; + mLoadingBox->setVisible(mVisible); + setVisible(true); - if (!visible) + if (!mVisible) { + mShowWallpaper = false; draw(); return; } - mVisible = visible; - mLoadingBox->setVisible(mVisible); - - setVisible(true); + mShowWallpaper = MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame; if (mShowWallpaper) { @@ -208,7 +207,7 @@ namespace MWGui else mImportantLabel = false; // label was already shown on loading screen - mViewer->getSceneData()->setComputeBoundingSphereCallback(NULL); + mViewer->getSceneData()->setComputeBoundingSphereCallback(nullptr); mViewer->getSceneData()->dirtyBound(); //std::cout << "loading took " << mTimer.time_m() - mLoadingOnTime << std::endl; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 5017b88932..1b9e024ba6 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -25,10 +25,10 @@ namespace MWGui : WindowBase("openmw_mainmenu.layout") , mWidth (w), mHeight (h) , mVFS(vfs), mButtonBox(0) - , mBackground(NULL) - , mVideoBackground(NULL) - , mVideo(NULL) - , mSaveGameDialog(NULL) + , mBackground(nullptr) + , mVideoBackground(nullptr) + , mVideo(nullptr) + , mSaveGameDialog(nullptr) { getWidget(mVersionText, "VersionText"); mVersionText->setCaption(versionDescription); @@ -147,13 +147,13 @@ namespace MWGui if (mVideo && !show) { MyGUI::Gui::getInstance().destroyWidget(mVideoBackground); - mVideoBackground = NULL; - mVideo = NULL; + mVideoBackground = nullptr; + mVideo = nullptr; } if (mBackground && !show) { MyGUI::Gui::getInstance().destroyWidget(mBackground); - mBackground = NULL; + mBackground = nullptr; } if (!show) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index c1ff9510d3..b9b59965db 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -33,7 +33,7 @@ namespace { - const int cellSize = 8192; + const int cellSize = Constants::CellSizeInUnits; enum LocalMapWidgetDepth { @@ -161,8 +161,8 @@ namespace MWGui , mCurX(0) , mCurY(0) , mInterior(false) - , mLocalMap(NULL) - , mCompass(NULL) + , mLocalMap(nullptr) + , mCompass(nullptr) , mChanged(true) , mFogOfWarToggled(true) , mFogOfWarEnabled(fogOfWarEnabled) @@ -388,7 +388,7 @@ namespace MWGui box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } else - box->setRenderItemTexture(NULL); + box->setRenderItemTexture(nullptr); } } mMapTextures.swap(textures); @@ -598,7 +598,7 @@ namespace MWGui addDetectionMarkers(MWBase::World::Detect_Enchantment); // Add marker for the spot marked with Mark magic effect - MWWorld::CellStore* markedCell = NULL; + MWWorld::CellStore* markedCell = nullptr; ESM::Position markedPosition; MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); if (markedCell && markedCell->isExterior() == !mInterior @@ -627,11 +627,11 @@ namespace MWGui , LocalMapBase(customMarkers, localMapRender) , NoDrop(drag, mMainWidget) , mGlobalMap(0) - , mGlobalMapImage(NULL) - , mGlobalMapOverlay(NULL) + , mGlobalMapImage(nullptr) + , mGlobalMapOverlay(nullptr) , mGlobal(Settings::Manager::getBool("global", "Map")) - , mEventBoxGlobal(NULL) - , mEventBoxLocal(NULL) + , mEventBoxGlobal(nullptr) + , mEventBoxLocal(nullptr) , mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue)) , mEditNoteDialog() { @@ -872,7 +872,7 @@ namespace MWGui if (!destNotes.empty()) { - MarkerUserData data (NULL); + MarkerUserData data (nullptr); data.notes = destNotes; data.caption = markerWidget->getUserString("Caption_TextOneLine"); markerWidget->setUserData(data); diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 9974c6d16e..b9d4c80f4f 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -20,8 +20,6 @@ namespace MWGui { -const int MerchantRepair::sLineHeight = 18; - MerchantRepair::MerchantRepair() : WindowBase("openmw_merchantrepair.layout") { @@ -39,6 +37,7 @@ void MerchantRepair::setPtr(const MWWorld::Ptr &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(); @@ -67,28 +66,26 @@ void MerchantRepair::setPtr(const MWWorld::Ptr &actor) 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, - sLineHeight, + lineHeight, MyGUI::Align::Default ); - currentY += sLineHeight; + currentY += lineHeight; button->setUserString("Price", MyGUI::utility::toString(price)); button->setUserData(MWWorld::Ptr(*iter)); button->setCaptionWithReplacing(name); - button->setSize(mList->getWidth(),sLineHeight); + button->setSize(mList->getWidth(), lineHeight); button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel); button->setUserString("ToolTipType", "ItemPtr"); button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); diff --git a/apps/openmw/mwgui/merchantrepair.hpp b/apps/openmw/mwgui/merchantrepair.hpp index 4e4e7164fd..26887ae2ca 100644 --- a/apps/openmw/mwgui/merchantrepair.hpp +++ b/apps/openmw/mwgui/merchantrepair.hpp @@ -27,8 +27,6 @@ protected: void onMouseWheel(MyGUI::Widget* _sender, int _rel); void onRepairButtonClick(MyGUI::Widget* sender); void onOkButtonClick(MyGUI::Widget* sender); - - static const int sLineHeight; }; } diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 2fbce97d46..e83c4b238a 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -20,8 +20,8 @@ namespace MWGui MessageBoxManager::MessageBoxManager (float timePerChar) { - mInterMessageBoxe = NULL; - mStaticMessageBox = NULL; + mInterMessageBoxe = nullptr; + mStaticMessageBox = nullptr; mLastButtonPressed = -1; mMessageBoxSpeed = timePerChar; } @@ -42,14 +42,14 @@ namespace MWGui mInterMessageBoxe->setVisible(false); delete mInterMessageBoxe; - mInterMessageBoxe = NULL; + mInterMessageBoxe = nullptr; } std::vector::iterator it(mMessageBoxes.begin()); for (; it != mMessageBoxes.end(); ++it) { if (*it == mStaticMessageBox) - mStaticMessageBox = NULL; + mStaticMessageBox = nullptr; delete *it; } mMessageBoxes.clear(); @@ -81,11 +81,11 @@ namespace MWGui ++it; } - if(mInterMessageBoxe != NULL && mInterMessageBoxe->mMarkedToDelete) { + if(mInterMessageBoxe != nullptr && mInterMessageBoxe->mMarkedToDelete) { mLastButtonPressed = mInterMessageBoxe->readPressedButton(); mInterMessageBoxe->setVisible(false); delete mInterMessageBoxe; - mInterMessageBoxe = NULL; + mInterMessageBoxe = nullptr; MWBase::Environment::get().getInputManager()->changeInputMode( MWBase::Environment::get().getWindowManager()->isGuiMode()); } @@ -119,17 +119,17 @@ namespace MWGui void MessageBoxManager::removeStaticMessageBox () { removeMessageBox(mStaticMessageBox); - mStaticMessageBox = NULL; + mStaticMessageBox = nullptr; } bool MessageBoxManager::createInteractiveMessageBox (const std::string& message, const std::vector& buttons) { - if (mInterMessageBoxe != NULL) + if (mInterMessageBoxe != nullptr) { Log(Debug::Warning) << "Warning: replacing an interactive message box that was not answered yet"; mInterMessageBoxe->setVisible(false); delete mInterMessageBoxe; - mInterMessageBoxe = NULL; + mInterMessageBoxe = nullptr; } mInterMessageBoxe = new InteractiveMessageBox(*this, message, buttons); @@ -140,7 +140,7 @@ namespace MWGui bool MessageBoxManager::isInteractiveMessageBox () { - return mInterMessageBoxe != NULL; + return mInterMessageBoxe != nullptr; } @@ -373,7 +373,7 @@ namespace MWGui } } } - return NULL; + return nullptr; } void InteractiveMessageBox::mousePressed (MyGUI::Widget* pressed) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 8041c50c59..23ad87fd68 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -254,6 +254,8 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); mSelected->type = Type_MagicItem; + mSelected->id = item.getCellRef().getRefId(); + mSelected->name = item.getClass().getName(item); mSelected->button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); mSelected->button->setIcon(item); @@ -271,20 +273,19 @@ namespace MWGui 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 ESM::Spell* spell = esmStore.get().find(spellId); + mSelected->type = Type_Magic; mSelected->id = spellId; + mSelected->name = spell->mName; mSelected->button->setItem(MWWorld::Ptr()); mSelected->button->setUserString("ToolTipType", "Spell"); mSelected->button->setUserString("Spell", spellId); - const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); - // use the icon of the first effect - const ESM::Spell* spell = esmStore.get().find(spellId); - - const ESM::MagicEffect* effect = - esmStore.get().find(spell->mEffects.mList.front().mEffectID); + const ESM::MagicEffect* effect = esmStore.get().find(spell->mEffects.mList.front().mEffectID); std::string path = effect->mIcon; int slashPos = path.rfind('\\'); @@ -423,9 +424,7 @@ namespace MWGui if (!spells.hasSpell(spellId)) { - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + spell->mName); + MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + key->name); return; } @@ -547,28 +546,26 @@ namespace MWGui return; mSelected = &mKey[i]; - mSelected->type = (QuickKeysMenu::QuickKeyType) it->mType; - mSelected->id = it->mId; - switch (mSelected->type) + switch (it->mType) { case Type_Magic: - if (MWBase::Environment::get().getWorld()->getStore().get().search(mSelected->id)) - onAssignMagic(mSelected->id); + if (MWBase::Environment::get().getWorld()->getStore().get().search(it->mId)) + onAssignMagic(it->mId); break; case Type_Item: case Type_MagicItem: { // Find the item by id - MWWorld::Ptr item = store.findReplacement(mSelected->id); + MWWorld::Ptr item = store.findReplacement(it->mId); if (item.isEmpty()) - unassign(&mKey[i]); + unassign(mSelected); else { - if (mSelected->type == Type_Item) + if (it->mType == Type_Item) onAssignItem(item); - else if (mSelected->type == Type_MagicItem) + else // if (it->mType == Type_MagicItem) onAssignMagicItem(item); } @@ -576,7 +573,7 @@ namespace MWGui } case Type_Unassigned: case Type_HandToHand: - unassign(&mKey[i]); + unassign(mSelected); break; } diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 96c0d7de4f..be0dff6600 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -129,10 +129,10 @@ namespace MWGui updateSkills(); updateSpellPowers(); - mPreviewImage->setRenderItemTexture(NULL); + mPreviewImage->setRenderItemTexture(nullptr); - mPreview.reset(NULL); - mPreviewTexture.reset(NULL); + mPreview.reset(nullptr); + mPreviewTexture.reset(nullptr); mPreview.reset(new MWRender::RaceSelectionPreview(mParent, mResourceSystem)); mPreview->rebuild(); @@ -190,10 +190,10 @@ namespace MWGui { WindowModal::onClose(); - mPreviewImage->setRenderItemTexture(NULL); + mPreviewImage->setRenderItemTexture(nullptr); - mPreviewTexture.reset(NULL); - mPreview.reset(NULL); + mPreviewTexture.reset(nullptr); + mPreview.reset(nullptr); } // widget controls @@ -212,7 +212,7 @@ namespace MWGui void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position) { - float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5f) * 3.14f * 2; + float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5f) * osg::PI * 2; mPreview->setAngle (angle); mCurrentAngle = angle; diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index a6ed16bd40..56bd97d5e3 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -18,7 +18,6 @@ #include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" #include "itemwidget.hpp" @@ -31,7 +30,7 @@ namespace MWGui Recharge::Recharge() : WindowBase("openmw_recharge_dialog.layout") - , mItemSelectionDialog(NULL) + , mItemSelectionDialog(nullptr) { getWidget(mBox, "Box"); getWidget(mGemBox, "GemBox"); @@ -90,7 +89,7 @@ void Recharge::updateView() mBox->update(); Gui::Box* box = dynamic_cast(mMainWidget); - if (box == NULL) + if (box == nullptr) throw std::runtime_error("main widget must be a box"); box->notifyChildrenSizeChanged(); @@ -139,7 +138,6 @@ void Recharge::onItemClicked(MyGUI::Widget *sender, const MWWorld::Ptr& item) MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); float luckTerm = 0.1f * stats.getAttribute(ESM::Attribute::Luck).getModified(); if (luckTerm < 1|| luckTerm > 10) @@ -152,7 +150,7 @@ void Recharge::onItemClicked(MyGUI::Widget *sender, const MWWorld::Ptr& item) if (intelligenceTerm < 1) intelligenceTerm = 1; - float x = (npcStats.getSkill(ESM::Skill::Enchant).getModified() + intelligenceTerm + luckTerm) * stats.getFatigueTerm(); + float x = (player.getClass().getSkill(player, ESM::Skill::Enchant) + intelligenceTerm + luckTerm) * stats.getFatigueTerm(); int roll = Misc::Rng::roll0to99(); if (roll < x) { diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 11a8aece29..ea79e0326e 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -27,7 +27,7 @@ namespace MWGui Repair::Repair() : WindowBase("openmw_repair.layout") - , mItemSelectionDialog(NULL) + , mItemSelectionDialog(nullptr) { getWidget(mRepairBox, "RepairBox"); getWidget(mToolBox, "ToolBox"); @@ -99,7 +99,7 @@ void Repair::updateRepairView() mRepairBox->update(); Gui::Box* box = dynamic_cast(mMainWidget); - if (box == NULL) + if (box == nullptr) throw std::runtime_error("main widget must be a box"); box->notifyChildrenSizeChanged(); diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index 61a7a2ac0a..f2f1cf8921 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -26,9 +26,6 @@ namespace namespace MWGui { - - const int ReviewDialog::sLineHeight = 18; - ReviewDialog::ReviewDialog() : WindowModal("openmw_chargen_review.layout"), mUpdateSkillArea(false) @@ -261,8 +258,9 @@ namespace MWGui groupWidget->setCaption(label); mSkillWidgets.push_back(groupWidget); - coord1.top += sLineHeight; - coord2.top += sLineHeight; + int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + coord1.top += lineHeight; + 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) @@ -282,8 +280,9 @@ namespace MWGui mSkillWidgets.push_back(skillNameWidget); mSkillWidgets.push_back(skillValueWidget); - coord1.top += sLineHeight; - coord2.top += sLineHeight; + int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + coord1.top += lineHeight; + coord2.top += lineHeight; return skillValueWidget; } @@ -298,8 +297,9 @@ namespace MWGui mSkillWidgets.push_back(skillNameWidget); - coord1.top += sLineHeight; - coord2.top += sLineHeight; + int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + coord1.top += lineHeight; + coord2.top += lineHeight; } void ReviewDialog::addItem(const ESM::Spell* spell, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) @@ -312,8 +312,9 @@ namespace MWGui mSkillWidgets.push_back(widget); - coord1.top += sLineHeight; - coord2.top += sLineHeight; + int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + coord1.top += lineHeight; + coord2.top += lineHeight; } void ReviewDialog::addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) @@ -378,7 +379,7 @@ namespace MWGui // starting spells std::vector spells; - const ESM::Race* race = NULL; + const ESM::Race* race = nullptr; if (!mRaceId.empty()) race = MWBase::Environment::get().getWorld()->getStore().get().find(mRaceId); diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index 8e9ec0ec79..f46ad280d5 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -87,8 +87,6 @@ namespace MWGui void addItem(const ESM::Spell* spell, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void updateSkillArea(); - static const int sLineHeight; - MyGUI::TextBox *mNameWidget, *mRaceWidget, *mClassWidget, *mBirthSignWidget; MyGUI::ScrollView* mSkillView; diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 45790cbf5f..2027210d7b 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -37,8 +37,8 @@ namespace MWGui SaveGameDialog::SaveGameDialog() : WindowModal("openmw_savegame_dialog.layout") , mSaving(true) - , mCurrentCharacter(NULL) - , mCurrentSlot(NULL) + , mCurrentCharacter(nullptr) + , mCurrentSlot(nullptr) { getWidget(mScreenshot, "Screenshot"); getWidget(mCharacterSelection, "SelectCharacter"); @@ -99,7 +99,7 @@ namespace MWGui if (mSaveList->getItemCount() == 0) { size_t previousIndex = mCharacterSelection->getIndexSelected(); - mCurrentCharacter = NULL; + mCurrentCharacter = nullptr; mCharacterSelection->removeItemAt(previousIndex); if (mCharacterSelection->getItemCount()) { @@ -127,6 +127,9 @@ namespace MWGui void SaveGameDialog::onEditSelectAccept(MyGUI::EditBox *sender) { accept(); + + // To do not spam onEditSelectAccept() again and again + MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void SaveGameDialog::onOpen() @@ -143,8 +146,8 @@ namespace MWGui mCharacterSelection->setCaption(""); mCharacterSelection->removeAllItems(); - mCurrentCharacter = NULL; - mCurrentSlot = NULL; + mCurrentCharacter = nullptr; + mCurrentSlot = nullptr; mSaveList->removeAllItems(); onSlotSelected(mSaveList, MyGUI::ITEM_NONE); @@ -247,12 +250,12 @@ namespace MWGui void SaveGameDialog::accept(bool reallySure) { // Remove for MyGUI 3.2.2 - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr); if (mSaving) { // If overwriting an existing slot, ask for confirmation first - if (mCurrentSlot != NULL && !reallySure) + if (mCurrentSlot != nullptr && !reallySure) { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); dialog->askForConfirmation("#{sMessage4}"); @@ -315,7 +318,7 @@ namespace MWGui MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); unsigned int i=0; - const MWState::Character* character = NULL; + const MWState::Character* character = nullptr; for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it, ++i) { if (i == pos) @@ -324,7 +327,7 @@ namespace MWGui assert(character && "Can't find selected character"); mCurrentCharacter = character; - mCurrentSlot = NULL; + mCurrentSlot = nullptr; fillSaveList(); } @@ -376,7 +379,7 @@ namespace MWGui if (pos == MyGUI::ITEM_NONE || !mCurrentCharacter) { - mCurrentSlot = NULL; + mCurrentSlot = nullptr; mInfoText->setCaption(""); mScreenshot->setImageTexture(""); return; @@ -385,7 +388,7 @@ namespace MWGui if (mSaving) mSaveNameEdit->setCaption(sender->getItemNameAt(pos)); - mCurrentSlot = NULL; + mCurrentSlot = nullptr; unsigned int i=0; for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i) { @@ -401,7 +404,7 @@ namespace MWGui timeinfo = localtime(&time); // Use system/environment locale settings for datetime formatting - char* oldLctime = setlocale(LC_TIME, NULL); + char* oldLctime = setlocale(LC_TIME, nullptr); setlocale(LC_TIME, ""); const int size=1024; diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index 9a9a1ae01a..e75e4954e4 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -99,7 +99,14 @@ namespace MWGui ScreenFader::~ScreenFader() { - MyGUI::Gui::getInstance().eventFrameStart -= MyGUI::newDelegate(this, &ScreenFader::onFrameStart); + try + { + MyGUI::Gui::getInstance().eventFrameStart -= MyGUI::newDelegate(this, &ScreenFader::onFrameStart); + } + catch(const MyGUI::Exception& e) + { + Log(Debug::Error) << "Error in the destructor: " << e.what(); + } } void ScreenFader::onFrameStart(float dt) diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index 9876013f13..bc5d161d83 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -18,8 +18,6 @@ namespace MWGui { - const int SpellBuyingWindow::sLineHeight = 18; - SpellBuyingWindow::SpellBuyingWindow() : WindowBase("openmw_spell_buying_window.layout") , mCurrentY(0) @@ -52,21 +50,23 @@ namespace MWGui // TODO: refactor to use MyGUI::ListBox + 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, - sLineHeight, + lineHeight, MyGUI::Align::Default ); - mCurrentY += sLineHeight; + mCurrentY += lineHeight; toAdd->setUserData(price); toAdd->setCaptionWithReplacing(spell.mName+" - "+MyGUI::utility::toString(price)+"#{sgp}"); - toAdd->setSize(mSpellsView->getWidth(),sLineHeight); + toAdd->setSize(mSpellsView->getWidth(), lineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &SpellBuyingWindow::onMouseWheel); toAdd->setUserString("ToolTipType", "Spell"); toAdd->setUserString("Spell", spell.mId); diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp index 3414c1b940..30727a545d 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.hpp +++ b/apps/openmw/mwgui/spellbuyingwindow.hpp @@ -48,8 +48,6 @@ namespace MWGui void clearSpells(); int mCurrentY; - static const int sLineHeight; - void updateLabels(); virtual void onReferenceUnavailable(); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 65d66f9e2e..46d8b38ecb 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -55,7 +55,7 @@ namespace MWGui EditEffectDialog::EditEffectDialog() : WindowModal("openmw_edit_effect.layout") , mEditing(false) - , mMagicEffect(NULL) + , mMagicEffect(nullptr) , mConstantEffect(false) { init(mEffect); @@ -147,7 +147,9 @@ namespace MWGui mDurationValue->setCaption("1"); mMagnitudeMinValue->setCaption("1"); - mMagnitudeMaxValue->setCaption("- 1"); + const std::string to = MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-"); + + mMagnitudeMaxValue->setCaption(to + " 1"); mAreaValue->setCaption("0"); setVisible(true); @@ -312,8 +314,9 @@ namespace MWGui } mEffect.mMagnMax = pos+1; + const std::string to = MWBase::Environment::get().getWindowManager()->getGameSettingString("sTo", "-"); - mMagnitudeMaxValue->setCaption("- " + MyGUI::utility::toString(pos+1)); + mMagnitudeMaxValue->setCaption(to + " " + MyGUI::utility::toString(pos+1)); eventEffectModified(mEffect); } @@ -420,6 +423,9 @@ namespace MWGui void SpellCreationDialog::onAccept(MyGUI::EditBox *sender) { onBuyButtonClicked(sender); + + // To do not spam onAccept() again and again + MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void SpellCreationDialog::onOpen() @@ -475,7 +481,7 @@ namespace MWGui mPriceLabel->setCaption(MyGUI::utility::toString(int(price))); - float chance = MWMechanics::calcSpellBaseSuccessChance(&mSpell, MWMechanics::getPlayer(), NULL); + float chance = MWMechanics::calcSpellBaseSuccessChance(&mSpell, MWMechanics::getPlayer(), nullptr); int intChance = std::min(100, int(chance)); mSuccessChance->setCaption(MyGUI::utility::toString(intChance)); @@ -485,11 +491,11 @@ namespace MWGui EffectEditorBase::EffectEditorBase(Type type) - : mAvailableEffectsList(NULL) - , mUsedEffectsView(NULL) + : mAvailableEffectsList(nullptr) + , mUsedEffectsView(nullptr) , mAddEffectDialog() - , mSelectAttributeDialog(NULL) - , mSelectSkillDialog(NULL) + , mSelectAttributeDialog(nullptr) + , mSelectSkillDialog(nullptr) , mSelectedEffect(0) , mSelectedKnownEffectId(0) , mConstantEffect(false) diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 0933737ca2..6dadebca27 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -71,7 +71,7 @@ namespace MWGui { newSpell.mType = Spell::Type_Spell; std::string cost = std::to_string(spell->mData.mCost); - std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor, NULL, true, true))); + std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor, nullptr, true, true))); newSpell.mCostColumn = cost + "/" + chance; } else diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp index 758e6b306a..879ce471f0 100644 --- a/apps/openmw/mwgui/spellview.cpp +++ b/apps/openmw/mwgui/spellview.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "tooltips.hpp" @@ -23,7 +24,7 @@ namespace MWGui } SpellView::SpellView() - : mScrollView(NULL) + : mScrollView(nullptr) , mShowCostColumn(true) , mHighlightSelected(true) { @@ -34,7 +35,7 @@ namespace MWGui Base::initialiseOverride(); assignWidget(mScrollView, "ScrollView"); - if (mScrollView == NULL) + if (mScrollView == nullptr) throw std::runtime_error("Item view needs a scroll view"); mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); @@ -130,7 +131,7 @@ namespace MWGui mLines.push_back(LineInfo(t, costChance, i)); } else - mLines.push_back(LineInfo(t, (MyGUI::Widget*)NULL, i)); + mLines.push_back(LineInfo(t, (MyGUI::Widget*)nullptr, i)); t->setStateSelected(spell.mSelected); } @@ -176,7 +177,7 @@ namespace MWGui { maxSpellIndexFound = spellIndex; Gui::SharedStateButton* costButton = reinterpret_cast(it->mRightWidget); - if ((costButton != NULL) && (costButton->getCaption() != spell.mCostColumn)) + if ((costButton != nullptr) && (costButton->getCaption() != spell.mCostColumn)) { costButton->setCaption(spell.mCostColumn); } @@ -237,10 +238,10 @@ namespace MWGui MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 18), MyGUI::Align::Left | MyGUI::Align::Top); separator->setNeedMouseFocus(false); - mLines.push_back(LineInfo(separator, (MyGUI::Widget*)NULL, NoSpellIndex)); + mLines.push_back(LineInfo(separator, (MyGUI::Widget*)nullptr, NoSpellIndex)); } - MyGUI::TextBox* groupWidget = mScrollView->createWidget("SandBrightText", + MyGUI::TextBox* groupWidget = mScrollView->createWidget("SandBrightText", MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), MyGUI::Align::Left | MyGUI::Align::Top); groupWidget->setCaptionWithReplacing(label); @@ -249,7 +250,7 @@ namespace MWGui if (label2 != "") { - MyGUI::TextBox* groupWidget2 = mScrollView->createWidget("SandBrightText", + MyGUI::TextBox* groupWidget2 = mScrollView->createWidget("SandBrightText", MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), MyGUI::Align::Left | MyGUI::Align::Top); groupWidget2->setCaptionWithReplacing(label2); @@ -259,7 +260,7 @@ namespace MWGui mLines.push_back(LineInfo(groupWidget, groupWidget2, NoSpellIndex)); } else - mLines.push_back(LineInfo(groupWidget, (MyGUI::Widget*)NULL, NoSpellIndex)); + mLines.push_back(LineInfo(groupWidget, (MyGUI::Widget*)nullptr, NoSpellIndex)); } diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 38de9288bf..2177e9e2af 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -32,7 +32,7 @@ namespace MWGui SpellWindow::SpellWindow(DragAndDrop* drag) : WindowPinnableBase("openmw_spell_window.layout") , NoDrop(drag, mMainWidget) - , mSpellView(NULL) + , mSpellView(nullptr) , mUpdateTimer(0.0f) { mSpellIcons = new SpellIcons(); @@ -72,7 +72,7 @@ namespace MWGui // Reset the filter focus when opening the window MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (focus == mFilterEdit) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr); updateSpells(); } diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index db73351bfd..e58993a55e 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -23,13 +23,10 @@ namespace MWGui { - - const int StatsWindow::sLineHeight = 18; - StatsWindow::StatsWindow (DragAndDrop* drag) : WindowPinnableBase("openmw_stats_window.layout") , NoDrop(drag, mMainWidget) - , mSkillView(NULL) + , mSkillView(nullptr) , mMajorSkills() , mMinorSkills() , mMiscSkills() @@ -71,7 +68,7 @@ namespace MWGui for (int i = 0; i < ESM::Skill::Length; ++i) { mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue())); - mSkillWidgetMap.insert(std::make_pair(i, std::make_pair((MyGUI::TextBox*)NULL, (MyGUI::TextBox*)NULL))); + mSkillWidgetMap.insert(std::make_pair(i, std::make_pair((MyGUI::TextBox*)nullptr, (MyGUI::TextBox*)nullptr))); } MyGUI::Window* t = mMainWidget->castType(); @@ -376,8 +373,9 @@ namespace MWGui groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); mSkillWidgets.push_back(groupWidget); - coord1.top += sLineHeight; - coord2.top += sLineHeight; + int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + coord1.top += lineHeight; + 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) @@ -401,8 +399,9 @@ namespace MWGui mSkillWidgets.push_back(skillNameWidget); mSkillWidgets.push_back(skillValueWidget); - coord1.top += sLineHeight; - coord2.top += sLineHeight; + int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + coord1.top += lineHeight; + coord2.top += lineHeight; return std::make_pair(skillNameWidget, skillValueWidget); } @@ -421,8 +420,9 @@ namespace MWGui mSkillWidgets.push_back(skillNameWidget); - coord1.top += sLineHeight; - coord2.top += sLineHeight; + int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + coord1.top += lineHeight; + coord2.top += lineHeight; return skillNameWidget; } diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index 487953e002..8dab2f3d99 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -53,8 +53,6 @@ namespace MWGui void onWindowResize(MyGUI::Window* window); void onMouseWheel(MyGUI::Widget* _sender, int _rel); - static const int sLineHeight; - MyGUI::Widget* mLeftPane; MyGUI::Widget* mRightPane; diff --git a/apps/openmw/mwgui/textinput.cpp b/apps/openmw/mwgui/textinput.cpp index 169c9e742e..54f2d3be9b 100644 --- a/apps/openmw/mwgui/textinput.cpp +++ b/apps/openmw/mwgui/textinput.cpp @@ -65,6 +65,9 @@ namespace MWGui void TextInputDialog::onTextAccepted(MyGUI::Edit* _sender) { onOkClicked(_sender); + + // To do not spam onTextAccepted() again and again + MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } std::string TextInputDialog::getTextInput() const diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 20814aac50..0bd0d191cc 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -421,7 +422,7 @@ namespace MWGui std::string realImage = MWBase::Environment::get().getWindowManager()->correctIconPath(image); - MyGUI::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); @@ -429,7 +430,7 @@ namespace MWGui int captionHeight = std::max(caption != "" ? captionSize.height : 0, imageSize); - MyGUI::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); @@ -447,7 +448,7 @@ namespace MWGui MyGUI::ImageBox* icon = mDynamicToolTipBox->createWidget("MarkerButton", MyGUI::IntCoord(padding.left, totalSize.height+padding.top, 8, 8), MyGUI::Align::Default); icon->setColour(MyGUI::Colour(1.0f, 0.3f, 0.3f)); - MyGUI::EditBox* edit = mDynamicToolTipBox->createWidget("SandText", + 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); edit->setEditMultiLine(true); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 3871baa09d..4404b2b1a6 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -1,7 +1,5 @@ #include "tradewindow.hpp" -#include - #include #include #include @@ -52,8 +50,8 @@ namespace MWGui TradeWindow::TradeWindow() : WindowBase("openmw_trade_window.layout") - , mSortModel(NULL) - , mTradeModel(NULL) + , mSortModel(nullptr) + , mTradeModel(nullptr) , mItemToSell(-1) , mCurrentBalance(0) , mCurrentMerchantOffer(0) @@ -96,7 +94,7 @@ namespace MWGui mTotalBalance->eventValueChanged += MyGUI::newDelegate(this, &TradeWindow::onBalanceValueChanged); mTotalBalance->eventEditSelectAccept += MyGUI::newDelegate(this, &TradeWindow::onAccept); - mTotalBalance->setMinValue(INT_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); } @@ -207,7 +205,7 @@ namespace MWGui else { mItemToSell = mSortModel->mapToSource(index); - sellItem (NULL, count); + sellItem (nullptr, count); } } @@ -367,6 +365,9 @@ namespace MWGui void TradeWindow::onAccept(MyGUI::EditBox *sender) { onOfferButtonClicked(sender); + + // To do not spam onAccept() again and again + MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None); } void TradeWindow::onCancelButtonClicked(MyGUI::Widget* _sender) @@ -428,7 +429,7 @@ namespace MWGui void TradeWindow::onIncreaseButtonTriggered() { // prevent overflows, and prevent entering INT_MIN since abs(INT_MIN) is undefined - if (mCurrentBalance == INT_MAX || mCurrentBalance == INT_MIN+1) + if (mCurrentBalance == std::numeric_limits::max() || mCurrentBalance == std::numeric_limits::min()+1) return; if (mCurrentBalance < 0) mCurrentBalance -= 1; else mCurrentBalance += 1; @@ -513,8 +514,8 @@ namespace MWGui void TradeWindow::resetReference() { ReferenceInterface::resetReference(); - mItemView->setModel(NULL); - mTradeModel = NULL; - mSortModel = NULL; + mItemView->setModel(nullptr); + mTradeModel = nullptr; + mSortModel = nullptr; } } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index b309da27d0..60e6584c7a 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -73,14 +73,12 @@ namespace MWGui mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); - MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats (actor); - // NPC can train you in his best 3 skills std::vector< std::pair > skills; for (int i=0; i player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) return; - MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats (mPtr); - if (npcStats.getSkill (skillId).getModified () <= pcStats.getSkill (skillId).getBase ()) + if (mPtr.getClass().getSkill(mPtr, skillId) <= pcStats.getSkill (skillId).getBase ()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sServiceTrainingWords}"); return; @@ -168,6 +165,7 @@ namespace MWGui player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); // add gold to NPC trading gold pool + MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats(mPtr); npcStats.setGoldPool(npcStats.getGoldPool() + price); // advance time diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 23a5b322fd..5f5430524f 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -22,8 +22,6 @@ namespace MWGui { - const int TravelWindow::sLineHeight = 18; - TravelWindow::TravelWindow() : WindowBase("openmw_travel_window.layout") , mCurrentY(0) @@ -79,9 +77,11 @@ namespace MWGui else price *= std::max(1, static_cast(followers.size())); - MyGUI::Button* toAdd = mDestinationsView->createWidget("SandTextButton", 0, mCurrentY, 200, sLineHeight, MyGUI::Align::Default); + int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; + + MyGUI::Button* toAdd = mDestinationsView->createWidget("SandTextButton", 0, mCurrentY, 200, lineHeight, MyGUI::Align::Default); toAdd->setEnabled(price <= playerGold); - mCurrentY += sLineHeight; + mCurrentY += lineHeight; if(interior) toAdd->setUserString("interior","y"); else @@ -92,7 +92,7 @@ namespace MWGui toAdd->setUserString("price",oss.str()); toAdd->setCaptionWithReplacing("#{sCell=" + name + "} - " + MyGUI::utility::toString(price)+"#{sgp}"); - toAdd->setSize(mDestinationsView->getWidth(),sLineHeight); + toAdd->setSize(mDestinationsView->getWidth(),lineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel); toAdd->setUserString("Destination", name); toAdd->setUserData(pos); diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp index 5ae4660475..dcf0b77272 100644 --- a/apps/openmw/mwgui/travelwindow.hpp +++ b/apps/openmw/mwgui/travelwindow.hpp @@ -41,8 +41,6 @@ namespace MWGui void clearDestinations(); int mCurrentY; - static const int sLineHeight; - void updateLabels(); virtual void onReferenceUnavailable(); diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index 28432b811d..5dec6e48ad 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -16,7 +16,7 @@ namespace MWGui { VideoWidget::VideoWidget() - : mVFS(NULL) + : mVFS(nullptr) { mPlayer.reset(new Video::VideoPlayer()); setNeedKeyFocus(true); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index c5a06a12c9..037fcb19be 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -83,7 +83,7 @@ namespace MWGui void WaitDialog::setPtr(const MWWorld::Ptr &ptr) { - setCanRest(!ptr.isEmpty() || MWBase::Environment::get().getWorld ()->canRest () == 0); + setCanRest(!ptr.isEmpty() || MWBase::Environment::get().getWorld ()->canRest () == MWBase::World::Rest_Allowed); if (mUntilHealedButton->getVisible()) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mUntilHealedButton); @@ -120,9 +120,14 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->popGuiMode (); } - int canRest = MWBase::Environment::get().getWorld ()->canRest (); + MWBase::World::RestPermitted canRest = MWBase::Environment::get().getWorld ()->canRest (); - if (canRest == 2) + if (canRest == MWBase::World::Rest_EnemiesAreNearby) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); + MWBase::Environment::get().getWindowManager()->popGuiMode (); + } + else if (canRest == MWBase::World::Rest_PlayerIsUnderwater) { // resting underwater or mid-air not allowed MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage1}"); @@ -276,7 +281,7 @@ namespace MWGui mSleeping = canRest; Gui::Box* box = dynamic_cast(mMainWidget); - if (box == NULL) + if (box == nullptr) throw std::runtime_error("main widget must be a box"); box->notifyChildrenSizeChanged(); center(); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 024a91fc60..c3bc1ec196 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -23,8 +23,8 @@ namespace MWGui MWSkill::MWSkill() : mSkillId(ESM::Skill::Length) - , mSkillNameWidget(NULL) - , mSkillValueWidget(NULL) + , mSkillNameWidget(nullptr) + , mSkillValueWidget(nullptr) { } @@ -114,8 +114,8 @@ namespace MWGui MWAttribute::MWAttribute() : mId(-1) - , mAttributeNameWidget(NULL) - , mAttributeValueWidget(NULL) + , mAttributeNameWidget(nullptr) + , mAttributeValueWidget(nullptr) { } @@ -204,7 +204,7 @@ namespace MWGui /* MWSpell */ MWSpell::MWSpell() - : mSpellNameWidget(NULL) + : mSpellNameWidget(nullptr) { } @@ -286,7 +286,7 @@ namespace MWGui { // 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 = NULL; + MWSpellEffectPtr effect = nullptr; int maxwidth = coord.width; for (SpellEffectList::iterator it=mEffectList.begin(); @@ -359,8 +359,8 @@ namespace MWGui /* MWSpellEffect */ MWSpellEffect::MWSpellEffect() - : mImageWidget(NULL) - , mTextWidget(NULL) + : mImageWidget(nullptr) + , mTextWidget(nullptr) , mRequestedWidth(0) { } @@ -489,9 +489,9 @@ namespace MWGui MWDynamicStat::MWDynamicStat() : mValue(0) , mMax(1) - , mTextWidget(NULL) - , mBarWidget(NULL) - , mBarTextWidget(NULL) + , mTextWidget(nullptr) + , mBarWidget(nullptr) + , mBarTextWidget(nullptr) { } diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index 93440a50a8..86c780325a 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -33,11 +33,11 @@ void WindowBase::setVisible(bool visible) if (!visible) { MyGUI::Widget* keyFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); - while (keyFocus != mMainWidget && keyFocus != NULL) + while (keyFocus != mMainWidget && keyFocus != nullptr) keyFocus = keyFocus->getParent(); if (keyFocus == mMainWidget) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr); } } diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index fde1f2ac93..7d8fb2f1e6 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -23,7 +23,7 @@ namespace MWGui public: WindowBase(const std::string& parLayout); - virtual MyGUI::Widget* getDefaultKeyFocus() { return NULL; } + virtual MyGUI::Widget* getDefaultKeyFocus() { return nullptr; } // Events typedef MyGUI::delegates::CMultiDelegate1 EventHandle_WindowBase; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 45897b88cf..e4515fdc3f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -38,13 +38,13 @@ #include #include +#include #include #include #include #include -#include #include #include @@ -132,46 +132,46 @@ namespace MWGui WindowManager::WindowManager( 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::map& fallbackMap, const std::string& versionDescription) - : mStore(NULL) + const std::string& logpath, const std::string& resourcePath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, + ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap, const std::string& versionDescription, const std::string& userDataPath) + : mStore(nullptr) , mResourceSystem(resourceSystem) , mWorkQueue(workQueue) , mViewer(viewer) , mConsoleOnlyScripts(consoleOnlyScripts) , mCurrentModals() - , mHud(NULL) - , mMap(NULL) - , mLocalMapRender(NULL) - , mToolTips(NULL) - , mStatsWindow(NULL) - , mMessageBoxManager(NULL) - , mConsole(NULL) - , mDialogueWindow(NULL) - , mDragAndDrop(NULL) - , mInventoryWindow(NULL) - , mScrollWindow(NULL) - , mBookWindow(NULL) - , mCountDialog(NULL) - , mTradeWindow(NULL) - , mSettingsWindow(NULL) - , mConfirmationDialog(NULL) - , mSpellWindow(NULL) - , mQuickKeysMenu(NULL) - , mLoadingScreen(NULL) - , mWaitDialog(NULL) - , mSoulgemDialog(NULL) - , mVideoBackground(NULL) - , mVideoWidget(NULL) - , mWerewolfFader(NULL) - , mBlindnessFader(NULL) - , mHitFader(NULL) - , mScreenFader(NULL) - , mDebugWindow(NULL) - , mJailScreen(NULL) + , 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(NULL) - , mInputBlocker(NULL) + , mCharGen(nullptr) + , mInputBlocker(nullptr) , mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD")) , mSubtitlesEnabled(Settings::Manager::getBool ("subtitles", "GUI")) , mHitFaderEnabled(Settings::Manager::getBool ("hit fader", "GUI")) @@ -185,9 +185,9 @@ namespace MWGui , mPlayerMajorSkills() , mPlayerMinorSkills() , mPlayerSkillValues() - , mGui(NULL) + , mGui(nullptr) , mGuiModes() - , mCursorManager(NULL) + , mCursorManager(nullptr) , mGarbageDialogs() , mShown(GW_ALL) , mForceHidden(GW_None) @@ -196,6 +196,7 @@ namespace MWGui , mFallbackMap(fallbackMap) , mShowOwned(0) , mEncoding(encoding) + , mFontHeight(16) , mVersionDescription(versionDescription) { float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); @@ -210,8 +211,8 @@ namespace MWGui MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); // Load fonts - mFontLoader.reset(new Gui::FontLoader(encoding, resourceSystem->getVFS())); - mFontLoader->loadAllFonts(exportFonts); + mFontLoader.reset(new Gui::FontLoader(encoding, resourceSystem->getVFS(), userDataPath)); + mFontLoader->loadBitmapFonts(exportFonts); //Register own widgets with MyGUI MyGUI::FactoryManager::getInstance().registerFactory("Widget"); @@ -233,11 +234,19 @@ namespace MWGui SpellView::registerComponents(); Gui::registerAllWidgets(); + int fontSize = Settings::Manager::getInt("font size", "GUI"); + fontSize = std::min(std::max(12, fontSize), 20); + mFontHeight = fontSize; + + MyGUI::ResourceManager::getInstance().unregisterLoadXmlDelegate("Resource"); + MyGUI::ResourceManager::getInstance().registerLoadXmlDelegate("Resource") = newDelegate(this, &WindowManager::loadFontDelegate); + MyGUI::FactoryManager::getInstance().registerFactory("Controller"); MyGUI::FactoryManager::getInstance().registerFactory("Controller"); MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); MyGUI::ResourceManager::getInstance().load("core.xml"); + loadUserFonts(); bool keyboardNav = Settings::Manager::getBool("keyboard navigation", "GUI"); mKeyboardNavigation.reset(new KeyboardNavigation()); @@ -284,6 +293,99 @@ namespace MWGui mShowOwned = Settings::Manager::getInt("show owned", "Game"); } + void WindowManager::loadFontDelegate(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version) + { + const std::string templateName = "Journalbook "; + MyGUI::xml::ElementEnumerator font = _node->getElementEnumerator(); + bool createCopy = false; + while (font.next("Resource")) + { + std::string type, name; + font->findAttribute("type", type); + font->findAttribute("name", name); + + if (name.empty()) + continue; + + if (Misc::StringUtils::ciEqual(type, "ResourceTrueTypeFont")) + { + createCopy = true; + + // For TrueType fonts we should override Size and Resolution properties + // to allow to configure font size via config file, without need to edit XML files. + // Also we should take UI scaling factor in account. + int resolution = Settings::Manager::getInt("ttf resolution", "GUI"); + resolution = std::min(960, std::max(48, resolution)); + + float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); + resolution *= uiScale; + + MyGUI::xml::ElementPtr resolutionNode = font->createChild("Property"); + resolutionNode->addAttribute("key", "Resolution"); + resolutionNode->addAttribute("value", std::to_string(resolution)); + + MyGUI::xml::ElementPtr sizeNode = font->createChild("Property"); + sizeNode->addAttribute("key", "Size"); + sizeNode->addAttribute("value", std::to_string(mFontHeight)); + } + else if (Misc::StringUtils::ciEqual(type, "ResourceSkin")) + { + // We should adjust line height for MyGUI widgets depending on font size + MyGUI::xml::ElementPtr heightNode = font->createChild("Property"); + heightNode->addAttribute("key", "HeightLine"); + heightNode->addAttribute("value", std::to_string(mFontHeight+2)); + } + } + + MyGUI::ResourceManager::getInstance().loadFromXmlNode(_node, _file, _version); + + if (createCopy) + { + MyGUI::xml::ElementPtr copy = _node->createCopy(); + + MyGUI::xml::ElementEnumerator copyFont = copy->getElementEnumerator(); + while (copyFont.next("Resource")) + { + std::string type, name; + copyFont->findAttribute("type", type); + copyFont->findAttribute("name", name); + + if (name.empty()) + continue; + + if (Misc::StringUtils::ciEqual(type, "ResourceTrueTypeFont")) + { + // Since the journal and books use the custom scaling factor depending on resolution, + // setup separate fonts with different Resolution to fit these windows. + // These fonts have an internal prefix. + int resolution = Settings::Manager::getInt("ttf resolution", "GUI"); + resolution = std::min(960, std::max(48, resolution)); + + float currentX = Settings::Manager::getInt("resolution x", "Video"); + float currentY = Settings::Manager::getInt("resolution y", "Video"); + // TODO: read size from openmw_layout.xml + float heightScale = (currentY / 520); + float widthScale = (currentX / 600); + float uiScale = std::min(widthScale, heightScale); + resolution *= uiScale; + + MyGUI::xml::ElementPtr resolutionNode = copyFont->createChild("Property"); + resolutionNode->addAttribute("key", "Resolution"); + resolutionNode->addAttribute("value", std::to_string(resolution)); + + copyFont->setAttribute("name", "Journalbook " + name); + } + } + + MyGUI::ResourceManager::getInstance().loadFromXmlNode(copy, _file, _version); + } + } + + void WindowManager::loadUserFonts() + { + mFontLoader->loadTrueTypeFonts(); + } + void WindowManager::initUI() { // Get size info from the Gui object @@ -504,6 +606,11 @@ namespace MWGui updateVisible(); } + int WindowManager::getFontHeight() const + { + return mFontHeight; + } + void WindowManager::setNewGame(bool newgame) { if (newgame) @@ -518,35 +625,43 @@ namespace MWGui WindowManager::~WindowManager() { - mKeyboardNavigation.reset(); + try + { + mKeyboardNavigation.reset(); - MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); - MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear(); - MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear(); - MyGUI::ClipboardManager::getInstance().eventClipboardChanged.clear(); - MyGUI::ClipboardManager::getInstance().eventClipboardRequested.clear(); + MyGUI::ResourceManager::getInstance().unregisterLoadXmlDelegate("Resource"); + MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); + MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear(); + MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear(); + MyGUI::ClipboardManager::getInstance().eventClipboardChanged.clear(); + MyGUI::ClipboardManager::getInstance().eventClipboardRequested.clear(); - for (WindowBase* window : mWindows) - delete window; - mWindows.clear(); + for (WindowBase* window : mWindows) + delete window; + mWindows.clear(); - delete mMessageBoxManager; - delete mLocalMapRender; - delete mCharGen; - delete mDragAndDrop; - delete mSoulgemDialog; - delete mCursorManager; - delete mToolTips; + delete mMessageBoxManager; + delete mLocalMapRender; + delete mCharGen; + delete mDragAndDrop; + delete mSoulgemDialog; + delete mCursorManager; + delete mToolTips; - cleanupGarbage(); + cleanupGarbage(); - mFontLoader.reset(); + mFontLoader.reset(); - mGui->shutdown(); - delete mGui; + mGui->shutdown(); + delete mGui; - mGuiPlatform->shutdown(); - delete mGuiPlatform; + mGuiPlatform->shutdown(); + delete mGuiPlatform; + } + catch(const MyGUI::Exception& e) + { + Log(Debug::Error) << "Error in the destructor: " << e.what(); + } } void WindowManager::setStore(const MWWorld::ESMStore &store) @@ -589,7 +704,7 @@ namespace MWGui setCursorVisible(!gameMode); if (gameMode) - setKeyFocusWidget (NULL); + setKeyFocusWidget (nullptr); // Icons of forced hidden windows are displayed setMinimapVisibility((mAllowed & GW_Map) && (!mMap->pinned() || (mForceHidden & GW_Map))); @@ -1268,8 +1383,11 @@ namespace MWGui void WindowManager::setSelectedWeapon(const MWWorld::Ptr& item) { mSelectedWeapon = item; - int durabilityPercent = - static_cast(item.getClass().getItemHealth(item) / static_cast(item.getClass().getItemMaxHealth(item)) * 100); + int durabilityPercent = 100; + if (item.getClass().hasItemHealth(item)) + { + durabilityPercent = static_cast(item.getClass().getItemHealth(item) / static_cast(item.getClass().getItemMaxHealth(item)) * 100); + } mHud->setSelectedWeapon(item, durabilityPercent); mInventoryWindow->setTitle(item.getClass().getName(item)); } @@ -1533,7 +1651,7 @@ namespace MWGui // Remove this method for MyGUI 3.2.2 void WindowManager::setKeyFocusWidget(MyGUI::Widget *widget) { - if (widget == NULL) + if (widget == nullptr) MyGUI::InputManager::getInstance().resetKeyFocusWidget(); else MyGUI::InputManager::getInstance().setKeyFocusWidget(widget); @@ -1795,7 +1913,7 @@ namespace MWGui } if (mCurrentModals.empty()) { - mKeyboardNavigation->setModalWindow(NULL); + mKeyboardNavigation->setModalWindow(nullptr); mKeyboardNavigation->restoreFocus(getMode()); } else @@ -2060,9 +2178,9 @@ namespace MWGui return mTextColours; } - bool WindowManager::injectKeyPress(MyGUI::KeyCode key, unsigned int text) + bool WindowManager::injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat) { - if (!mKeyboardNavigation->injectKeyPress(key, text)) + if (!mKeyboardNavigation->injectKeyPress(key, text, repeat)) { MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); bool widgetActive = MyGUI::InputManager::getInstance().injectKeyPress(key, text); @@ -2091,6 +2209,11 @@ namespace MWGui return true; } + bool WindowManager::injectKeyRelease(MyGUI::KeyCode key) + { + return MyGUI::InputManager::getInstance().injectKeyRelease(key); + } + void WindowManager::GuiModeState::update(bool visible) { for (unsigned int i=0; i FactionList; WindowManager(osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, - const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, - Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap, const std::string& versionDescription); + const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, + ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap, const std::string& versionDescription, const std::string& localPath); virtual ~WindowManager(); /// Set the ESMStore to use for retrieving of GUI-related strings. void setStore (const MWWorld::ESMStore& store); void initUI(); + virtual void loadUserFonts(); virtual Loading::Listener* getLoadingScreen(); @@ -246,6 +247,7 @@ namespace MWGui virtual const MWWorld::Ptr& getSelectedEnchantItem() const; virtual void setSelectedWeapon(const MWWorld::Ptr& item); virtual const MWWorld::Ptr& getSelectedWeapon() const; + virtual int getFontHeight() const; virtual void unsetSelectedSpell(); virtual void unsetSelectedWeapon(); @@ -379,7 +381,8 @@ namespace MWGui virtual const MWGui::TextColours& getTextColours(); - virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text); + virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat=false); + virtual bool injectKeyRelease(MyGUI::KeyCode key); private: const MWWorld::ESMStore* mStore; @@ -401,6 +404,8 @@ namespace MWGui MWWorld::Ptr mSelectedEnchantItem; MWWorld::Ptr mSelectedWeapon; + void loadFontDelegate(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version); + std::vector mCurrentModals; // Markers placed manually by the player. Must be shared between both map views (the HUD map and the map window). @@ -513,6 +518,8 @@ namespace MWGui ToUTF8::FromType mEncoding; + int mFontHeight; + std::string mVersionDescription; MWGui::TextColours mTextColours; diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp index a6984b5a48..271d4e3a85 100644 --- a/apps/openmw/mwgui/windowpinnablebase.cpp +++ b/apps/openmw/mwgui/windowpinnablebase.cpp @@ -14,7 +14,7 @@ namespace MWGui mPinButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonPressed); - MyGUI::Button* button = NULL; + MyGUI::Button* button = nullptr; MyGUI::VectorWidgetPtr widgets = window->getSkinWidgetsByName("Action"); for (MyGUI::VectorWidgetPtr::iterator it = widgets.begin(); it != widgets.end(); ++it) { diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index b24c8cc149..0d017ba90f 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -46,9 +46,9 @@ namespace MWInput , mScreenCaptureHandler(screenCaptureHandler) , mScreenCaptureOperation(screenCaptureOperation) , mJoystickLastUsed(false) - , mPlayer(NULL) - , mInputManager(NULL) - , mVideoWrapper(NULL) + , mPlayer(nullptr) + , mInputManager(nullptr) + , mVideoWrapper(nullptr) , mUserFile(userFile) , mDragDrop(false) , mGrabCursor (Settings::Manager::getBool("grab cursor", "Input")) @@ -84,7 +84,7 @@ namespace MWInput Settings::Manager::getFloat("contrast", "Video")); std::string file = userFileExists ? userFile : ""; - mInputBinder = new ICS::InputControlSystem(file, true, this, NULL, A_Last); + mInputBinder = new ICS::InputControlSystem(file, true, this, nullptr, A_Last); loadKeyDefaults(); loadControllerDefaults(); @@ -214,7 +214,7 @@ namespace MWInput break; } - MWBase::Environment::get().getWindowManager()->injectKeyPress(key, 0); + MWBase::Environment::get().getWindowManager()->injectKeyPress(key, 0, false); } void InputManager::channelChanged(ICS::Channel* channel, float currentValue, float previousValue) @@ -676,6 +676,9 @@ namespace MWInput Settings::Manager::getInt("resolution y", "Video"), Settings::Manager::getBool("fullscreen", "Video"), Settings::Manager::getBool("window border", "Video")); + + // We should reload TrueType fonts to fit new resolution + MWBase::Environment::get().getWindowManager()->loadUserFonts(); } } @@ -720,7 +723,7 @@ namespace MWInput bool consumed = false; if (kc != OIS::KC_UNASSIGNED && !mInputBinder->detectingBindingState()) { - consumed = MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); + consumed = MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Enum(kc), 0, arg.repeat); if (SDL_IsTextInputActive() && // Little trick to check if key is printable ( !(SDLK_SCANCODE_MASK & arg.keysym.sym) && std::isprint(arg.keysym.sym))) consumed = true; @@ -1026,10 +1029,6 @@ namespace MWInput if (!MWBase::Environment::get().getWindowManager()->getRestEnabled () || MWBase::Environment::get().getWindowManager()->isGuiMode ()) return; - if(mPlayer->enemiesNearby()) {//Check if in combat - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); //Nope, - return; - } MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_Rest); //Open rest GUI } @@ -1108,6 +1107,7 @@ namespace MWInput 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::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Journal); @@ -1153,7 +1153,7 @@ namespace MWInput if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { if (!SDL_IsTextInputActive() && !isLeftOrRightButton(A_Activate, mInputBinder, mFakeDeviceID, mJoystickLastUsed)) - MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Return, 0); + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Return, 0, false); } else if (mControlSwitch["playercontrols"]) mPlayer->activate(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 0ee49ac45e..2a68591a8f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -501,12 +501,6 @@ namespace MWMechanics } } - void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration) - { - updateDrowning(ptr, duration); - calculateNpcStatModifiers(ptr, duration); - } - void Actors::adjustMagicEffects (const MWWorld::Ptr& creature) { CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); @@ -551,10 +545,10 @@ namespace MWMechanics void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, bool sleep) { - if (ptr.getClass().getCreatureStats(ptr).isDead()) + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + if (stats.isDead()) return; - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); if (sleep) @@ -571,12 +565,6 @@ namespace MWMechanics stats.setMagicka(stat); } - int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); - - float normalizedEncumbrance = ptr.getClass().getNormalizedEncumbrance(ptr); - if (normalizedEncumbrance > 1) - normalizedEncumbrance = 1; - // Current fatigue can be above base value due to a fortify effect. // In that case stop here and don't try to restore. DynamicStat fatigue = stats.getFatigue(); @@ -588,6 +576,12 @@ namespace MWMechanics float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat (); float fEndFatigueMult = settings.find("fEndFatigueMult")->mValue.getFloat (); + int 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; @@ -926,13 +920,8 @@ namespace MWMechanics return ctrl->isSneaking(); } - void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration) + void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer) { - PtrActorMap::iterator it = mActors.find(ptr); - if (it == mActors.end()) - return; - CharacterController* ctrl = it->second->getCharacterController(); - NpcStats &stats = ptr.getClass().getNpcStats(ptr); // When npc stats are just initialized, mTimeToStartDrowning == -1 and we should get value from GMST @@ -940,9 +929,10 @@ namespace MWMechanics if (stats.getTimeToStartDrowning() == -1.f) stats.setTimeToStartDrowning(fHoldBreathTime); - if (ptr.getClass().isNpc() && stats.getTimeToStartDrowning() < fHoldBreathTime / 2) + if (stats.getTimeToStartDrowning() < fHoldBreathTime / 2) { - if(ptr != MWMechanics::getPlayer() ) { + if(!isPlayer) + { MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdBreathe) //Only add it once seq.stack(MWMechanics::AiBreathe(), ptr); @@ -950,7 +940,7 @@ namespace MWMechanics } MWBase::World *world = MWBase::Environment::get().getWorld(); - bool knockedOutUnderwater = (ctrl->isKnockedOut() && world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3()))); + 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) { @@ -965,7 +955,7 @@ namespace MWMechanics stats.setTimeToStartDrowning(timeLeft); } - bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); + bool godmode = isPlayer && MWBase::Environment::get().getWorld()->getGodModeState(); if(timeLeft == 0.0f && !godmode) { @@ -980,7 +970,7 @@ namespace MWMechanics if(!sndmgr->getSoundPlaying(ptr, "drown")) sndmgr->playSound3D(ptr, "drown", 1.0f, 1.0f); - if(ptr == world->getPlayerPtr()) + if(isPlayer) MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); } } @@ -1242,21 +1232,24 @@ namespace MWMechanics void Actors::updateCombatMusic () { MWWorld::Ptr player = getPlayer(); - int hostilesCount = 0; // need to know this to play Battle music + 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(); - for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + if (aiActive) { - if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { - bool inProcessingRange = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() - <= sqrAiProcessingDistance; + if (iter->first == player) continue; - if (MWBase::Environment::get().getMechanicsManager()->isAIActive() && inProcessingRange) + bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= sqrAiProcessingDistance; + if (inProcessingRange) { - if (iter->first != player) + MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); + if (!stats.isDead() && stats.getAiSequence().isInCombat()) { - MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); - if (stats.getAiSequence().isInCombat() && !stats.isDead()) hostilesCount++; + hasHostiles = true; + break; } } } @@ -1265,13 +1258,13 @@ namespace MWMechanics // check if we still have any player enemies to switch music static int currentMusic = 0; - if (currentMusic != 1 && hostilesCount == 0 && !(player.getClass().getCreatureStats(player).isDead() && + if (currentMusic != 1 && !hasHostiles && !(player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getSoundManager()->isMusicPlaying())) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); currentMusic = 1; } - else if (currentMusic != 2 && hostilesCount > 0) + else if (currentMusic != 2 && hasHostiles) { MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); currentMusic = 2; @@ -1298,17 +1291,29 @@ namespace MWMechanics bool showTorches = MWBase::Environment::get().getWorld()->useTorches(); 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 + bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); + int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId(); + if (attackedByPlayerId != -1) + { + const MWWorld::Ptr playerHitAttemptActor = MWBase::Environment::get().getWorld()->searchPtrViaActorId(attackedByPlayerId); + + if (!playerHitAttemptActor.isInCell()) + player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); + } + // AI and magic effects update for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { bool isPlayer = iter->first == player; + CharacterController* ctrl = iter->second->getCharacterController(); - float distSqr = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2(); + float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2(); // AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this // (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not) // This distance could be made configurable later, but the setting must be marked with a big warning: @@ -1316,7 +1321,7 @@ namespace MWMechanics bool inProcessingRange = distSqr <= sqrAiProcessingDistance; if (isPlayer) - iter->second->getCharacterController()->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell()); + ctrl->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell()); // If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player. if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead() @@ -1328,11 +1333,6 @@ namespace MWMechanics player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); } - const MWWorld::Ptr playerHitAttemptActor = MWBase::Environment::get().getWorld()->searchPtrViaActorId(player.getClass().getCreatureStats(player).getHitAttemptActorId()); - - if (!playerHitAttemptActor.isInCell()) - player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); - if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) { bool cellChanged = MWBase::Environment::get().getWorld()->hasCellChanged(); @@ -1343,7 +1343,7 @@ namespace MWMechanics return; // for now abort update of the old cell when cell changes by teleportation magic effect // a better solution might be to apply cell changes at the end of the frame } - if (MWBase::Environment::get().getMechanicsManager()->isAIActive() && inProcessingRange) + if (aiActive && inProcessingRange) { if (timerUpdateAITargets == 0) { @@ -1381,7 +1381,7 @@ namespace MWMechanics } } - iter->second->getCharacterController()->setHeadTrackTarget(headTrackTarget); + ctrl->setHeadTrackTarget(headTrackTarget); } if (iter->first.getClass().isNpc() && iter->first != player) @@ -1391,13 +1391,14 @@ namespace MWMechanics { CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); if (isConscious(iter->first)) - stats.getAiSequence().execute(iter->first, *iter->second->getCharacterController(), duration); + stats.getAiSequence().execute(iter->first, *ctrl, duration); } } if(iter->first.getTypeName() == typeid(ESM::NPC).name()) { - updateNpc(iter->first, duration); + updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer); + calculateNpcStatModifiers(iter->first, duration); if (timerUpdateEquippedLight == 0) updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches); @@ -1419,11 +1420,11 @@ namespace MWMechanics iter->second->getCharacterController()->updateContinuousVfx(); // Animation/movement update - CharacterController* playerCharacter = NULL; + CharacterController* playerCharacter = nullptr; for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const float animationDistance = aiProcessingDistance + 400; // Slightly larger than AI distance so there is time to switch back to the idle animation. - const float distSqr = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2(); + const float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2(); bool isPlayer = iter->first == player; bool inAnimationRange = isPlayer || (animationDistance == 0 || distSqr <= animationDistance*animationDistance); int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower) @@ -1437,22 +1438,24 @@ namespace MWMechanics inAnimationRange = true; active = std::max(1, active); } - iter->second->getCharacterController()->setActive(active); + + CharacterController* ctrl = iter->second->getCharacterController(); + ctrl->setActive(active); if (!inAnimationRange) continue; if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) - iter->second->getCharacterController()->skipAnim(); + ctrl->skipAnim(); // Handle player last, in case a cell transition occurs by casting a teleportation spell // (would invalidate the iterator) if (iter->first == getPlayer()) { - playerCharacter = iter->second->getCharacterController(); + playerCharacter = ctrl; continue; } - iter->second->getCharacterController()->update(duration); + ctrl->update(duration); } if (playerCharacter) @@ -1512,7 +1515,7 @@ namespace MWMechanics continue; // is the player in range and can they be detected - if ((observer.getRefData().getPosition().asVec3() - player.getRefData().getPosition().asVec3()).length2() <= radius*radius + if ((observer.getRefData().getPosition().asVec3() - playerPos).length2() <= radius*radius && MWBase::Environment::get().getWorld()->getLOS(player, observer)) { if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, observer)) @@ -1656,17 +1659,19 @@ namespace MWMechanics void Actors::rest(bool sleep) { float duration = 3600.f / MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (iter->first.getClass().getCreatureStats(iter->first).isDead()) continue; - restoreDynamicStats(iter->first, sleep); + if (!sleep || iter->first == player) + restoreDynamicStats(iter->first, sleep); if ((!iter->first.getRefData().getBaseNode()) || - (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() > sqrAiProcessingDistance) + (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > sqrAiProcessingDistance) continue; adjustMagicEffects (iter->first); @@ -1679,7 +1684,10 @@ namespace MWMechanics MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first); if (animation) - animation->updateEffects(); + { + animation->removeEffects(); + MWBase::Environment::get().getWorld()->applyLoopingParticles(iter->first); + } } @@ -1979,7 +1987,7 @@ namespace MWMechanics for (; it != mActors.end(); ++it) { delete it->second; - it->second = NULL; + it->second = nullptr; } mActors.clear(); mDeathCount.clear(); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 8c94ce45f2..a1e0e511da 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -28,8 +28,6 @@ namespace MWMechanics void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); - void updateNpc(const MWWorld::Ptr &ptr, float duration); - void adjustMagicEffects (const MWWorld::Ptr& creature); void calculateDynamicStats (const MWWorld::Ptr& ptr); @@ -39,7 +37,7 @@ namespace MWMechanics void calculateRestoration (const MWWorld::Ptr& ptr, float duration); - void updateDrowning (const MWWorld::Ptr& ptr, float duration); + void updateDrowning (const MWWorld::Ptr& ptr, float duration, bool isKnockedOut, bool isPlayer); void updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip); diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index bb03ff53b2..e7d1ecee12 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -33,9 +33,9 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont float distance = x * x + y * y + z * z; if(distance < 10 * 10) { //Got stuck, didn't move if(mAdjAngle == 0) //Try going in various directions - mAdjAngle = 1.57079632679f; //pi/2 - else if (mAdjAngle == 1.57079632679f) - mAdjAngle = -1.57079632679f; + mAdjAngle = osg::PI / 2; + else if (mAdjAngle == osg::PI / 2) + mAdjAngle = -osg::PI / 2; else mAdjAngle = 0; mDuration = 1; //reset timer diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 9824347e32..a96832b698 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -4,6 +4,10 @@ #include +#include + +#include "../mwphysics/collisiontype.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -456,7 +460,48 @@ namespace MWMechanics mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } + else 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), + // 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 + 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; + + // 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. + // This approach allows us to detect small obstacles (e.g. crates) and curved walls. + 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 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); + if (isObstacleDetected) + return; + + // Check if there is nothing behind - probably actor is near cliff. + // A current approach: cast ray 1.5-yard ray down in 1.5 yard behind actor from 35% of actor's height. + // 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); + 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)) { // apply sideway movement (kind of dodging) with some probability @@ -468,20 +513,6 @@ namespace MWMechanics mCombatMove = true; } } - - // Backing up behaviour - // Actor backs up slightly further away than opponent's weapon range - // (in vanilla - only as far as oponent's weapon range), - // or not at all if opponent is using a ranged weapon - if (isDistantCombat) - { - // actor should not back up into water - if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.5f)) - return; - - if (!targetUsesRanged && distToTarget <= rangeAttackOfTarget*1.5) // Don't back up if the target is wielding ranged weapon - mMovement.mPosition[1] = -1; - } } void AiCombatStorage::updateCombatMove(float duration) @@ -586,7 +617,7 @@ std::string chooseBestAttack(const ESM::Weapon* weapon) { std::string attackType; - if (weapon != NULL) + if (weapon != nullptr) { //the more damage attackType deals the more probability it has int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 88feba4811..f89e716782 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -64,7 +64,7 @@ namespace MWMechanics mAttackRange(0.0f), mCombatMove(false), mLastTargetPos(0,0,0), - mCell(NULL), + mCell(nullptr), mCurrentAction(), mActionCooldown(0.0f), mStrength(), diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 6fffff6377..804e0dd059 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -138,7 +138,7 @@ namespace MWMechanics const ESM::Weapon* ActionWeapon::getWeapon() const { if (mWeapon.isEmpty()) - return NULL; + return nullptr; return mWeapon.get()->mBase; } diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index 466ae2dc43..cf7dc78fd9 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -17,7 +17,7 @@ namespace MWMechanics virtual void prepare(const MWWorld::Ptr& actor) = 0; virtual float getCombatRange (bool& isRanged) const = 0; virtual float getActionCooldown() { return 0.f; } - virtual const ESM::Weapon* getWeapon() const { return NULL; }; + virtual const ESM::Weapon* getWeapon() const { return nullptr; }; virtual bool isAttackingOrSpell() const { return true; } virtual bool isFleeing() const { return false; } }; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index e6cca05235..9a42f191ef 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -275,7 +275,7 @@ bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ)); - if (destInLOS != NULL) *destInLOS = isPathClear; + if (destInLOS != nullptr) *destInLOS = isPathClear; if (!isPathClear) return false; diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index f1941ff1d2..5a468ae30c 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -114,7 +114,7 @@ namespace MWMechanics /// 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 NULL function will return ray cast check result + /// \param destInLOS If not nullptr function will return ray cast check result /// \return If can shortcut the path bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear); diff --git a/apps/openmw/mwmechanics/aistate.hpp b/apps/openmw/mwmechanics/aistate.hpp index 19f0ecf994..40f4ab1d4e 100644 --- a/apps/openmw/mwmechanics/aistate.hpp +++ b/apps/openmw/mwmechanics/aistate.hpp @@ -59,7 +59,7 @@ namespace MWMechanics bool empty() const { - return mStorage == NULL; + return mStorage == nullptr; } const std::type_info& getType() const @@ -67,16 +67,12 @@ namespace MWMechanics return typeid(mStorage); } - - DerivedClassStorage():mStorage(NULL){} + DerivedClassStorage():mStorage(nullptr){} ~DerivedClassStorage() { if(mStorage) delete mStorage; } - - - }; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index d36c5930f8..caacf8cb64 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -300,9 +300,8 @@ namespace MWMechanics bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); do { // Determine a random location within radius of original position - const float pi = 3.14159265359f; const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance; - const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi; + const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * osg::PI; const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection); const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection); const float destinationZ = mInitialActorPosition.z(); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 4a0811f507..479ed4869c 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -77,7 +77,7 @@ namespace MWMechanics mReaction(0), mSaidGreeting(Greet_None), mGreetingTimer(0), - mCell(NULL), + mCell(nullptr), mState(Wander_ChooseAction), mIsWanderingManually(false), mCanWanderAlongPathGrid(true), diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index b8f8203070..c199bfb3f8 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -26,10 +26,10 @@ #include "magiceffects.hpp" #include "creaturestats.hpp" -#include "npcstats.hpp" MWMechanics::Alchemy::Alchemy() : mValue(0) + , mPotionName("") { } @@ -317,10 +317,9 @@ void MWMechanics::Alchemy::increaseSkill() float MWMechanics::Alchemy::getAlchemyFactor() const { const CreatureStats& creatureStats = mAlchemist.getClass().getCreatureStats (mAlchemist); - const NpcStats& npcStats = mAlchemist.getClass().getNpcStats (mAlchemist); return - (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + + (mAlchemist.getClass().getSkill(mAlchemist, ESM::Skill::Alchemy) + 0.1f * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified() + 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()); } @@ -336,6 +335,25 @@ int MWMechanics::Alchemy::countIngredients() const return ingredients; } +int MWMechanics::Alchemy::countPotionsToBrew() const +{ + Result readyStatus = getReadyStatus(); + if (readyStatus != Result_Success) + return 0; + + int toBrew = -1; + + for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter) + if (!iter->isEmpty()) + { + int count = iter->getRefData().getCount(); + if ((count > 0 && count < toBrew) || toBrew < 0) + toBrew = count; + } + + return toBrew; +} + void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc) { mAlchemist = npc; @@ -396,6 +414,12 @@ void MWMechanics::Alchemy::clear() mTools.clear(); mIngredients.clear(); mEffects.clear(); + setPotionName(""); +} + +void MWMechanics::Alchemy::setPotionName(const std::string& name) +{ + mPotionName = name; } int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient) @@ -446,8 +470,7 @@ MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::endEffects() const bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWWorld::Ptr &npc) { - MWMechanics::NpcStats& npcStats = npc.getClass().getNpcStats(npc); - int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); + int alchemySkill = npc.getClass().getSkill (npc, ESM::Skill::Alchemy); static const float fWortChanceValue = MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->mValue.getFloat(); return (potionEffectIndex <= 1 && alchemySkill >= fWortChanceValue) @@ -456,7 +479,7 @@ bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWW || (potionEffectIndex <= 7 && alchemySkill >= fWortChanceValue*4); } -MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name) +MWMechanics::Alchemy::Result MWMechanics::Alchemy::getReadyStatus() const { if (mTools[ESM::Apparatus::MortarPestle].isEmpty()) return Result_NoMortarAndPestle; @@ -464,15 +487,43 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na if (countIngredients()<2) return Result_LessThanTwoIngredients; - if (name.empty()) + if (mPotionName.empty()) return Result_NoName; if (listEffects().empty()) - { - removeIngredients(); return Result_NoEffects; + + return Result_Success; +} + +MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name, int& count) +{ + setPotionName(name); + Result readyStatus = getReadyStatus(); + + if (readyStatus == Result_NoEffects) + removeIngredients(); + + if (readyStatus != Result_Success) + return readyStatus; + + Result result = Result_RandomFailure; + int brewedCount = 0; + for (int i = 0; i < count; ++i) + { + if (createSingle() == Result_Success) + { + result = Result_Success; + brewedCount++; + } } + count = brewedCount; + return result; +} + +MWMechanics::Alchemy::Result MWMechanics::Alchemy::createSingle () +{ if (beginEffects() == endEffects()) { // all effects were nullified due to insufficient skill @@ -486,7 +537,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na return Result_RandomFailure; } - addPotion (name); + addPotion(mPotionName); removeIngredients(); diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index f351881e0d..9f9f0b21c0 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -51,11 +51,14 @@ namespace MWMechanics TIngredientsContainer mIngredients; TEffectsContainer mEffects; int mValue; + std::string mPotionName; void applyTools (int flags, float& value) const; void updateEffects(); + Result getReadyStatus() const; + 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 @@ -70,6 +73,10 @@ namespace MWMechanics void increaseSkill(); ///< Increase alchemist's skill. + 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. + float getAlchemyFactor() const; int countIngredients() const; @@ -79,6 +86,8 @@ namespace MWMechanics TEffectsIterator endEffects() const; public: + int countPotionsToBrew() const; + ///< calculates maximum amount of potions, which you can make from selected ingredients static bool knownEffect (unsigned int potionEffectIndex, const MWWorld::Ptr& npc); ///< Does npc have sufficient alchemy skill to know about this potion effect? @@ -100,6 +109,9 @@ namespace MWMechanics void clear(); ///< Remove alchemist, tools and ingredients. + void setPotionName(const std::string& name); + ///< Set name of potion to create + std::set listEffects() const; ///< List all effects shared by at least two ingredients. @@ -115,8 +127,8 @@ namespace MWMechanics std::string suggestPotionName (); ///< Suggest a name for the potion, based on the current effects - Result create (const std::string& name); - ///< Try to create a potion from the ingredients, place it in the inventory of the alchemist and + 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 }; diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index a52fcc059d..144449cf09 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -1,7 +1,6 @@ #include "autocalcspell.hpp" #include "spellcasting.hpp" -#include #include #include "../mwworld/esmstore.hpp" @@ -50,7 +49,7 @@ namespace MWMechanics caps.mCount = 0; caps.mLimit = iAutoSpellSchoolMax[i]; caps.mReachedLimit = iAutoSpellSchoolMax[i] <= 0; - caps.mMinCost = INT_MAX; + caps.mMinCost = std::numeric_limits::max(); caps.mWeakestSpell.clear(); schoolCaps[i] = caps; } @@ -101,7 +100,7 @@ namespace MWMechanics if (found != selectedSpells.end()) selectedSpells.erase(found); - cap.mMinCost = INT_MAX; + cap.mMinCost = std::numeric_limits::max(); for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) { const ESM::Spell* testSpell = spells.find(*weakIt); @@ -150,8 +149,8 @@ namespace MWMechanics float baseMagicka = fPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; bool reachedLimit = false; - const ESM::Spell* weakestSpell = NULL; - int minCost = INT_MAX; + const ESM::Spell* weakestSpell = nullptr; + int minCost = std::numeric_limits::max(); std::vector selectedSpells; @@ -188,7 +187,7 @@ namespace MWMechanics if (it != selectedSpells.end()) selectedSpells.erase(it); - minCost = INT_MAX; + minCost = std::numeric_limits::max(); for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) { const ESM::Spell* testSpell = esmStore.get().find(*weakIt); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index fddd351fa6..ce844674f4 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -243,7 +243,7 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i return prefix + toString(roll); } -void CharacterController::refreshHitRecoilAnims() +void CharacterController::refreshHitRecoilAnims(CharacterState& idle) { bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); @@ -348,92 +348,130 @@ void CharacterController::refreshHitRecoilAnims() mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); } if (mHitState != CharState_None) - mIdleState = CharState_None; + idle = CharState_None; } -void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, bool force) +void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, CharacterState& movement, bool force) { - if(force || jump != mJumpState) + if (!force && jump == mJumpState && idle == CharState_None && movement == CharState_None) + return; + + if (jump != JumpState_None && !(mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) // FIXME { - mIdleState = CharState_None; - bool startAtLoop = (jump == mJumpState); - mJumpState = jump; + idle = CharState_None; + movement = CharState_None; + } - std::string jumpAnimName; - MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; - if(mJumpState != JumpState_None) + std::string jumpAnimName; + MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; + if (jump != JumpState_None) + { + jumpAnimName = "jump"; + if(weap != sWeaponTypeListEnd) { - jumpAnimName = "jump"; - if(weap != sWeaponTypeListEnd) + jumpAnimName += weap->shortgroup; + if(!mAnimation->hasAnimation(jumpAnimName)) { - jumpAnimName += weap->shortgroup; - if(!mAnimation->hasAnimation(jumpAnimName)) - { - jumpmask = MWRender::Animation::BlendMask_LowerBody; - jumpAnimName = "jump"; + jumpmask = MWRender::Animation::BlendMask_LowerBody; + jumpAnimName = "jump"; - // For crossbow animations use 1h ones as fallback - if (mWeaponType == WeapType_Crossbow) - jumpAnimName += "1h"; - } + // Since we apply movement only for lower body, do not reset idle animations. + // For upper body there will be idle animation. + if (idle == CharState_None) + idle = CharState_Idle; + + // For crossbow animations use 1h ones as fallback + if (mWeaponType == WeapType_Crossbow) + jumpAnimName += "1h"; } } + } - if (!mCurrentJump.empty()) - { - mAnimation->disable(mCurrentJump); - mCurrentJump.clear(); - } + if (!force && jump == mJumpState) + return; - if(mJumpState == JumpState_InAir) + mJumpState = jump; + + if (!mCurrentJump.empty()) + { + mAnimation->disable(mCurrentJump); + mCurrentJump.clear(); + } + + if(mJumpState == JumpState_InAir) + { + if (mAnimation->hasAnimation(jumpAnimName)) { - if (mAnimation->hasAnimation(jumpAnimName)) - { - mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, - 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); - mCurrentJump = jumpAnimName; - } + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, + 1.0f, "start", "stop", 0.f, ~0ul); + mCurrentJump = jumpAnimName; } - else if (mJumpState == JumpState_Landing) + } + else if (mJumpState == JumpState_Landing) + { + if (mAnimation->hasAnimation(jumpAnimName)) { - if (mAnimation->hasAnimation(jumpAnimName)) - { - mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, - 1.0f, "loop stop", "stop", 0.0f, 0); - mCurrentJump = jumpAnimName; - } + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, + 1.0f, "loop stop", "stop", 0.0f, 0); + mCurrentJump = jumpAnimName; } } } -void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force) +void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force) { - if(force || movement != mMovementState) + if (movement == mMovementState && idle == mIdleState && !force) + return; + + 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) { - mMovementState = movement; - std::string movementAnimName; - MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; - const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); - if(movestate != sMovementListEnd) + movementAnimName = movestate->groupname; + if(weap != sWeaponTypeListEnd) { - movementAnimName = movestate->groupname; - if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos) + std::string::size_type swimpos = movementAnimName.find("swim"); + if (swimpos == std::string::npos) { - if (mWeaponType == WeapType_Spell && (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight)) // Spellcasting stance turning is a special case + if (mWeaponType == WeapType_Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case movementAnimName = weap->shortgroup + movementAnimName; else movementAnimName += weap->shortgroup; - if(!mAnimation->hasAnimation(movementAnimName)) + } + + if(!mAnimation->hasAnimation(movementAnimName)) + { + movemask = MWRender::Animation::BlendMask_LowerBody; + movementAnimName = movestate->groupname; + + if (swimpos == std::string::npos) { - movemask = MWRender::Animation::BlendMask_LowerBody; - movementAnimName = movestate->groupname; + // Since we apply movement only for lower body, do not reset idle animations. + // For upper body there will be idle animation. + if (idle == CharState_None) + idle = CharState_Idle; // For crossbow animations use 1h ones as fallback if (mWeaponType == WeapType_Crossbow) movementAnimName += "1h"; } + else if (idle == CharState_None) + { + idle = CharState_IdleSwim; + } } + } + } + if(force || movement != mMovementState) + { + mMovementState = movement; + if(movestate != sMovementListEnd) + { if(!mAnimation->hasAnimation(movementAnimName)) { std::string::size_type swimpos = movementAnimName.find("swim"); @@ -451,6 +489,10 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character } else { + // For crossbow animations use 1h ones as fallback + if (mWeaponType == WeapType_Crossbow) + movementAnimName += "1h"; + movementAnimName.erase(swimpos, 4); if (weap != sWeaponTypeListEnd) { @@ -468,10 +510,9 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character } // If we're playing the same animation, start it from the point it ended - bool sameAnim = (movementAnimName == mCurrentMovement); - float startPoint = 0.f; - if (sameAnim) - mAnimation->getInfo(mCurrentMovement, &startPoint); + float startpoint = 0.f; + if (!mCurrentMovement.empty() && movementAnimName == mCurrentMovement) + mAnimation->getInfo(mCurrentMovement, &startpoint); mMovementAnimationControlled = true; @@ -520,14 +561,22 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character } mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, - 1.f, (!sameAnim ? "start" : "loop start"), "stop", startPoint, ~0ul, true); + 1.f, "start", "stop", startpoint, ~0ul, true); } } } void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) { - if(force || idle != mIdleState || mIdleState == CharState_None || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) + // 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())) { mIdleState = idle; size_t numLoops = ~0ul; @@ -586,24 +635,16 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat return; if (mPtr.getClass().isActor()) - refreshHitRecoilAnims(); + refreshHitRecoilAnims(idle); const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); if (!mPtr.getClass().hasInventoryStore(mPtr)) weap = sWeaponTypeListEnd; - refreshJumpAnims(weap, jump, force); - refreshMovementAnims(weap, movement, force); + refreshJumpAnims(weap, jump, idle, movement, force); + refreshMovementAnims(weap, movement, idle, force); // idle handled last as it can depend on the other states - // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), - // the idle animation should be displayed - if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped) - || (mMovementState != CharState_None && !isTurning()) - || mHitState != CharState_None) - && !mPtr.getClass().isBipedal(mPtr)) - idle = CharState_None; - refreshIdleAnims(weap, idle, force); } @@ -890,7 +931,7 @@ CharacterController::~CharacterController() if (mAnimation) { persistAnimationState(); - mAnimation->setTextKeyListener(NULL); + mAnimation->setTextKeyListener(nullptr); } } @@ -938,17 +979,13 @@ void CharacterController::handleTextKey(const std::string &groupname, const std: } } - if (soundgen == "land") // Morrowind ignores land soundgen for some reason - return; - std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); if(!sound.empty()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(soundgen == "left" || soundgen == "right") + // NB: landing sound is not played for NPCs here + if(soundgen == "left" || soundgen == "right" || soundgen == "land") { - // Don't make foot sounds local for the player, it makes sense to keep them - // positioned on the ground. sndMgr->playSound3D(mPtr, sound, volume, pitch, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); } @@ -1118,7 +1155,7 @@ bool CharacterController::updateCreatureState() if (!spellid.empty() && canCast) { - MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell); + MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell); cast.playSpellCastingEffects(spellid); if (!mAnimation->hasAnimation("spellcast")) @@ -1195,7 +1232,7 @@ bool CharacterController::updateCarriedLeftVisible(WeaponType weaptype) const } } -bool CharacterController::updateWeaponState() +bool CharacterController::updateWeaponState(CharacterState& idle) { const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); @@ -1241,6 +1278,18 @@ bool CharacterController::updateWeaponState() bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; + // 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); + if (mPtr == getPlayer()) + MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); + } + if(!isKnockedOut() && !isKnockedDown() && !isRecovery()) { std::string weapgroup; @@ -1394,6 +1443,14 @@ bool CharacterController::updateWeaponState() 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))) + { + mCurrentWeapon = chooseRandomAttackAnimation(); + } + if(mWeaponType == WeapType_Spell) { // Unset casting flag, otherwise pressing the mouse button down would @@ -1414,7 +1471,7 @@ bool CharacterController::updateWeaponState() if(!spellid.empty() && canCast) { - MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell); + MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell); cast.playSpellCastingEffects(spellid); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -1557,11 +1614,11 @@ bool CharacterController::updateWeaponState() // We should reset player's idle animation in the first-person mode. if (resetIdle && mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson()) - mIdleState = CharState_None; + idle = CharState_None; // In other cases we should not break swim and sneak animations if (resetIdle && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) - mIdleState = CharState_None; + idle = CharState_None; animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) @@ -1617,7 +1674,9 @@ bool CharacterController::updateWeaponState() } mAnimation->setPitchFactor(0.f); - if (mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown) + if (mWeaponType == WeapType_BowAndArrow || + mWeaponType == WeapType_Thrown || + mWeaponType == WeapType_Crossbow) { switch (mUpperBodyState) { @@ -1631,29 +1690,14 @@ bool CharacterController::updateWeaponState() break; case UpperCharState_FollowStartToFollowStop: if (animPlaying) - mAnimation->setPitchFactor(1.f-complete); - break; - default: - break; - } - } - else if (mWeaponType == WeapType_Crossbow) - { - switch (mUpperBodyState) - { - case UpperCharState_EquipingWeap: - mAnimation->setPitchFactor(complete); - break; - case UpperCharState_UnEquipingWeap: - mAnimation->setPitchFactor(1.f-complete); - break; - case UpperCharState_WeapEquiped: - case UpperCharState_StartToMinAttack: - case UpperCharState_MinAttackToMaxAttack: - case UpperCharState_MaxAttackToMinHit: - case UpperCharState_MinHitToHit: - case UpperCharState_FollowStartToFollowStop: - mAnimation->setPitchFactor(1.f); + { + // technically we do not need a pitch for crossbow reload animation, + // but we should avoid abrupt repositioning + if (mWeaponType == WeapType_Crossbow) + mAnimation->setPitchFactor(std::max(0.f, 1.f-complete*10.f)); + else + mAnimation->setPitchFactor(1.f-complete); + } break; default: break; @@ -1817,7 +1861,8 @@ void CharacterController::update(float duration) if (isKnockedOut()) mTimeUntilWake -= duration; - bool godmode = mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); + bool isPlayer = mPtr == MWMechanics::getPlayer(); + bool godmode = isPlayer && MWBase::Environment::get().getWorld()->getGodModeState(); if(!cls.isActor()) updateAnimQueue(); @@ -1882,7 +1927,7 @@ void CharacterController::update(float duration) isrunning = isrunning && mHasMovedInXY; // advance athletics - if(mHasMovedInXY && mPtr == getPlayer()) + if(mHasMovedInXY && isPlayer) { if(inwater) { @@ -1978,8 +2023,12 @@ void CharacterController::update(float duration) } // advance acrobatics - if (mPtr == getPlayer()) + // also set jumping flag to allow GetPCJumping works + if (isPlayer) + { cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); + MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); + } // decrease fatigue const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat(); @@ -2002,7 +2051,7 @@ void CharacterController::update(float duration) jumpstate = JumpState_Landing; vec.z() = 0.0f; - float height = cls.getCreatureStats(mPtr).land(); + float height = cls.getCreatureStats(mPtr).land(isPlayer); float healthLost = getFallDamage(mPtr, height); if (healthLost > 0.0f) @@ -2025,16 +2074,22 @@ void CharacterController::update(float duration) else { // report acrobatics progression - if (mPtr == getPlayer()) + if (isPlayer) cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); } } - // Play landing sound - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - std::string sound = cls.getSoundIdFromSndGen(mPtr, "land"); - if (!sound.empty()) + // Play landing sound for NPCs + if (mPtr.getClass().isNpc()) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + std::string sound = "DefaultLand"; + osg::Vec3f pos(mPtr.getRefData().getPosition().asVec3()); + if (world->isUnderwater(mPtr.getCell(), pos) || world->isWalkingOnWater(mPtr)) + sound = "DefaultLandWater"; + sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); + } } else { @@ -2047,7 +2102,7 @@ void CharacterController::update(float duration) // Do not play turning animation for player if rotation speed is very slow. // Actual threshold should take framerate in account. float rotationThreshold = 0; - if (mPtr == getPlayer()) + if (isPlayer) rotationThreshold = 0.015 * 60 * duration; if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) @@ -2072,17 +2127,23 @@ void CharacterController::update(float duration) : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack))); } - else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) + else if(rot.z() != 0.0f) { - if(rot.z() > rotationThreshold) - movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; - else if(rot.z() < -rotationThreshold) - movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; + // It seems only bipedal actors use turning animations. + // Also do not use turning animations in the first-person view and when sneaking. + bool isFirstPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); + if (!sneak && !isFirstPlayer && mPtr.getClass().isBipedal(mPtr)) + { + if(rot.z() > rotationThreshold) + movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; + else if(rot.z() < -rotationThreshold) + movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; + } } } // Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering - if (mPtr == getPlayer()) + if (isPlayer) { float threshold = mCurrentMovement.find("swim") == std::string::npos ? 0.4f : 0.8f; float complete; @@ -2095,16 +2156,21 @@ void CharacterController::update(float duration) } else { - mTurnAnimationThreshold -= duration; - if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft || - movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft) + if (mPtr.getClass().isBipedal(mPtr)) { - mTurnAnimationThreshold = 0.05f; - } - else if (movestate == CharState_None && isTurning() - && mTurnAnimationThreshold > 0) - { - movestate = mMovementState; + 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; + } } } @@ -2113,14 +2179,13 @@ void CharacterController::update(float duration) if(mAnimQueue.empty() || inwater || sneak) { - // Note: turning animations should not interrupt idle ones. - // Also movement should not stop idle animation for spellcasting stance. - if (inwater) + // Note: turning animations should not interrupt idle ones + if (movestate != CharState_None && !isTurning()) + idlestate = CharState_None; + else if (inwater) idlestate = CharState_IdleSwim; else if (sneak && !inJump) idlestate = CharState_IdleSneak; - else if (movestate != CharState_None && !isTurning() && mWeaponType != WeapType_Spell) - idlestate = CharState_None; else idlestate = CharState_Idle; } @@ -2131,7 +2196,7 @@ void CharacterController::update(float duration) { // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) - forcestateupdate = updateWeaponState() || forcestateupdate; + forcestateupdate = updateWeaponState(idlestate) || forcestateupdate; else forcestateupdate = updateCreatureState() || forcestateupdate; @@ -2145,9 +2210,11 @@ void CharacterController::update(float duration) if (isTurning()) { // Adjust animation speed from 1.0 to 1.5 multiplier - float turnSpeed = std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI)); if (duration > 0) + { + 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 (mMovementState != CharState_None && mAdjustMovementAnimSpeed) { @@ -2253,7 +2320,7 @@ void CharacterController::persistAnimationState() { anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup); float complete; - mAnimation->getInfo(anim.mGroup, &complete, NULL); + mAnimation->getInfo(anim.mGroup, &complete, nullptr); anim.mTime = complete; } else @@ -2385,7 +2452,7 @@ bool CharacterController::isPersistentAnimPlaying() bool CharacterController::isAnimPlaying(const std::string &groupName) { - if(mAnimation == NULL) + if(mAnimation == nullptr) return false; return mAnimation->isPlaying(groupName); } @@ -2682,9 +2749,9 @@ void CharacterController::updateHeadTracking(float duration) if (const MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) { const osg::Node* node = anim->getNode("Head"); - if (node == NULL) + if (node == nullptr) node = anim->getNode("Bip01 Head"); - if (node != NULL) + if (node != nullptr) { nodepaths = node->getParentalNodePaths(); if (!nodepaths.empty()) diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 43d26e52f3..f97614ef45 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -213,14 +213,14 @@ class CharacterController : public MWRender::Animation::TextKeyListener void setAttackTypeBasedOnMovement(); void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); - void refreshHitRecoilAnims(); - void refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, bool force=false); - void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force=false); + void refreshHitRecoilAnims(CharacterState& idle); + void refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, CharacterState& movement, bool force=false); + void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force=false); void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false); void clearAnimQueue(bool clearPersistAnims = false); - bool updateWeaponState(); + bool updateWeaponState(CharacterState& idle); bool updateCreatureState(); void updateIdleStormState(bool inwater); @@ -240,8 +240,8 @@ class CharacterController : public MWRender::Animation::TextKeyListener void playRandomDeath(float startpoint = 0.0f); /// choose a random animation group with \a prefix and numeric suffix - /// @param num if non-NULL, the chosen animation number will be written here - std::string chooseRandomGroup (const std::string& prefix, int* num = NULL) const; + /// @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(WeaponType weaptype) const; diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 2994eac289..7cc6ea7843 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -7,6 +7,7 @@ #include #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -387,8 +388,11 @@ namespace MWMechanics mFallHeight += height; } - float CreatureStats::land() + float CreatureStats::land(bool isPlayer) { + if (isPlayer) + MWBase::Environment::get().getWorld()->getPlayer().setJumping(false); + float height = mFallHeight; mFallHeight = 0; return height; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 057a6f6025..503ac7d68a 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -95,7 +95,7 @@ namespace MWMechanics /// Reset the fall height /// @return total fall height - float land(); + float land(bool isPlayer=false); const AttributeValue & getAttribute(int index) const; diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index c0bffc4cb5..e4bb0cf3f0 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -10,7 +10,6 @@ #include "../mwbase/mechanicsmanager.hpp" #include "creaturestats.hpp" -#include "npcstats.hpp" #include "spellcasting.hpp" #include "actorutil.hpp" @@ -280,11 +279,11 @@ namespace MWMechanics float Enchanting::getEnchantChance() const { - const NpcStats& npcStats = mEnchanter.getClass().getNpcStats (mEnchanter); + const CreatureStats& stats = mEnchanter.getClass().getCreatureStats(mEnchanter); - float chance1 = (npcStats.getSkill (ESM::Skill::Enchant).getModified() + - (0.25f * npcStats.getAttribute (ESM::Attribute::Intelligence).getModified()) - + (0.125f * npcStats.getAttribute (ESM::Attribute::Luck).getModified())); + float chance1 = (mEnchanter.getClass().getSkill(mEnchanter, ESM::Skill::Enchant) + + (0.25f * stats.getAttribute (ESM::Attribute::Intelligence).getModified()) + + (0.125f * stats.getAttribute (ESM::Attribute::Luck).getModified())); const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 859b2e5229..619ed91b3e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -203,7 +203,7 @@ namespace MWMechanics } // F_PCStart spells - const ESM::Race* race = NULL; + const ESM::Race* race = nullptr; if (mRaceSelected) race = esmStore.get().find(player->mRace); @@ -453,9 +453,17 @@ namespace MWMechanics void MechanicsManager::rest(bool sleep) { + if (sleep) + MWBase::Environment::get().getWorld()->rest(); + mActors.rest(sleep); } + void MechanicsManager::restoreDynamicStats(MWWorld::Ptr actor, bool sleep) + { + mActors.restoreDynamicStats(actor, sleep); + } + int MechanicsManager::getHoursToRest() const { return mActors.getHoursToRest(mWatched); @@ -631,10 +639,10 @@ namespace MWMechanics // 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 = static_cast(std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100)); + float a = static_cast(std::min(playerPtr.getClass().getSkill(playerPtr, ESM::Skill::Mercantile), 100)); float b = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float c = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); - float d = static_cast(std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100)); + float d = static_cast(std::min(ptr.getClass().getSkill(ptr, ESM::Skill::Mercantile), 100)); float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm(); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index af12d4d98f..3682e97cec 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -91,6 +91,8 @@ namespace MWMechanics virtual void setPlayerClass (const ESM::Class& class_); ///< Set player class to custom class. + virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep); + virtual void rest(bool sleep); ///< If the player is sleeping or waiting, this should be called every hour. /// @param sleep is the player sleeping or waiting? diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index cb9ef54773..47d886e3fd 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -298,7 +298,12 @@ void MWMechanics::NpcStats::levelUp() // "When you gain a level, in addition to increasing three primary attributes, your Health // will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level, // the Health increase is calculated from the increased Endurance" - setHealth(getHealth().getBase() + endurance * gmst.find("fLevelUpHealthEndMult")->mValue.getFloat()); + // Note: we should add bonus Health points to current level too. + float healthGain = endurance * gmst.find("fLevelUpHealthEndMult")->mValue.getFloat(); + MWMechanics::DynamicStat health(getHealth()); + health.setBase(getHealth().getBase() + healthGain); + health.setCurrent(std::max(1.f, getHealth().getCurrent() + healthGain)); + setHealth(health); setLevel(getLevel()+1); } diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index d8821276e4..e95c0a704f 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -20,7 +20,7 @@ Objects::~Objects() for (; it != mObjects.end();++it) { delete it->second; - it->second = NULL; + it->second = nullptr; } } diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 6a84e0ef94..c553745014 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -16,7 +16,7 @@ namespace MWMechanics bool proximityToDoor(const MWWorld::Ptr& actor, float minDist); /// Returns door pointer within range. No guarantee is given as to which one - /** \return Pointer to the door, or NULL if none exists **/ + /** \return Pointer to the door, or empty pointer if none exists **/ const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); class ObstacleCheck diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index b1b04f304d..c16cff9e10 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -5,6 +5,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwphysics/collisiontype.hpp" + #include "../mwworld/cellstore.hpp" #include "pathgrid.hpp" @@ -120,8 +122,8 @@ namespace MWMechanics } PathFinder::PathFinder() - : mPathgrid(NULL) - , mCell(NULL) + : mPathgrid(nullptr) + , mCell(nullptr) { } @@ -246,8 +248,9 @@ namespace MWMechanics converter.toWorld(temp); // Add Z offset since path node can overlap with other objects. // Also ignore doors in raytesting. + int mask = MWPhysics::CollisionType_World; bool isPathClear = !MWBase::Environment::get().getWorld()->castRay( - startPoint.mX, startPoint.mY, startPoint.mZ+16, temp.mX, temp.mY, temp.mZ+16, true); + startPoint.mX, startPoint.mY, startPoint.mZ+16, temp.mX, temp.mY, temp.mZ+16, mask); if (isPathClear) mPath.pop_front(); } diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index ea4c973b72..6b5db64eab 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -50,8 +50,8 @@ namespace namespace MWMechanics { PathgridGraph::PathgridGraph(const MWWorld::CellStore *cell) - : mCell(NULL) - , mPathgrid(NULL) + : mCell(nullptr) + , mPathgrid(nullptr) , mIsExterior(0) , mGraph(0) , mIsGraphConstructed(false) diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 3ebef36bf1..8c5e698960 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -14,7 +14,6 @@ #include "../mwworld/esmstore.hpp" #include "creaturestats.hpp" -#include "npcstats.hpp" #include "actorutil.hpp" namespace MWMechanics @@ -34,12 +33,11 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) mTool.getCellRef().setCharge(uses-1); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); float fatigueTerm = stats.getFatigueTerm(); int pcStrength = stats.getAttribute(ESM::Attribute::Strength).getModified(); int pcLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); - int armorerSkill = npcStats.getSkill(ESM::Skill::Armorer).getModified(); + int armorerSkill = player.getClass().getSkill(player, ESM::Skill::Armorer); float fRepairAmountMult = MWBase::Environment::get().getWorld()->getStore().get() .find("fRepairAmountMult")->mValue.getFloat(); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index c8a4dd7706..18d4ec2e5a 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -11,7 +11,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "npcstats.hpp" #include "creaturestats.hpp" namespace MWMechanics @@ -21,10 +20,9 @@ namespace MWMechanics : mActor(actor) { CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); - NpcStats& npcStats = actor.getClass().getNpcStats(actor); mAgility = static_cast(creatureStats.getAttribute(ESM::Attribute::Agility).getModified()); mLuck = static_cast(creatureStats.getAttribute(ESM::Attribute::Luck).getModified()); - mSecuritySkill = static_cast(npcStats.getSkill(ESM::Skill::Security).getModified()); + mSecuritySkill = static_cast(actor.getClass().getSkill(actor, ESM::Skill::Security)); mFatigueTerm = creatureStats.getFatigueTerm(); } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index b857c6d7e2..a91aa2f31c 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -65,7 +65,7 @@ namespace MWMechanics maxMagn = effect.mMagnMax; } - int duration = 0; + int duration = 1; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) duration = effect.mDuration; @@ -242,9 +242,9 @@ namespace MWMechanics // This makes spells that are easy to cast harder to resist and vice versa float castChance = 100.f; - if (spell != NULL && !caster.isEmpty() && caster.getClass().isActor()) + if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor()) { - castChance = getSpellSuccessChance(spell, caster, NULL, false); // Uncapped casting chance + castChance = getSpellSuccessChance(spell, caster, nullptr, false); // Uncapped casting chance } if (castChance > 0) x *= 50 / castChance; @@ -582,7 +582,7 @@ namespace MWMechanics ActiveSpells::ActiveEffect effect_ = effect; effect_.mMagnitude *= -1; absorbEffects.push_back(effect_); - if (reflected && Settings::Manager::getBool("classic reflect absorb attribute behavior", "Game")) + if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game")) target.getClass().getCreatureStats(target).getActiveSpells().addSpell("", true, absorbEffects, mSourceName, caster.getClass().getCreatureStats(caster).getActorId()); else @@ -737,7 +737,7 @@ namespace MWMechanics } else if (effectId == ESM::MagicEffect::Recall) { - MWWorld::CellStore* markedCell = NULL; + MWWorld::CellStore* markedCell = nullptr; ESM::Position markedPosition; MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); @@ -964,10 +964,9 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getStore().get().find ( effect.mEffectID); - const MWMechanics::NpcStats& npcStats = mCaster.getClass().getNpcStats(mCaster); const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); - float x = (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + + 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(); diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 2844e7f23f..5ac406368a 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -39,8 +39,8 @@ 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 getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true, bool checkMagicka=false); - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true, bool checkMagicka=false); + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=false); + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=false); int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); @@ -58,14 +58,14 @@ 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 getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, - const ESM::Spell* spell = NULL, const MagicEffects* effects = NULL); + const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); /// Get an effect multiplier for applying an effect cast by the given actor in the given spell (optional). /// @return effect multiplier from 0 to 2. (100% net resistance to 100% net weakness) /// @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 = NULL, const MagicEffects* effects = NULL); + const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 807b56608e..3b242266c2 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -14,7 +14,7 @@ #include "../mwworld/actionequip.hpp" #include "../mwworld/cellstore.hpp" -#include "npcstats.hpp" +#include "creaturestats.hpp" #include "spellcasting.hpp" #include "combat.hpp" @@ -538,7 +538,7 @@ namespace MWMechanics case ESM::MagicEffect::DrainSkill: if (enemy.isEmpty() || !enemy.getClass().isNpc()) return 0.f; - if (enemy.getClass().getNpcStats(enemy).getSkill(effect.mSkill).getModified() <= 0) + if (enemy.getClass().getSkill(enemy, effect.mSkill) <= 0) return 0.f; break; diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 818065ae44..aaa06f6e44 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -119,7 +119,7 @@ namespace MWMechanics rating *= getHitChance(actor, enemy, value) / 100.f; - if (weapon->mData.mType < ESM::Weapon::MarksmanBow) + if (weapon->mData.mType < ESM::Weapon::Arrow) rating *= weapon->mData.mSpeed; return rating * ratingMult; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 90668914f2..2ce498f377 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -486,7 +487,7 @@ namespace MWPhysics physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f)); else { - inertia.z() += time * -627.2f; + inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter; if (inertia.z() < 0) inertia.z() *= slowFall; if (slowFall < 1.f) { @@ -809,7 +810,7 @@ namespace MWPhysics btScalar mLeastDistSqr; DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3 &origin) - : mMe(me), mTargets(targets), mOrigin(origin), mObject(NULL), mContactPoint(0,0,0), + : mMe(me), mTargets(targets), mOrigin(origin), mObject(nullptr), mContactPoint(0,0,0), mLeastDistSqr(std::numeric_limits::max()) { } @@ -872,7 +873,7 @@ namespace MWPhysics object.setCollisionShape(&shape); object.setWorldTransform(btTransform(toBullet(orient), toBullet(center))); - const btCollisionObject* me = NULL; + const btCollisionObject* me = nullptr; std::vector targetCollisionObjects; const Actor* physactor = getActor(actor); @@ -905,7 +906,7 @@ namespace MWPhysics float PhysicsSystem::getHitDistance(const osg::Vec3f &point, const MWWorld::ConstPtr &target) const { - btCollisionObject* targetCollisionObj = NULL; + btCollisionObject* targetCollisionObj = nullptr; const Actor* actor = getActor(target); if (actor) targetCollisionObj = actor->getCollisionObject(); @@ -967,7 +968,7 @@ namespace MWPhysics btVector3 btFrom = toBullet(from); btVector3 btTo = toBullet(to); - const btCollisionObject* me = NULL; + const btCollisionObject* me = nullptr; std::vector targetCollisionObjects; if (!ignore.isEmpty()) @@ -1126,7 +1127,7 @@ namespace MWPhysics std::vector PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const { - btCollisionObject* me = NULL; + btCollisionObject* me = nullptr; ObjectMap::const_iterator found = mObjects.find(ptr); if (found != mObjects.end()) @@ -1254,7 +1255,7 @@ namespace MWPhysics ActorMap::iterator found = mActors.find(ptr); if (found != mActors.end()) return found->second; - return NULL; + return nullptr; } const Actor *PhysicsSystem::getActor(const MWWorld::ConstPtr &ptr) const @@ -1262,7 +1263,7 @@ namespace MWPhysics ActorMap::const_iterator found = mActors.find(ptr); if (found != mActors.end()) return found->second; - return NULL; + return nullptr; } const Object* PhysicsSystem::getObject(const MWWorld::ConstPtr &ptr) const @@ -1270,7 +1271,7 @@ namespace MWPhysics ObjectMap::const_iterator found = mObjects.find(ptr); if (found != mObjects.end()) return found->second; - return NULL; + return nullptr; } void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) @@ -1336,6 +1337,16 @@ namespace MWPhysics if (!shape) return; + // Try to get shape from basic model as fallback for creatures + if (!ptr.getClass().isNpc() && shape->mCollisionBoxHalfExtents.length2() == 0) + { + const std::string fallbackModel = ptr.getClass().getModel(ptr); + if (fallbackModel != mesh) + { + shape = mShapeManager->getShape(fallbackModel); + } + } + Actor* actor = new Actor(ptr, shape, mCollisionWorld); mActors.insert(std::make_pair(ptr, actor)); } @@ -1394,6 +1405,7 @@ namespace MWPhysics mStandingCollisions.clear(); } + const MWWorld::Ptr player = MWMechanics::getPlayer(); const MWBase::World *world = MWBase::Environment::get().getWorld(); PtrVelocityList::iterator iter = mMovementQueue.begin(); for(;iter != mMovementQueue.end();++iter) @@ -1451,7 +1463,7 @@ namespace MWPhysics MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); if ((wasOnGround && physicActor->getOnGround()) || flying || world->isSwimming(iter->first) || slowFall < 1) - stats.land(); + stats.land(iter->first == player); else if (heightDiff < 0) stats.addToFallHeight(-heightDiff); diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 14a5dac48a..204ec14678 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -86,7 +86,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star mPlaneNormal = osg::Vec3f(0.0f, 0.0f, 1.0f); mFraction = 1.0f; mHitPoint = end; - mHitObject = NULL; + mHitObject = nullptr; } } diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index d92b95a71c..e6aff8d19e 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -40,6 +40,9 @@ ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr addHiddenItemLight(*iter, light); } } + + // Make sure we cleaned object from effects, just in cast if we re-use node + removeEffects(); } ActorAnimation::~ActorAnimation() diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1ace2f8b4b..6e2c76d1d8 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -91,32 +91,6 @@ namespace std::vector > mToRemove; }; - class NodeMapVisitor : public osg::NodeVisitor - { - public: - typedef std::map > NodeMap; - - NodeMapVisitor(NodeMap& map) - : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) - , mMap(map) - {} - - void apply(osg::MatrixTransform& trans) - { - // Take transformation for first found node in file - const std::string nodeName = Misc::StringUtils::lowerCase(trans.getName()); - if (mMap.find(nodeName) == mMap.end()) - { - mMap[nodeName] = &trans; - } - - traverse(trans); - } - - private: - NodeMap& mMap; - }; - NifOsg::TextKeyMap::const_iterator findGroupStart(const NifOsg::TextKeyMap &keys, const std::string &groupname) { NifOsg::TextKeyMap::const_iterator iter(keys.begin()); @@ -213,13 +187,12 @@ namespace RemoveFinishedCallbackVisitor() : RemoveVisitor() , mHasMagicEffects(false) - , mEffectId(-1) { } RemoveFinishedCallbackVisitor(int effectId) : RemoveVisitor() - , mEffectId(effectId) + , mHasMagicEffects(false) { } @@ -239,9 +212,63 @@ namespace MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast(callback); if (vfxCallback) { - bool finished = vfxCallback->mFinished; - bool toRemove = mEffectId >= 0 && vfxCallback->mParams.mEffectId == mEffectId; - if (finished || toRemove) + if (vfxCallback->mFinished) + mToRemove.push_back(std::make_pair(group.asNode(), group.getParent(0))); + else + mHasMagicEffects = true; + } + } + } + + virtual void apply(osg::MatrixTransform &node) + { + traverse(node); + } + + virtual void apply(osg::Geometry&) + { + } + + private: + int mEffectId; + }; + + class RemoveCallbackVisitor : public RemoveVisitor + { + public: + bool mHasMagicEffects; + + RemoveCallbackVisitor() + : RemoveVisitor() + , mHasMagicEffects(false) + , mEffectId(-1) + { + } + + RemoveCallbackVisitor(int effectId) + : RemoveVisitor() + , mHasMagicEffects(false) + , mEffectId(effectId) + { + } + + virtual void apply(osg::Node &node) + { + traverse(node); + } + + virtual void apply(osg::Group &group) + { + traverse(group); + + osg::Callback* callback = group.getUpdateCallback(); + if (callback) + { + MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast(callback); + if (vfxCallback) + { + bool toRemove = mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId; + if (toRemove) mToRemove.push_back(std::make_pair(group.asNode(), group.getParent(0))); else mHasMagicEffects = true; @@ -340,7 +367,7 @@ namespace void applyNode(osg::Node& node) { if (node.getStateSet()) - node.setStateSet(NULL); + node.setStateSet(nullptr); if (node.getNodeMask() == 0x1 && node.getNumParents() == 1) mToRemove.push_back(std::make_pair(&node, node.getParent(0))); @@ -467,7 +494,7 @@ namespace MWRender } if (mDone) return; - + // Set the starting time to measure glow duration from if this is a temporary glow if ((mDuration >= 0) && mStartingTime == 0) mStartingTime = nv->getFrameStamp()->getSimulationTime(); @@ -571,8 +598,8 @@ namespace MWRender } else { - // Remove effect immediately - mParams.mObjects.reset(); + // Hide effect immediately + node->setNodeMask(0); mFinished = true; } } @@ -608,12 +635,12 @@ namespace MWRender Animation::Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) : mInsert(parentNode) - , mSkeleton(NULL) + , mSkeleton(nullptr) , mNodeMapCreated(false) , mPtr(ptr) , mResourceSystem(resourceSystem) , mAccumulate(1.f, 1.f, 0.f) - , mTextKeyListener(NULL) + , mTextKeyListener(nullptr) , mHeadYawRadians(0.f) , mHeadPitchRadians(0.f) , mHasMagicEffects(false) @@ -799,7 +826,7 @@ namespace MWRender for(size_t i = 0;i < sNumBlendMasks;i++) mAnimationTimePtr[i]->setTimePtr(std::shared_ptr()); - mAccumCtrl = NULL; + mAccumCtrl = nullptr; mAnimSources.clear(); @@ -1041,7 +1068,7 @@ namespace MWRender { if (!mNodeMapCreated && mObjectRoot) { - NodeMapVisitor visitor(mNodeMap); + SceneUtil::NodeMapVisitor visitor(mNodeMap); mObjectRoot->accept(visitor); mNodeMapCreated = true; } @@ -1057,12 +1084,12 @@ namespace MWRender node->removeUpdateCallback(it->second); // Should be no longer needed with OSG 3.4 - it->second->setNestedCallback(NULL); + it->second->setNestedCallback(nullptr); } mActiveControllers.clear(); - mAccumCtrl = NULL; + mAccumCtrl = nullptr; for(size_t blendMask = 0;blendMask < sNumBlendMasks;blendMask++) { @@ -1312,7 +1339,7 @@ namespace MWRender if(state.getTime() >= state.mLoopStopTime) break; - } + } if(timepassed <= 0.0f) break; @@ -1364,7 +1391,7 @@ namespace MWRender { osg::ref_ptr created = sceneMgr->getInstance(model); - CleanObjectRootVisitor removeDrawableVisitor; + SceneUtil::CleanObjectRootVisitor removeDrawableVisitor; created->accept(removeDrawableVisitor); removeDrawableVisitor.remove(); @@ -1389,14 +1416,14 @@ namespace MWRender previousStateset = mObjectRoot->getStateSet(); mObjectRoot->getParent(0)->removeChild(mObjectRoot); } - mObjectRoot = NULL; - mSkeleton = NULL; + mObjectRoot = nullptr; + mSkeleton = nullptr; mNodeMap.clear(); mNodeMapCreated = false; mActiveControllers.clear(); - mAccumRoot = NULL; - mAccumCtrl = NULL; + mAccumRoot = nullptr; + mAccumCtrl = nullptr; if (!forceskeleton) { @@ -1433,7 +1460,7 @@ namespace MWRender if (isCreature) { - RemoveTriBipVisitor removeTriBipVisitor; + SceneUtil::RemoveTriBipVisitor removeTriBipVisitor; mObjectRoot->accept(removeTriBipVisitor); removeTriBipVisitor.remove(); } @@ -1527,9 +1554,9 @@ namespace MWRender osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem); mGlowUpdater = glowUpdater; node->addUpdateCallback(glowUpdater); - + // set a texture now so that the ShaderVisitor can find it - osg::ref_ptr writableStateSet = NULL; + osg::ref_ptr writableStateSet = nullptr; if (!node->getStateSet()) writableStateSet = node->getOrCreateStateSet(); else @@ -1633,7 +1660,6 @@ namespace MWRender params.mLoop = loop; params.mEffectId = effectId; params.mBoneName = bonename; - params.mObjects = PartHolderPtr(new PartHolder(node)); params.mAnimTime = std::shared_ptr(new EffectAnimationTime); trans->addUpdateCallback(new UpdateVfxCallback(params)); @@ -1648,12 +1674,17 @@ namespace MWRender void Animation::removeEffect(int effectId) { - RemoveFinishedCallbackVisitor visitor(effectId); + RemoveCallbackVisitor visitor(effectId); mInsert->accept(visitor); visitor.remove(); mHasMagicEffects = visitor.mHasMagicEffects; } + void Animation::removeEffects() + { + removeEffect(-1); + } + void Animation::getLoopingEffects(std::vector &out) const { if (!mHasMagicEffects) @@ -1704,7 +1735,7 @@ namespace MWRender std::string lowerName = Misc::StringUtils::lowerCase(name); NodeMap::const_iterator found = getNodeMap().find(lowerName); if (found == getNodeMap().end()) - return NULL; + return nullptr; else return found->second; } @@ -1735,7 +1766,7 @@ namespace MWRender } else { - mObjectRoot->setStateSet(NULL); + mObjectRoot->setStateSet(nullptr); mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } @@ -1762,7 +1793,7 @@ namespace MWRender if (mGlowLight) { mInsert->removeChild(mGlowLight); - mGlowLight = NULL; + mGlowLight = nullptr; } } else @@ -1776,7 +1807,7 @@ namespace MWRender if (mGlowLight) { mInsert->removeChild(mGlowLight); - mGlowLight = NULL; + mGlowLight = nullptr; } osg::ref_ptr light (new osg::Light); @@ -1797,7 +1828,7 @@ namespace MWRender void Animation::addControllers() { - mHeadController = NULL; + mHeadController = nullptr; if (mPtr.getClass().isBipedal(mPtr)) { @@ -1917,12 +1948,12 @@ namespace MWRender PartHolder::~PartHolder() { if (mNode.get() && !mNode->getNumParents()) - Log(Debug::Verbose) << "Part 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 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 e10d2c995a..47edbdc0b3 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -74,7 +74,6 @@ typedef std::shared_ptr PartHolderPtr; struct EffectParams { std::string mModelName; // Just here so we don't add the same effect twice - PartHolderPtr mObjects; std::shared_ptr mAnimTime; float mMaxControllerLength; int mEffectId; @@ -370,6 +369,7 @@ public: */ void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = "", float scale = 1.0f); 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, @@ -425,7 +425,7 @@ public: * \param speedmult Stores the animation speed multiplier * \return True if the animation is active, false otherwise. */ - bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const; + 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; @@ -453,7 +453,7 @@ public: /// 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 NULL if not existing. + /// 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; diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index eb3775cb46..4cf76e473a 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -54,9 +54,9 @@ void DebugDrawer::destroyGeometry() if (mGeometry) { mParentNode->removeChild(mGeometry); - mGeometry = NULL; - mVertices = NULL; - mDrawArrays = NULL; + mGeometry = nullptr; + mVertices = nullptr; + mDrawArrays = nullptr; } } diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index cb6188a54e..74f2ba9117 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -45,7 +45,7 @@ namespace MWRender Camera::Camera (osg::Camera* camera) : mHeightScale(1.f), mCamera(camera), - mAnimation(NULL), + mAnimation(nullptr), mFirstPersonView(true), mPreviewMode(false), mFreeLook(true), diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 2e0249e608..075d2e03c6 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -239,7 +239,7 @@ namespace MWRender void CharacterPreview::rebuild() { - mAnimation = NULL; + mAnimation = nullptr; mAnimation = new NpcAnimation(mCharacter, mNode, mResourceSystem, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); @@ -380,7 +380,7 @@ namespace MWRender void InventoryPreview::updatePtr(const MWWorld::Ptr &ptr) { - mCharacter = MWWorld::Ptr(ptr.getBase(), NULL); + mCharacter = MWWorld::Ptr(ptr.getBase(), nullptr); } void InventoryPreview::onSetup() @@ -403,7 +403,7 @@ namespace MWRender , mRef(&mBase) , mPitchRadians(osg::DegreesToRadians(6.f)) { - mCharacter = MWWorld::Ptr(&mRef, NULL); + mCharacter = MWWorld::Ptr(&mRef, nullptr); } RaceSelectionPreview::~RaceSelectionPreview() diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 6db223bd54..a262d00211 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -170,7 +170,7 @@ void CreatureWeaponAnimation::releaseArrow(float attackStrength) osg::Group *CreatureWeaponAnimation::getArrowBone() { if (!mWeapon) - return NULL; + return nullptr; SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); mWeapon->getNode()->accept(findVisitor); @@ -180,7 +180,7 @@ osg::Group *CreatureWeaponAnimation::getArrowBone() osg::Node *CreatureWeaponAnimation::getWeaponNode() { - return mWeapon ? mWeapon->getNode().get() : NULL; + return mWeapon ? mWeapon->getNode().get() : nullptr; } Resource::ResourceSystem *CreatureWeaponAnimation::getResourceSystem() diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index fae524faa7..d1b6cd239f 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -1,7 +1,5 @@ #include "globalmap.hpp" -#include - #include #include #include @@ -273,9 +271,9 @@ namespace MWRender void GlobalMap::worldPosToImageSpace(float x, float z, float& imageX, float& imageY) { - imageX = float(x / 8192.f - mMinX) / (mMaxX - mMinX + 1); + imageX = float(x / float(Constants::CellSizeInUnits) - mMinX) / (mMaxX - mMinX + 1); - imageY = 1.f-float(z / 8192.f - mMinY) / (mMaxY - mMinY + 1); + imageY = 1.f-float(z / float(Constants::CellSizeInUnits) - mMinY) / (mMaxY - mMinY + 1); } void GlobalMap::cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY) @@ -565,7 +563,7 @@ namespace MWRender requestOverlayTextureUpdate(0, 0, mWidth, mHeight, osg::ref_ptr(), true, false); - mWorkItem = NULL; + mWorkItem = nullptr; } } diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index 5edce2c3f6..38c28a72a9 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -14,7 +14,7 @@ namespace MWRender { LandManager::LandManager(int loadFlags) - : ResourceManager(NULL) + : ResourceManager(nullptr) , mLoadFlags(loadFlags) { } @@ -32,7 +32,7 @@ osg::ref_ptr LandManager::getLand(int x, int y) { const ESM::Land* land = MWBase::Environment::get().getWorld()->getStore().get().search(x,y); if (!land) - return NULL; + return nullptr; osg::ref_ptr landObj (new ESMTerrain::LandObject(land, mLoadFlags)); mCache->addEntryToObjectCache(idstr, landObj.get()); return landObj; diff --git a/apps/openmw/mwrender/landmanager.hpp b/apps/openmw/mwrender/landmanager.hpp index 392a8b406e..81253bba35 100644 --- a/apps/openmw/mwrender/landmanager.hpp +++ b/apps/openmw/mwrender/landmanager.hpp @@ -19,7 +19,7 @@ namespace MWRender public: LandManager(int loadFlags); - /// @note Will return NULL if not found. + /// @note Will return nullptr if not found. osg::ref_ptr getLand(int x, int y); virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 5e501ecf86..8ea4e39917 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -71,11 +72,16 @@ namespace MWRender LocalMap::LocalMap(osg::Group* root) : mRoot(root) , mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) - , mMapWorldSize(8192.f) + , mMapWorldSize(Constants::CellSizeInUnits) , mCellDistance(Settings::Manager::getInt("local map cell distance", "Map")) , mAngle(0.f) , mInterior(false) { + // Increase map resolution, if use UI scaling + float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); + if (uiScale > 1.0) + mMapResolution *= uiScale; + SceneUtil::FindByNameVisitor find("Scene Root"); mRoot->accept(find); mSceneRoot = find.mFoundNode; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 98f8ce892d..c7669f5e90 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -71,7 +72,7 @@ std::string getVampireHead(const std::string& race, bool female) } if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) - sVampireMapping[thisCombination] = NULL; + sVampireMapping[thisCombination] = nullptr; const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination]; if (!bodyPart) @@ -304,7 +305,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr par void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) { assert(viewMode != VM_HeadOnly); - if(mViewMode == viewMode) + if(mViewMode == viewMode) return; mViewMode = viewMode; @@ -451,37 +452,15 @@ void NpcAnimation::updateNpcBase() } } + bool is1stPerson = mViewMode == VM_FirstPerson; bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - std::string smodel; - if (mViewMode != VM_FirstPerson) - { - if (isWerewolf) - smodel = "meshes\\wolf\\skin.nif"; - else if (isBeast) - smodel = "meshes\\base_animkna.nif"; - else if (isFemale) - smodel = "meshes\\base_anim_female.nif"; - else - smodel = "meshes\\base_anim.nif"; - } - else - { - if (isWerewolf) - smodel = "meshes\\wolf\\skin.1st.nif"; - else if (isBeast) - smodel = "meshes\\base_animkna.1st.nif"; - else if (isFemale) - smodel = "meshes\\base_anim_female.1st.nif"; - else - smodel = "meshes\\base_anim.1st.nif"; - } - + std::string smodel = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); smodel = Misc::ResourceHelpers::correctActorModelPath(smodel, mResourceSystem->getVFS()); setObjectRoot(smodel, true, true, false); - if(mViewMode != VM_FirstPerson) + if(!is1stPerson) { const std::string base = "meshes\\xbase_anim.nif"; if (smodel != base) @@ -556,7 +535,7 @@ void NpcAnimation::updateParts() }; static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]); - bool wasArrowAttached = (mAmmunition.get() != NULL); + bool wasArrowAttached = (mAmmunition.get() != nullptr); mAmmunition.reset(); const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); @@ -675,7 +654,7 @@ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const st } osg::Vec3f NpcAnimation::runAnimation(float timepassed) -{ +{ osg::Vec3f ret = Animation::runAnimation(timepassed); mHeadAnimationTime->update(timepassed); @@ -844,7 +823,7 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectormData.mPart == ESM::BodyPart::MP_Wrist || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) - bodypart = NULL; + bodypart = nullptr; } else if (!bodypart) Log(Debug::Warning) << "Warning: Failed to find body part '" << part->mFemale << "'"; @@ -859,7 +838,7 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectormData.mPart == ESM::BodyPart::MP_Wrist || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) - bodypart = NULL; + bodypart = nullptr; } else if (!bodypart) Log(Debug::Warning) << "Warning: Failed to find body part '" << part->mMale << "'"; @@ -876,7 +855,7 @@ void NpcAnimation::addControllers() { Animation::addControllers(); - mFirstPersonNeckController = NULL; + mFirstPersonNeckController = nullptr; WeaponAnimation::deleteControllers(); if (mViewMode == VM_FirstPerson) @@ -968,7 +947,7 @@ osg::Group* NpcAnimation::getArrowBone() { PartHolderPtr part = mObjectParts[ESM::PRT_Weapon]; if (!part) - return NULL; + return nullptr; SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); part->getNode()->accept(findVisitor); @@ -980,7 +959,7 @@ osg::Node* NpcAnimation::getWeaponNode() { PartHolderPtr part = mObjectParts[ESM::PRT_Weapon]; if (!part) - return NULL; + return nullptr; return part->getNode(); } @@ -1104,7 +1083,7 @@ const std::vector& NpcAnimation::getBodyParts(const std:: sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail)); } - parts.resize(ESM::PRT_Count, NULL); + parts.resize(ESM::PRT_Count, nullptr); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 7f3a8da2dc..335ca5d5a3 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -75,16 +75,16 @@ private: void updateNpcBase(); PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename, - const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=NULL); + 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=NULL); + 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=NULL); + bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr); virtual void setRenderBin(); @@ -155,7 +155,7 @@ public: virtual void updatePtr(const MWWorld::Ptr& updated); /// Get a list of body parts that may be used by an NPC of given race and gender. - /// @note This is a fixed size list, one list item for each ESM::PartReferenceType, may contain NULL body parts. + /// @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/objects.cpp b/apps/openmw/mwrender/objects.cpp index db6772eaad..a0f48ad275 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -126,14 +126,14 @@ bool Objects::removeObject (const MWWorld::Ptr& ptr) if (ptr.getClass().isActor()) { if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).setInvListener(NULL, ptr); + ptr.getClass().getInventoryStore(ptr).setInvListener(nullptr, ptr); - ptr.getClass().getContainerStore(ptr).setContListener(NULL); + ptr.getClass().getContainerStore(ptr).setContListener(nullptr); } ptr.getRefData().getBaseNode()->getParent(0)->removeChild(ptr.getRefData().getBaseNode()); - ptr.getRefData().setBaseNode(NULL); + ptr.getRefData().setBaseNode(nullptr); return true; } return false; @@ -153,8 +153,8 @@ void Objects::removeCell(const MWWorld::CellStore* store) if (ptr.getClass().isNpc() && ptr.getRefData().getCustomData()) { MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); - invStore.setInvListener(NULL, ptr); - invStore.setContListener(NULL); + invStore.setInvListener(nullptr, ptr); + invStore.setContListener(nullptr); } mObjects.erase(iter++); @@ -218,7 +218,7 @@ Animation* Objects::getAnimation(const MWWorld::Ptr &ptr) if(iter != mObjects.end()) return iter->second; - return NULL; + return nullptr; } const Animation* Objects::getAnimation(const MWWorld::ConstPtr &ptr) const @@ -227,7 +227,7 @@ const Animation* Objects::getAnimation(const MWWorld::ConstPtr &ptr) const if(iter != mObjects.end()) return iter->second; - return NULL; + return nullptr; } } diff --git a/apps/openmw/mwrender/pathgrid.cpp b/apps/openmw/mwrender/pathgrid.cpp index aae97fe35a..ee4120d6f8 100644 --- a/apps/openmw/mwrender/pathgrid.cpp +++ b/apps/openmw/mwrender/pathgrid.cpp @@ -27,8 +27,8 @@ namespace MWRender Pathgrid::Pathgrid(osg::ref_ptr root) : mPathgridEnabled(false) , mRootNode(root) - , mPathGridRoot(NULL) - , mInteriorPathgridNode(NULL) + , mPathGridRoot(nullptr) + , mInteriorPathgridNode(nullptr) { } @@ -94,7 +94,7 @@ void Pathgrid::togglePathgrid() if (mPathGridRoot) { mRootNode->removeChild(mPathGridRoot); - mPathGridRoot = NULL; + mPathGridRoot = nullptr; } } } @@ -124,7 +124,7 @@ void Pathgrid::enableCellPathgrid(const MWWorld::CellStore *store) } else { - assert(mInteriorPathgridNode == NULL); + assert(mInteriorPathgridNode == nullptr); mInteriorPathgridNode = cellPathGrid; } } @@ -146,7 +146,7 @@ void Pathgrid::disableCellPathgrid(const MWWorld::CellStore *store) if (mInteriorPathgridNode) { mPathGridRoot->removeChild(mInteriorPathgridNode); - mInteriorPathgridNode = NULL; + mInteriorPathgridNode = nullptr; } } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ce0a41c9a0..476beb990b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -232,7 +232,7 @@ namespace MWRender mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); - if (getenv("OPENMW_DONT_PRECOMPILE") == NULL) + if (getenv("OPENMW_DONT_PRECOMPILE") == nullptr) { mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); @@ -336,7 +336,7 @@ namespace MWRender RenderingManager::~RenderingManager() { // let background loading thread finish before we delete anything else - mWorkQueue = NULL; + mWorkQueue = nullptr; } MWRender::Objects& RenderingManager::getObjects() @@ -805,7 +805,7 @@ namespace MWRender 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); @@ -830,19 +830,14 @@ namespace MWRender 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(NULL); + quad->setUpdateCallback(nullptr); screenshotCamera->addChild(quad); - mRootNode->addChild(screenshotCamera); - renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH); - screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren()); - mRootNode->removeChild(screenshotCamera); - return true; } @@ -867,6 +862,8 @@ namespace MWRender image->setDataType(GL_UNSIGNED_BYTE); image->setPixelFormat(texture->getInternalFormat()); + mRootNode->addChild(camera); + // The draw needs to complete before we can copy back our image. osg::ref_ptr callback (new NotifyDrawCompletedCallback); camera->setFinalDrawCallback(callback); @@ -882,32 +879,17 @@ namespace MWRender // 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 RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) { osg::ref_ptr rttCamera (new osg::Camera); - rttCamera->setNodeMask(Mask_RenderToTexture); - rttCamera->attach(osg::Camera::COLOR_BUFFER, image); - rttCamera->setRenderOrder(osg::Camera::PRE_RENDER); - rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); - rttCamera->setViewport(0, 0, w, h); - - osg::ref_ptr texture (new osg::Texture2D); - texture->setInternalFormat(GL_RGB); - 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); - rttCamera->attach(osg::Camera::COLOR_BUFFER, texture); - - image->setDataType(GL_UNSIGNED_BYTE); - image->setPixelFormat(texture->getInternalFormat()); - rttCamera->setUpdateCallback(new NoTraverseCallback); rttCamera->addChild(mSceneRoot); @@ -916,14 +898,9 @@ namespace MWRender rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); - mRootNode->addChild(rttCamera); - rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderCameraToImage(rttCamera.get(),image,w,h); - - rttCamera->removeChildren(0, rttCamera->getNumChildren()); - mRootNode->removeChild(rttCamera); } osg::Vec4f RenderingManager::getScreenBounds(const MWWorld::Ptr& ptr) @@ -975,7 +952,7 @@ namespace MWRender result.mHitNormalWorld = intersection.getWorldIntersectNormal(); result.mRatio = intersection.ratio; - PtrHolder* ptrHolder = NULL; + PtrHolder* ptrHolder = nullptr; for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) { osg::UserDataContainer* userDataContainer = (*it)->getUserDataContainer(); @@ -1113,7 +1090,7 @@ namespace MWRender void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) { - NpcAnimation *anim = NULL; + NpcAnimation *anim = nullptr; if(ptr == mPlayerAnimation->getPtr()) anim = mPlayerAnimation.get(); else diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 141a3eb874..21bd48d9ae 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -196,7 +196,7 @@ void RippleSimulation::emitRipple(const osg::Vec3f &pos) { if (std::abs(pos.z() - mParticleNode->getPosition().z()) < 20) { - osgParticle::Particle* p = mParticleSystem->createParticle(NULL); + 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)); } diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 24769019b1..d6a0b9f019 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -629,7 +629,7 @@ private: if (mSunFlashNode) { mSunFlashNode->removeCullCallback(mSunFlashCallback); - mSunFlashCallback = NULL; + mSunFlashCallback = nullptr; } } @@ -671,7 +671,7 @@ private: if (mSunGlareNode) { mSunGlareNode->removeCullCallback(mSunGlareCallback); - mSunGlareCallback = NULL; + mSunGlareCallback = nullptr; } } @@ -1095,8 +1095,8 @@ private: SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) : mSceneManager(sceneManager) - , mCamera(NULL) - , mRainIntensityUniform(NULL) + , mCamera(nullptr) + , mRainIntensityUniform(nullptr) , mAtmosphereNightRoll(0.f) , mCreated(false) , mIsStorm(false) @@ -1302,7 +1302,7 @@ public: { if (stateset->getAttribute(osg::StateAttribute::MATERIAL)) { - SceneUtil::CompositeStateSetUpdater* composite = NULL; + SceneUtil::CompositeStateSetUpdater* composite = nullptr; osg::Callback* callback = node.getUpdateCallback(); while (callback) @@ -1383,12 +1383,12 @@ public: virtual osg::Object *cloneType() const override { - return NULL; + return nullptr; } virtual osg::Object *clone(const osg::CopyOp &op) const override { - return NULL; + return nullptr; } virtual void operate(osgParticle::Particle *P, double dt) override @@ -1520,10 +1520,10 @@ void SkyManager::destroyRain() return; mRootNode->removeChild(mRainNode); - mRainNode = NULL; - mRainParticleSystem = NULL; - mRainShooter = NULL; - mRainFader = NULL; + mRainNode = nullptr; + mRainParticleSystem = nullptr; + mRainShooter = nullptr; + mRainFader = nullptr; } SkyManager::~SkyManager() @@ -1531,7 +1531,7 @@ SkyManager::~SkyManager() if (mRootNode) { mRootNode->getParent(0)->removeChild(mRootNode); - mRootNode = NULL; + mRootNode = nullptr; } } @@ -1554,7 +1554,7 @@ bool SkyManager::isEnabled() bool SkyManager::hasRain() { - return mRainNode != NULL; + return mRainNode != nullptr; } void SkyManager::update(float duration) @@ -1658,7 +1658,7 @@ void SkyManager::setWeather(const WeatherResult& weather) if (mParticleEffect) { mParticleNode->removeChild(mParticleEffect); - mParticleEffect = NULL; + mParticleEffect = nullptr; mParticleFaders.clear(); } @@ -1667,7 +1667,7 @@ void SkyManager::setWeather(const WeatherResult& weather) if (mParticleNode) { mRootNode->removeChild(mParticleNode); - mParticleNode = NULL; + mParticleNode = nullptr; } } else diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 52832ad874..f668b0820f 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -27,6 +27,8 @@ #include +#include + #include #include @@ -401,7 +403,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem { mSimulation.reset(new RippleSimulation(parent, resourceSystem, fallback)); - mWaterGeom = SceneUtil::createWaterGeometry(CELL_SIZE*150, 40, 900); + mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits*150, 40, 900); mWaterGeom->setDrawCallback(new DepthClampCallback); mWaterGeom->setNodeMask(Mask_Water); @@ -439,13 +441,13 @@ void Water::updateWaterMaterial() { mReflection->removeChildren(0, mReflection->getNumChildren()); mParent->removeChild(mReflection); - mReflection = NULL; + mReflection = nullptr; } if (mRefraction) { mRefraction->removeChildren(0, mRefraction->getNumChildren()); mParent->removeChild(mRefraction); - mRefraction = NULL; + mRefraction = nullptr; } if (Settings::Manager::getBool("shader", "Water")) @@ -577,7 +579,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); node->setStateSet(shaderStateset); - node->setUpdateCallback(NULL); + node->setUpdateCallback(nullptr); } void Water::processChangedSettings(const Settings::CategorySettingVector& settings) @@ -593,13 +595,13 @@ Water::~Water() { mReflection->removeChildren(0, mReflection->getNumChildren()); mParent->removeChild(mReflection); - mReflection = NULL; + mReflection = nullptr; } if (mRefraction) { mRefraction->removeChildren(0, mRefraction->getNumChildren()); mParent->removeChild(mRefraction); - mRefraction = NULL; + mRefraction = nullptr; } } @@ -679,7 +681,8 @@ bool Water::isUnderwater(const osg::Vec3f &pos) const osg::Vec3f Water::getSceneNodeCoordinates(int gridX, int gridY) { - return osg::Vec3f(static_cast(gridX * CELL_SIZE + (CELL_SIZE / 2)), static_cast(gridY * CELL_SIZE + (CELL_SIZE / 2)), mTop); + return osg::Vec3f(static_cast(gridX * Constants::CellSizeInUnits + (Constants::CellSizeInUnits / 2)), + static_cast(gridY * Constants::CellSizeInUnits + (Constants::CellSizeInUnits / 2)), mTop); } void Water::addEmitter (const MWWorld::Ptr& ptr, float scale, float force) diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index e2413cfa05..32a7977d2a 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -50,8 +50,6 @@ namespace MWRender /// Water rendering class Water { - static const int CELL_SIZE = 8192; - osg::ref_ptr mRainIntensityUniform; osg::ref_ptr mParent; diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 32990482a2..d5a8cb10d9 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -164,7 +164,7 @@ void WeaponAnimation::addControllers(const std::map >::const_iterator found = nodes.find(i == 0 ? "bip01 spine1" : "bip01 spine2"); if (found != nodes.end()) @@ -180,7 +180,7 @@ void WeaponAnimation::addControllers(const std::mapgetPlayerPtr(); if (world->findExteriorPosition(cell, pos)) { - MWWorld::ActionTeleport("", pos, false).execute(world->getPlayerPtr()); - world->fixPosition(world->getPlayerPtr()); + 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(world->getPlayerPtr()); + MWWorld::ActionTeleport(cell, pos, false).execute(playerPtr); } } }; @@ -76,14 +77,15 @@ namespace MWScript ESM::Position pos; MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Ptr playerPtr = world->getPlayerPtr(); 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; - MWWorld::ActionTeleport("", pos, false).execute(world->getPlayerPtr()); - world->fixPosition(world->getPlayerPtr()); + MWWorld::ActionTeleport("", pos, false).execute(playerPtr); + world->adjustPosition(playerPtr, false); } }; diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index f4e729da18..07b7a3bffd 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -288,10 +288,17 @@ namespace MWScript return "None"; } - std::string InterpreterContext::getNPCName() const + std::string InterpreterContext::getActorName() const { - ESM::NPC npc = *getReferenceImp().get()->mBase; - return npc.mName; + const MWWorld::Ptr& ptr = getReferenceImp(); + if (ptr.getClass().isNpc()) + { + const ESM::NPC* npc = ptr.get()->mBase; + return npc->mName; + } + + const ESM::Creature* creature = ptr.get()->mBase; + return creature->mName; } std::string InterpreterContext::getNPCRace() const diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index ac8e1833b4..5f1f4f7ab8 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -93,7 +93,7 @@ namespace MWScript virtual std::string getActionBinding(const std::string& action) const; - virtual std::string getNPCName() const; + virtual std::string getActorName() const; virtual std::string getNPCRace() const; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 8d592f29c6..7250e9fcfc 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -107,8 +107,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWBase::World* world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world->getPlayerPtr(); - runtime.push (!world->isOnGround(player) && !world->isFlying(player)); + runtime.push (world->getPlayer().getJumping()); } }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 29160d47e4..53316c44c9 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1156,7 +1156,7 @@ namespace MWScript // HACK: disable/enable object to re-add it to the scene properly (need a new Animation). MWBase::Environment::get().getWorld()->disable(ptr); // resets runtime state such as inventory, stats and AI. does not reset position in the world - ptr.getRefData().setCustomData(NULL); + ptr.getRefData().setCustomData(nullptr); if (wasEnabled) MWBase::Environment::get().getWorld()->enable(ptr); } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 16d7e80364..2695aed764 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -484,7 +484,7 @@ namespace MWScript if (!player.isInCell()) throw std::runtime_error("player not in a cell"); - MWWorld::CellStore* store = NULL; + MWWorld::CellStore* store = nullptr; if (player.getCell()->isExterior()) { int cx,cy; @@ -732,14 +732,13 @@ namespace MWScript } }; - template class OpFixme : public Interpreter::Opcode0 { public: virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr ptr = R()(runtime); + const MWWorld::Ptr ptr = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->fixPosition(ptr); } }; @@ -784,8 +783,7 @@ namespace MWScript interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngle, new OpGetStartingAngle); interpreter.installSegment5(Compiler::Transformation::opcodeGetStartingAngleExplicit, new OpGetStartingAngle); interpreter.installSegment5(Compiler::Transformation::opcodeResetActors, new OpResetActors); - interpreter.installSegment5(Compiler::Transformation::opcodeFixme, new OpFixme); - interpreter.installSegment5(Compiler::Transformation::opcodeFixmeExplicit, new OpFixme); + interpreter.installSegment5(Compiler::Transformation::opcodeFixme, new OpFixme); } } } diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 7dffd685a3..53452c3054 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -122,7 +122,7 @@ bool FFmpeg_Decoder::getAVAudioData() if(!mDataBuf || mDataBufLen < mFrame->nb_samples) { av_freep(&mDataBuf); - if(av_samples_alloc(&mDataBuf, NULL, av_get_channel_layout_nb_channels(mOutputChannelLayout), + if(av_samples_alloc(&mDataBuf, nullptr, av_get_channel_layout_nb_channels(mOutputChannelLayout), mFrame->nb_samples, mOutputSampleFormat, 0) < 0) return false; else @@ -181,34 +181,34 @@ void FFmpeg_Decoder::open(const std::string &fname) close(); mDataStream = mResourceMgr->get(fname); - if((mFormatCtx=avformat_alloc_context()) == NULL) + if((mFormatCtx=avformat_alloc_context()) == nullptr) throw std::runtime_error("Failed to allocate context"); try { - mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); - if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) + 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 != NULL) + if (mFormatCtx->pb != nullptr) { - if (mFormatCtx->pb->buffer != NULL) + if (mFormatCtx->pb->buffer != nullptr) { av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = NULL; + mFormatCtx->pb->buffer = nullptr; } av_free(mFormatCtx->pb); - mFormatCtx->pb = NULL; + mFormatCtx->pb = nullptr; } avformat_free_context(mFormatCtx); } - mFormatCtx = NULL; + mFormatCtx = nullptr; throw std::runtime_error("Failed to allocate input stream"); } - if(avformat_find_stream_info(mFormatCtx, NULL) < 0) + 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++) @@ -231,7 +231,7 @@ void FFmpeg_Decoder::open(const std::string &fname) std::to_string((*mStream)->codec->codec_id); throw std::runtime_error(ss); } - if(avcodec_open2((*mStream)->codec, codec, NULL) < 0) + if(avcodec_open2((*mStream)->codec, codec, nullptr) < 0) throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name); @@ -255,17 +255,17 @@ void FFmpeg_Decoder::open(const std::string &fname) { if(mStream) avcodec_close((*mStream)->codec); - mStream = NULL; + mStream = nullptr; - if (mFormatCtx != NULL) + if (mFormatCtx != nullptr) { - if (mFormatCtx->pb->buffer != NULL) + if (mFormatCtx->pb->buffer != nullptr) { av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = NULL; + mFormatCtx->pb->buffer = nullptr; } av_free(mFormatCtx->pb); - mFormatCtx->pb = NULL; + mFormatCtx->pb = nullptr; avformat_close_input(&mFormatCtx); } @@ -276,7 +276,7 @@ void FFmpeg_Decoder::close() { if(mStream) avcodec_close((*mStream)->codec); - mStream = NULL; + mStream = nullptr; av_free_packet(&mPacket); av_freep(&mFrame); @@ -285,20 +285,20 @@ void FFmpeg_Decoder::close() if(mFormatCtx) { - if (mFormatCtx->pb != NULL) + 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 != NULL) + if (mFormatCtx->pb->buffer != nullptr) { av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = NULL; + mFormatCtx->pb->buffer = nullptr; } av_free(mFormatCtx->pb); - mFormatCtx->pb = NULL; + mFormatCtx->pb = nullptr; } avformat_close_input(&mFormatCtx); } @@ -373,7 +373,7 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * (*mStream)->codec->sample_fmt, // input sample format (*mStream)->codec->sample_rate, // input sample rate 0, // logging level offset - NULL); // log context + nullptr); // log context if(!mSwr) throw std::runtime_error("Couldn't allocate SwrContext"); if(swr_init(mSwr) < 0) @@ -417,17 +417,17 @@ size_t FFmpeg_Decoder::getSampleOffset() FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs) : Sound_Decoder(vfs) - , mFormatCtx(NULL) - , mStream(NULL) - , mFrame(NULL) + , mFormatCtx(nullptr) + , mStream(nullptr) + , mFrame(nullptr) , mFrameSize(0) , mFramePos(0) , mNextPts(0.0) , mSwr(0) , mOutputSampleFormat(AV_SAMPLE_FMT_NONE) , mOutputChannelLayout(0) - , mDataBuf(NULL) - , mFrameData(NULL) + , mDataBuf(nullptr) + , mFrameData(nullptr) , mDataBufLen(0) { memset(&mPacket, 0, sizeof(mPacket)); diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index 4975625169..faa8d94ca3 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -19,7 +19,7 @@ namespace MWSound { public: MWSoundDecoderBridge(MWSound::MovieAudioDecoder* decoder) - : Sound_Decoder(NULL) + : Sound_Decoder(nullptr) , mDecoder(decoder) { } diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 42dd6b5fda..39e872b0c9 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -34,10 +35,6 @@ namespace { -// The game uses 64 units per yard, or approximately 69.99125109 units per meter. -// Should this be defined publically somewhere? -const float UnitsPerMeter = 69.99125109f; - const int sLoudnessFPS = 20; // loudness values per second of audio ALCenum checkALCError(ALCdevice *device, const char *func, int line) @@ -575,10 +572,10 @@ std::vector OpenAL_Output::enumerate() std::vector devlist; const ALCchar *devnames; - if(alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT")) - devnames = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); + if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) + devnames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); else - devnames = alcGetString(NULL, ALC_DEVICE_SPECIFIER); + devnames = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); while(devnames && *devnames) { devlist.push_back(devnames); @@ -818,13 +815,13 @@ bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname LoadEffect(mWaterEffect, EFX_REVERB_PRESET_UNDERWATER); } - alListenerf(AL_METERS_PER_UNIT, 1.0f / UnitsPerMeter); + alListenerf(AL_METERS_PER_UNIT, 1.0f / Constants::UnitsPerMeter); } skip_efx: alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); - // Speed of sound is in units per second. Given the default speed of sound is 343.3 (assumed + // 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(343.3f * UnitsPerMeter); + alSpeedOfSound(Constants::SoundSpeedInAir * Constants::UnitsPerMeter); alGetError(); mInitialized = true; @@ -1400,8 +1397,7 @@ void OpenAL_Output::updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdi if(env != mListenerEnv) { - // Speed of sound in water is 1484m/s, and in air is 343.3m/s (roughly) - alSpeedOfSound(((env == Env_Underwater) ? 1484.0f : 343.3f) * UnitsPerMeter); + alSpeedOfSound(((env == Env_Underwater) ? Constants::SoundSpeedUnderwater : Constants::SoundSpeedInAir) * Constants::UnitsPerMeter); // Update active sources with the environment's direct filter if(mWaterFilter) @@ -1483,7 +1479,8 @@ void OpenAL_Output::resumeSounds(int types) OpenAL_Output::OpenAL_Output(SoundManager &mgr) - : Sound_Output(mgr), mDevice(0), mContext(0) + : Sound_Output(mgr) + , mDevice(0), mContext(0) , mListenerPos(0.0f, 0.0f, 0.0f), mListenerEnv(Env_Normal) , mWaterFilter(0), mWaterEffect(0), mDefaultEffect(0), mEffectSlot(0) , mStreamThread(new StreamThread) diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index afc8697331..b6a26c99a8 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -26,10 +26,10 @@ namespace MWSound struct { bool EXT_EFX : 1; bool SOFT_HRTF : 1; - } ALC; + } ALC = {false, false}; struct { bool SOFT_source_spatialize : 1; - } AL; + } AL = {false}; typedef std::deque IDDq; IDDq mFreeSources; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 9c12584d71..7b722a8352 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -245,20 +245,30 @@ namespace MWSound DecoderPtr SoundManager::loadVoice(const std::string &voicefile) { - DecoderPtr decoder = getDecoder(); - // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. - if(mVFS->exists(voicefile)) - decoder->open(voicefile); - else + try { - 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); + 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); + } + + return decoder; + } + catch(std::exception &e) + { + Log(Debug::Error) << "Failed to load audio from " << voicefile << ": " << e.what(); } - return decoder; + return nullptr; } Sound *SoundManager::getSoundRef() @@ -471,6 +481,8 @@ namespace MWSound mVFS->normalizeFilename(voicefile); DecoderPtr decoder = loadVoice(voicefile); + if (!decoder) + return; MWBase::World *world = MWBase::Environment::get().getWorld(); const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); @@ -503,6 +515,8 @@ namespace MWSound mVFS->normalizeFilename(voicefile); DecoderPtr decoder = loadVoice(voicefile); + if (!decoder) + return; stopSay(MWWorld::ConstPtr()); Stream *sound = playVoice(decoder, osg::Vec3f(), true); @@ -819,7 +833,7 @@ namespace MWSound } const ESM::Region *regn = world->getStore().get().search(regionName); - if(regn == NULL) + if(regn == nullptr) return; if(total == 0) diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 4064a05afe..d8a4cfc8cc 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -117,7 +117,7 @@ namespace MWSound Sound_Buffer *lookupSound(const std::string &soundId) const; Sound_Buffer *loadSound(const std::string &soundId); - // returns a decoder to start streaming + // returns a decoder to start streaming, or nullptr if the sound was not found DecoderPtr loadVoice(const std::string &voicefile); Sound *getSoundRef(); diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index a91bd4e8ce..d440ba8696 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -46,7 +46,7 @@ void MWState::CharacterManager::deleteSlot(const MWState::Character *character, // All slots deleted, cleanup and remove this character it->cleanup(); if (character == mCurrent) - mCurrent = NULL; + mCurrent = nullptr; mCharacters.erase(it); } } @@ -96,7 +96,7 @@ std::list::iterator MWState::CharacterManager::findCharacter void MWState::CharacterManager::setCurrentCharacter (const Character *character) { if (!character) - mCurrent = NULL; + mCurrent = nullptr; else { std::list::iterator it = findCharacter(character); diff --git a/apps/openmw/mwstate/quicksavemanager.cpp b/apps/openmw/mwstate/quicksavemanager.cpp index 59658ce6e1..df078e026c 100644 --- a/apps/openmw/mwstate/quicksavemanager.cpp +++ b/apps/openmw/mwstate/quicksavemanager.cpp @@ -20,7 +20,7 @@ void MWState::QuickSaveManager::visitSave(const Slot *saveSlot) bool MWState::QuickSaveManager::isOldestSave(const Slot *compare) { - if(mOldestSlotVisited == NULL) + if(mOldestSlotVisited == nullptr) return true; return (compare->mTimeStamp <= mOldestSlotVisited->mTimeStamp); } @@ -33,6 +33,6 @@ bool MWState::QuickSaveManager::shouldCreateNewSlot() const MWState::Slot *MWState::QuickSaveManager::getNextQuickSaveSlot() { if(shouldCreateNewSlot()) - return NULL; + return nullptr; return mOldestSlotVisited; } diff --git a/apps/openmw/mwstate/quicksavemanager.hpp b/apps/openmw/mwstate/quicksavemanager.hpp index e52cd609f7..a5237d7c30 100644 --- a/apps/openmw/mwstate/quicksavemanager.hpp +++ b/apps/openmw/mwstate/quicksavemanager.hpp @@ -28,7 +28,7 @@ namespace MWState{ const Slot *getNextQuickSaveSlot(); ///< Get the slot that the next quicksave should use. /// - ///\return Either the oldest quicksave slot visited, or NULL if a new slot can be made + ///\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 5604338206..9804531d70 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -55,7 +55,7 @@ void MWState::StateManager::cleanup (bool force) MWBase::Environment::get().getMechanicsManager()->clear(); mState = State_NoGame; - mCharacterManager.setCurrentCharacter(NULL); + mCharacterManager.setCurrentCharacter(nullptr); mTimePlayed = 0; MWMechanics::CreatureStats::cleanup(); diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index d858859a69..a173e87fbc 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -5,6 +5,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwgui/inventorywindow.hpp" + #include "class.hpp" #include "containerstore.hpp" @@ -14,6 +16,17 @@ namespace MWWorld void ActionTake::executeImp (const Ptr& actor) { + // When in GUI mode, we should use drag and drop + if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); + if (mode == MWGui::GM_Inventory || mode == MWGui::GM_Container) + { + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(getTarget()); + return; + } + } + MWBase::Environment::get().getMechanicsManager()->itemTaken( actor, getTarget(), MWWorld::Ptr(), getTarget().getRefData().getCount()); MWWorld::Ptr newitem = *actor.getClass().getContainerStore (actor).add (getTarget(), getTarget().getRefData().getCount(), actor); diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 93705f0059..f54edc8cb2 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -36,7 +36,7 @@ namespace MWWorld void ActionTeleport::teleport(const Ptr &actor) { MWBase::World* world = MWBase::Environment::get().getWorld(); - actor.getClass().getCreatureStats(actor).land(); + actor.getClass().getCreatureStats(actor).land(actor == world->getPlayerPtr()); if(actor == world->getPlayerPtr()) { world->getPlayer().setTeleported(true); diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 9163f1f2e3..a06c208b59 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -206,13 +206,13 @@ namespace MWWorld { mTerrainPreloadItem->abort(); mTerrainPreloadItem->waitTillDone(); - mTerrainPreloadItem = NULL; + mTerrainPreloadItem = nullptr; } if (mUpdateCacheItem) { mUpdateCacheItem->waitTillDone(); - mUpdateCacheItem = NULL; + mUpdateCacheItem = nullptr; } for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it) diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 5cac12b9c9..2b032e1d70 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -151,6 +151,19 @@ MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name) return &result->second; } +void MWWorld::Cells::rest () +{ + for (auto &interior : mInteriors) + { + interior.second.rest(); + } + + for (auto &exterior : mExteriors) + { + exterior.second.rest(); + } +} + MWWorld::CellStore *MWWorld::Cells::getCell (const ESM::CellId& id) { if (id.mPaged) @@ -327,7 +340,7 @@ public: } catch (...) { - return NULL; + return nullptr; } } }; diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index 0b7d9444fd..bd730329bb 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -61,6 +61,7 @@ namespace MWWorld /// @note name must be lower case Ptr getPtr (const std::string& name); + void rest (); /// Get all Ptrs referencing \a name in exterior cells /// @note Due to the current implementation of getPtr this only supports one Ptr per cell. diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 05ff1e3260..239d714a06 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -17,6 +17,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -155,7 +156,7 @@ namespace ESM::RefNum mRefNumToFind; SearchByRefNumVisitor(const ESM::RefNum& toFind) - : mFound(NULL) + : mFound(nullptr) , mRefNumToFind(toFind) { } @@ -258,7 +259,7 @@ namespace MWWorld { MWWorld::Ptr copied = object.getClass().copyToCell(object, *cellToMoveTo, object.getRefData().getCount()); object.getRefData().setCount(0); - object.getRefData().setBaseNode(NULL); + object.getRefData().setBaseNode(nullptr); return copied; } @@ -900,7 +901,7 @@ namespace MWWorld CellStore* otherCell = callback->getCellStore(movedTo); - if (otherCell == NULL) + if (otherCell == nullptr) { Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef->mRef.getRefId() << " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location."; @@ -954,6 +955,29 @@ namespace MWWorld } } + void CellStore::rest() + { + if (mState == State_Loaded) + { + for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) + { + Ptr ptr = getCurrentPtr(&*it); + if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + { + MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, true); + } + } + for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) + { + Ptr ptr = getCurrentPtr(&*it); + if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + { + MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, true); + } + } + } + } + void CellStore::respawn() { if (mState == State_Loaded) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index dd54bdd6ac..1c4d8f5d87 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -64,7 +64,7 @@ namespace MWWorld // 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 NULL until the cell is explored to save some memory + // Note this is nullptr until the cell is explored to save some memory std::shared_ptr mFogState; const ESM::Cell *mCell; @@ -183,6 +183,8 @@ namespace MWWorld /// @return updated MWWorld::Ptr with the new CellStore pointer set. MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo); + void rest(); + /// 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. @@ -365,7 +367,7 @@ namespace MWWorld struct GetCellStoreCallback { public: - ///@note must return NULL if the cell is not found + ///@note must return nullptr if the cell is not found virtual CellStore* getCellStore(const ESM::CellId& cellId) = 0; }; diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index cfb07f7492..e68b383b77 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -17,7 +17,7 @@ namespace MWWorld { if (ptr.getRefData().getBaseNode()) { - ptr.getRefData().setBaseNode(NULL); + ptr.getRefData().setBaseNode(nullptr); mObjects.push_back (ptr); } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 8fa16ff515..6a24ae4f01 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -114,7 +114,7 @@ void MWWorld::ContainerStore::storeStates (const CellRefList& collection, const std::string MWWorld::ContainerStore::sGoldId = "gold_001"; -MWWorld::ContainerStore::ContainerStore() : mListener(NULL), mCachedWeight (0), mWeightUpToDate (false) {} +MWWorld::ContainerStore::ContainerStore() : mListener(nullptr), mCachedWeight (0), mWeightUpToDate (false) {} MWWorld::ContainerStore::~ContainerStore() {} @@ -286,7 +286,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr MWWorld::Ptr item = *it; // we may have copied an item from the world, so reset a few things first - item.getRefData().setBaseNode(NULL); // 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; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 7f3018c559..647ae261ba 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -99,7 +99,7 @@ void MWWorld::InventoryStore::readEquipmentState(const MWWorld::ContainerStoreIt } MWWorld::InventoryStore::InventoryStore() - : mListener(NULL) + : mListener(nullptr) , mUpdatesEnabled (true) , mFirstAutoEquip(true) , mSelectedEnchantItem(end()) @@ -250,11 +250,10 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); - MWMechanics::NpcStats& stats = actor.getClass().getNpcStats(actor); static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); - int unarmoredSkill = stats.getSkill(ESM::Skill::Unarmored).getModified(); + int unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored); float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); @@ -384,7 +383,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) for (int j = 0; j < static_cast(weaponSkillsLength); ++j) { - int skillValue = stats.getSkill(static_cast(weaponSkills[j])).getModified(); + int skillValue = actor.getClass().getSkill(actor, static_cast(weaponSkills[j])); if (skillValue > max && !weaponSkillVisited[j]) { diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 2631f513fb..4b61c0a08d 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -76,11 +76,11 @@ namespace MWWorld template struct LiveCellRef : public LiveCellRefBase { - LiveCellRef(const ESM::CellRef& cref, const X* b = NULL) + LiveCellRef(const ESM::CellRef& cref, const X* b = nullptr) : LiveCellRefBase(typeid(X).name(), cref), mBase(b) {} - LiveCellRef(const X* b = NULL) + LiveCellRef(const X* b = nullptr) : LiveCellRefBase(typeid(X).name()), mBase(b) {} diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 389f599832..85181e998e 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -30,13 +30,14 @@ namespace MWWorld Player::Player (const ESM::NPC *player) : mCellStore(0), mLastKnownExteriorPosition(0,0,0), - mMarkedCell(NULL), + mMarkedCell(nullptr), mAutoMove(false), mForwardBackward(0), mTeleported(false), mCurrentCrimeId(-1), mPaidCrimeId(-1), - mAttackingOrSpell(false) + mAttackingOrSpell(false), + mJumping(false) { ESM::CellRef cellRef; cellRef.blank(); @@ -255,6 +256,16 @@ namespace MWWorld return mAttackingOrSpell; } + void Player::setJumping(bool jumping) + { + mJumping = jumping; + } + + bool Player::getJumping() const + { + return mJumping; + } + bool Player::isInCombat() { return MWBase::Environment::get().getMechanicsManager()->getActorsFighting(getPlayer()).size() != 0; } @@ -286,6 +297,7 @@ namespace MWWorld mForwardBackward = 0; mTeleported = false; mAttackingOrSpell = false; + mJumping = false; mCurrentCrimeId = -1; mPaidCrimeId = -1; mPreviousItems.clear(); @@ -394,7 +406,7 @@ namespace MWWorld { 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 = NULL; + mCellStore = nullptr; } if (!player.mBirthsign.empty()) diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index d4a9f6050c..7f37de0100 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -38,7 +38,7 @@ namespace MWWorld osg::Vec3f mLastKnownExteriorPosition; ESM::Position mMarkedPosition; - // If no position was marked, this is NULL + // If no position was marked, this is nullptr CellStore* mMarkedCell; bool mAutoMove; @@ -56,6 +56,7 @@ namespace MWWorld MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length]; bool mAttackingOrSpell; + bool mJumping; public: @@ -111,6 +112,9 @@ namespace MWWorld 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 bool isInCombat(); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 21218fab01..4698ba0114 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include @@ -460,7 +462,7 @@ namespace MWWorld { // gravity constant - must be way lower than the gravity affecting actors, since we're not // simulating aerodynamics at all - it->mVelocity -= osg::Vec3f(0, 0, 627.2f * 0.1f) * duration; + it->mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; osg::Vec3f pos(it->mNode->getPosition()); osg::Vec3f newPos = pos + it->mVelocity * duration; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index ed6dde310c..30e73df588 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -94,7 +94,7 @@ namespace void updateObjectScale(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering) { - if (ptr.getRefData().getBaseNode() != NULL) + if (ptr.getRefData().getBaseNode() != nullptr) { float scale = ptr.getCellRef().getScale(); osg::Vec3f scaleVec (scale, scale, scale); @@ -330,7 +330,7 @@ namespace MWWorld while (active!=mActiveCells.end()) unloadCell (active++); assert(mActiveCells.empty()); - mCurrentCell = NULL; + mCurrentCell = nullptr; mPreloader->clear(); } @@ -345,7 +345,7 @@ namespace MWWorld getGridCenter(cellX, cellY); float centerX, centerY; MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); - const float maxDistance = 8192/2 + mCellLoadingThreshold; // 1/2 cell size + threshold + const float maxDistance = Constants::CellSizeInUnits / 2 + mCellLoadingThreshold; // 1/2 cell size + threshold float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y())); if (distance > maxDistance) { @@ -519,7 +519,7 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); - bool loadcell = (mCurrentCell == NULL); + bool loadcell = (mCurrentCell == nullptr); if(!loadcell) loadcell = *mCurrentCell != *cell; @@ -793,7 +793,7 @@ namespace MWWorld float dist = std::max(std::abs(thisCellCenterX - playerPos.x()), std::abs(thisCellCenterY - playerPos.y())); dist = std::min(dist,std::max(std::abs(thisCellCenterX - predictedPos.x()), std::abs(thisCellCenterY - predictedPos.y()))); - float loadDist = 8192/2 + 8192 - mCellLoadingThreshold + mPreloadDistance; + float loadDist = Constants::CellSizeInUnits / 2 + Constants::CellSizeInUnits - mCellLoadingThreshold + mPreloadDistance; if (dist < loadDist) preloadCell(MWBase::Environment::get().getWorld()->getExterior(cellX+dx, cellY+dy)); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 2ac78bb85d..b6bf2b7eba 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -96,7 +96,7 @@ namespace MWWorld typename Static::const_iterator it = mStatic.find(index); if (it != mStatic.end()) return &(it->second); - return NULL; + return nullptr; } template const T *IndexedStore::find(int index) const @@ -165,7 +165,7 @@ namespace MWWorld std::for_each(mShared.begin(), mShared.end(), GetRecords(id, &results)); if(!results.empty()) return results[Misc::Rng::rollDice(results.size())]; - return NULL; + return nullptr; } template const T *Store::find(const std::string &id) const @@ -358,7 +358,7 @@ namespace MWWorld const LandTextureList <exl = mStatic[plugin]; if (index >= ltexl.size()) - return NULL; + return nullptr; return <exl[index]; } const ESM::LandTexture *Store::find(size_t index, size_t plugin) const @@ -867,7 +867,7 @@ namespace MWWorld //========================================================================= Store::Store() - : mCells(NULL) + : mCells(nullptr) { } @@ -887,7 +887,7 @@ namespace MWWorld // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. // Check whether mCell is an interior cell. This isn't perfect, will break if a Region with the same name as an interior cell is created. // A proper fix should be made for future versions of the file format. - bool interior = mCells->search(pathgrid.mCell) != NULL; + bool interior = mCells->search(pathgrid.mCell) != nullptr; // Try to overwrite existing record if (interior) @@ -917,14 +917,14 @@ namespace MWWorld Exterior::const_iterator it = mExt.find(std::make_pair(x,y)); if (it != mExt.end()) return &(it->second); - return NULL; + return nullptr; } const ESM::Pathgrid *Store::search(const std::string& name) const { Interior::const_iterator it = mInt.find(name); if (it != mInt.end()) return &(it->second); - return NULL; + return nullptr; } const ESM::Pathgrid *Store::find(int x, int y) const { diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 00ea6f66f8..2ed81af482 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -166,7 +166,7 @@ namespace MWWorld */ bool isDynamic(const std::string &id) const; - /** Returns a random record that starts with the named ID, or NULL if not found. */ + /** Returns a random record that starts with the named ID, or nullptr if not found. */ const T *searchRandom(const std::string &id) const; const T *find(const std::string &id) const; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 2a9e8d7cc0..7d3f41894d 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -751,13 +751,16 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); - float peakHour = mSunriseTime + (mSunsetTime - mSunriseTime) / 2; - if (time.getHour() < mSunriseTime || time.getHour() > mSunsetTime) - mRendering.getSkyManager()->setGlareTimeOfDayFade(0); + float peakHour = mSunriseTime + (mTimeSettings.mNightStart - mSunriseTime) / 2; + float glareFade = 1.f; + if (time.getHour() < mSunriseTime || time.getHour() > mTimeSettings.mNightStart) + glareFade = 0.f; else if (time.getHour() < peakHour) - mRendering.getSkyManager()->setGlareTimeOfDayFade(1 - (peakHour - time.getHour()) / (peakHour - mSunriseTime)); + glareFade = 1.f - (peakHour - time.getHour()) / (peakHour - mSunriseTime); else - mRendering.getSkyManager()->setGlareTimeOfDayFade(1 - (time.getHour() - peakHour) / (mSunsetTime - peakHour)); + glareFade = 1.f - (time.getHour() - peakHour) / (mTimeSettings.mNightStart - peakHour); + + mRendering.getSkyManager()->setGlareTimeOfDayFade(glareFade); mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time)); mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time)); @@ -765,7 +768,7 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, mResult.mDLFogOffset/100.0f, mResult.mFogColor); mRendering.setAmbientColour(mResult.mAmbientColor); - mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView); + mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade); mRendering.getSkyManager()->setWeather(mResult); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 48cb1dda3c..0f20fa05a1 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -245,7 +246,7 @@ namespace MWWorld if (findExteriorPosition (mStartCell, pos)) { changeToExteriorCell (pos, true); - fixPosition(getPlayerPtr()); + adjustPosition(getPlayerPtr(), false); } else { @@ -260,7 +261,7 @@ namespace MWWorld if (!getPlayerPtr().isInCell()) { ESM::Position pos; - const int cellSize = 8192; + const int cellSize = Constants::CellSizeInUnits; pos.pos[0] = cellSize/2; pos.pos[1] = cellSize/2; pos.pos[2] = 0; @@ -1110,7 +1111,7 @@ namespace MWWorld void World::deleteObject (const Ptr& ptr) { - if (!ptr.getRefData().isDeleted() && ptr.getContainerStore() == NULL) + if (!ptr.getRefData().isDeleted() && ptr.getContainerStore() == nullptr) { if (ptr == getPlayerPtr()) throw std::runtime_error("can not delete player object"); @@ -1159,11 +1160,17 @@ namespace MWWorld osg::Vec3f vec(x, y, z); - CellStore *currCell = ptr.isInCell() ? ptr.getCell() : NULL; // currCell == NULL should only happen for player, during initial startup + CellStore *currCell = ptr.isInCell() ? ptr.getCell() : nullptr; // currCell == nullptr should only happen for player, during initial startup bool isPlayer = ptr == mPlayer->getPlayer(); bool haveToMove = isPlayer || (currCell && mWorldScene->isCellActive(*currCell)); MWWorld::Ptr newPtr = ptr; + if (!isPlayer && !currCell) + throw std::runtime_error("Can not move actor \"" + ptr.getCellRef().getRefId() + "\" to another cell: current cell is nullptr"); + + if (!newCell) + throw std::runtime_error("Can not move actor \"" + ptr.getCellRef().getRefId() + "\" to another cell: new cell is nullptr"); + if (currCell != newCell) { removeContainerScripts(ptr); @@ -1185,10 +1192,10 @@ namespace MWWorld addContainerScripts (getPlayerPtr(), newCell); newPtr = getPlayerPtr(); } - else if (currCell) + else { bool currCellActive = mWorldScene->isCellActive(*currCell); - bool newCellActive = newCell && mWorldScene->isCellActive(*newCell); + bool newCellActive = mWorldScene->isCellActive(*newCell); if (!currCellActive && newCellActive) { newPtr = currCell->moveTo(ptr, newCell); @@ -1336,7 +1343,7 @@ namespace MWWorld if (force || !isFlying(ptr)) { - osg::Vec3f traced = mPhysics->traceDown(ptr, pos, 500); + osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits); if (traced.z() < pos.z()) pos.z() = traced.z(); } @@ -1346,13 +1353,37 @@ namespace MWWorld void World::fixPosition(const Ptr &actor) { - const float dist = 8000; - osg::Vec3f pos (actor.getRefData().getPosition().asVec3()); - pos.z() += dist; + const float distance = 128.f; + ESM::Position esmPos = actor.getRefData().getPosition(); + osg::Quat orientation(esmPos.rot[2], osg::Vec3f(0,0,-1)); + osg::Vec3f pos (esmPos.asVec3()); - osg::Vec3f traced = mPhysics->traceDown(actor, pos, dist*1.1f); + int direction = 0; + int fallbackDirections[4] = {direction, (direction+3)%4, (direction+2)%4, (direction+1)%4}; + + osg::Vec3f targetPos = pos; + for (int i=0; i<4; ++i) + { + direction = fallbackDirections[i]; + if (direction == 0) targetPos = pos + (orientation * osg::Vec3f(0,1,0)) * distance; + else if(direction == 1) targetPos = pos - (orientation * osg::Vec3f(0,1,0)) * distance; + else if(direction == 2) targetPos = pos - (orientation * osg::Vec3f(1,0,0)) * distance; + else if(direction == 3) targetPos = pos + (orientation * osg::Vec3f(1,0,0)) * distance; + + // destination is free + if (!castRay(pos.x(), pos.y(), pos.z(), targetPos.x(), targetPos.y(), targetPos.z())) + break; + } + + targetPos.z() += distance / 2.f; // move up a bit to get out from geometry, will snap down later + osg::Vec3f traced = mPhysics->traceDown(actor, targetPos, Constants::CellSizeInUnits); if (traced != pos) - moveObject(actor, actor.getCell(), traced.x(), traced.y(), traced.z()); + { + esmPos.pos[0] = traced.x(); + esmPos.pos[1] = traced.y(); + esmPos.pos[2] = traced.z(); + MWWorld::ActionTeleport("", esmPos, false).execute(actor); + } } void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust) @@ -1422,13 +1453,13 @@ namespace MWWorld ipos.rot[2] = referenceObject.getRefData().getPosition().rot[2]; MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false); - placed.getClass().adjustPosition(placed, true); // snap to ground + adjustPosition(placed, true); // snap to ground return placed; } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const { - const int cellSize = 8192; + const int cellSize = Constants::CellSizeInUnits; x = static_cast(cellSize * cellX); y = static_cast(cellSize * cellY); @@ -1442,10 +1473,8 @@ namespace MWWorld void World::positionToIndex (float x, float y, int &cellX, int &cellY) const { - const int cellSize = 8192; - - cellX = static_cast(std::floor(x / cellSize)); - cellY = static_cast(std::floor(y / cellSize)); + cellX = static_cast(std::floor(x / Constants::CellSizeInUnits)); + cellY = static_cast(std::floor(y / Constants::CellSizeInUnits)); } void World::queueMovement(const Ptr &ptr, const osg::Vec3f &velocity) @@ -1476,15 +1505,18 @@ namespace MWWorld moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); } - bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors) + bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2) + { + int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_Door; + bool result = castRay(x1, y1, z1, x2, y2, z2, mask); + return result; + } + + bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, int mask) { osg::Vec3f a(x1,y1,z1); osg::Vec3f b(x2,y2,z2); - int mask = MWPhysics::CollisionType_World; - if (!ignoreDoors) - mask |= MWPhysics::CollisionType_Door; - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), std::vector(), mask); return result.mHit; } @@ -1552,7 +1584,13 @@ namespace MWWorld bool World::toggleCollisionMode() { - return mPhysics->toggleCollisionMode(); + if (mPhysics->toggleCollisionMode()) + { + adjustPosition(getPlayerPtr(), true); + return true; + } + + return false; } bool World::toggleRenderMode (MWRender::RenderMode mode) @@ -1783,7 +1821,7 @@ namespace MWWorld osg::Vec3f forward = listenerOrient * osg::Vec3f(0,1,0); osg::Vec3f up = listenerOrient * osg::Vec3f(0,0,1); - bool underwater = isUnderwater(getPlayerPtr().getCell(), listenerPos); + bool underwater = isUnderwater(getPlayerPtr().getCell(), mRendering->getCameraPosition()); MWBase::Environment::get().getSoundManager()->setListenerPosDir(listenerPos, forward, up, underwater); } @@ -2163,7 +2201,7 @@ namespace MWWorld pos.z() += heightRatio*2*mPhysics->getRenderingHalfExtents(object).z(); - const CellStore *currCell = object.isInCell() ? object.getCell() : NULL; // currCell == NULL should only happen for player, during initial startup + const CellStore *currCell = object.isInCell() ? object.getCell() : nullptr; // currCell == nullptr should only happen for player, during initial startup return isUnderwater(currCell, pos); } @@ -2292,7 +2330,7 @@ namespace MWWorld applyLoopingParticles(player); } - int World::canRest () + World::RestPermitted World::canRest () const { CellStore *currentCell = mWorldScene->getCurrentCell(); @@ -2304,13 +2342,16 @@ namespace MWWorld if (!actor) throw std::runtime_error("can't find player"); + if(mPlayer->enemiesNearby()) + return Rest_EnemiesAreNearby; + if ((actor->getCollisionMode() && !mPhysics->isOnSolidGround(player)) || isUnderwater(currentCell, playerPos) || isWalkingOnWater(player)) - return 2; + return Rest_PlayerIsUnderwater; if((currentCell->getCell()->mData.mFlags&ESM::Cell::NoSleep) || player.getClass().getNpcStats(player).isWerewolf()) - return 1; + return Rest_OnlyWaiting; - return 0; + return Rest_Allowed; } MWRender::Animation* World::getAnimation(const MWWorld::Ptr &ptr) @@ -2599,31 +2640,52 @@ namespace MWWorld return false; } + std::vector sortedDoors; const DoorList &doors = cellStore->getReadOnlyDoors().mList; - for (DoorList::const_iterator it = doors.begin(); it != doors.end(); ++it) { - if (!it->mRef.getTeleport()) { + for (DoorList::const_iterator it = doors.begin(); it != doors.end(); ++it) + { + if (!it->mRef.getTeleport()) + { continue; } + sortedDoors.push_back(&it->mRef); + } + + // Sort teleporting doors alphabetically, first by ID, then by destination cell to make search consistent + std::sort(sortedDoors.begin(), sortedDoors.end(), [] (const MWWorld::CellRef *lhs, const MWWorld::CellRef *rhs) + { + if (lhs->getRefId() != rhs->getRefId()) + return lhs->getRefId() < rhs->getRefId(); + + return lhs->getDestCell() < rhs->getDestCell(); + }); + + for (std::vector::const_iterator it = sortedDoors.begin(); it != sortedDoors.end(); ++it) + { MWWorld::CellStore *source = 0; // door to exterior - if (it->mRef.getDestCell().empty()) { + if ((*it)->getDestCell().empty()) + { int x, y; - ESM::Position doorDest = it->mRef.getDoorDest(); + ESM::Position doorDest = (*it)->getDoorDest(); positionToIndex(doorDest.pos[0], doorDest.pos[1], x, y); source = getExterior(x, y); } // door to interior - else { - source = getInterior(it->mRef.getDestCell()); + else + { + source = getInterior((*it)->getDestCell()); } - if (0 != source) { + if (0 != source) + { // Find door leading to our current teleport door // and use its destination to position inside cell. const DoorList &destinationDoors = source->getReadOnlyDoors().mList; - for (DoorList::const_iterator jt = destinationDoors.begin(); jt != destinationDoors.end(); ++jt) { - if (it->mRef.getTeleport() && + for (DoorList::const_iterator jt = destinationDoors.begin(); jt != destinationDoors.end(); ++jt) + { + if ((*it)->getTeleport() && Misc::StringUtils::ciEqual(name, jt->mRef.getDestCell())) { /// \note Using _any_ door pointed to the interior, @@ -2814,7 +2876,7 @@ namespace MWWorld // if the faced object can not be activated, do not use it if (!target.isEmpty() && !target.getClass().canBeActivated(target)) - target = NULL; + target = nullptr; if (target.isEmpty()) { @@ -2867,14 +2929,14 @@ namespace MWWorld target = result1.first; hitPosition = result1.second; if (dist1 > getMaxActivationDistance()) - target = NULL; + target = nullptr; } else if (result2.mHit) { target = result2.mHitObject; hitPosition = result2.mHitPointWorld; if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target)) - target = NULL; + target = nullptr; } } } @@ -3110,6 +3172,10 @@ namespace MWWorld return closestMarker; } + void World::rest() + { + mCells.rest(); + } void World::teleportToClosestMarker (const MWWorld::Ptr& ptr, const std::string& id) @@ -3239,9 +3305,9 @@ namespace MWWorld float World::feetToGameUnits(float feet) { - // Looks like there is no GMST for this. This factor was determined in experiments - // with the Telekinesis effect. - return feet * 22; + // Original engine rounds size upward + static const int unitsPerFoot = ceil(Constants::UnitsPerFoot); + return feet * unitsPerFoot; } float World::getActivationDistancePlusTelekinesis() diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 536a40468a..1592453a20 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -402,9 +402,11 @@ namespace MWWorld ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. - bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors=false) override; + bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, int mask) override; ///< cast a Ray and return true if there is an object in the ray path. + bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override; + bool toggleCollisionMode() override; ///< Toggle collision mode for player. If disabled player object should ignore /// collisions and gravity. @@ -551,12 +553,10 @@ namespace MWWorld void enableActorCollision(const MWWorld::Ptr& actor, bool enable) override; - int canRest() override; - ///< check if the player is allowed to rest \n - /// 0 - yes \n - /// 1 - only waiting \n - /// 2 - player is underwater \n - /// 3 - enemies are nearby (not implemented) + RestPermitted canRest() const override; + ///< check if the player is allowed to rest + + void rest() override; /// \todo Probably shouldn't be here MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) override; diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index ac21470ded..63e4bd6af4 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -28,7 +28,7 @@ struct ContentFileTest : public ::testing::Test for (std::vector::const_iterator it = mContentFiles.begin(); it != mContentFiles.end(); ++it) { ESM::ESMReader lEsm; - lEsm.setEncoder(NULL); + lEsm.setEncoder(nullptr); lEsm.setIndex(index); lEsm.setGlobalReaderList(&readerList); lEsm.open(it->string()); @@ -309,7 +309,7 @@ TEST_F(StoreTest, overwrite_test) // verify that changes were actually applied const RecordType* overwrittenRec = mEsmStore.get().search(recordId); - ASSERT_TRUE (overwrittenRec != NULL); + ASSERT_TRUE (overwrittenRec != nullptr); ASSERT_TRUE (overwrittenRec && overwrittenRec->mModel == "the_new_model"); } diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index a04e721d85..3ec98a9353 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -96,7 +96,7 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() tr("Select master file"), QDir::currentPath(), QString(tr("Morrowind master file (*.esm)")), - NULL, + nullptr, QFileDialog::DontResolveSymlinks); if (selectedFile.isEmpty()) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 551767f8f0..5c245afd08 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -51,6 +51,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer + actorutil ) add_component_dir (nif @@ -77,7 +78,7 @@ add_component_dir (esm loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile - aisequence magiceffects util custommarkerstate stolenitems transport animationstate controlsstate + aisequence magiceffects util custommarkerstate stolenitems transport animationstate controlsstate mappings ) add_component_dir (esmterrain @@ -85,7 +86,7 @@ add_component_dir (esmterrain ) add_component_dir (misc - utf8stream stringops resourcehelpers rng messageformatparser + constants utf8stream stringops resourcehelpers rng messageformatparser weakcache ) add_component_dir (debug @@ -130,7 +131,7 @@ add_component_dir (myguiplatform ) add_component_dir (widgets - box imagebutton tags list numericeditbox sharedstatebutton windowcaption widgets + box fontwrapper imagebutton tags list numericeditbox sharedstatebutton windowcaption widgets ) add_component_dir (fontloader diff --git a/components/compiler/declarationparser.cpp b/components/compiler/declarationparser.cpp index ffac252d54..e85c8c3ec2 100644 --- a/components/compiler/declarationparser.cpp +++ b/components/compiler/declarationparser.cpp @@ -24,7 +24,7 @@ bool Compiler::DeclarationParser::parseName (const std::string& name, const Toke if (type!=' ') { /// \todo add option to make re-declared local variables an error - getErrorHandler().warning ("can't re-declare local variable (ignoring declaration)", + getErrorHandler().warning ("ignoring local variable re-declaration", loc); mState = State_End; diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 7cb0abfd11..6b849ec3aa 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -422,7 +422,7 @@ namespace Compiler { if (!hasExplicit) { - getErrorHandler().warning ("stray explicit reference (ignoring it)", loc); + getErrorHandler().warning ("ignoring stray explicit reference", loc); mExplicit.clear(); } @@ -791,7 +791,7 @@ namespace Compiler ++optionalCount; } else - getErrorHandler().warning ("Ignoring extra argument", + getErrorHandler().warning ("ignoring extra argument", stringParser.getTokenLoc()); } else if (*iter=='X') @@ -805,7 +805,7 @@ namespace Compiler if (parser.isEmpty()) break; else - getErrorHandler().warning("Ignoring extra argument", parser.getTokenLoc()); + getErrorHandler().warning("ignoring extra argument", parser.getTokenLoc()); } else if (*iter=='z') { @@ -817,7 +817,7 @@ namespace Compiler if (discardParser.isEmpty()) break; else - getErrorHandler().warning("Ignoring extra argument", discardParser.getTokenLoc()); + getErrorHandler().warning("ignoring extra argument", discardParser.getTokenLoc()); } else if (*iter=='j') { diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 7638d0f784..54b8c03cbe 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -276,7 +276,7 @@ namespace Compiler extensions.registerInstruction ("gotojail", "", opcodeGoToJail); extensions.registerFunction ("getlocked", 'l', "", opcodeGetLocked, opcodeGetLockedExplicit); extensions.registerFunction ("geteffect", 'l', "S", opcodeGetEffect, opcodeGetEffectExplicit); - extensions.registerInstruction ("addsoulgem", "cc", opcodeAddSoulGem, opcodeAddSoulGemExplicit); + extensions.registerInstruction ("addsoulgem", "ccz", opcodeAddSoulGem, opcodeAddSoulGemExplicit); extensions.registerInstruction ("removesoulgem", "c/l", opcodeRemoveSoulGem, opcodeRemoveSoulGemExplicit); extensions.registerInstruction ("drop", "cl", opcodeDrop, opcodeDropExplicit); extensions.registerInstruction ("dropsoulgem", "c", opcodeDropSoulGem, opcodeDropSoulGemExplicit); @@ -463,7 +463,7 @@ namespace Compiler extensions.registerInstruction ("modpccrimelevel", "f", opcodeModPCCrimeLevel); extensions.registerInstruction ("addspell", "cz", opcodeAddSpell, opcodeAddSpellExplicit); - extensions.registerInstruction ("removespell", "c", opcodeRemoveSpell, + extensions.registerInstruction ("removespell", "cz", opcodeRemoveSpell, opcodeRemoveSpellExplicit); extensions.registerInstruction ("removespelleffects", "c", opcodeRemoveSpellEffects, opcodeRemoveSpellEffectsExplicit); @@ -546,7 +546,7 @@ namespace Compiler extensions.registerInstruction("moveworld","cf",opcodeMoveWorld,opcodeMoveWorldExplicit); extensions.registerFunction("getstartingangle",'f',"c",opcodeGetStartingAngle,opcodeGetStartingAngleExplicit); extensions.registerInstruction("resetactors","",opcodeResetActors); - extensions.registerInstruction("fixme","",opcodeFixme, opcodeFixmeExplicit); + extensions.registerInstruction("fixme","",opcodeFixme); extensions.registerInstruction("ra","",opcodeResetActors); } } diff --git a/components/compiler/junkparser.cpp b/components/compiler/junkparser.cpp index 7608e9bab4..5910cca43d 100644 --- a/components/compiler/junkparser.cpp +++ b/components/compiler/junkparser.cpp @@ -29,7 +29,7 @@ bool Compiler::JunkParser::parseName (const std::string& name, const TokenLoc& l bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (keyword==mIgnoreKeyword) - reportWarning ("found junk (ignoring it)", loc); + reportWarning ("ignoring found junk", loc); else scanner.putbackKeyword (keyword, loc); @@ -39,7 +39,7 @@ bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scann bool Compiler::JunkParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_member) - reportWarning ("found junk (ignoring it)", loc); + reportWarning ("ignoring found junk", loc); else scanner.putbackSpecial (code, loc); diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 602bb826fb..3f9d2e7903 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -38,7 +38,7 @@ namespace Compiler { case 'l': - Generator::report (mCode, mLiterals, "%g"); + Generator::report (mCode, mLiterals, "%d"); break; case 'f': @@ -88,7 +88,7 @@ namespace Compiler { if (mState==PotentialEndState) { - getErrorHandler().warning ("stray string argument (ignoring it)", loc); + getErrorHandler().warning ("ignoring stray string argument", loc); mState = EndState; return true; } @@ -233,7 +233,7 @@ namespace Compiler if (mState==SetPotentialMemberVarState && keyword==Scanner::K_to) { - getErrorHandler().warning ("unknown variable (ignoring set instruction)", loc); + getErrorHandler().warning ("unknown variable, ignoring set instruction", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return false; @@ -286,7 +286,7 @@ namespace Compiler { if (!hasExplicit && mState==ExplicitState) { - getErrorHandler().warning ("stray explicit reference (ignoring it)", loc); + getErrorHandler().warning ("ignoring stray explicit reference", loc); mExplicit.clear(); } @@ -344,7 +344,7 @@ namespace Compiler { if (!hasExplicit && !mExplicit.empty()) { - getErrorHandler().warning ("stray explicit reference (ignoring it)", loc); + getErrorHandler().warning ("ignoring stray explicit reference", loc); mExplicit.clear(); } @@ -360,7 +360,7 @@ namespace Compiler if (mState==ExplicitState) { // drop stray explicit reference - getErrorHandler().warning ("stray explicit reference (ignoring it)", loc); + getErrorHandler().warning ("ignoring stray explicit reference", loc); mState = BeginState; mExplicit.clear(); } @@ -412,19 +412,19 @@ namespace Compiler case Scanner::K_else: - getErrorHandler().warning ("stray else (ignoring it)", loc); + getErrorHandler().warning ("ignoring stray else", loc); mState = EndState; return true; case Scanner::K_endif: - getErrorHandler().warning ("stray endif (ignoring it)", loc); + getErrorHandler().warning ("ignoring stray endif", loc); mState = EndState; return true; case Scanner::K_begin: - getErrorHandler().warning ("stray begin (ignoring it)", loc); + getErrorHandler().warning ("ignoring stray begin", loc); mState = EndState; return true; } @@ -491,7 +491,7 @@ namespace Compiler { if (mState==EndState && code==Scanner::S_open) { - getErrorHandler().warning ("stray '[' or '(' at the end of the line (ignoring it)", + getErrorHandler().warning ("ignoring stray '[' or '(' at the end of the line", loc); return true; } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index aef92b311b..c141eec682 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -504,7 +504,6 @@ namespace Compiler const int opcodeMoveWorldExplicit = 0x2000209; const int opcodeResetActors = 0x20002f4; const int opcodeFixme = 0x2000302; - const int opcodeFixmeExplicit = 0x2000303; } namespace User diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index bc785a2767..80cd6e4c3b 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -48,6 +48,8 @@ namespace ContentSelectorModel QModelIndex indexFromItem(const EsmFile *item) const; const EsmFile *item(const QString &name) const; + const EsmFile *item(int row) const; + EsmFile *item(int row); QStringList gameFiles() const; bool isEnabled (QModelIndex index) const; @@ -65,8 +67,6 @@ namespace ContentSelectorModel private: void addFile(EsmFile *file); - const EsmFile *item(int row) const; - EsmFile *item(int row); void sortFiles(); diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 5e16064f4b..4f95b7fe46 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -67,6 +68,7 @@ void ContentSelectorView::ContentSelector::buildContextMenu() mContextMenu = new QMenu(ui.addonView); mContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems())); mContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems())); + mContextMenu->addAction(tr("&Copy Path(s) to Clipboard"), this, SLOT(slotCopySelectedItemsPaths())); } void ContentSelectorView::ContentSelector::setProfileContent(const QStringList &fileList) @@ -111,6 +113,11 @@ void ContentSelectorView::ContentSelector::clearCheckStates() mContentModel->uncheckAll(); } +void ContentSelectorView::ContentSelector::setEncoding(const QString &encoding) +{ + mContentModel->setEncoding(encoding); +} + void ContentSelectorView::ContentSelector::setContentList(const QStringList &list) { if (list.isEmpty()) @@ -189,7 +196,7 @@ void ContentSelectorView::ContentSelector::setGameFileSelected(int index, bool s { QString fileName = ui.gameFileView->itemText(index); const ContentSelectorModel::EsmFile* file = mContentModel->item(fileName); - if (file != NULL) + if (file != nullptr) { QModelIndex index2(mContentModel->indexFromItem(file)); mContentModel->setData(index2, selected, Qt::UserRole + 1); @@ -239,4 +246,21 @@ void ContentSelectorView::ContentSelector::slotUncheckMultiSelectedItems() void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems() { setCheckStateForMultiSelectedItems(true); -} \ No newline at end of file +} + +void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths() +{ + QClipboard *clipboard = QApplication::clipboard(); + QString filepaths; + foreach (const QModelIndex& index, ui.addonView->selectionModel()->selectedIndexes()) + { + int row = mAddonProxyModel->mapToSource(index).row(); + const ContentSelectorModel::EsmFile *file = mContentModel->item(row); + filepaths += file->filePath() + "\n"; + } + + if (!filepaths.isEmpty()) + { + clipboard->setText(filepaths); + } +} diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 323f926ed6..bc47224e4c 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -32,6 +32,7 @@ namespace ContentSelectorView void setProfileContent (const QStringList &fileList); void clearCheckStates(); + void setEncoding (const QString &encoding); void setContentList(const QStringList &list); ContentSelectorModel::ContentFileList selectedFiles() const; @@ -69,6 +70,7 @@ namespace ContentSelectorView void slotShowContextMenu(const QPoint& pos); void slotCheckMultiSelectedItems(); void slotUncheckMultiSelectedItems(); + void slotCopySelectedItemsPaths(); }; } diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 04b239a7fa..2e2ddd1f27 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -71,7 +71,7 @@ static const struct { { "Illegal instruction", SIGILL }, { "FPU exception", SIGFPE }, { "System BUS error", SIGBUS }, -{ NULL, 0 } +{ nullptr, 0 } }; static const struct { @@ -88,7 +88,7 @@ static const struct { { ILL_COPROC, "Coprocessor error" }, { ILL_BADSTK, "Internal stack error" }, #endif - { 0, NULL } + { 0, nullptr } }; static const struct { @@ -103,7 +103,7 @@ static const struct { { FPE_FLTRES, "Floating point inexact result" }, { FPE_FLTINV, "Floating point invalid operation" }, { FPE_FLTSUB, "Subscript out of range" }, - { 0, NULL } + { 0, nullptr } }; static const struct { @@ -114,7 +114,7 @@ static const struct { { SEGV_MAPERR, "Address not mapped to object" }, { SEGV_ACCERR, "Invalid permissions for mapped object" }, #endif - { 0, NULL } + { 0, nullptr } }; static const struct { @@ -126,7 +126,7 @@ static const struct { { BUS_ADRERR, "Non-existent physical address" }, { BUS_OBJERR, "Object specific hardware error" }, #endif - { 0, NULL } + { 0, nullptr } }; static int (*cc_user_info)(char*, char*); @@ -140,7 +140,7 @@ static void gdb_info(pid_t pid) /* Create a temp file to put gdb commands into */ strcpy(respfile, "/tmp/gdb-respfile-XXXXXX"); - if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL) + if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != nullptr) { fprintf(f, "attach %d\n" "shell echo \"\"\n" @@ -175,17 +175,18 @@ static void gdb_info(pid_t pid) fflush(stdout); /* Clean up */ - remove(respfile); + if (remove(respfile) != 0) + Log(Debug::Warning) << "Warning: can not remove file '" << respfile << "': " << std::strerror(errno); } else { /* Error creating temp file */ if(fd >= 0) { - if (close(fd) == 0) - remove(respfile); - else - Log(Debug::Warning) << "Warning: can not close and remove file '" << respfile << "': " << std::strerror(errno); + if (close(fd) != 0) + Log(Debug::Warning) << "Warning: can not close file '" << respfile << "': " << std::strerror(errno); + else if (remove(respfile) != 0) + Log(Debug::Warning) << "Warning: can not remove file '" << respfile << "': " << std::strerror(errno); } printf("!!! Could not create gdb command file\n"); } @@ -266,7 +267,7 @@ static void crash_catcher(int signum, siginfo_t *siginfo, void *context) close(fd[0]); close(fd[1]); - execl(argv0, argv0, crash_switch, NULL); + execl(argv0, argv0, crash_switch, nullptr); safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1); _exit(1); @@ -406,7 +407,7 @@ static void crash_handler(const char *logfile) if(logfile) { std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !"; - SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL); + SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); } exit(0); @@ -443,7 +444,7 @@ int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *sig altss.ss_sp = altstack; altss.ss_flags = 0; altss.ss_size = sizeof(altstack); - sigaltstack(&altss, NULL); + sigaltstack(&altss, nullptr); memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = crash_catcher; @@ -454,7 +455,7 @@ int crashCatcherInstallHandlers(int argc, char **argv, int num_signals, int *sig while(num_signals--) { if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && *signals != SIGABRT && - *signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1) + *signals != SIGBUS) || sigaction(*signals, &sa, nullptr) == -1) { *signals = 0; retval = -1; @@ -501,7 +502,7 @@ static bool is_debugger_present() // Call sysctl. size = sizeof(info); - junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0); assert(junk == 0); // We're being debugged if the P_TRACED flag is set. @@ -515,7 +516,7 @@ void crashCatcherInstall(int argc, char **argv, const std::string &crashLogPath) if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_present()) { int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; - if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str(), NULL) == -1) + if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str(), nullptr) == -1) { Log(Debug::Warning) << "Installing crash handler failed"; } diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index a4c59c2216..e89a659568 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -101,7 +101,7 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c #if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) if (!isatty(fileno(stdin))) #endif - SDL_ShowSimpleMessageBox(0, (appName + ": Fatal error").c_str(), e.what(), NULL); + SDL_ShowSimpleMessageBox(0, (appName + ": Fatal error").c_str(), e.what(), nullptr); Log(Debug::Error) << "Error: " << e.what(); diff --git a/components/debug/debugging.hpp b/components/debug/debugging.hpp index 59536d685c..f47f58e451 100644 --- a/components/debug/debugging.hpp +++ b/components/debug/debugging.hpp @@ -41,8 +41,6 @@ namespace Debug { return size; } - - char mDebugLevel; }; #if defined(_WIN32) && defined(_DEBUG) diff --git a/components/debug/debuglog.hpp b/components/debug/debuglog.hpp index f4a8e17bef..f5cdffeda1 100644 --- a/components/debug/debuglog.hpp +++ b/components/debug/debuglog.hpp @@ -8,12 +8,13 @@ namespace Debug { enum Level { - NoLevel = 0, Error = 1, Warning = 2, Info = 3, Verbose = 4, - Marker = Verbose + Marker = Verbose, + + NoLevel = 5 // Do not filter messages in this case }; extern Level CurrentDebugLevel; @@ -30,6 +31,11 @@ public: mLock(sLock), mLevel(level) { + // If the app has no logging system enabled, log level is not specified. + // Show all messages without marker - we just use the plain cout in this case. + if (Debug::CurrentDebugLevel == Debug::NoLevel) + return; + if (mLevel <= Debug::CurrentDebugLevel) std::cout << static_cast(mLevel); } diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 67b9d6a385..a6b947dd3e 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -23,8 +23,8 @@ ESMReader::ESMReader() : mIdx(0) , mRecordFlags(0) , mBuffer(50*1024) - , mGlobalReaderList(NULL) - , mEncoder(NULL) + , mGlobalReaderList(nullptr) + , mEncoder(nullptr) , mFileSize(0) { } diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 4772aeb6fc..6e84fa7d4f 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -33,8 +33,8 @@ public: int getVer() const { return mHeader.mData.version; } int getRecordCount() const { return mHeader.mData.records; } float getFVer() const { return (mHeader.mData.version == VER_12) ? 1.2f : 1.3f; } - const std::string getAuthor() const { return mHeader.mData.author.toString(); } - const std::string getDesc() const { return mHeader.mData.desc.toString(); } + const std::string getAuthor() const { return mHeader.mData.author; } + const std::string getDesc() const { return mHeader.mData.desc; } const std::vector &getGameFiles() const { return mHeader.mMaster; } const Header& getHeader() const { return mHeader; } int getFormat() const; diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index b8c78a2b95..09fca4b262 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -10,9 +10,9 @@ namespace ESM { ESMWriter::ESMWriter() : mRecords() - , mStream(NULL) + , mStream(nullptr) , mHeaderPos() - , mEncoder(NULL) + , mEncoder(nullptr) , mRecordCount(0) , mCounting(true) , mHeader() diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 249d812b10..bc50167189 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -78,14 +78,14 @@ struct Cell struct DATAstruct { - int mFlags; - int mX, mY; + int mFlags {0}; + int mX {0}, mY {0}; }; struct AMBIstruct { - Color mAmbient, mSunlight, mFog; - float mFogDensity; + Color mAmbient {0}, mSunlight {0}, mFog {0}; + float mFogDensity {0.f}; }; Cell() : mName(""), diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index a91dfe3d3d..9e5e9d07e4 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -16,7 +16,7 @@ namespace ESM , mY(0) , mPlugin(0) , mDataTypes(0) - , mLandData(NULL) + , mLandData(nullptr) { } @@ -73,7 +73,7 @@ namespace ESM mContext = esm.getContext(); - mLandData = NULL; + mLandData = nullptr; // Skip the land data here. Load it when the cell is loaded. while (esm.hasMoreSubs()) @@ -298,7 +298,7 @@ namespace ESM if (mLandData) { delete mLandData; - mLandData = NULL; + mLandData = nullptr; } } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index cccb472de9..b4b66c6019 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -3,6 +3,8 @@ #include +#include + #include "esmcommon.hpp" namespace ESM @@ -53,7 +55,7 @@ struct Land static const int LAND_SIZE = 65; // cell terrain size in world coords - static const int REAL_SIZE = 8192; + static const int REAL_SIZE = Constants::CellSizeInUnits; // total number of vertices static const int LAND_NUM_VERTS = LAND_SIZE * LAND_SIZE; @@ -127,9 +129,9 @@ struct Land /** * Actually loads data into target - * If target is NULL, assumed target is mLandData + * If target is nullptr, assumed target is mLandData */ - void loadData(int flags, LandData* target = NULL) const; + void loadData(int flags, LandData* target = nullptr) const; /** * Frees memory allocated for mLandData diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 474c2b4234..808b416d7b 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -30,6 +30,12 @@ namespace ESM // The tmp buffer is a null-byte separated string list, we // just have to pick out one string at a time. char* str = &tmp[0]; + if (!str && mVarNames.size() > 0) + { + Log(Debug::Warning) << "SCVR with no variable names"; + return; + } + for (size_t i = 0; i < mVarNames.size(); i++) { // Support '\r' terminated strings like vanilla. See Bug #1324. diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index b16145467c..d953f1dc23 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -32,8 +32,8 @@ void ESM::Header::load (ESMReader &esm) esm.getSubHeader(); esm.getT(mData.version); esm.getT(mData.type); - mData.author.assign( esm.getString(mData.author.data_size()) ); - mData.desc.assign( esm.getString(mData.desc.data_size()) ); + mData.author.assign( esm.getString(32) ); + mData.desc.assign( esm.getString(256) ); esm.getT(mData.records); } @@ -73,8 +73,8 @@ void ESM::Header::save (ESMWriter &esm) esm.startSubRecord("HEDR"); esm.writeT(mData.version); esm.writeT(mData.type); - esm.writeFixedSizeString(mData.author.toString(), mData.author.data_size()); - esm.writeFixedSizeString(mData.desc.toString(), mData.desc.data_size()); + esm.writeFixedSizeString(mData.author, 32); + esm.writeFixedSizeString(mData.desc, 256); esm.writeT(mData.records); esm.endRecord("HEDR"); diff --git a/components/esm/loadtes3.hpp b/components/esm/loadtes3.hpp index 896936cf5c..5b26ac7d2d 100644 --- a/components/esm/loadtes3.hpp +++ b/components/esm/loadtes3.hpp @@ -21,8 +21,8 @@ namespace ESM */ unsigned int version; int type; // 0=esp, 1=esm, 32=ess (unused) - NAME32 author; // Author's name - NAME256 desc; // File description + std::string author; // Author's name + std::string desc; // File description int records; // Number of records }; diff --git a/components/esm/mappings.cpp b/components/esm/mappings.cpp new file mode 100644 index 0000000000..440e735739 --- /dev/null +++ b/components/esm/mappings.cpp @@ -0,0 +1,134 @@ +#include "mappings.hpp" + +#include + +namespace ESM +{ + ESM::BodyPart::MeshPart getMeshPart(ESM::PartReferenceType type) + { + switch(type) + { + case ESM::PRT_Head: + return ESM::BodyPart::MP_Head; + case ESM::PRT_Hair: + return ESM::BodyPart::MP_Hair; + case ESM::PRT_Neck: + return ESM::BodyPart::MP_Neck; + case ESM::PRT_Cuirass: + return ESM::BodyPart::MP_Chest; + case ESM::PRT_Groin: + return ESM::BodyPart::MP_Groin; + case ESM::PRT_RHand: + return ESM::BodyPart::MP_Hand; + case ESM::PRT_LHand: + return ESM::BodyPart::MP_Hand; + case ESM::PRT_RWrist: + return ESM::BodyPart::MP_Wrist; + case ESM::PRT_LWrist: + return ESM::BodyPart::MP_Wrist; + case ESM::PRT_RForearm: + return ESM::BodyPart::MP_Forearm; + case ESM::PRT_LForearm: + return ESM::BodyPart::MP_Forearm; + case ESM::PRT_RUpperarm: + return ESM::BodyPart::MP_Upperarm; + case ESM::PRT_LUpperarm: + return ESM::BodyPart::MP_Upperarm; + case ESM::PRT_RFoot: + return ESM::BodyPart::MP_Foot; + case ESM::PRT_LFoot: + return ESM::BodyPart::MP_Foot; + case ESM::PRT_RAnkle: + return ESM::BodyPart::MP_Ankle; + case ESM::PRT_LAnkle: + return ESM::BodyPart::MP_Ankle; + case ESM::PRT_RKnee: + return ESM::BodyPart::MP_Knee; + case ESM::PRT_LKnee: + return ESM::BodyPart::MP_Knee; + case ESM::PRT_RLeg: + return ESM::BodyPart::MP_Upperleg; + case ESM::PRT_LLeg: + return ESM::BodyPart::MP_Upperleg; + case ESM::PRT_Tail: + return ESM::BodyPart::MP_Tail; + default: + throw std::runtime_error("PartReferenceType " + + std::to_string(type) + " not associated with a mesh part"); + } + } + + std::string getBoneName(ESM::PartReferenceType type) + { + switch(type) + { + case ESM::PRT_Head: + return "head"; + case ESM::PRT_Hair: + return "head"; // This is purposeful. + case ESM::PRT_Neck: + return "neck"; + case ESM::PRT_Cuirass: + return "chest"; + case ESM::PRT_Groin: + return "groin"; + case ESM::PRT_Skirt: + return "groin"; + case ESM::PRT_RHand: + return "right hand"; + case ESM::PRT_LHand: + return "left hand"; + case ESM::PRT_RWrist: + return "right wrist"; + case ESM::PRT_LWrist: + return "left wrist"; + case ESM::PRT_Shield: + return "shield bone"; + case ESM::PRT_RForearm: + return "right forearm"; + case ESM::PRT_LForearm: + return "left forearm"; + case ESM::PRT_RUpperarm: + return "right upper arm"; + case ESM::PRT_LUpperarm: + return "left upper arm"; + case ESM::PRT_RFoot: + return "right foot"; + case ESM::PRT_LFoot: + return "left foot"; + case ESM::PRT_RAnkle: + return "right ankle"; + case ESM::PRT_LAnkle: + return "left ankle"; + case ESM::PRT_RKnee: + return "right knee"; + case ESM::PRT_LKnee: + return "left knee"; + case ESM::PRT_RLeg: + return "right upper leg"; + case ESM::PRT_LLeg: + return "left upper leg"; + case ESM::PRT_RPauldron: + return "right clavicle"; + case ESM::PRT_LPauldron: + return "left clavicle"; + case ESM::PRT_Weapon: + return "weapon bone"; + case ESM::PRT_Tail: + return "tail"; + default: + throw std::runtime_error("unknown PartReferenceType"); + } + } + + std::string getMeshFilter(ESM::PartReferenceType type) + { + switch(type) + { + case ESM::PRT_Hair: + return "hair"; + default: + return getBoneName(type); + } + } +} diff --git a/components/esm/mappings.hpp b/components/esm/mappings.hpp new file mode 100644 index 0000000000..f930fef152 --- /dev/null +++ b/components/esm/mappings.hpp @@ -0,0 +1,16 @@ +#ifndef OPENMW_ESM_MAPPINGS_H +#define OPENMW_ESM_MAPPINGS_H + +#include + +#include +#include + +namespace ESM +{ + ESM::BodyPart::MeshPart getMeshPart(ESM::PartReferenceType type); + std::string getBoneName(ESM::PartReferenceType type); + std::string getMeshFilter(ESM::PartReferenceType type); +} + +#endif diff --git a/components/esm/transport.cpp b/components/esm/transport.cpp index 063c033299..11676ea723 100644 --- a/components/esm/transport.cpp +++ b/components/esm/transport.cpp @@ -1,5 +1,7 @@ #include "transport.hpp" +#include + #include #include @@ -16,7 +18,11 @@ namespace ESM } else if (esm.retSubName().intval == ESM::FourCC<'D','N','A','M'>::value) { - mList.back().mCellName = esm.getHString(); + const std::string name = esm.getHString(); + if (mList.empty()) + Log(Debug::Warning) << "Encountered DNAM record without DODT record, skipped."; + else + mList.back().mCellName = name; } } diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index f77e662765..dfdc9718df 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -50,7 +50,7 @@ namespace ESMTerrain const ESM::Land::LandData *LandObject::getData(int flags) const { if ((mData.mDataLoaded & flags) != flags) - return NULL; + return nullptr; return &mData; } @@ -267,8 +267,8 @@ namespace ESMTerrain height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; (*positions)[static_cast(vertX*numVerts + vertY)] - = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * 8192, - (vertY / float(numVerts - 1) - 0.5f) * size * 8192, + = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, + (vertY / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, height); if (normalData) @@ -477,8 +477,8 @@ namespace ESMTerrain float Storage::getHeightAt(const osg::Vec3f &worldPos) { - int cellX = static_cast(std::floor(worldPos.x() / 8192.f)); - int cellY = static_cast(std::floor(worldPos.y() / 8192.f)); + int cellX = static_cast(std::floor(worldPos.x() / float(Constants::CellSizeInUnits))); + int cellY = static_cast(std::floor(worldPos.y() / float(Constants::CellSizeInUnits))); osg::ref_ptr land = getLand(cellX, cellY); if (!land) @@ -491,8 +491,8 @@ namespace ESMTerrain // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition // Normalized position in the cell - float nX = (worldPos.x() - (cellX * 8192))/8192.f; - float nY = (worldPos.y() - (cellY * 8192))/8192.f; + float nX = (worldPos.x() - (cellX * Constants::CellSizeInUnits)) / float(Constants::CellSizeInUnits); + float nY = (worldPos.y() - (cellY * Constants::CellSizeInUnits)) / float(Constants::CellSizeInUnits); // get left / bottom points (rounded down) float factor = ESM::Land::LAND_SIZE - 1.0f; @@ -524,10 +524,10 @@ namespace ESMTerrain */ // Build all 4 positions in normalized cell space, using point-sampled height - osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(data, startX, startY) / 8192.f); - osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(data, endX, startY) / 8192.f); - osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(data, endX, endY) / 8192.f); - osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(data, startX, endY) / 8192.f); + osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(data, startX, startY) / float(Constants::CellSizeInUnits)); + osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(data, endX, startY) / float(Constants::CellSizeInUnits)); + osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(data, endX, endY) / float(Constants::CellSizeInUnits)); + osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(data, startX, endY) / float(Constants::CellSizeInUnits)); // define this plane in terrain space osg::Plane plane; // FIXME: deal with differing triangle alignment @@ -555,7 +555,7 @@ namespace ESMTerrain // Solve plane equation for z return (-plane.getNormal().x() * nX -plane.getNormal().y() * nY - - plane[3]) / plane.getNormal().z() * 8192; + - plane[3]) / plane.getNormal().z() * Constants::CellSizeInUnits; } diff --git a/components/files/androidpath.cpp b/components/files/androidpath.cpp index 84886f4734..bf5c985774 100644 --- a/components/files/androidpath.cpp +++ b/components/files/androidpath.cpp @@ -40,15 +40,15 @@ namespace boost::filesystem::path getUserHome() { const char* dir = getenv("HOME"); - if (dir == NULL) + if (dir == nullptr) { struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) + if (pwd != nullptr) { dir = pwd->pw_dir; } } - if (dir == NULL) + if (dir == nullptr) return boost::filesystem::path(); else return boost::filesystem::path(dir); diff --git a/components/files/constrainedfilestream.cpp b/components/files/constrainedfilestream.cpp index b239ec6a17..419af0d6c2 100644 --- a/components/files/constrainedfilestream.cpp +++ b/components/files/constrainedfilestream.cpp @@ -103,21 +103,16 @@ namespace Files }; - ConstrainedFileStream::ConstrainedFileStream(const char *filename, size_t start, size_t length) - : std::istream(new ConstrainedFileStreamBuf(filename, start, length)) + ConstrainedFileStream::ConstrainedFileStream(std::unique_ptr buf) + : std::istream(buf.get()) + , mBuf(std::move(buf)) { - } - ConstrainedFileStream::~ConstrainedFileStream() - { - delete rdbuf(); - } - - IStreamPtr openConstrainedFileStream(const char *filename, size_t start, size_t length) { - return IStreamPtr(new ConstrainedFileStream(filename, start, length)); + auto buf = std::unique_ptr(new ConstrainedFileStreamBuf(filename, start, length)); + return IStreamPtr(new ConstrainedFileStream(std::move(buf))); } } diff --git a/components/files/constrainedfilestream.hpp b/components/files/constrainedfilestream.hpp index 05ae0fbecc..bf67c7b973 100644 --- a/components/files/constrainedfilestream.hpp +++ b/components/files/constrainedfilestream.hpp @@ -11,9 +11,11 @@ namespace Files class ConstrainedFileStream : public std::istream { public: - ConstrainedFileStream(const char *filename, - size_t start=0, size_t length=0xFFFFFFFF); - virtual ~ConstrainedFileStream(); + ConstrainedFileStream(std::unique_ptr buf); + virtual ~ConstrainedFileStream() {}; + +private: + std::unique_ptr mBuf; }; typedef std::shared_ptr IStreamPtr; diff --git a/components/files/linuxpath.cpp b/components/files/linuxpath.cpp index 1f6a3d9130..3743eef4c9 100644 --- a/components/files/linuxpath.cpp +++ b/components/files/linuxpath.cpp @@ -14,15 +14,15 @@ namespace boost::filesystem::path getUserHome() { const char* dir = getenv("HOME"); - if (dir == NULL) + if (dir == nullptr) { struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) + if (pwd != nullptr) { dir = pwd->pw_dir; } } - if (dir == NULL) + if (dir == nullptr) return boost::filesystem::path(); else return boost::filesystem::path(dir); diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp index 169a6f8131..aff15777f4 100644 --- a/components/files/lowlevelfile.cpp +++ b/components/files/lowlevelfile.cpp @@ -21,22 +21,22 @@ LowLevelFile::LowLevelFile () { - mHandle = NULL; + mHandle = nullptr; } LowLevelFile::~LowLevelFile () { - if (mHandle != NULL) + if (mHandle != nullptr) fclose (mHandle); } void LowLevelFile::open (char const * filename) { - assert (mHandle == NULL); + assert (mHandle == nullptr); mHandle = fopen (filename, "rb"); - if (mHandle == NULL) + if (mHandle == nullptr) { std::ostringstream os; os << "Failed to open '" << filename << "' for reading."; @@ -46,16 +46,16 @@ void LowLevelFile::open (char const * filename) void LowLevelFile::close () { - assert (mHandle != NULL); + assert (mHandle != nullptr); fclose (mHandle); - mHandle = NULL; + mHandle = nullptr; } size_t LowLevelFile::size () { - assert (mHandle != NULL); + assert (mHandle != nullptr); long oldPosition = ftell (mHandle); @@ -78,7 +78,7 @@ size_t LowLevelFile::size () void LowLevelFile::seek (size_t Position) { - assert (mHandle != NULL); + assert (mHandle != nullptr); if (fseek (mHandle, Position, SEEK_SET) != 0) throw std::runtime_error ("A seek operation on a file failed."); @@ -86,7 +86,7 @@ void LowLevelFile::seek (size_t Position) size_t LowLevelFile::tell () { - assert (mHandle != NULL); + assert (mHandle != nullptr); long Position = ftell (mHandle); @@ -98,7 +98,7 @@ size_t LowLevelFile::tell () size_t LowLevelFile::read (void * data, size_t size) { - assert (mHandle != NULL); + assert (mHandle != nullptr); int amount = fread (data, 1, size, mHandle); @@ -296,7 +296,7 @@ void LowLevelFile::seek (size_t Position) { assert (mHandle != INVALID_HANDLE_VALUE); - if (SetFilePointer (mHandle, Position, NULL, SEEK_SET) == INVALID_SET_FILE_POINTER) + if (SetFilePointer (mHandle, Position, nullptr, SEEK_SET) == INVALID_SET_FILE_POINTER) if (GetLastError () != NO_ERROR) throw std::runtime_error ("A seek operation on a file failed."); } @@ -305,7 +305,7 @@ size_t LowLevelFile::tell () { assert (mHandle != INVALID_HANDLE_VALUE); - DWORD value = SetFilePointer (mHandle, 0, NULL, SEEK_CUR); + DWORD value = SetFilePointer (mHandle, 0, nullptr, SEEK_CUR); if (value == INVALID_SET_FILE_POINTER && GetLastError () != NO_ERROR) throw std::runtime_error ("A query operation on a file failed."); @@ -319,7 +319,7 @@ size_t LowLevelFile::read (void * data, size_t size) DWORD read; - if (!ReadFile (mHandle, data, size, &read, NULL)) + if (!ReadFile (mHandle, data, size, &read, nullptr)) throw std::runtime_error ("A read operation on a file failed."); return read; diff --git a/components/files/macospath.cpp b/components/files/macospath.cpp index b49b72e46a..32c35c98f1 100644 --- a/components/files/macospath.cpp +++ b/components/files/macospath.cpp @@ -14,15 +14,15 @@ namespace boost::filesystem::path getUserHome() { const char* dir = getenv("HOME"); - if (dir == NULL) + if (dir == nullptr) { struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) + if (pwd != nullptr) { dir = pwd->pw_dir; } } - if (dir == NULL) + if (dir == nullptr) return boost::filesystem::path(); else return boost::filesystem::path(dir); diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index 4b7c6c50a8..2354e6f31c 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -42,7 +42,7 @@ boost::filesystem::path WindowsPath::getUserConfigPath() const WCHAR path[MAX_PATH + 1]; memset(path, 0, sizeof(path)); - if(SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, NULL, 0, path))) + if(SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, nullptr, 0, path))) { userPath = boost::filesystem::path(bconv::utf_to_utf(path)); } @@ -63,7 +63,7 @@ boost::filesystem::path WindowsPath::getGlobalConfigPath() const WCHAR path[MAX_PATH + 1]; memset(path, 0, sizeof(path)); - if(SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE, NULL, 0, path))) + if(SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE, nullptr, 0, path))) { globalPath = boost::filesystem::path(bconv::utf_to_utf(path)); } @@ -99,7 +99,7 @@ boost::filesystem::path WindowsPath::getInstallPath() const std::vector buf(512); int len = 512; - if (RegQueryValueEx(hKey, TEXT("Installed Path"), NULL, NULL, (LPBYTE)&buf[0], (LPDWORD)&len) == ERROR_SUCCESS) + if (RegQueryValueEx(hKey, TEXT("Installed Path"), nullptr, nullptr, (LPBYTE)&buf[0], (LPDWORD)&len) == ERROR_SUCCESS) { installPath = &buf[0]; } diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 790df7fa84..0378e294eb 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -145,8 +145,9 @@ namespace namespace Gui { - FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs) + FontLoader::FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, const std::string& userDataPath) : mVFS(vfs) + , mUserDataPath(userDataPath) { if (encoding == ToUTF8::WINDOWS_1252) mEncoding = ToUTF8::CP437; @@ -161,11 +162,21 @@ namespace Gui mTextures.clear(); for (std::vector::iterator it = mFonts.begin(); it != mFonts.end(); ++it) - MyGUI::ResourceManager::getInstance().removeByName((*it)->getResourceName()); + { + try + { + MyGUI::ResourceManager::getInstance().removeByName((*it)->getResourceName()); + } + catch(const MyGUI::Exception& e) + { + Log(Debug::Error) << "Error in the destructor: " << e.what(); + } + } + mFonts.clear(); } - void FontLoader::loadAllFonts(bool exportToFile) + void FontLoader::loadBitmapFonts(bool exportToFile) { const std::map& index = mVFS->getIndex(); @@ -188,6 +199,25 @@ namespace Gui } } + void FontLoader::loadTrueTypeFonts() + { + osgMyGUI::DataManager* dataManager = dynamic_cast(&osgMyGUI::DataManager::getInstance()); + if (!dataManager) + { + Log(Debug::Error) << "Can not load TrueType fonts: osgMyGUI::DataManager is not available."; + return; + } + + const std::string cfg = dataManager->getDataPath(""); + const std::string fontFile = mUserDataPath + "/" + "Fonts" + "/" + "openmw_font.xml"; + if (!boost::filesystem::exists(fontFile)) + return; + + dataManager->setResourcePath(mUserDataPath + "/" + "Fonts"); + MyGUI::ResourceManager::getInstance().load("openmw_font.xml"); + dataManager->setResourcePath(cfg); + } + typedef struct { @@ -470,6 +500,14 @@ namespace Gui font->deserialization(root, MyGUI::Version(3,2,0)); + // Setup "book" version of font as fallback if we will not use TrueType fonts + MyGUI::ResourceManualFont* bookFont = static_cast( + MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); + mFonts.push_back(bookFont); + bookFont->deserialization(root, MyGUI::Version(3,2,0)); + bookFont->setResourceName("Journalbook " + resourceName); + + // Remove automatically registered fonts for (std::vector::iterator it = mFonts.begin(); it != mFonts.end();) { if ((*it)->getResourceName() == font->getResourceName()) @@ -477,10 +515,17 @@ namespace Gui MyGUI::ResourceManager::getInstance().removeByName(font->getResourceName()); it = mFonts.erase(it); } + else if ((*it)->getResourceName() == bookFont->getResourceName()) + { + MyGUI::ResourceManager::getInstance().removeByName(bookFont->getResourceName()); + it = mFonts.erase(it); + } else ++it; } + MyGUI::ResourceManager::getInstance().addResource(font); + MyGUI::ResourceManager::getInstance().addResource(bookFont); } } diff --git a/components/fontloader/fontloader.hpp b/components/fontloader/fontloader.hpp index b92815f136..39301f9f5f 100644 --- a/components/fontloader/fontloader.hpp +++ b/components/fontloader/fontloader.hpp @@ -1,6 +1,9 @@ #ifndef OPENMW_COMPONENTS_FONTLOADER_H #define OPENMW_COMPONENTS_FONTLOADER_H +#include "boost/filesystem/operations.hpp" + +#include #include namespace VFS @@ -23,15 +26,17 @@ namespace Gui class FontLoader { public: - FontLoader (ToUTF8::FromType encoding, const VFS::Manager* vfs); + FontLoader (ToUTF8::FromType encoding, const VFS::Manager* vfs, const std::string& userDataPath); ~FontLoader(); /// @param exportToFile export the converted fonts (Images and XML with glyph metrics) to files? - void loadAllFonts (bool exportToFile); + void loadBitmapFonts (bool exportToFile); + void loadTrueTypeFonts (); private: ToUTF8::FromType mEncoding; const VFS::Manager* mVFS; + std::string mUserDataPath; std::vector mTextures; std::vector mFonts; diff --git a/components/interpreter/context.hpp b/components/interpreter/context.hpp index 881687366b..4c320879e6 100644 --- a/components/interpreter/context.hpp +++ b/components/interpreter/context.hpp @@ -55,7 +55,7 @@ namespace Interpreter virtual std::string getActionBinding(const std::string& action) const = 0; - virtual std::string getNPCName() const = 0; + virtual std::string getActorName() const = 0; virtual std::string getNPCRace() const = 0; diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 3c6226d9ce..0ceed80d53 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -138,7 +138,7 @@ namespace Interpreter{ retval << context.getNPCRace(); } else if((found = check(temp, "name", &i, &start))){ - retval << context.getNPCName(); + retval << context.getActorName(); } } else { // In messagebox or book, not dialogue diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index 03b7e186f5..77e5a00790 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -65,23 +65,18 @@ namespace Interpreter } else if (notation == ShortestNotation) { - std::string scientific; - std::string fixed; - - out << std::scientific << value; - - scientific = out.str(); + out << value; + std::string standard = out.str(); out.str(std::string()); out.clear(); - out << std::fixed << value; + out << std::scientific << value; + std::string scientific = out.str(); - fixed = out.str(); - - mFormattedMessage += fixed.length() < scientific.length() ? fixed : scientific; + mFormattedMessage += standard.length() < scientific.length() ? standard : scientific; } - else + else { out << std::scientific << value; mFormattedMessage += out.str(); diff --git a/components/misc/constants.hpp b/components/misc/constants.hpp new file mode 100644 index 0000000000..7174ae8887 --- /dev/null +++ b/components/misc/constants.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_CONSTANTS_H +#define OPENMW_CONSTANTS_H + +namespace Constants +{ + +// The game uses 64 units per yard +const float UnitsPerMeter = 69.99125109f; +const float UnitsPerFoot = 21.33333333f; + +// Sound speed in meters per second +const float SoundSpeedInAir = 343.3f; +const float SoundSpeedUnderwater = 1484.0f; + +// Gravity constant in m/sec^2 +// Note: 8.96 m/sec^2 = 9.8 yards/sec^2 +// Probaly original engine's developers just forgot +// that their engine uses yards instead of meters +// and used standart gravity value as it is +const float GravityConst = 8.96f; + +// Size of one exterior cell in game units +const int CellSizeInUnits = 8192; + +} + +#endif diff --git a/components/misc/weakcache.hpp b/components/misc/weakcache.hpp new file mode 100644 index 0000000000..022a722dba --- /dev/null +++ b/components/misc/weakcache.hpp @@ -0,0 +1,138 @@ +#ifndef OPENMW_COMPONENTS_WEAKCACHE_HPP +#define OPENMW_COMPONENTS_WEAKCACHE_HPP + +#include +#include +#include + +namespace Misc +{ + /// \class WeakCache + /// Provides a container to weakly store pointers to shared data. + template + class WeakCache + { + public: + using WeakPtr = std::weak_ptr; + using StrongPtr = std::shared_ptr; + using Map = std::unordered_map; + + class iterator + { + public: + iterator(WeakCache* cache, typename Map::iterator current, typename Map::iterator end); + iterator& operator++(); + bool operator==(const iterator& other); + bool operator!=(const iterator& other); + StrongPtr operator*(); + private: + WeakCache* mCache; + typename Map::iterator mCurrent, mEnd; + StrongPtr mPtr; + }; + + /// Stores a weak pointer to the item. + void insert(Key key, StrongPtr value, bool prune=true); + + /// Retrieves the item associated with the key. + /// \return An item or null. + StrongPtr get(Key key); + + iterator begin(); + iterator end(); + + /// Removes known invalid entries + void prune(); + + private: + Map mData; + std::vector mDirty; + }; + + + template + WeakCache::iterator::iterator(WeakCache* cache, typename Map::iterator current, typename Map::iterator end) + : mCache(cache) + , mCurrent(current) + , mEnd(end) + { + // Move to 1st available valid item + for ( ; mCurrent != mEnd; ++mCurrent) + { + mPtr = mCurrent->second.lock(); + if (mPtr) break; + else mCache->mDirty.push_back(mCurrent->first); + } + } + + template + typename WeakCache::iterator& WeakCache::iterator::operator++() + { + auto next = mCurrent; + ++next; + return *this = iterator(mCache, next, mEnd); + } + + template + bool WeakCache::iterator::operator==(const iterator& other) + { + return mCurrent == other.mCurrent; + } + + template + bool WeakCache::iterator::operator!=(const iterator& other) + { + return !(*this == other); + } + + template + typename WeakCache::StrongPtr WeakCache::iterator::operator*() + { + return mPtr; + } + + + template + void WeakCache::insert(Key key, StrongPtr value, bool shouldPrune) + { + mData[key] = WeakPtr(value); + if (shouldPrune) prune(); + } + + template + typename WeakCache::StrongPtr WeakCache::get(Key key) + { + auto searchIt = mData.find(key); + if (searchIt != mData.end()) + return searchIt->second.lock(); + else + return StrongPtr(); + } + + template + typename WeakCache::iterator WeakCache::begin() + { + return iterator(this, mData.begin(), mData.end()); + } + + template + typename WeakCache::iterator WeakCache::end() + { + return iterator(this, mData.end(), mData.end()); + } + + template + void WeakCache::prune() + { + // Remove empty entries + for (auto& key : mDirty) + { + auto it = mData.find(key); + if (it != mData.end() && it->second.use_count() == 0) + mData.erase(it); + } + mDirty.clear(); + } +} + +#endif diff --git a/components/myguiplatform/additivelayer.cpp b/components/myguiplatform/additivelayer.cpp index aa2ef08b3b..49558cf8e1 100644 --- a/components/myguiplatform/additivelayer.cpp +++ b/components/myguiplatform/additivelayer.cpp @@ -27,7 +27,7 @@ namespace osgMyGUI MyGUI::OverlappedLayer::renderToTarget(_target, _update); - renderManager.setInjectState(NULL); + renderManager.setInjectState(nullptr); } } diff --git a/components/myguiplatform/myguidatamanager.cpp b/components/myguiplatform/myguidatamanager.cpp index ddd7ca342d..c90e092215 100644 --- a/components/myguiplatform/myguidatamanager.cpp +++ b/components/myguiplatform/myguidatamanager.cpp @@ -24,7 +24,7 @@ MyGUI::IDataStream *DataManager::getData(const std::string &name) if (stream->fail()) { Log(Debug::Error) << "DataManager::getData: Failed to open '" << name << "'"; - return NULL; + return nullptr; } return new MyGUI::DataFileStream(stream.release()); } diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index 5ab7ab736b..845d0c4849 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -52,7 +52,7 @@ public: { public: FrameUpdate() - : mRenderManager(NULL) + : mRenderManager(nullptr) { } @@ -76,7 +76,7 @@ public: { public: CollectDrawCalls() - : mRenderManager(NULL) + : mRenderManager(nullptr) { } @@ -134,9 +134,9 @@ public: { state->bindVertexBufferObject(bufferobject); - glVertexPointer(3, GL_FLOAT, sizeof(MyGUI::Vertex), (char*)NULL); - glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(MyGUI::Vertex), (char*)NULL + 12); - glTexCoordPointer(2, GL_FLOAT, sizeof(MyGUI::Vertex), (char*)NULL + 16); + glVertexPointer(3, GL_FLOAT, sizeof(MyGUI::Vertex), (char*)nullptr); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(MyGUI::Vertex), (char*)nullptr + 12); + glTexCoordPointer(2, GL_FLOAT, sizeof(MyGUI::Vertex), (char*)nullptr + 16); } else { @@ -355,7 +355,7 @@ RenderManager::RenderManager(osgViewer::Viewer *viewer, osg::Group *sceneroot, R , mUpdate(false) , mIsInitialise(false) , mInvScalingFactor(1.f) - , mInjectState(NULL) + , mInjectState(nullptr) { if (scalingFactor != 0.f) mInvScalingFactor = 1.f / scalingFactor; @@ -537,7 +537,7 @@ void RenderManager::destroyTexture(MyGUI::ITexture *texture) MyGUI::ITexture* RenderManager::getTexture(const std::string &name) { if (name.empty()) - return NULL; + return nullptr; MapTexture::const_iterator item = mTextures.find(name); if(item == mTextures.end()) diff --git a/components/myguiplatform/myguirendermanager.hpp b/components/myguiplatform/myguirendermanager.hpp index 4a0aae3cda..8c9e3e30f3 100644 --- a/components/myguiplatform/myguirendermanager.hpp +++ b/components/myguiplatform/myguirendermanager.hpp @@ -98,7 +98,7 @@ public: /** @see IRenderTarget::doRender */ virtual void doRender(MyGUI::IVertexBuffer *buffer, MyGUI::ITexture *texture, size_t count); - /** specify a StateSet to inject for rendering. The StateSet will be used by future doRender calls until you reset it to NULL again. */ + /** specify a StateSet to inject for rendering. The StateSet will be used by future doRender calls until you reset it to nullptr again. */ void setInjectState(osg::StateSet* stateSet); /** @see IRenderTarget::getInfo */ diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index 6c53a96998..756893974b 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -23,7 +23,7 @@ namespace osgMyGUI } OSGTexture::OSGTexture(osg::Texture2D *texture) - : mImageManager(NULL) + : mImageManager(nullptr) , mTexture(texture) , mFormat(MyGUI::PixelFormat::Unknow) , mUsage(MyGUI::TextureUsage::Default) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 4061247b50..66bbfdb65c 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -162,7 +162,7 @@ void NIFFile::parse(Files::IStreamPtr stream) for(size_t i = 0;i < recNum;i++) { - Record *r = NULL; + Record *r = nullptr; std::string rec = nif.getString(); if(rec.empty()) @@ -182,7 +182,7 @@ void NIFFile::parse(Files::IStreamPtr stream) else fail("Unknown record type " + rec); - assert(r != NULL); + assert(r != nullptr); assert(r->recType != RC_MISSING); r->recName = rec; r->recIndex = i; @@ -203,7 +203,7 @@ void NIFFile::parse(Files::IStreamPtr stream) } else { - roots[i] = NULL; + roots[i] = nullptr; warn("Null Root found"); } } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index c32969d1b6..d9afbbed71 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -53,7 +53,7 @@ public: boundXYZ = nif->getVector3(); } - parent = NULL; + parent = nullptr; isBone = false; } @@ -64,7 +64,7 @@ public: props.post(nif); } - // Parent node, or NULL for the root node. As far as I'm aware, only + // Parent node, or nullptr for the root node. As far as I'm aware, only // NiNodes (or types derived from NiNodes) can be parents. NiNode *parent; diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 09c3809872..e8aa8cb5b5 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -40,25 +40,25 @@ public: void post(NIFFile *nif) { if(index < 0) - ptr = NULL; + ptr = nullptr; else { Record *r = nif->getRecord(index); // And cast it ptr = dynamic_cast(r); - assert(ptr != NULL); + assert(ptr != nullptr); } } /// Look up the actual object from the index const X* getPtr() const { - assert(ptr != NULL); + assert(ptr != nullptr); return ptr; } X* getPtr() { - assert(ptr != NULL); + assert(ptr != nullptr); return ptr; } @@ -75,7 +75,7 @@ public: /// Pointers are allowed to be empty bool empty() const - { return ptr == NULL; } + { return ptr == nullptr; } }; /** A list of references to other records. These are read as a list, diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 5239e93fcb..7b206e40c2 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -25,7 +25,7 @@ namespace osg::Matrixf getWorldTransform(const Nif::Node *node) { - if(node->parent != NULL) + if(node->parent != nullptr) return node->trafo.toMatrix() * getWorldTransform(node->parent); return node->trafo.toMatrix(); } @@ -61,8 +61,8 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { mShape = new Resource::BulletShape; - mCompoundShape = NULL; - mStaticMesh = NULL; + mCompoundShape = nullptr; + mStaticMesh = nullptr; if (nif.numRoots() < 1) { @@ -71,10 +71,10 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) } Nif::Record *r = nif.getRoot(0); - assert(r != NULL); + assert(r != nullptr); Nif::Node *node = dynamic_cast(r); - if (node == NULL) + if (node == nullptr) { warn("First root in file was not a node, but a " + r->recName + ". Skipping file."); @@ -216,7 +216,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n { // Get the next extra data in the list e = e->extra.getPtr(); - assert(e != NULL); + assert(e != nullptr); if (e->recType == Nif::RC_NiStringExtraData) { @@ -263,7 +263,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf &transform, bool isAnimated) { - assert(shape != NULL); + assert(shape != nullptr); // If the object was marked "NCO" earlier, it shouldn't collide with // anything. So don't do anything. diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3198e995c5..eb20b77021 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -200,7 +200,7 @@ namespace NifOsg } const Nif::Record *r = nif->getRoot(0); - assert(r != NULL); + assert(r != nullptr); if(r->recType != Nif::RC_NiSequenceStreamHelper) { @@ -257,12 +257,12 @@ namespace NifOsg const Nif::Record* r = nif->getRoot(0); const Nif::Node* nifNode = dynamic_cast(r); - if (nifNode == NULL) + if (nifNode == nullptr) nif->fail("First root was not a node, but a " + r->recName); osg::ref_ptr textkeys (new TextKeyMapHolder); - osg::ref_ptr created = handleNode(nifNode, NULL, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); + osg::ref_ptr created = handleNode(nifNode, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); if (nif->getUseSkinning()) { @@ -297,7 +297,7 @@ namespace NifOsg { // Get the lowest numbered recIndex of the NiTexturingProperty root node. // This is what is overridden when a spell effect "particle texture" is used. - if (nifNode->parent == NULL && !mFoundFirstRootTexturingProperty && props[i].getPtr()->recType == Nif::RC_NiTexturingProperty) + if (nifNode->parent == nullptr && !mFoundFirstRootTexturingProperty && props[i].getPtr()->recType == Nif::RC_NiTexturingProperty) { mFirstRootTextureIndex = props[i].getPtr()->recIndex; mFoundFirstRootTexturingProperty = true; @@ -338,7 +338,7 @@ namespace NifOsg osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) { if (!st) - return NULL; + return nullptr; osg::ref_ptr image; if (!st->external && !st->data.empty()) @@ -464,10 +464,10 @@ namespace NifOsg } osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, - std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=NULL) + std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=nullptr) { - if (rootNode != NULL && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) - return NULL; + if (rootNode != nullptr && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) + return nullptr; osg::ref_ptr node = createNode(nifNode); @@ -791,7 +791,7 @@ namespace NifOsg // Load the initial state of the particle system, i.e. the initial particles and their positions, velocity and colors. void handleParticleInitialState(const Nif::Node* nifNode, osgParticle::ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) { - const Nif::NiAutoNormalParticlesData *particledata = NULL; + const Nif::NiAutoNormalParticlesData *particledata = nullptr; if(nifNode->recType == Nif::RC_NiAutoNormalParticles) particledata = static_cast(nifNode)->data.getPtr(); else if(nifNode->recType == Nif::RC_NiRotatingParticles) @@ -873,7 +873,7 @@ namespace NifOsg osg::ref_ptr partsys (new ParticleSystem); partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); - const Nif::NiParticleSystemController* partctrl = NULL; + const Nif::NiParticleSystemController* partctrl = nullptr; for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) @@ -1193,11 +1193,11 @@ namespace NifOsg break; default: Log(Debug::Info) << "Unhandled internal pixel format " << pixelData->fmt << " in " << mFilename; - return NULL; + return nullptr; } if (pixelData->mipmaps.empty()) - return NULL; + return nullptr; int width = 0; int height = 0; @@ -1211,7 +1211,7 @@ namespace NifOsg if (mipSize + mip.dataOffset > pixelData->data.size()) { Log(Debug::Info) << "Internal texture's mipmap data out of bounds, ignoring texture"; - return NULL; + return nullptr; } if (i != 0) @@ -1226,7 +1226,7 @@ namespace NifOsg if (width <= 0 || height <= 0) { Log(Debug::Info) << "Internal Texture Width and height must be non zero, ignoring texture"; - return NULL; + return nullptr; } unsigned char* data = new unsigned char[pixelData->data.size()]; diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 03e836a0fd..f5c055a15a 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -39,7 +39,7 @@ osgParticle::Particle* ParticleSystem::createParticle(const osgParticle::Particl { if (numParticles()-numDeadParticles() < mQuota) return osgParticle::ParticleSystem::createParticle(ptemplate); - return NULL; + return nullptr; } void InverseWorldMatrix::operator()(osg::Node *node, osg::NodeVisitor *nv) @@ -312,7 +312,7 @@ void Emitter::emitParticles(double dt) FindGroupByRecIndex::FindGroupByRecIndex(int recIndex) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mFound(NULL) + , mFound(nullptr) , mRecIndex(recIndex) { } diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index dbdbf0c6e3..15a747dd3a 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -12,7 +12,7 @@ namespace Resource { BulletShape::BulletShape() - : mCollisionShape(NULL) + : mCollisionShape(nullptr) { } @@ -32,7 +32,7 @@ BulletShape::~BulletShape() void BulletShape::deleteShape(btCollisionShape* shape) { - if(shape!=NULL) + if(shape!=nullptr) { if(shape->isCompound()) { diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 622506d6b0..4193cd5b42 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -24,7 +24,7 @@ namespace Resource struct GetTriangleFunctor { GetTriangleFunctor() - : mTriMesh(NULL) + : mTriMesh(nullptr) { } diff --git a/components/resource/multiobjectcache.hpp b/components/resource/multiobjectcache.hpp index 527247bf96..b73e29d50f 100644 --- a/components/resource/multiobjectcache.hpp +++ b/components/resource/multiobjectcache.hpp @@ -30,7 +30,7 @@ namespace Resource void addEntryToObjectCache(const std::string& filename, osg::Object* object); - /** Take an Object from cache. Return NULL if no object found. */ + /** Take an Object from cache. Return nullptr if no object found. */ osg::ref_ptr takeFromObjectCache(const std::string& fileName); /** call releaseGLObjects on all objects attached to the object cache.*/ diff --git a/components/sceneutil/actorutil.cpp b/components/sceneutil/actorutil.cpp new file mode 100644 index 0000000000..988a61f60e --- /dev/null +++ b/components/sceneutil/actorutil.cpp @@ -0,0 +1,30 @@ +#include "actorutil.hpp" + +namespace SceneUtil +{ + std::string getActorSkeleton(bool firstPerson, bool isFemale, bool isBeast, bool isWerewolf) + { + if (!firstPerson) + { + if (isWerewolf) + return "meshes\\wolf\\skin.nif"; + else if (isBeast) + return "meshes\\base_animkna.nif"; + else if (isFemale) + return "meshes\\base_anim_female.nif"; + else + return "meshes\\base_anim.nif"; + } + else + { + if (isWerewolf) + return "meshes\\wolf\\skin.1st.nif"; + else if (isBeast) + return "meshes\\base_animkna.1st.nif"; + else if (isFemale) + return "meshes\\base_anim_female.1st.nif"; + else + return "meshes\\base_anim.1st.nif"; + } + } +} diff --git a/components/sceneutil/actorutil.hpp b/components/sceneutil/actorutil.hpp new file mode 100644 index 0000000000..7bdbbaa922 --- /dev/null +++ b/components/sceneutil/actorutil.hpp @@ -0,0 +1,11 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_ACTORUTIL_HPP +#define OPENMW_COMPONENTS_SCENEUTIL_ACTORUTIL_HPP + +#include + +namespace SceneUtil +{ + std::string getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf); +} + +#endif diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 08f36cfcf1..99dd7bad3c 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -26,7 +26,7 @@ namespace SceneUtil osg::StateSet* CopyOp::operator ()(const osg::StateSet* stateset) const { if (!stateset) - return NULL; + return nullptr; if (stateset->getDataVariance() == osg::StateSet::DYNAMIC) return osg::clone(stateset, *this); return const_cast(stateset); diff --git a/components/sceneutil/controller.cpp b/components/sceneutil/controller.cpp index 8fef00333b..dfc72918aa 100644 --- a/components/sceneutil/controller.cpp +++ b/components/sceneutil/controller.cpp @@ -19,7 +19,7 @@ namespace SceneUtil bool Controller::hasInput() const { - return mSource.get() != NULL; + return mSource.get() != nullptr; } float Controller::getInputValue(osg::NodeVisitor* nv) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 018abeefc6..e102653b90 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -102,7 +102,7 @@ namespace SceneUtil if (LightManager* lightManager = dynamic_cast(path[i])) return lightManager; } - return NULL; + return nullptr; } // Set on a LightSource. Adds the light source to its light manager for the current frame. @@ -299,7 +299,7 @@ namespace SceneUtil virtual osg::Object* cloneType() const { return new DisableLight(mIndex); } virtual osg::Object* clone(const osg::CopyOp& copyop) const { return new DisableLight(*this,copyop); } - virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast(obj)!=NULL; } + virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast(obj)!=nullptr; } virtual const char* libraryName() const { return "SceneUtil"; } virtual const char* className() const { return "DisableLight"; } virtual Type getType() const { return LIGHT; } @@ -323,17 +323,17 @@ namespace SceneUtil virtual void apply(osg::State& state) const { int lightNum = GL_LIGHT0 + mIndex; - glLightfv( lightNum, GL_AMBIENT, mNull.ptr() ); - glLightfv( lightNum, GL_DIFFUSE, mNull.ptr() ); - glLightfv( lightNum, GL_SPECULAR, mNull.ptr() ); + glLightfv( lightNum, GL_AMBIENT, mnullptr.ptr() ); + glLightfv( lightNum, GL_DIFFUSE, mnullptr.ptr() ); + glLightfv( lightNum, GL_SPECULAR, mnullptr.ptr() ); LightStateCache* cache = getLightStateCache(state.getContextID()); - cache->lastAppliedLight[mIndex] = NULL; + cache->lastAppliedLight[mIndex] = nullptr; } private: unsigned int mIndex; - osg::Vec4f mNull; + osg::Vec4f mnullptr; }; void LightManager::setStartLight(int start) @@ -447,7 +447,7 @@ namespace SceneUtil { unsigned int maxLights = static_cast (8 - mLightManager->getStartLight()); - osg::StateSet* stateset = NULL; + osg::StateSet* stateset = nullptr; if (mLightList.size() > maxLights) { diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 55d0c69cde..d35aa6058e 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -158,7 +158,7 @@ namespace SceneUtil { public: LightListCallback() - : mLightManager(NULL) + : mLightManager(nullptr) , mLastFrameNumber(0) {} LightListCallback(const LightListCallback& copy, const osg::CopyOp& copyop) diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 5ad9bd6dce..d44724e8fb 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -45,7 +45,7 @@ namespace SceneUtil SceneUtil::FindByNameVisitor visitor("AttachLight"); node->accept(visitor); - osg::Group* attachTo = NULL; + osg::Group* attachTo = nullptr; if (visitor.mFoundNode) { attachTo = visitor.mFoundNode; diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index b86744599e..5d50c369d9 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -166,7 +166,7 @@ class CollectLowestTransformsVisitor : public BaseOptimizerVisitor } else { - // for all current objects mark a NULL transform for them. + // for all current objects mark a nullptr transform for them. registerWithCurrentObjects(0); } } @@ -826,7 +826,7 @@ void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Transform& transform) isOperationPermissible(transform)) { osg::Matrix matrix; - transform.computeWorldToLocalMatrix(matrix,NULL); + transform.computeWorldToLocalMatrix(matrix,nullptr); if (matrix.isIdentity()) { _redundantNodeList.insert(&transform); @@ -1000,7 +1000,7 @@ struct LessGeometryPrimitiveType }; -/// Shortcut to get size of an array, even if pointer is NULL. +/// Shortcut to get size of an array, even if pointer is nullptr. inline unsigned int getSize(const osg::Array * a) { return a ? a->getNumElements() : 0; } /// When merging geometries, tests if two arrays can be merged, regarding to their number of components, and the number of vertices. @@ -1170,7 +1170,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) MergeList::iterator eachMergeList=mergeListTmp.begin(); for(;eachMergeList!=mergeListTmp.end();++eachMergeList) { - if (!eachMergeList->empty() && eachMergeList->front()!=NULL + if (!eachMergeList->empty() && eachMergeList->front()!=nullptr && isAbleToMerge(*eachMergeList->front(),*geomToPush)) { eachMergeList->push_back(geomToPush); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index c409bcd5cc..30a3f076cf 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -37,7 +37,7 @@ namespace SceneUtil { RigGeometry::RigGeometry() - : mSkeleton(NULL) + : mSkeleton(nullptr) , mLastFrameNumber(0) , mBoundsFirstFrame(true) { @@ -47,7 +47,7 @@ RigGeometry::RigGeometry() RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) : Drawable(copy, copyop) - , mSkeleton(NULL) + , mSkeleton(nullptr) , mInfluenceMap(copy.mInfluenceMap) , mLastFrameNumber(0) , mBoundsFirstFrame(true) @@ -98,7 +98,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); } else - mSourceTangents = NULL; + mSourceTangents = nullptr; } } @@ -296,7 +296,7 @@ void RigGeometry::updateGeomToSkelMatrix(const osg::NodePath& nodePath) { if (!geomToSkelMatrix) geomToSkelMatrix = new osg::RefMatrix; - trans->computeWorldToLocalMatrix(*geomToSkelMatrix, NULL); + trans->computeWorldToLocalMatrix(*geomToSkelMatrix, nullptr); } } } diff --git a/components/sceneutil/skeleton.cpp b/components/sceneutil/skeleton.cpp index 58ed9a27cf..9be440d93e 100644 --- a/components/sceneutil/skeleton.cpp +++ b/components/sceneutil/skeleton.cpp @@ -64,7 +64,7 @@ Bone* Skeleton::getBone(const std::string &name) BoneCache::iterator found = mBoneCache.find(Misc::StringUtils::lowerCase(name)); if (found == mBoneCache.end()) - return NULL; + return nullptr; // find or insert in the bone hierarchy @@ -81,7 +81,7 @@ Bone* Skeleton::getBone(const std::string &name) if (!matrixTransform) continue; - Bone* child = NULL; + Bone* child = nullptr; for (unsigned int i=0; imChildren.size(); ++i) { if (bone->mChildren[i]->mNode == *it) @@ -117,7 +117,7 @@ void Skeleton::updateBoneMatrices(unsigned int traversalNumber) if (mRootBone.get()) { for (unsigned int i=0; imChildren.size(); ++i) - mRootBone->mChildren[i]->update(NULL); + mRootBone->mChildren[i]->update(nullptr); } mNeedToUpdateBoneMatrices = false; @@ -167,7 +167,7 @@ void Skeleton::childRemoved(unsigned int, unsigned int) } Bone::Bone() - : mNode(NULL) + : mNode(nullptr) { } diff --git a/components/sceneutil/statesetupdater.cpp b/components/sceneutil/statesetupdater.cpp index 5a5ecaded0..121cdaca67 100644 --- a/components/sceneutil/statesetupdater.cpp +++ b/components/sceneutil/statesetupdater.cpp @@ -30,8 +30,8 @@ namespace SceneUtil void StateSetUpdater::reset() { - mStateSets[0] = NULL; - mStateSets[1] = NULL; + mStateSets[0] = nullptr; + mStateSets[1] = nullptr; } StateSetUpdater::StateSetUpdater() diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 2f6123e34d..3c3559a083 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -1,9 +1,12 @@ #include "visitor.hpp" +#include #include #include +#include + #include namespace SceneUtil @@ -23,7 +26,7 @@ namespace SceneUtil { if (Misc::StringUtils::ciEqual(node.className(), mNameToFind)) mFoundNodes.push_back(&node); - + traverse(node); } @@ -54,4 +57,103 @@ namespace SceneUtil partsys->setFreezeOnCull(false); } + void NodeMapVisitor::apply(osg::MatrixTransform& trans) + { + // Take transformation for first found node in file + const std::string nodeName = Misc::StringUtils::lowerCase(trans.getName()); + if (mMap.find(nodeName) == mMap.end()) + { + mMap[nodeName] = &trans; + } + + traverse(trans); + } + + void RemoveVisitor::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(); + } + } + + void CleanObjectRootVisitor::apply(osg::Drawable& drw) + { + applyDrawable(drw); + } + + void CleanObjectRootVisitor::apply(osg::Group& node) + { + applyNode(node); + } + + void CleanObjectRootVisitor::apply(osg::MatrixTransform& node) + { + applyNode(node); + } + + void CleanObjectRootVisitor::apply(osg::Node& node) + { + applyNode(node); + } + + void CleanObjectRootVisitor::applyNode(osg::Node& node) + { + if (node.getStateSet()) + node.setStateSet(nullptr); + + if (node.getNodeMask() == 0x1 && node.getNumParents() == 1) + mToRemove.push_back(std::make_pair(&node, node.getParent(0))); + else + traverse(node); + } + + void CleanObjectRootVisitor::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.push_back(std::make_pair(parentGroup, parentParent)); + return; + } + } + + mToRemove.push_back(std::make_pair(&node, parentGroup)); + } + + void RemoveTriBipVisitor::apply(osg::Drawable& drw) + { + applyImpl(drw); + } + + void RemoveTriBipVisitor::apply(osg::Group& node) + { + traverse(node); + } + + void RemoveTriBipVisitor::apply(osg::MatrixTransform& node) + { + traverse(node); + } + + void RemoveTriBipVisitor::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.push_back(std::make_pair(&node, parent)); + } + } } diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index 265fd6d02a..02a351da7c 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H #define OPENMW_COMPONENTS_SCENEUTIL_VISITOR_H +#include #include // Commonly used scene graph visitors @@ -8,14 +9,14 @@ namespace SceneUtil { // Find a Group by name, case-insensitive - // If not found, mFoundNode will be NULL + // If not found, mFoundNode will be nullptr class FindByNameVisitor : public osg::NodeVisitor { public: FindByNameVisitor(const std::string& nameToFind) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mNameToFind(nameToFind) - , mFoundNode(NULL) + , mFoundNode(nullptr) { } @@ -58,6 +59,65 @@ namespace SceneUtil virtual void apply(osg::Drawable& drw); }; + /// Maps names to nodes + class NodeMapVisitor : public osg::NodeVisitor + { + public: + typedef std::map > NodeMap; + + NodeMapVisitor(NodeMap& map) + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + , mMap(map) + { + } + + void apply(osg::MatrixTransform& trans); + + private: + NodeMap& mMap; + }; + + /// @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(); + + protected: + // + typedef std::vector > RemoveVec; + std::vector > mToRemove; + }; + + // Removes all drawables from a graph. + class CleanObjectRootVisitor : public RemoveVisitor + { + public: + virtual void apply(osg::Drawable& drw); + virtual void apply(osg::Group& node); + virtual void apply(osg::MatrixTransform& node); + virtual void apply(osg::Node& node); + + void applyNode(osg::Node& node); + void applyDrawable(osg::Node& node); + }; + + class RemoveTriBipVisitor : public RemoveVisitor + { + public: + virtual void apply(osg::Drawable& drw); + virtual void apply(osg::Group& node); + virtual void apply(osg::MatrixTransform& node); + + void applyImpl(osg::Node& node); + }; } #endif diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index 37e8563e12..0ca5e4d3e0 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -97,7 +97,7 @@ osg::ref_ptr WorkQueue::removeWorkItem() return item; } else - return NULL; + return nullptr; } unsigned int WorkQueue::getNumItems() const diff --git a/components/sceneutil/workqueue.hpp b/components/sceneutil/workqueue.hpp index 7de6a5af52..2084df328b 100644 --- a/components/sceneutil/workqueue.hpp +++ b/components/sceneutil/workqueue.hpp @@ -57,7 +57,7 @@ namespace SceneUtil void addWorkItem(osg::ref_ptr item, bool front=false); /// Get the next work item from the front of the queue. If the queue is empty, waits until a new item is added. - /// If the workqueue is in the process of being destroyed, may return NULL. + /// If the workqueue is in the process of being destroyed, may return nullptr. /// @par Used internally by the WorkThread. osg::ref_ptr removeWorkItem(); diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 1560b74b3e..6d21175753 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -190,7 +190,7 @@ namespace CursorDecompression SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); SDL_Texture *cursorTexture = SDL_CreateTextureFromSurface(renderer, cursorSurface); - SDL_RenderCopyEx(renderer, cursorTexture, NULL, NULL, -rotDegrees, NULL, SDL_FLIP_VERTICAL); + SDL_RenderCopyEx(renderer, cursorTexture, nullptr, nullptr, -rotDegrees, nullptr, SDL_FLIP_VERTICAL); SDL_DestroyTexture(cursorTexture); SDL_FreeSurface(cursorSurface); diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index f9767238f0..dc6129e43f 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -77,7 +77,7 @@ void GraphicsWindowSDL2::init() return; WindowData *inheritedWindowData = dynamic_cast(_traits->inheritedWindowData.get()); - mWindow = inheritedWindowData ? inheritedWindowData->mWindow : NULL; + mWindow = inheritedWindowData ? inheritedWindowData->mWindow : nullptr; mOwnsWindow = (mWindow == 0); if(mOwnsWindow) @@ -162,7 +162,7 @@ bool GraphicsWindowSDL2::releaseContextImplementation() return false; } - return SDL_GL_MakeCurrent(NULL, NULL)==0; + return SDL_GL_MakeCurrent(nullptr, nullptr)==0; } @@ -170,11 +170,11 @@ void GraphicsWindowSDL2::closeImplementation() { if(mContext) SDL_GL_DeleteContext(mContext); - mContext = NULL; + mContext = nullptr; if(mWindow && mOwnsWindow) SDL_DestroyWindow(mWindow); - mWindow = NULL; + mWindow = nullptr; mValid = false; mRealized = false; diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 5fc828fb59..f7111fdf80 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -10,10 +10,10 @@ namespace SDLUtil InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr viewer, bool grab) : mSDLWindow(window), mViewer(viewer), - mMouseListener(NULL), - mKeyboardListener(NULL), - mWindowListener(NULL), - mConListener(NULL), + mMouseListener(nullptr), + mKeyboardListener(nullptr), + mWindowListener(nullptr), + mConListener(nullptr), mWarpX(0), mWarpY(0), mWarpCompensate(false), @@ -234,7 +234,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v bool InputWrapper::isKeyDown(SDL_Scancode key) { - return (SDL_GetKeyboardState(NULL)[key]) != 0; + return (SDL_GetKeyboardState(nullptr)[key]) != 0; } /// \brief Moves the mouse to the specified point within the viewport diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 28f4300c29..f3d52f5f04 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -115,7 +115,7 @@ namespace Shader if (stream.fail()) { Log(Debug::Error) << "Failed to open " << p.string(); - return NULL; + return nullptr; } std::stringstream buffer; buffer << stream.rdbuf(); @@ -123,7 +123,7 @@ namespace Shader // parse includes std::string source = buffer.str(); if (!parseIncludes(boost::filesystem::path(mPath), source)) - return NULL; + return nullptr; templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, source)).first; } @@ -136,7 +136,7 @@ namespace Shader { // Add to the cache anyway to avoid logging the same error over and over. mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), nullptr)); - return NULL; + return nullptr; } osg::ref_ptr shader (new osg::Shader(shaderType)); diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index bd820a725c..6b2e021242 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -26,7 +26,7 @@ namespace Shader /// @param shaderTemplate The filename of the shader template. /// @param defines Define values that can be retrieved by the shader template. /// @param shaderType The type of shader (usually vertex or fragment shader). - /// @note May return NULL on failure. + /// @note May return nullptr on failure. /// @note Thread safe. osg::ref_ptr getShader(const std::string& shaderTemplate, const DefineMap& defines, osg::Shader::Type shaderType); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index cbd950ea3b..a0a6832d06 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -28,7 +28,7 @@ namespace Shader , mMaterialOverridden(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) - , mNode(NULL) + , mNode(nullptr) { } @@ -102,15 +102,15 @@ namespace Shader void ShaderVisitor::applyStateSet(osg::ref_ptr stateset, osg::Node& node) { - osg::StateSet* writableStateSet = NULL; + osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); if (!texAttributes.empty()) { - const osg::Texture* diffuseMap = NULL; - const osg::Texture* normalMap = NULL; - const osg::Texture* specularMap = NULL; + const osg::Texture* diffuseMap = nullptr; + const osg::Texture* normalMap = nullptr; + const osg::Texture* specularMap = nullptr; for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); @@ -153,7 +153,7 @@ namespace Shader } } - if (mAutoUseNormalMaps && diffuseMap != NULL && normalMap == NULL && diffuseMap->getImage(0)) + if (mAutoUseNormalMaps && diffuseMap != nullptr && normalMap == nullptr && diffuseMap->getImage(0)) { std::string normalMapFileName = diffuseMap->getImage(0)->getFileName(); @@ -195,7 +195,7 @@ namespace Shader mRequirements.back().mNormalHeight = normalHeight; } } - if (mAutoUseSpecularMaps && diffuseMap != NULL && specularMap == NULL && diffuseMap->getImage(0)) + if (mAutoUseSpecularMaps && diffuseMap != nullptr && specularMap == nullptr && diffuseMap->getImage(0)) { std::string specularMapFileName = diffuseMap->getImage(0)->getFileName(); boost::replace_last(specularMapFileName, ".", mSpecularMapPattern + "."); @@ -254,7 +254,7 @@ namespace Shader return; osg::Node& node = *reqs.mNode; - osg::StateSet* writableStateSet = NULL; + osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) writableStateSet = node.getOrCreateStateSet(); else @@ -321,7 +321,7 @@ namespace Shader // make sure that all UV sets are there for (std::map::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it) { - if (sourceGeometry.getTexCoordArray(it->first) == NULL) + if (sourceGeometry.getTexCoordArray(it->first) == nullptr) { sourceGeometry.setTexCoordArray(it->first, sourceGeometry.getTexCoordArray(0)); changed = true; @@ -342,7 +342,7 @@ namespace Shader void ShaderVisitor::apply(osg::Geometry& geometry) { - bool needPop = (geometry.getStateSet() != NULL); + bool needPop = (geometry.getStateSet() != nullptr); if (geometry.getStateSet()) // TODO: check if stateset affects shader permutation before pushing it { pushRequirements(geometry); @@ -365,7 +365,7 @@ namespace Shader void ShaderVisitor::apply(osg::Drawable& drawable) { // non-Geometry drawable (e.g. particle system) - bool needPop = (drawable.getStateSet() != NULL); + bool needPop = (drawable.getStateSet() != nullptr); if (drawable.getStateSet()) { diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 7575113ef2..b23b0b76c0 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -22,7 +22,7 @@ namespace Terrain { ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer) - : ResourceManager(NULL) + : ResourceManager(nullptr) , mStorage(storage) , mSceneManager(sceneMgr) , mTextureManager(textureManager) diff --git a/components/terrain/compositemaprenderer.cpp b/components/terrain/compositemaprenderer.cpp index d3fdfa1f8e..3dc0aa41c5 100644 --- a/components/terrain/compositemaprenderer.cpp +++ b/components/terrain/compositemaprenderer.cpp @@ -45,7 +45,7 @@ void CompositeMapRenderer::drawImplementation(osg::RenderInfo &renderInfo) const CompositeMap* node = *mImmediateCompileSet.begin(); mCompiled.insert(node); - compile(*node, renderInfo, NULL); + compile(*node, renderInfo, nullptr); mImmediateCompileSet.erase(mImmediateCompileSet.begin()); } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index f4fc8df897..2222cbb844 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -41,7 +41,7 @@ bool adjacent(ChildDirection dir, Direction dir2) QuadTreeNode* searchNeighbour (QuadTreeNode* currentNode, Direction dir) { if (currentNode->getDirection() == Root) - return NULL; // Arrived at root node, the root node does not have neighbours + return nullptr; // Arrived at root node, the root node does not have neighbours QuadTreeNode* nextNode; if (adjacent(currentNode->getDirection(), dir)) @@ -52,7 +52,7 @@ QuadTreeNode* searchNeighbour (QuadTreeNode* currentNode, Direction dir) if (nextNode && nextNode->getNumChildren()) return nextNode->getChild(reflect(currentNode->getDirection(), dir)); else - return NULL; + return nullptr; } QuadTreeNode::QuadTreeNode(QuadTreeNode* parent, ChildDirection direction, float size, const osg::Vec2f& center) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index e75deb2f70..40f7a6fb0c 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -4,6 +4,8 @@ #include +#include + #include "quadtreenode.hpp" #include "storage.hpp" #include "viewdata.hpp" @@ -81,7 +83,7 @@ public: { float dist = distanceToBox(node->getBoundingBox(), eyePoint); int nativeLodLevel = Log2(static_cast(node->getSize()/mMinSize)); - int lodLevel = Log2(static_cast(dist/(8192*mMinSize))); + int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize))); return nativeLodLevel <= lodLevel; } @@ -94,8 +96,8 @@ class RootNode : public QuadTreeNode { public: RootNode(float size, const osg::Vec2f& center) - : QuadTreeNode(NULL, Root, size, center) - , mWorld(NULL) + : QuadTreeNode(nullptr, Root, size, center) + , mWorld(nullptr) { } @@ -315,7 +317,7 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, ChunkManager* chunk unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, vd); if (lodFlags != entry.mLodFlags) { - entry.mRenderingNode = NULL; + entry.mRenderingNode = nullptr; entry.mLodFlags = lodFlags; } } @@ -365,7 +367,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) if (udc && udc->getUserData()) { mCompositeMapRenderer->setImmediate(static_cast(udc->getUserData())); - udc->setUserData(NULL); + udc->setUserData(nullptr); } entry.mRenderingNode->accept(nv); } @@ -428,7 +430,7 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &eyePoint) ensureQuadTreeBuilt(); ViewData* vd = static_cast(view); - traverse(mRootNode.get(), vd, NULL, mRootNode->getLodCallback(), eyePoint, false); + traverse(mRootNode.get(), vd, nullptr, mRootNode->getLodCallback(), eyePoint, false); for (unsigned int i=0; igetNumEntries(); ++i) { diff --git a/components/terrain/terraindrawable.hpp b/components/terrain/terraindrawable.hpp index 79a28deebf..6bef60bc73 100644 --- a/components/terrain/terraindrawable.hpp +++ b/components/terrain/terraindrawable.hpp @@ -24,7 +24,7 @@ namespace Terrain public: virtual osg::Object* cloneType() const { return new TerrainDrawable (); } virtual osg::Object* clone(const osg::CopyOp& copyop) const { return new TerrainDrawable (*this,copyop); } - virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast(obj)!=NULL; } + virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast(obj)!=nullptr; } virtual const char* className() const { return "TerrainDrawable"; } virtual const char* libraryName() const { return "Terrain"; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 74f683774c..dbc1429da2 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -34,7 +34,7 @@ TerrainGrid::~TerrainGrid() void TerrainGrid::cacheCell(View* view, int x, int y) { osg::Vec2f center(x+0.5f, y+0.5f); - static_cast(view)->mLoaded = buildTerrain(NULL, 1.f, center); + static_cast(view)->mLoaded = buildTerrain(nullptr, 1.f, center); } osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) @@ -57,7 +57,7 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu { osg::ref_ptr node = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0); if (!node) - return NULL; + return nullptr; if (parent) parent->addChild(node); @@ -71,7 +71,7 @@ void TerrainGrid::loadCell(int x, int y) return; // already loaded osg::Vec2f center(x+0.5f, y+0.5f); - osg::ref_ptr terrainNode = buildTerrain(NULL, 1.f, center); + osg::ref_ptr terrainNode = buildTerrain(nullptr, 1.f, center); if (!terrainNode) return; // no terrain defined diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index 5c70f65f20..2f9cb66413 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -64,7 +64,7 @@ void ViewData::reset(unsigned int frame) { // clear any unused entries for (unsigned int i=mNumEntries; isecond; if (vd->getFrameLastUsed() + 2 < frame) { - vd->setViewer(NULL); + vd->setViewer(nullptr); vd->clear(); mUnusedViews.push_back(vd); mViews.erase(it++); diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index ae71693bdf..da10047832 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -90,7 +90,7 @@ namespace Terrain /// Create a View to use with preload feature. The caller is responsible for deleting the view. /// @note Thread safe. - virtual View* createView() { return NULL; } + virtual View* createView() { return nullptr; } /// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads. virtual void preload(View* view, const osg::Vec3f& eyePoint) {} diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index 14a32f178d..ef0f432075 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -5,7 +5,7 @@ namespace Translation { Storage::Storage() - : mEncoder(NULL) + : mEncoder(nullptr) { } diff --git a/components/widgets/box.cpp b/components/widgets/box.cpp index bf18cef5b9..d38410a2c9 100644 --- a/components/widgets/box.cpp +++ b/components/widgets/box.cpp @@ -48,7 +48,7 @@ namespace Gui } else { - TextBox::setPropertyOverride (_key, _value); + Gui::TextBox::setPropertyOverride (_key, _value); } } @@ -81,11 +81,10 @@ namespace Gui } else { - EditBox::setPropertyOverride (_key, _value); + Gui::EditBox::setPropertyOverride (_key, _value); } } - MyGUI::IntSize AutoSizedButton::getRequestedSize() { MyGUI::IntSize padding(24, 8); @@ -111,16 +110,14 @@ namespace Gui } else { - Button::setPropertyOverride (_key, _value); + Gui::Button::setPropertyOverride (_key, _value); } } - Box::Box() : mSpacing(4) , mPadding(0) , mAutoResize(false) { - } void Box::notifyChildrenSizeChanged () diff --git a/components/widgets/box.hpp b/components/widgets/box.hpp index 66be007191..9bab575c48 100644 --- a/components/widgets/box.hpp +++ b/components/widgets/box.hpp @@ -4,10 +4,27 @@ #include #include #include +#include #include +#include "fontwrapper.hpp" + namespace Gui { + class Button : public FontWrapper + { + MYGUI_RTTI_DERIVED( Button ) + }; + + class TextBox : public FontWrapper + { + MYGUI_RTTI_DERIVED( TextBox ) + }; + + class EditBox : public FontWrapper + { + MYGUI_RTTI_DERIVED( EditBox ) + }; class AutoSizedWidget { @@ -22,7 +39,7 @@ namespace Gui MyGUI::Align mExpandDirection; }; - class AutoSizedTextBox : public AutoSizedWidget, public MyGUI::TextBox + class AutoSizedTextBox : public AutoSizedWidget, public TextBox { MYGUI_RTTI_DERIVED( AutoSizedTextBox ) @@ -32,9 +49,10 @@ namespace Gui protected: virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + std::string mFontSize; }; - class AutoSizedEditBox : public AutoSizedWidget, public MyGUI::EditBox + class AutoSizedEditBox : public AutoSizedWidget, public EditBox { MYGUI_RTTI_DERIVED( AutoSizedEditBox ) @@ -47,9 +65,10 @@ namespace Gui protected: virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + std::string mFontSize; }; - class AutoSizedButton : public AutoSizedWidget, public MyGUI::Button + class AutoSizedButton : public AutoSizedWidget, public Button { MYGUI_RTTI_DERIVED( AutoSizedButton ) @@ -59,6 +78,7 @@ namespace Gui protected: virtual void setPropertyOverride(const std::string& _key, const std::string& _value); + std::string mFontSize; }; /** diff --git a/components/widgets/fontwrapper.hpp b/components/widgets/fontwrapper.hpp new file mode 100644 index 0000000000..2424b22a7b --- /dev/null +++ b/components/widgets/fontwrapper.hpp @@ -0,0 +1,45 @@ +#ifndef OPENMW_WIDGETS_WRAPPER_H +#define OPENMW_WIDGETS_WRAPPER_H + +#include "widgets.hpp" + +#include + +namespace Gui +{ + template + class FontWrapper : public T + { + public: + virtual void setFontName(const std::string& name) + { + T::setFontName(name); + T::setPropertyOverride ("FontHeight", mFontSize); + } + + protected: + FontWrapper() + { + // Note: we can not use the WindowManager here, so there is a code duplication a bit. + int fontSize = Settings::Manager::getInt("font size", "GUI"); + fontSize = std::min(std::max(12, fontSize), 20); + mFontSize = std::to_string(fontSize); + } + + virtual void setPropertyOverride(const std::string& _key, const std::string& _value) + { + T::setPropertyOverride (_key, _value); + + // There is a bug in MyGUI: when it initializes the FontName property, it reset the font height. + // We should restore it. + if (_key == "FontName") + { + T::setPropertyOverride ("FontHeight", mFontSize); + } + } + + std::string mFontSize; + }; +} + +#endif diff --git a/components/widgets/numericeditbox.cpp b/components/widgets/numericeditbox.cpp index efe4806536..e8ba226f70 100644 --- a/components/widgets/numericeditbox.cpp +++ b/components/widgets/numericeditbox.cpp @@ -59,6 +59,11 @@ namespace Gui } } + int NumericEditBox::getValue() + { + return mValue; + } + void NumericEditBox::setMinValue(int minValue) { mMinValue = minValue; diff --git a/components/widgets/numericeditbox.hpp b/components/widgets/numericeditbox.hpp index ca7674f229..137583d37e 100644 --- a/components/widgets/numericeditbox.hpp +++ b/components/widgets/numericeditbox.hpp @@ -3,13 +3,15 @@ #include +#include "fontwrapper.hpp" + namespace Gui { /** * @brief A variant of the EditBox that only allows integer inputs */ - class NumericEditBox : public MyGUI::EditBox + class NumericEditBox : public FontWrapper { MYGUI_RTTI_DERIVED(NumericEditBox) @@ -17,7 +19,8 @@ namespace Gui NumericEditBox() : mValue(0), mMinValue(std::numeric_limits::min()), mMaxValue(std::numeric_limits::max()) - {} + { + } void initialiseOverride(); void shutdownOverride(); @@ -27,6 +30,7 @@ namespace Gui /// @note Does not trigger eventValueChanged void setValue (int value); + int getValue(); void setMinValue(int minValue); void setMaxValue(int maxValue); diff --git a/components/widgets/sharedstatebutton.cpp b/components/widgets/sharedstatebutton.cpp index 6859a3065a..f4456275bd 100644 --- a/components/widgets/sharedstatebutton.cpp +++ b/components/widgets/sharedstatebutton.cpp @@ -7,7 +7,6 @@ namespace Gui : mIsMousePressed(false) , mIsMouseFocus(false) { - } void SharedStateButton::shutdownOverride() diff --git a/components/widgets/sharedstatebutton.hpp b/components/widgets/sharedstatebutton.hpp index 3d7fbc84e4..4143493967 100644 --- a/components/widgets/sharedstatebutton.hpp +++ b/components/widgets/sharedstatebutton.hpp @@ -3,6 +3,8 @@ #include +#include "fontwrapper.hpp" + namespace Gui { @@ -11,7 +13,7 @@ namespace Gui typedef std::vector ButtonGroup; /// @brief A button that applies its own state changes to other widgets, to do this you define it as part of a ButtonGroup. - class SharedStateButton : public MyGUI::Button + class SharedStateButton : public FontWrapper { MYGUI_RTTI_DERIVED(SharedStateButton) diff --git a/components/widgets/widgets.cpp b/components/widgets/widgets.cpp index 92f2084dfa..c1a9a50531 100644 --- a/components/widgets/widgets.cpp +++ b/components/widgets/widgets.cpp @@ -18,9 +18,12 @@ namespace Gui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); diff --git a/components/widgets/widgets.hpp b/components/widgets/widgets.hpp index d171321357..a5ba84b11e 100644 --- a/components/widgets/widgets.hpp +++ b/components/widgets/widgets.hpp @@ -1,9 +1,10 @@ #ifndef OPENMW_COMPONENTS_WIDGETS_H #define OPENMW_COMPONENTS_WIDGETS_H +extern int GuiFontHeight; + namespace Gui { - /// Register all widgets from this component with MyGUI's factory manager. void registerAllWidgets(); diff --git a/components/widgets/windowcaption.cpp b/components/widgets/windowcaption.cpp index 1c8fb5608d..0fed4beadf 100644 --- a/components/widgets/windowcaption.cpp +++ b/components/widgets/windowcaption.cpp @@ -6,9 +6,9 @@ namespace Gui { WindowCaption::WindowCaption() - : mLeft(NULL) - , mRight(NULL) - , mClient(NULL) + : mLeft(nullptr) + , mRight(nullptr) + , mClient(nullptr) { } diff --git a/docs/source/conf.py b/docs/source/conf.py index da59c02e1d..60b25ae57b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -169,8 +169,8 @@ def setup(app): # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static', - 'manuals/openmw-cs/_static' +html_static_path = [ + '_static' ] # Add any extra paths that contain custom files (such as robots.txt or diff --git a/docs/source/reference/modding/settings/GUI.rst b/docs/source/reference/modding/settings/GUI.rst index 3040fc48ba..c8f4e16f80 100644 --- a/docs/source/reference/modding/settings/GUI.rst +++ b/docs/source/reference/modding/settings/GUI.rst @@ -12,6 +12,27 @@ This setting scales the GUI interface windows. A value of 1.0 results in the normal scale. Larger values are useful to increase the scale of the GUI for high resolution displays. This setting can only be configured by editing the settings configuration file. +font size +--------- + +:Type: integer +:Range: 12 to 20 +:Default: 16 + +Allows to specify glyph size for in-game fonts. +Note: default bitmap fonts are supposed to work with 16px size, otherwise glyphs will be blurry. +TrueType fonts do not have this issue. + +ttf resolution +-------------- + +:Type: integer +:Range: 48 to 960 +:Default: 96 + +Allows to specify resolution for in-game TrueType fonts. +Note: actual resolution depends on "scaling factor" setting value, this value is for 1.0 scaling factor. + menu transparency ----------------- diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index b733cc7c49..4c3f4579f7 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -16,7 +16,7 @@ If the setting is 2, the crosshair is the color of the color crosshair owned set If the setting is 3, both the tool tip background and the crosshair are colored. The crosshair is not visible if crosshair is false. -This setting can only be configured by editing the settings configuration file. +This setting can be configured in Advanced tab of the launcher. show projectile damage ---------------------- @@ -27,7 +27,7 @@ show projectile damage If this setting is true, the damage bonus of arrows and bolts will show on their tooltip. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in Advanced tab of the launcher. show melee info --------------- @@ -36,9 +36,9 @@ show melee info :Range: True/False :Default: False -If this setting is true, the reach and speed of melee weapons will show on their tooltip. +If this setting is true, the reach and speed of weapons will show on their tooltip. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in Advanced tab of the launcher. show enchant chance ------------------- @@ -49,7 +49,7 @@ show enchant chance Whether or not the chance of success will be displayed in the enchanting menu. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in Advanced tab of the launcher. best attack ----------- @@ -73,13 +73,13 @@ can loot during death animation If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. However disposing corpses during death animation is not recommended - death counter may not be incremented, and this behaviour can break quests. -This is how original Morrowind behaves. +This is how Morrowind behaves. If this setting is false, player has to wait until end of death animation in all cases. This case is more safe, but makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in Advanced tab of the launcher. difficulty ---------- @@ -97,19 +97,19 @@ and values above 500 will result in the player inflicting no damage. This setting can be controlled in game with the Difficulty slider in the Prefs panel of the Options menu. -classic reflect absorb attribute behavior +classic reflected absorb spells behavior ----------------------------------------- :Type: boolean :Range: True/False :Default: True -If this setting is true, "Absorb Attribute" spells which were reflected by the target are not "mirrored", -and the caster will absorb their own attribute resulting in no effect on both the caster and the target. +If this setting is true, effects of Absorb spells which were reflected by the target are not mirrored, +and the caster will absorb their own stat resulting in no effect on either the caster and the target. This makes the gameplay as a mage easier, but these spells become imbalanced. -This is how the original Morrowind behaves. +This is how Morrowind behaves. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in Advanced tab of the launcher. show effect duration -------------------- @@ -121,7 +121,7 @@ show effect duration Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in Advanced tab of the launcher. enchanted weapons are magical ----------------------------- @@ -131,9 +131,9 @@ enchanted weapons are magical :Default: True Make enchanted weapons without Magical flag bypass normal weapons resistance (and weakness) certain creatures have. -This is how original Morrowind behaves. +This is how Morrowind behaves. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in Advanced tab of the launcher. prevent merchant equipping -------------------------- @@ -144,7 +144,7 @@ prevent merchant equipping Prevent merchants from equipping items that are sold to them. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in Advanced tab of the launcher. followers attack on sight ------------------------- @@ -157,7 +157,7 @@ Make player followers and escorters start combat with enemies who have started c Otherwise they wait for the enemies or the player to do an attack first. Please note this setting has not been extensively tested and could have side effects with certain quests. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in Advanced tab of the launcher. use additional anim sources --------------------------- @@ -182,4 +182,4 @@ barter disposition change is permanent If this setting is true, disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them. This imitates the option Morrowind Code Patch offers. -This setting can be toggled with a checkbox in Advanced tab of the launcher. +This setting can be toggled in Advanced tab of the launcher. diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index 56d76a8d10..f7c86b5678 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -8,10 +8,26 @@ If you are familiar with ``.ini`` tweaks in Morrowind or the other games, this w All settings described in this section are changed in ``settings.cfg``, located in your OpenMW user directory. See :doc:`../paths` for this location. +Changing Settings +################# + +#. Once you have located your ``settings.cfg`` file, open it in a plain text editor. +#. Find the setting(s) you wish to change in the following pages. +#. If the setting is not already in ``settings.cfg``, + add it by copy and pasting the name exactly as written in this guide. +#. Set the value of the setting by typing ``= `` after the setting on the same line, + using an appropriate value in place of ````. +#. If this is the first setting from it's category that you're adding, + be sure to add the heading in square brackets ``[]`` above it using just the setting type, + i.e. without the word "Settings". + + For example, to delay tooltips popping up by 1 second, add the line ``tooltip delay = 1.0``. + Then to the line above, type ``[GUI]``, as the tooltip delay setting comes from the "GUI Settings" section. + Although this guide attempts to be comprehensive and up to date, you will always be able to find the full list of settings available and their default values in ``settings-default.cfg`` in your main OpenMW installation directory. -The ranges I have included with each setting are the physically possible ranges, not recommendations. +The ranges included with each setting are the physically possible ranges, not recommendations. .. warning:: As the title suggests, these are advanced settings. diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 17d0929174..be1ecebf0c 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -1,5 +1,5 @@ -Shader Settings -############### +Shaders Settings +################ force shaders ------------- diff --git a/files/mygui/core.skin b/files/mygui/core.skin index 9aa5664510..ee9135554e 100644 --- a/files/mygui/core.skin +++ b/files/mygui/core.skin @@ -2,7 +2,6 @@ - diff --git a/files/mygui/openmw_alchemy_window.layout b/files/mygui/openmw_alchemy_window.layout index a8e5431fbf..714872fc3f 100644 --- a/files/mygui/openmw_alchemy_window.layout +++ b/files/mygui/openmw_alchemy_window.layout @@ -74,7 +74,20 @@ - + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_book.layout b/files/mygui/openmw_book.layout index c83c4982b2..e6f0f858c0 100644 --- a/files/mygui/openmw_book.layout +++ b/files/mygui/openmw_book.layout @@ -32,11 +32,13 @@ + + diff --git a/files/mygui/openmw_chargen_birth.layout b/files/mygui/openmw_chargen_birth.layout index a137d83d13..18d4123407 100644 --- a/files/mygui/openmw_chargen_birth.layout +++ b/files/mygui/openmw_chargen_birth.layout @@ -16,7 +16,7 @@ > - + diff --git a/files/mygui/openmw_chargen_class.layout b/files/mygui/openmw_chargen_class.layout index 98c8e4fded..1402d9b5cf 100644 --- a/files/mygui/openmw_chargen_class.layout +++ b/files/mygui/openmw_chargen_class.layout @@ -27,11 +27,11 @@ - + - + @@ -73,7 +73,7 @@ - + diff --git a/files/mygui/openmw_chargen_create_class.layout b/files/mygui/openmw_chargen_create_class.layout index c04cfe0646..fe491eb6d3 100644 --- a/files/mygui/openmw_chargen_create_class.layout +++ b/files/mygui/openmw_chargen_create_class.layout @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -74,7 +74,7 @@ - + diff --git a/files/mygui/openmw_chargen_generate_class_result.layout b/files/mygui/openmw_chargen_generate_class_result.layout index d49f57dded..48a203b18f 100644 --- a/files/mygui/openmw_chargen_generate_class_result.layout +++ b/files/mygui/openmw_chargen_generate_class_result.layout @@ -24,7 +24,7 @@ - + diff --git a/files/mygui/openmw_chargen_race.layout b/files/mygui/openmw_chargen_race.layout index 31f0436d57..ea7ec6179f 100644 --- a/files/mygui/openmw_chargen_race.layout +++ b/files/mygui/openmw_chargen_race.layout @@ -1,7 +1,7 @@ - + @@ -54,28 +54,28 @@ - + - + - + - + - + - + - + diff --git a/files/mygui/openmw_chargen_select_attribute.layout b/files/mygui/openmw_chargen_select_attribute.layout index 0821d4a579..57bd4ebc65 100644 --- a/files/mygui/openmw_chargen_select_attribute.layout +++ b/files/mygui/openmw_chargen_select_attribute.layout @@ -1,28 +1,31 @@ - - + + - + - - - - - - - - + + + + + + + + - - - + + + + + + diff --git a/files/mygui/openmw_chargen_select_skill.layout b/files/mygui/openmw_chargen_select_skill.layout index d1061882d1..3737ea9043 100644 --- a/files/mygui/openmw_chargen_select_skill.layout +++ b/files/mygui/openmw_chargen_select_skill.layout @@ -1,10 +1,10 @@ - - + + - + @@ -44,20 +44,23 @@ - - - - - - - - - + + + + + + + + + - - - + + + + + + diff --git a/files/mygui/openmw_chargen_select_specialization.layout b/files/mygui/openmw_chargen_select_specialization.layout index c4fe6c6318..47dc32f629 100644 --- a/files/mygui/openmw_chargen_select_specialization.layout +++ b/files/mygui/openmw_chargen_select_specialization.layout @@ -2,7 +2,7 @@ - + @@ -22,9 +22,12 @@ - - - + + + + + + diff --git a/files/mygui/openmw_confirmation_dialog.layout b/files/mygui/openmw_confirmation_dialog.layout index c5eb573a7c..246c8aa8ff 100644 --- a/files/mygui/openmw_confirmation_dialog.layout +++ b/files/mygui/openmw_confirmation_dialog.layout @@ -13,7 +13,7 @@ - + diff --git a/files/mygui/openmw_container_window.layout b/files/mygui/openmw_container_window.layout index 6bb585e50e..5783779db9 100644 --- a/files/mygui/openmw_container_window.layout +++ b/files/mygui/openmw_container_window.layout @@ -9,7 +9,7 @@ - + diff --git a/files/mygui/openmw_count_window.layout b/files/mygui/openmw_count_window.layout index 520d49fa3f..2e083dceac 100644 --- a/files/mygui/openmw_count_window.layout +++ b/files/mygui/openmw_count_window.layout @@ -16,12 +16,12 @@ - + - + - + diff --git a/files/mygui/openmw_enchanting_dialog.layout b/files/mygui/openmw_enchanting_dialog.layout index ed3cfb03e8..4738cdc13d 100644 --- a/files/mygui/openmw_enchanting_dialog.layout +++ b/files/mygui/openmw_enchanting_dialog.layout @@ -50,7 +50,7 @@ - + diff --git a/files/mygui/openmw_font.xml b/files/mygui/openmw_font.xml index e4037561d7..6ffe3017e5 100644 --- a/files/mygui/openmw_font.xml +++ b/files/mygui/openmw_font.xml @@ -2,8 +2,6 @@ - - diff --git a/files/mygui/openmw_inventory_window.layout b/files/mygui/openmw_inventory_window.layout index 6221799b5e..e935e2f5cb 100644 --- a/files/mygui/openmw_inventory_window.layout +++ b/files/mygui/openmw_inventory_window.layout @@ -29,7 +29,7 @@ - + diff --git a/files/mygui/openmw_journal.layout b/files/mygui/openmw_journal.layout index 964b5ea955..5d6e290662 100644 --- a/files/mygui/openmw_journal.layout +++ b/files/mygui/openmw_journal.layout @@ -25,10 +25,12 @@ + + @@ -59,8 +61,9 @@ - - + + + diff --git a/files/mygui/openmw_journal.skin.xml b/files/mygui/openmw_journal.skin.xml index 42be2bb629..0702d8c5eb 100644 --- a/files/mygui/openmw_journal.skin.xml +++ b/files/mygui/openmw_journal.skin.xml @@ -2,7 +2,7 @@ - + @@ -13,15 +13,13 @@ + - - - - + - + diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 5b6f14dd5c..9af0e79660 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -127,7 +127,6 @@ - @@ -140,8 +139,6 @@ - - @@ -153,7 +150,6 @@ - diff --git a/files/mygui/openmw_trade_window.layout b/files/mygui/openmw_trade_window.layout index 0af9d59fe8..30e22302d3 100644 --- a/files/mygui/openmw_trade_window.layout +++ b/files/mygui/openmw_trade_window.layout @@ -48,7 +48,7 @@ - + @@ -62,7 +62,7 @@ - + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index a272ae84a0..ef618b3160 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -156,10 +156,10 @@ - + - + @@ -443,7 +443,6 @@ - @@ -458,7 +457,6 @@ ------------------------------------------------------ --> - @@ -593,7 +591,6 @@ - @@ -730,7 +727,6 @@ - diff --git a/files/opencs/cell.png b/files/opencs/cell.png index 9127dd5e5c..4c3fbe2515 100644 Binary files a/files/opencs/cell.png and b/files/opencs/cell.png differ diff --git a/files/opencs/debug-profile.png b/files/opencs/debug-profile.png new file mode 100644 index 0000000000..ddb01da437 Binary files /dev/null and b/files/opencs/debug-profile.png differ diff --git a/files/opencs/edit-undo.png b/files/opencs/edit-undo.png deleted file mode 100644 index 8b0fef9a83..0000000000 Binary files a/files/opencs/edit-undo.png and /dev/null differ diff --git a/files/opencs/error-log.png b/files/opencs/error-log.png new file mode 100644 index 0000000000..df698d1453 Binary files /dev/null and b/files/opencs/error-log.png differ diff --git a/files/opencs/Info.png b/files/opencs/info.png similarity index 100% rename from files/opencs/Info.png rename to files/opencs/info.png diff --git a/files/opencs/instance.png b/files/opencs/instance.png new file mode 100644 index 0000000000..ce63e64ed6 Binary files /dev/null and b/files/opencs/instance.png differ diff --git a/files/opencs/leveled-creature.png b/files/opencs/levelled-creature.png similarity index 100% rename from files/opencs/leveled-creature.png rename to files/opencs/levelled-creature.png diff --git a/files/opencs/leveled-item.png b/files/opencs/levelled-item.png similarity index 100% rename from files/opencs/leveled-item.png rename to files/opencs/levelled-item.png diff --git a/files/opencs/magic-effect.png b/files/opencs/magic-effect.png index 4901724c59..44b682bf4f 100644 Binary files a/files/opencs/magic-effect.png and b/files/opencs/magic-effect.png differ diff --git a/files/opencs/menu-close.png b/files/opencs/menu-close.png new file mode 100644 index 0000000000..81bc986775 Binary files /dev/null and b/files/opencs/menu-close.png differ diff --git a/files/opencs/menu-exit.png b/files/opencs/menu-exit.png new file mode 100644 index 0000000000..f583536fb6 Binary files /dev/null and b/files/opencs/menu-exit.png differ diff --git a/files/opencs/menu-merge.png b/files/opencs/menu-merge.png new file mode 100644 index 0000000000..c76288ae16 Binary files /dev/null and b/files/opencs/menu-merge.png differ diff --git a/files/opencs/menu-new-addon.png b/files/opencs/menu-new-addon.png new file mode 100644 index 0000000000..df137b2b20 Binary files /dev/null and b/files/opencs/menu-new-addon.png differ diff --git a/files/opencs/menu-new-game.png b/files/opencs/menu-new-game.png new file mode 100644 index 0000000000..701daf34b1 Binary files /dev/null and b/files/opencs/menu-new-game.png differ diff --git a/files/opencs/menu-new-window.png b/files/opencs/menu-new-window.png new file mode 100644 index 0000000000..4a42da0d12 Binary files /dev/null and b/files/opencs/menu-new-window.png differ diff --git a/files/opencs/menu-open.png b/files/opencs/menu-open.png new file mode 100644 index 0000000000..3766e17549 Binary files /dev/null and b/files/opencs/menu-open.png differ diff --git a/files/opencs/menu-preferences.png b/files/opencs/menu-preferences.png new file mode 100644 index 0000000000..4644297ad0 Binary files /dev/null and b/files/opencs/menu-preferences.png differ diff --git a/files/opencs/menu-redo.png b/files/opencs/menu-redo.png new file mode 100644 index 0000000000..0cd0eedcae Binary files /dev/null and b/files/opencs/menu-redo.png differ diff --git a/files/opencs/menu-reload.png b/files/opencs/menu-reload.png new file mode 100644 index 0000000000..2b780598c4 Binary files /dev/null and b/files/opencs/menu-reload.png differ diff --git a/files/opencs/menu-save.png b/files/opencs/menu-save.png new file mode 100644 index 0000000000..4be88765be Binary files /dev/null and b/files/opencs/menu-save.png differ diff --git a/files/opencs/menu-search.png b/files/opencs/menu-search.png new file mode 100644 index 0000000000..1ec63f0e82 Binary files /dev/null and b/files/opencs/menu-search.png differ diff --git a/files/opencs/menu-status-bar.png b/files/opencs/menu-status-bar.png new file mode 100644 index 0000000000..dfb72b1e13 Binary files /dev/null and b/files/opencs/menu-status-bar.png differ diff --git a/files/opencs/menu-undo.png b/files/opencs/menu-undo.png new file mode 100644 index 0000000000..bd177ce65a Binary files /dev/null and b/files/opencs/menu-undo.png differ diff --git a/files/opencs/menu-verify.png b/files/opencs/menu-verify.png new file mode 100644 index 0000000000..a7878ebb3f Binary files /dev/null and b/files/opencs/menu-verify.png differ diff --git a/files/opencs/metadata.png b/files/opencs/metadata.png new file mode 100644 index 0000000000..34c78ffd61 Binary files /dev/null and b/files/opencs/metadata.png differ diff --git a/files/opencs/object.png b/files/opencs/object.png new file mode 100644 index 0000000000..4f552151db Binary files /dev/null and b/files/opencs/object.png differ diff --git a/files/opencs/placeholder.png b/files/opencs/placeholder.png index 1d3df3c47d..b9db9f1888 100644 Binary files a/files/opencs/placeholder.png and b/files/opencs/placeholder.png differ diff --git a/files/opencs/region-map.png b/files/opencs/region-map.png new file mode 100644 index 0000000000..7631847bee Binary files /dev/null and b/files/opencs/region-map.png differ diff --git a/files/opencs/region.png b/files/opencs/region.png index a100892437..2ebaeb0285 100644 Binary files a/files/opencs/region.png and b/files/opencs/region.png differ diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index 0afd855f92..93b47dbdda 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -15,6 +15,7 @@ clothing.png container.png creature.png + debug-profile.png dialogue-info.png dialogue-journal.png dialogue-regular.png @@ -27,34 +28,60 @@ journal-topics.png door.png enchantment.png + error-log.png faction.png filter.png global-variable.png gmst.png - Info.png + info.png ingredient.png + instance.png land-heightmap.png land-texture.png - leveled-creature.png - leveled-item.png + levelled-creature.png + levelled-item.png light.png lockpick.png magic-effect.png magicrabbit.png map.png + probe.png + menu-close.png + menu-exit.png + menu-new-addon.png + menu-new-game.png + menu-new-window.png + menu-merge.png + menu-open.png + menu-preferences.png + menu-reload.png + menu-redo.png + menu-save.png + menu-search.png + menu-status-bar.png + menu-undo.png + menu-verify.png + metadata.png miscellaneous.png list-modified.png npc.png + object.png pathgrid.png potion.png - probe.png race.png random-item.png random.png list-removed.png + region.png + region-map.png repair.png + run-log.png + run-openmw.png + scene.png script.png skill.png + start-script.png + stop-openmw.png sound-generator.png sound.png spell.png @@ -64,7 +91,6 @@ record-next.png record-previous.png record-delete.png - record-revert.png record-preview.png record-clone.png record-add.png diff --git a/files/opencs/run-log.png b/files/opencs/run-log.png new file mode 100644 index 0000000000..463ead176d Binary files /dev/null and b/files/opencs/run-log.png differ diff --git a/files/opencs/run-openmw.png b/files/opencs/run-openmw.png new file mode 100644 index 0000000000..1033d62baa Binary files /dev/null and b/files/opencs/run-openmw.png differ diff --git a/files/opencs/scene.png b/files/opencs/scene.png new file mode 100644 index 0000000000..a9fc03f769 Binary files /dev/null and b/files/opencs/scene.png differ diff --git a/files/opencs/start-script.png b/files/opencs/start-script.png new file mode 100644 index 0000000000..73ed157b9e Binary files /dev/null and b/files/opencs/start-script.png differ diff --git a/files/opencs/stop-openmw.png b/files/opencs/stop-openmw.png new file mode 100644 index 0000000000..d8f8096724 Binary files /dev/null and b/files/opencs/stop-openmw.png differ diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c2ac2eb1ca..ea6f6e7b66 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -142,6 +142,12 @@ global = false # Scales GUI window and widget size. (<1.0 is smaller, >1.0 is larger). scaling factor = 1.0 +# Size of in-game fonts +font size = 16 + +# Resolution of TrueType fonts glyphs +ttf resolution = 96 + # Transparency of GUI windows (0.0 to 1.0, transparent to opaque). menu transparency = 0.84 @@ -182,7 +188,7 @@ show owned = 0 # Show damage bonus of arrow and bolts. show projectile damage = false -# Show additional melee weapon info: reach and attack speed +# Show additional weapon info: reach and attack speed show melee info = false # Show success probability in self-enchant dialog @@ -194,8 +200,8 @@ best attack = false # Difficulty. Expressed as damage dealt and received. (e.g. -100 to 100). difficulty = 0 -# Replicate how reflected "absorb attribute" spells do not have any effect in Morrowind engine. The caster absorbs the attribute from themselves. -classic reflect absorb attribute behavior = true +# Make reflected Absorb spells have no practical effect, like in Morrowind. +classic reflected absorb spells behavior = true # Show duration of magic effect and lights in the spells window. show effect duration = false diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index ee91df75fa..4c4a253f1f 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -286,6 +286,6 @@ void main(void) #if REFRACTION gl_FragData[0].w = 1.0; #else - gl_FragData[0].w = clamp(fresnel*6.0 + specular, 0.0, 1.0); //clamp(fresnel*2.0 + specular, 0.0, 1.0); + gl_FragData[0].w = clamp(fresnel*6.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); #endif } diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 8395d028f6..0793345c8d 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -73,7 +73,7 @@ Enchanted weapons are magical - + @@ -86,6 +86,16 @@ + + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored -- like in Morrowind.</p></body></html> + + + Classic reflected Absorb spells behavior + + + diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 5a6b7265af..5c26e1044a 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -62,7 +62,7 @@ - Select a profiile + Select a content list