diff --git a/AUTHORS.md b/AUTHORS.md index cee20d898..f6a62c4ce 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -73,10 +73,10 @@ Programmers Finbar Crago (finbar-crago) Florian Weber (Florianjw) Gašper Sedej + Gijsbert ter Horst (Ghostbird) Gohan1989 gugus/gus guidoj - Hallfaer Tuilinn Haoda Wang (h313) hristoast Internecine diff --git a/CHANGELOG.md b/CHANGELOG.md index b6d95cd0d..a7b346414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.46.0 ------ + Bug #1515: Opening console masks dialogue, inventory menu Bug #2969: Scripted items can stack Bug #2987: Editor: some chance and AI data fields can overflow Bug #3006: 'else if' operator breaks script compilation @@ -12,7 +13,9 @@ Bug #3765: DisableTeleporting makes Mark/Recall/Intervention effects undetectable Bug #3778: [Mod] Improved Thrown Weapon Projectiles - weapons have wrong transformation during throw animation Bug #3812: Wrong multiline tooltips width when word-wrapping is enabled + Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect Bug #4329: Removed birthsign abilities are restored after reloading the save + Bug #4341: Error message about missing GDB is too vague Bug #4383: Bow model obscures crosshair when arrow is drawn Bug #4384: Resist Normal Weapons only checks ammunition for ranged weapons Bug #4411: Reloading a saved game while falling prevents damage in some cases @@ -35,6 +38,7 @@ Bug #4768: Fallback numerical value recovery chokes on invalid arguments Bug #4775: Slowfall effect resets player jumping flag Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken + Bug #4787: Sneaking makes 1st person walking/bobbing animation super-slow Bug #4797: Player sneaking and running stances are not accounted for when in air Bug #4800: Standing collisions are not updated immediately when an object is teleported without a cell change Bug #4803: Stray special characters before begin statement break script compilation @@ -50,14 +54,17 @@ Bug #4828: Potion looping effects VFX are not shown for NPCs Bug #4837: CTD when a mesh with NiLODNode root node with particles is loaded Bug #4841: Russian localization ignores implicit keywords + Bug #4844: Data race in savegame loading / GlobalMap render Bug #4847: Idle animation reset oddities Bug #4851: No shadows since switch to OSG Bug #4860: Actors outside of processing range visible for one frame after spawning Bug #4867: Arbitrary text after local variable declarations breaks script compilation Bug #4876: AI ratings handling inconsistencies Bug #4877: Startup script executes only on a new game start + Bug #4879: SayDone returns 0 on the frame Say is called Bug #4888: Global variable stray explicit reference calls break script compilation Bug #4896: Title screen music doesn't loop + Bug #4902: Using scrollbars in settings causes resolution to change Bug #4911: Editor: QOpenGLContext::swapBuffers() warning with Qt5 Bug #4916: Specular power (shininess) material parameter is ignored when shaders are used. Bug #4918: Abilities don't play looping VFX when they're initially applied @@ -71,6 +78,7 @@ Bug #4945: Poor random magic magnitude distribution Bug #4947: Player character doesn't use lip animation Bug #4948: Footstep sounds while levitating on ground level + Bug #4961: Flying creature combat engagement takes z-axis into account Bug #4963: Enchant skill progress is incorrect Bug #4964: Multiple effect spell projectile sounds play louder than vanilla Bug #4965: Global light attenuation settings setup is lacking @@ -82,7 +90,15 @@ Bug #4984: "Friendly hits" feature should be used only for player's followers Bug #4989: Object dimension-dependent VFX scaling behavior is inconsistent Bug #4990: Dead bodies prevent you from hitting + Bug #4999: Drop instruction behaves differently from vanilla + Bug #5001: Possible data race in the Animation::setAlpha() Bug #5004: Werewolves shield their eyes during storm + Bug #5018: Spell tooltips don't support purely negative magnitudes + Bug #5025: Data race in the ICO::setMaximumNumOfObjectsToCompilePerFrame() + Bug #5028: Offered price caps are not trading-specific + Bug #5038: Enchanting success chance calculations are blatantly wrong + Bug #5047: # in cell names sets color + Bug #5050: Invalid spell effects are not handled gracefully Feature #1774: Handle AvoidNode Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls @@ -90,12 +106,16 @@ Feature #3610: Option to invert X axis Feature #3893: Implicit target for "set" function in console Feature #3980: In-game option to disable controller + Feature #3999: Shift + Double Click should maximize/restore menu size Feature #4001: Toggle sneak controller shortcut Feature #4209: Editor: Faction rank sub-table + Feature #4255: Handle broken RepairedOnMe script function + Feature #4316: Implement RaiseRank/LowerRank functions properly Feature #4360: Improve default controller bindings Feature #4673: Weapon sheathing Feature #4675: Support for NiRollController Feature #4730: Native animated containers support + Feature #4784: Launcher: Duplicate Content Lists Feature #4812: Support NiSwitchNode Feature #4836: Daytime node switch Feature #4859: Make water reflections more configurable @@ -106,6 +126,12 @@ Feature #4968: Scalable UI widget skins Feature #4994: Persistent pinnable windows hiding Feature #5000: Compressed BSA format support + Feature #5010: Native graphics herbalism support + Feature #5031: Make GetWeaponType function return different values for tools + Feature #5033: Magic armor mitigation for creatures + Feature #5034: Make enchanting window stay open after a failed attempt + Feature #5036: Allow scripted faction leaving + Feature #5051: Provide a separate textures for scrollbars Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption Task #4721: Add NMake support to the Windows prebuild script diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index 43d0a05ed..d844f6886 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include std::string bodyPartLabel(int idx) { @@ -659,7 +659,7 @@ std::string bodyPartFlags(int flags) (ESM::BodyPart::BPF_Female| ESM::BodyPart::BPF_NotPlayable)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -680,7 +680,7 @@ std::string cellFlags(int flags) ESM::Cell::QuasiEx| 0x00000040)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -696,7 +696,7 @@ std::string containerFlags(int flags) ESM::Container::Organic| ESM::Container::Respawn)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -722,7 +722,7 @@ std::string creatureFlags(int flags) ESM::Creature::Weapon| ESM::Creature::Essential)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%02X)") % flags); + properties += Misc::StringUtils::format("(0x%02X)", flags); return properties; } @@ -737,7 +737,7 @@ std::string landFlags(int flags) if (flags & 0x00000004) properties += "Unknown3 "; if (flags & 0x00000002) properties += "Unknown2 "; if (flags & 0xFFFFFFF8) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -751,7 +751,7 @@ std::string itemListFlags(int flags) (ESM::ItemLevList::AllLevels| ESM::ItemLevList::Each)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -762,7 +762,7 @@ std::string creatureListFlags(int flags) if (flags & ESM::CreatureLevList::AllLevels) properties += "AllLevels "; int unused = (0xFFFFFFFF ^ ESM::CreatureLevList::AllLevels); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -790,7 +790,7 @@ std::string lightFlags(int flags) ESM::Light::Negative| ESM::Light::OffDefault)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -816,7 +816,7 @@ std::string magicEffectFlags(int flags) if (flags & ESM::MagicEffect::NegativeLight) properties += "NegativeLight "; if (flags & 0xFFFC0000) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -838,7 +838,7 @@ std::string npcFlags(int flags) ESM::NPC::Respawn| ESM::NPC::Essential)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%02X)") % flags); + properties += Misc::StringUtils::format("(0x%02X)", flags); return properties; } @@ -853,7 +853,7 @@ std::string raceFlags(int flags) (ESM::Race::Playable| ESM::Race::Beast)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -869,7 +869,7 @@ std::string spellFlags(int flags) ESM::Spell::F_PCStart| ESM::Spell::F_Always)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } @@ -886,6 +886,6 @@ std::string weaponFlags(int flags) (ESM::Weapon::Magical| ESM::Weapon::Silver)); if (flags & unused) properties += "Invalid "; - properties += str(boost::format("(0x%08X)") % flags); + properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 2149d1772..c4b14a341 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace { @@ -12,7 +12,7 @@ namespace void printAIPackage(ESM::AIPackage p) { std::cout << " AI Type: " << aiTypeLabel(p.mType) - << " (" << boost::format("0x%08X") % p.mType << ")" << std::endl; + << " (" << Misc::StringUtils::format("0x%08X", p.mType) << ")" << std::endl; if (p.mType == ESM::AI_Wander) { std::cout << " Distance: " << p.mWander.mDistance << std::endl; @@ -46,7 +46,7 @@ void printAIPackage(ESM::AIPackage p) std::cout << " Activate Unknown: " << p.mActivate.mUnk << std::endl; } else { - std::cout << " BadPackage: " << boost::format("0x%08x") % p.mType << std::endl; + std::cout << " BadPackage: " << Misc::StringUtils::format("0x%08X", p.mType) << std::endl; } if (!p.mCellName.empty()) @@ -64,7 +64,7 @@ std::string ruleString(ESM::DialInfo::SelectStruct ss) char indicator = rule[2]; std::string type_str = "INVALID"; - std::string func_str = str(boost::format("INVALID=%s") % rule.substr(1,3)); + std::string func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1,3)); int func; std::istringstream iss(rule.substr(2,2)); iss >> func; @@ -104,7 +104,7 @@ std::string ruleString(ESM::DialInfo::SelectStruct ss) // for all types not qual to one. If this wasn't true, go back to // the error message. if (type != '1' && rule[3] != 'X') - func_str = str(boost::format("INVALID=%s") % rule.substr(1,3)); + func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1,3)); char oper = rule[4]; std::string oper_str = "??"; @@ -122,8 +122,7 @@ std::string ruleString(ESM::DialInfo::SelectStruct ss) std::ostringstream stream; stream << ss.mValue; - std::string result = str(boost::format("%-12s %-32s %2s %s") - % type_str % func_str % oper_str % stream.str()); + std::string result = Misc::StringUtils::format("%-12s %-32s %2s %s", type_str, func_str, oper_str, stream.str()); return result; } @@ -156,13 +155,13 @@ void printTransport(const std::vector& transport) for (const ESM::Transport::Dest& dest : transport) { std::cout << " Destination Position: " - << boost::format("%12.3f") % dest.mPos.pos[0] << "," - << boost::format("%12.3f") % dest.mPos.pos[1] << "," - << boost::format("%12.3f") % dest.mPos.pos[2] << ")" << std::endl; + << Misc::StringUtils::format("%12.3f", dest.mPos.pos[0]) << "," + << Misc::StringUtils::format("%12.3f", dest.mPos.pos[1]) << "," + << Misc::StringUtils::format("%12.3f", dest.mPos.pos[2]) << ")" << std::endl; std::cout << " Destination Rotation: " - << boost::format("%9.6f") % dest.mPos.rot[0] << "," - << boost::format("%9.6f") % dest.mPos.rot[1] << "," - << boost::format("%9.6f") % dest.mPos.rot[2] << ")" << std::endl; + << Misc::StringUtils::format("%9.6f", dest.mPos.rot[0]) << "," + << Misc::StringUtils::format("%9.6f", dest.mPos.rot[1]) << "," + << Misc::StringUtils::format("%9.6f", dest.mPos.rot[2]) << ")" << std::endl; if (!dest.mCellName.empty()) std::cout << " Destination Cell: " << dest.mCellName << std::endl; } @@ -542,7 +541,7 @@ void Record::print() std::cout << " Water Level: " << mData.mWater << std::endl; } else - std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl; + std::cout << " Map Color: " << Misc::StringUtils::format("0x%08X", mData.mMapColor) << std::endl; std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; @@ -607,7 +606,7 @@ void Record::print() std::cout << " Flags: " << containerFlags(mData.mFlags) << std::endl; std::cout << " Weight: " << mData.mWeight << std::endl; for (const ESM::ContItem &item : mData.mInventory.mList) - std::cout << " Inventory: Count: " << boost::format("%4d") % item.mCount + std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) << " Item: " << item.mItem.toString() << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } @@ -653,7 +652,7 @@ void Record::print() std::cout << " Gold: " << mData.mData.mGold << std::endl; for (const ESM::ContItem &item : mData.mInventory.mList) - std::cout << " Inventory: Count: " << boost::format("%4d") % item.mCount + std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) << " Item: " << item.mItem.toString() << std::endl; for (const std::string &spell : mData.mSpells.mList) @@ -669,7 +668,7 @@ void Record::print() std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; - std::cout << " AI Services:" << boost::format("0x%08X") % mData.mAiData.mServices << std::endl; + std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; for (const ESM::AIPackage &package : mData.mAiPackage.mList) printAIPackage(package); @@ -1073,7 +1072,7 @@ void Record::print() } for (const ESM::ContItem &item : mData.mInventory.mList) - std::cout << " Inventory: Count: " << boost::format("%4d") % item.mCount + std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) << " Item: " << item.mItem.toString() << std::endl; for (const std::string &spell : mData.mSpells.mList) @@ -1089,7 +1088,7 @@ void Record::print() std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; - std::cout << " AI Services:" << boost::format("0x%08X") % mData.mAiData.mServices << std::endl; + std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; for (const ESM::AIPackage &package : mData.mAiPackage.mList) printAIPackage(package); @@ -1212,7 +1211,7 @@ void Record::print() std::cout << " ByteCode: "; for (const unsigned char &byte : mData.mScriptData) - std::cout << boost::format("%02X") % (int)(byte); + std::cout << Misc::StringUtils::format("%02X", (int)(byte)); std::cout << std::endl; if (mPrintPlain) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 6f2389de3..055426f30 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -39,10 +39,13 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: const QString encoding = mGameSettings.value("encoding", "win1252"); mSelector->setEncoding(encoding); - mProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this); + mNewProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this); + mCloneProfileDialog = new TextInputDialog(tr("Clone Content List"), tr("Content List name:"), this); - connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), - this, SLOT(updateOkButton(QString))); + connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), + this, SLOT(updateNewProfileOkButton(QString))); + connect(mCloneProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), + this, SLOT(updateCloneProfileOkButton(QString))); buildView(); loadSettings(); @@ -61,6 +64,7 @@ void Launcher::DataFilesPage::buildView() //tool buttons ui.newProfileButton->setToolTip ("Create a new Content List"); + ui.cloneProfileButton->setToolTip ("Clone the current Content List"); ui.deleteProfileButton->setToolTip ("Delete an existing Content List"); //combo box @@ -70,6 +74,7 @@ void Launcher::DataFilesPage::buildView() // Add the actions to the toolbuttons ui.newProfileButton->setDefaultAction (ui.newProfileAction); + ui.cloneProfileButton->setDefaultAction (ui.cloneProfileAction); ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); //establish connections @@ -246,10 +251,10 @@ void Launcher::DataFilesPage::slotProfileChanged(int index) void Launcher::DataFilesPage::on_newProfileAction_triggered() { - if (mProfileDialog->exec() != QDialog::Accepted) + if (mNewProfileDialog->exec() != QDialog::Accepted) return; - QString profile = mProfileDialog->lineEdit()->text(); + QString profile = mNewProfileDialog->lineEdit()->text(); if (profile.isEmpty()) return; @@ -273,6 +278,20 @@ void Launcher::DataFilesPage::addProfile (const QString &profile, bool setAsCurr setProfile (ui.profilesComboBox->findText (profile), false); } +void Launcher::DataFilesPage::on_cloneProfileAction_triggered() +{ + if (mCloneProfileDialog->exec() != QDialog::Accepted) + return; + + QString profile = mCloneProfileDialog->lineEdit()->text(); + + if (profile.isEmpty()) + return; + + mLauncherSettings.setContentList(profile, selectedFilePaths()); + addProfile(profile, true); +} + void Launcher::DataFilesPage::on_deleteProfileAction_triggered() { QString profile = ui.profilesComboBox->currentText(); @@ -295,17 +314,16 @@ void Launcher::DataFilesPage::on_deleteProfileAction_triggered() checkForDefaultProfile(); } -void Launcher::DataFilesPage::updateOkButton(const QString &text) +void Launcher::DataFilesPage::updateNewProfileOkButton(const QString &text) { // We do this here because we need the profiles combobox text - if (text.isEmpty()) { - mProfileDialog->setOkButtonEnabled(false); - return; - } + mNewProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1); +} - (ui.profilesComboBox->findText(text) == -1) - ? mProfileDialog->setOkButtonEnabled(true) - : mProfileDialog->setOkButtonEnabled(false); +void Launcher::DataFilesPage::updateCloneProfileOkButton(const QString &text) +{ + // We do this here because we need the profiles combobox text + mCloneProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1); } void Launcher::DataFilesPage::checkForDefaultProfile() diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 2cbace38e..36a0db616 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -62,9 +62,11 @@ namespace Launcher void slotProfileDeleted(const QString &item); void slotAddonDataChanged (); - void updateOkButton(const QString &text); + void updateNewProfileOkButton(const QString &text); + void updateCloneProfileOkButton(const QString &text); void on_newProfileAction_triggered(); + void on_cloneProfileAction_triggered(); void on_deleteProfileAction_triggered(); public: @@ -73,7 +75,8 @@ namespace Launcher private: - TextInputDialog *mProfileDialog; + TextInputDialog *mNewProfileDialog; + TextInputDialog *mCloneProfileDialog; Files::ConfigurationManager &mCfgMgr; diff --git a/apps/opencs/model/tools/referencecheck.cpp b/apps/opencs/model/tools/referencecheck.cpp index 76bfeb3ba..d8ff9f20e 100644 --- a/apps/opencs/model/tools/referencecheck.cpp +++ b/apps/opencs/model/tools/referencecheck.cpp @@ -53,7 +53,7 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages &message // If object have creature soul trapped, check if that creature reference is valid if (!cellRef.mSoul.empty()) if (mObjects.searchId(cellRef.mSoul) == -1) - messages.add(id, "Trapped soul object '" + cellRef.mOwner + "' does not exist", "", CSMDoc::Message::Severity_Error); + messages.add(id, "Trapped soul object '" + cellRef.mSoul + "' does not exist", "", CSMDoc::Message::Severity_Error); if (cellRef.mFaction.empty()) { diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 46a765844..e790d23f5 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -62,7 +62,7 @@ add_openmw_dir (mwsound add_openmw_dir (mwworld refdata worldimp scene globals class action nullaction actionteleport containerstore actiontalk actiontake manualref player cellvisitors failedaction - cells localscripts customdata inventorystore ptr actionopen actionread + cells localscripts customdata inventorystore ptr actionopen actionread actionharvest actionequip timestamp actionalchemy cellstore actionapply actioneat store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 90fcbf82b..928e90596 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -368,10 +368,10 @@ OMW::Engine::~Engine() mWorkQueue = nullptr; - mResourceSystem.reset(); - mViewer = nullptr; + mResourceSystem.reset(); + delete mEncoder; mEncoder = nullptr; @@ -404,7 +404,8 @@ void OMW::Engine::enableFSStrict(bool fsStrict) void OMW::Engine::setDataDirs (const Files::PathContainer& dataDirs) { mDataDirs = dataDirs; - mFileCollections = Files::Collections (dataDirs, !mFSStrict); + mDataDirs.insert(mDataDirs.begin(), (mResDir / "vfs")); + mFileCollections = Files::Collections (mDataDirs, !mFSStrict); } // Add BSA archive diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 604dfa8f2..d1ed97b6e 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -273,6 +273,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat cfgMgr.processPaths(dataDirs); + engine.setResourceDir(variables["resources"].as().toStdString()); engine.setDataDirs(dataDirs); // fallback archives @@ -282,8 +283,6 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.addArchive(*it); } - engine.setResourceDir(variables["resources"].as().toStdString()); - StringsVector content = variables["content"].as().toStdStringVector(); if (content.empty()) { diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 3fff7db2d..a8406903c 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -375,9 +375,6 @@ namespace MWBase virtual const Translation::Storage& getTranslationDataStorage() const = 0; - /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. - virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0; - virtual void loadUserFonts() = 0; virtual Loading::Listener* getLoadingScreen() = 0; @@ -408,6 +405,7 @@ namespace MWBase virtual void removeCurrentModal(MWGui::WindowModal* input) = 0; virtual void pinWindow (MWGui::GuiWindow window) = 0; + virtual void toggleMaximized(MWGui::Layout *layout) = 0; /// Fade the screen in, over \a time seconds virtual void fadeScreenIn(const float time, bool clearQueue=true, float delay=0.f) = 0; @@ -421,6 +419,7 @@ namespace MWBase virtual void activateHitOverlay(bool interrupt=true) = 0; virtual void setWerewolfOverlay(bool set) = 0; + virtual void toggleConsole() = 0; virtual void toggleDebugWindow() = 0; /// Cycle to next or previous spell @@ -436,6 +435,7 @@ namespace MWBase virtual std::string correctTexturePath(const std::string& path) = 0; virtual bool textureExists(const std::string& path) = 0; + virtual void addCell(MWWorld::CellStore* cell) = 0; virtual void removeCell(MWWorld::CellStore* cell) = 0; virtual void writeFog(MWWorld::CellStore* cell) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c360d9eb4..6f26ff757 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -187,7 +187,7 @@ namespace MWBase ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. - virtual MWWorld::Ptr searchPtr (const std::string& name, bool activeOnly) = 0; + virtual MWWorld::Ptr searchPtr (const std::string& name, bool activeOnly, bool searchInContainers = true) = 0; ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. @@ -750,6 +750,7 @@ namespace MWBase /// Return the distance between actor's weapon and target's collision box. virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; + virtual void addContainerScripts(const MWWorld::Ptr& reference, MWWorld::CellStore* cell) = 0; virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0; virtual bool isPlayerInJail() const = 0; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 14b82de19..4192f6e6a 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -27,6 +27,7 @@ #include "../mwworld/customdata.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/actionharvest.hpp" #include "../mwworld/actionopen.hpp" #include "../mwworld/actiontrap.hpp" #include "../mwphysics/physicssystem.hpp" @@ -34,6 +35,7 @@ #include "../mwgui/tooltips.hpp" +#include "../mwrender/animation.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -52,6 +54,10 @@ namespace MWClass { return *this; } + virtual const ContainerCustomData& asContainerCustomData() const + { + return *this; + } }; MWWorld::CustomData *ContainerCustomData::clone() const @@ -75,15 +81,30 @@ namespace MWClass // store ptr.getRefData().setCustomData (data.release()); + + MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); } } + bool canBeHarvested(const MWWorld::ConstPtr& ptr) + { + const MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); + if (animation == nullptr) + return false; + + return animation->canBeHarvested(); + } + void Container::respawn(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); if (ref->mBase->mFlags & ESM::Container::Respawn) { + // Container was not touched, there is no need to modify its content. + if (ptr.getRefData().getCustomData() == nullptr) + return; + MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(nullptr); } @@ -239,6 +260,12 @@ namespace MWClass { if(!isTrapped) { + if (canBeHarvested(ptr)) + { + std::shared_ptr action (new MWWorld::ActionHarvest(ptr)); + return action; + } + std::shared_ptr action (new MWWorld::ActionOpen(ptr)); return action; } @@ -289,9 +316,18 @@ namespace MWClass bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef *ref = ptr.get(); + if (getName(ptr).empty()) + return false; + + if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) + return !canBeHarvested(ptr) || data->asContainerCustomData().mContainerStore.hasVisibleItems(); - return (ref->mBase->mName != ""); + return true; + } + + bool Container::canBeActivated(const MWWorld::Ptr& ptr) const + { + return hasToolTip(ptr); } MWGui::ToolTipInfo Container::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index f3d6e6cd8..dc3f7587c 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -73,6 +73,8 @@ namespace MWClass const; ///< Write additional state from \a ptr into \a state. + virtual bool canBeActivated(const MWWorld::Ptr& ptr) const; + static void registerSelf(); virtual void respawn (const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 043b6c8d4..4ddaf0148 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -519,10 +519,10 @@ namespace MWClass */ } - damage = std::max(1.f, damage); - if(ishealth) { + damage *= damage / (damage + getArmorRating(ptr)); + damage = std::max(1.f, damage); if (!attacker.isEmpty()) { damage = scaleDamage(damage, attacker, ptr); @@ -747,7 +747,7 @@ namespace MWClass float Creature::getArmorRating (const MWWorld::Ptr& ptr) const { - // Note this is currently unused. Creatures do not use armor mitigation. + // Equipment armor rating is deliberately ignored. return getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Shield).getMagnitude(); } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 1b6cad350..d24496a9c 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -409,7 +409,7 @@ namespace MWClass store.get().find(cell->mRegion); //name as is, not a token - return region->mName; + return MyGUI::TextIterator::toTagsString(region->mName); } } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 634ccc27a..ee385ebb1 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -189,6 +189,7 @@ namespace MWClass info.effects = list; info.text = text; + info.isIngredient = true; return info; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 34fc452c9..588fb4c57 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1601,6 +1601,19 @@ namespace MWClass int Npc::getPrimaryFactionRank (const MWWorld::ConstPtr& ptr) const { + std::string factionID = ptr.getClass().getPrimaryFaction(ptr); + if(factionID.empty()) + return -1; + + // Search in the NPC data first + if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) + { + int rank = data->asNpcCustomData().mNpcStats.getFactionRank(factionID); + if (rank >= 0) + return rank; + } + + // Use base NPC record as a fallback const MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->getFactionRank(); } diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 9db7a055b..c1bdd023f 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -26,9 +28,6 @@ namespace MWGui { - const float AlchemyWindow::sCountChangeInitialPause = 0.5f; - const float AlchemyWindow::sCountChangeInterval = 0.1f; - AlchemyWindow::AlchemyWindow() : WindowBase("openmw_alchemy_window.layout") , mSortModel(nullptr) @@ -170,7 +169,7 @@ namespace MWGui update(); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mNameEdit); } void AlchemyWindow::onIngredientSelected(MyGUI::Widget* _sender) @@ -249,6 +248,7 @@ namespace MWGui params.mAttribute = effectKey.mArg; params.mIsConstant = true; params.mNoTarget = true; + params.mNoMagnitude = true; params.mKnown = mAlchemy->knownEffect(effectIndex, MWBase::Environment::get().getWorld()->getPlayerPtr()); @@ -281,10 +281,9 @@ namespace MWGui 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::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MyGUI::ControllerRepeatClick::getClassTypeName()); + MyGUI::ControllerRepeatClick* controller = static_cast(item); + controller->eventRepeatClick += newDelegate(this, &AlchemyWindow::onRepeatClick); MyGUI::ControllerManager::getInstance().addItem(widget, controller); } diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index c6eb00792..a3f1cb52e 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -4,9 +4,10 @@ #include #include +#include + #include -#include "controllers.hpp" #include "windowbase.hpp" namespace MWMechanics diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 6f6a621ad..8955606d2 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -71,7 +72,7 @@ namespace MWGui WindowModal::onOpen(); updateBirths(); updateSpells(); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mBirthList); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mBirthList); // Show the current birthsign by default const std::string &signId = diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index 86089051d..e4cf254e2 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -101,7 +101,7 @@ namespace MWGui setTakeButtonShow(showTakeButton); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mCloseButton); } void BookWindow::setTakeButtonShow(bool show) @@ -161,9 +161,9 @@ namespace MWGui mPrevPageButton->setVisible(prevPageVisible); if (focus == mNextPageButton && !nextPageVisible && prevPageVisible) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mPrevPageButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mPrevPageButton); else if (focus == mPrevPageButton && !prevPageVisible && nextPageVisible) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNextPageButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mNextPageButton); if (mPages.empty()) return; diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index a92ad934c..94c493be7 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -135,7 +136,7 @@ namespace MWGui WindowModal::onOpen (); updateClasses(); updateStats(); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mClassList); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mClassList); // Show the current class by default MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -436,7 +437,7 @@ namespace MWGui getWidget(mEditName, "EditName"); // Make sure the edit box has focus - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mEditName); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mEditName); MyGUI::Button* descriptionButton; getWidget(descriptionButton, "DescriptionButton"); @@ -902,7 +903,7 @@ namespace MWGui okButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sInputMenu1", "")); // Make sure the edit box has focus - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); } DescriptionDialog::~DescriptionDialog() diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index 65b079d85..c5a50c2e6 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -33,7 +34,7 @@ namespace MWGui mMessage->setSize(mMessage->getWidth(), mMessage->getTextSize().height + 24); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mOkButton); center(); } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index d210d2bfc..8174b1f41 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -1,6 +1,8 @@ #include "console.hpp" #include +#include +#include #include #include @@ -150,8 +152,9 @@ namespace MWGui void Console::onOpen() { // Give keyboard focus to the combo box whenever the console is - // turned on - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); + // turned on and place it over other widgets + MyGUI::InputManager::getInstance().setKeyFocusWidget(mCommandLine); + MyGUI::LayerManager::getInstance().upLayerItem(mMainWidget); } void Console::print(const std::string &msg, const std::string& color) @@ -471,7 +474,7 @@ namespace MWGui mPtr = object; } // User clicked on an object. Restore focus to the console command line. - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mCommandLine); } else { diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index ccd026b08..7c4f20004 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -254,7 +254,7 @@ namespace MWGui mItemView->setModel (mSortModel); mItemView->resetScrollBars(); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mCloseButton); setTitle(container.getClass().getName(container)); } @@ -284,7 +284,8 @@ namespace MWGui if (mModel) mModel->onClose(); - MWBase::Environment::get().getMechanicsManager()->onClose(mPtr); + if (!mPtr.isEmpty()) + MWBase::Environment::get().getMechanicsManager()->onClose(mPtr); } void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) @@ -297,7 +298,7 @@ namespace MWGui if(mDragAndDrop != nullptr && mDragAndDrop->mIsOnDragAndDrop) return; - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mCloseButton); /* Start of tes3mp addition @@ -377,7 +378,7 @@ namespace MWGui { if(mDragAndDrop == nullptr || !mDragAndDrop->mIsOnDragAndDrop) { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mCloseButton); onTakeAllButtonClicked(mTakeButton); diff --git a/apps/openmw/mwgui/controllers.cpp b/apps/openmw/mwgui/controllers.cpp index 72f2eb7f3..f3932d3ce 100644 --- a/apps/openmw/mwgui/controllers.cpp +++ b/apps/openmw/mwgui/controllers.cpp @@ -7,54 +7,6 @@ namespace MWGui { namespace Controllers { - - ControllerRepeatEvent::ControllerRepeatEvent() : - mInit(0.5f), - mStep(0.1f), - mEnabled(true), - mTimeLeft(0) - { - } - - ControllerRepeatEvent::~ControllerRepeatEvent() - { - } - - bool ControllerRepeatEvent::addTime(MyGUI::Widget* _widget, float _time) - { - if(mTimeLeft == 0) - mTimeLeft = mInit; - - mTimeLeft -= _time; - while (mTimeLeft <= 0) - { - mTimeLeft += mStep; - eventRepeatClick(_widget, this); - } - return true; - } - - void ControllerRepeatEvent::setRepeat(float init, float step) - { - mInit = init; - mStep = step; - } - - void ControllerRepeatEvent::setEnabled(bool enable) - { - mEnabled = enable; - } - - void ControllerRepeatEvent::setProperty(const std::string& _key, const std::string& _value) - { - } - - void ControllerRepeatEvent::prepareItem(MyGUI::Widget* _widget) - { - } - - // ------------------------------------------------------------- - void ControllerFollowMouse::prepareItem(MyGUI::Widget *_widget) { } diff --git a/apps/openmw/mwgui/controllers.hpp b/apps/openmw/mwgui/controllers.hpp index 4208f048c..b75fe79ab 100644 --- a/apps/openmw/mwgui/controllers.hpp +++ b/apps/openmw/mwgui/controllers.hpp @@ -13,39 +13,6 @@ namespace MWGui { namespace Controllers { - // Should be removed when upgrading to MyGUI 3.2.2 (current git), it has ControllerRepeatClick - class ControllerRepeatEvent : - public MyGUI::ControllerItem - { - MYGUI_RTTI_DERIVED( ControllerRepeatEvent ) - - public: - ControllerRepeatEvent(); - virtual ~ControllerRepeatEvent(); - - void setRepeat(float init, float step); - void setEnabled(bool enable); - virtual void setProperty(const std::string& _key, const std::string& _value); - - // Events - typedef MyGUI::delegates::CMultiDelegate2 EventHandle_RepeatClickVoid; - - /** Event : Repeat Click.\n - signature : void method(MyGUI::Widget* _sender, MyGUI::ControllerItem *_controller)\n - */ - EventHandle_RepeatClickVoid eventRepeatClick; - - private: - bool addTime(MyGUI::Widget* _widget, float _time); - void prepareItem(MyGUI::Widget* _widget); - - private: - float mInit; - float mStep; - bool mEnabled; - float mTimeLeft; - }; - /// Automatically positions a widget below the mouse cursor. class ControllerFollowMouse : public MyGUI::ControllerItem diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp index baf3a43ab..30ef50759 100644 --- a/apps/openmw/mwgui/countdialog.cpp +++ b/apps/openmw/mwgui/countdialog.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -47,7 +48,7 @@ namespace MWGui mMainWidget->getHeight()); // by default, the text edit field has the focus of the keyboard - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mItemEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mItemEdit); mSlider->setScrollPosition(maxCount-1); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 6b400c172..4c9e6b230 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -380,7 +381,7 @@ namespace MWGui { onTopicActivated(topic); if (mGoodbyeButton->getEnabled()) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mGoodbyeButton); } else if (topic == sPersuasion) mPersuasionDialog.setVisible(true); @@ -443,7 +444,7 @@ namespace MWGui return; } - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mGoodbyeButton); setTitle(mPtr.getClass().getName(mPtr)); @@ -622,7 +623,7 @@ namespace MWGui bool goodbyeWasEnabled = mGoodbyeButton->getEnabled(); mGoodbyeButton->setEnabled(goodbyeEnabled); if (goodbyeEnabled && !goodbyeWasEnabled) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mGoodbyeButton); bool topicsEnabled = !MWBase::Environment::get().getDialogueManager()->isInChoice() && !mGoodbye; mTopicsList->setEnabled(topicsEnabled); diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index fbdc1bf80..41f82bd2f 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -67,7 +68,7 @@ namespace MWGui void EnchantingDialog::onOpen() { center(); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mName); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mName); } void EnchantingDialog::setSoulGem(const MWWorld::Ptr &gem) @@ -107,20 +108,11 @@ namespace MWGui void EnchantingDialog::updateLabels() { - std::stringstream enchantCost; - enchantCost << std::setprecision(1) << std::fixed << mEnchanting.getEnchantPoints(); - mEnchantmentPoints->setCaption(enchantCost.str() + " / " + MyGUI::utility::toString(mEnchanting.getMaxEnchantValue())); - - mCharge->setCaption(MyGUI::utility::toString(mEnchanting.getGemCharge())); - - int successChance = int(mEnchanting.getEnchantChance()); - mSuccessChance->setCaption(MyGUI::utility::toString(std::max(0, successChance))); - - std::stringstream castCost; - castCost << mEnchanting.getEffectiveCastCost(); - mCastCost->setCaption(castCost.str()); - - mPrice->setCaption(MyGUI::utility::toString(mEnchanting.getEnchantPrice())); + mEnchantmentPoints->setCaption(std::to_string(static_cast(mEnchanting.getEnchantPoints(false))) + " / " + std::to_string(mEnchanting.getMaxEnchantValue())); + mCharge->setCaption(std::to_string(mEnchanting.getGemCharge())); + mSuccessChance->setCaption(std::to_string(std::max(0, std::min(100, mEnchanting.getEnchantChance())))); + mCastCost->setCaption(std::to_string(mEnchanting.getEffectiveCastCost())); + mPrice->setCaption(std::to_string(mEnchanting.getEnchantPrice())); switch(mEnchanting.getCastStyle()) { @@ -322,7 +314,7 @@ namespace MWGui return; } - if (mEnchanting.getEnchantPoints() > mEnchanting.getMaxEnchantValue()) + if (static_cast(mEnchanting.getEnchantPoints(false)) > mEnchanting.getMaxEnchantValue()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage29}"); return; @@ -348,7 +340,7 @@ namespace MWGui if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(item.getCellRef().getRefId(), mPtr)) { std::string msg = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage49")->mValue.getString(); - Misc::StringUtils::replace(msg, "%s", item.getClass().getName(item).c_str(), 2); + msg = Misc::StringUtils::format(msg, item.getClass().getName(item)); MWBase::Environment::get().getWindowManager()->messageBox(msg); MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, item, mPtr, 1); @@ -366,13 +358,19 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->playSound("enchant success"); MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu12}"); + MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); } else { MWBase::Environment::get().getWindowManager()->playSound("enchant fail"); MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage34}"); + if (!mEnchanting.getGem().isEmpty() && !mEnchanting.getGem().getRefData().getCount()) + { + setSoulGem(MWWorld::Ptr()); + mEnchanting.nextCastStyle(); + updateLabels(); + updateEffectsView(); + } } - - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); } } diff --git a/apps/openmw/mwgui/exposedwindow.cpp b/apps/openmw/mwgui/exposedwindow.cpp index 1a0484e72..90cfa09d3 100644 --- a/apps/openmw/mwgui/exposedwindow.cpp +++ b/apps/openmw/mwgui/exposedwindow.cpp @@ -2,12 +2,12 @@ namespace MWGui { - MyGUI::VectorWidgetPtr ExposedWindow::getSkinWidgetsByName (const std::string &name) + MyGUI::VectorWidgetPtr Window::getSkinWidgetsByName (const std::string &name) { return MyGUI::Widget::getSkinWidgetsByName (name); } - MyGUI::Widget* ExposedWindow::getSkinWidget(const std::string & _name, bool _throw) + MyGUI::Widget* Window::getSkinWidget(const std::string & _name, bool _throw) { MyGUI::VectorWidgetPtr widgets = getSkinWidgetsByName (_name); diff --git a/apps/openmw/mwgui/exposedwindow.hpp b/apps/openmw/mwgui/exposedwindow.hpp index 7df2fcb35..f1f5d3c2f 100644 --- a/apps/openmw/mwgui/exposedwindow.hpp +++ b/apps/openmw/mwgui/exposedwindow.hpp @@ -9,9 +9,9 @@ namespace MWGui /** * @brief subclass to provide access to some Widget internals. */ - class ExposedWindow : public MyGUI::Window + class Window : public MyGUI::Window { - MYGUI_RTTI_DERIVED(ExposedWindow) + MYGUI_RTTI_DERIVED(Window) public: MyGUI::VectorWidgetPtr getSkinWidgetsByName (const std::string &name); diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 97c7fffa0..653dcf49d 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -274,6 +274,7 @@ namespace MWGui if (!MWBase::Environment::get().getWindowManager ()->isGuiMode ()) return; + MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); if (mDragAndDrop->mIsOnDragAndDrop) { // drop item into the gameworld @@ -288,19 +289,19 @@ namespace MWGui WorldItemModel drop (mouseX, mouseY); mDragAndDrop->drop(&drop, nullptr); - MWBase::Environment::get().getWindowManager()->changePointer("arrow"); + winMgr->changePointer("arrow"); } else { - GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); + GuiMode mode = winMgr->getMode(); - if ( (mode != GM_Console) && (mode != GM_Container) && (mode != GM_Inventory) ) + if (!winMgr->isConsoleMode() && (mode != GM_Container) && (mode != GM_Inventory)) return; MWWorld::Ptr object = MWBase::Environment::get().getWorld()->getFacedObject(); - if (mode == GM_Console) - MWBase::Environment::get().getWindowManager()->setConsoleSelectedObject(object); + if (winMgr->isConsoleMode()) + winMgr->setConsoleSelectedObject(object); else //if ((mode == GM_Container) || (mode == GM_Inventory)) { // pick up object @@ -314,7 +315,7 @@ namespace MWGui an item here, and expect the server's reply to our packet to cause the actual picking up of items */ - //MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(object); + //winMgr->getInventoryWindow()->pickUpObject(object); { mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList(); objectList->reset(); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index b1ad81513..e99865903 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -176,28 +176,39 @@ namespace MWGui mItemView->setModel(nullptr); } + void InventoryWindow::toggleMaximized() + { + std::string setting = getModeSetting(); + + bool maximized = !Settings::Manager::getBool(setting + " maximized", "Windows"); + if (maximized) + setting += " maximized"; + + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + float x = Settings::Manager::getFloat(setting + " x", "Windows") * float(viewSize.width); + float y = Settings::Manager::getFloat(setting + " y", "Windows") * float(viewSize.height); + float w = Settings::Manager::getFloat(setting + " w", "Windows") * float(viewSize.width); + float h = Settings::Manager::getFloat(setting + " h", "Windows") * float(viewSize.height); + MyGUI::Window* window = mMainWidget->castType(); + window->setCoord(x, y, w, h); + + if (maximized) + Settings::Manager::setBool(setting, "Windows", maximized); + else + Settings::Manager::setBool(setting + " maximized", "Windows", maximized); + + adjustPanes(); + updatePreviewSize(); + } + void InventoryWindow::setGuiMode(GuiMode mode) { - std::string setting = "inventory"; mGuiMode = mode; - switch(mode) { - case GM_Container: - setPinButtonVisible(false); - setting += " container"; - break; - case GM_Companion: - setPinButtonVisible(false); - setting += " companion"; - break; - case GM_Barter: - setPinButtonVisible(false); - setting += " barter"; - break; - case GM_Inventory: - default: - setPinButtonVisible(true); - break; - } + std::string setting = getModeSetting(); + setPinButtonVisible(mode == GM_Inventory); + + if (Settings::Manager::getBool(setting + " maximized", "Windows")) + setting += " maximized"; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(setting + " x", "Windows") * viewSize.width), @@ -400,11 +411,11 @@ namespace MWGui adjustPanes(); } - void InventoryWindow::onWindowResize(MyGUI::Window* _sender) + std::string InventoryWindow::getModeSetting() const { - adjustPanes(); std::string setting = "inventory"; - switch(mGuiMode) { + switch(mGuiMode) + { case GM_Container: setting += " container"; break; @@ -418,6 +429,14 @@ namespace MWGui break; } + return setting; + } + + void InventoryWindow::onWindowResize(MyGUI::Window* _sender) + { + adjustPanes(); + std::string setting = getModeSetting(); + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); float x = _sender->getPosition().left / float(viewSize.width); float y = _sender->getPosition().top / float(viewSize.height); @@ -427,6 +446,9 @@ namespace MWGui Settings::Manager::setFloat(setting + " y", "Windows", y); Settings::Manager::setFloat(setting + " w", "Windows", w); Settings::Manager::setFloat(setting + " h", "Windows", h); + bool maximized = Settings::Manager::getBool(setting + " maximized", "Windows"); + if (maximized) + Settings::Manager::setBool(setting + " maximized", "Windows", false); if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize) { @@ -490,7 +512,9 @@ namespace MWGui void InventoryWindow::onTitleDoubleClicked() { - if (!mPinned) + if (MyGUI::InputManager::getInstance().isShiftPressed()) + toggleMaximized(); + else if (!mPinned) MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory); } diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index c60e37363..8d0a86bbf 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -65,6 +65,9 @@ namespace MWGui /// Cycle to previous/next weapon void cycle(bool next); + protected: + virtual void onTitleDoubleClicked(); + private: DragAndDrop* mDragAndDrop; @@ -104,11 +107,15 @@ namespace MWGui float mScaleFactor; float mUpdateTimer; + void toggleMaximized(); + void onItemSelected(int index); void onItemSelectedFromSourceModel(int index); void onBackgroundSelected(); + std::string getModeSetting() const; + void sellItem(MyGUI::Widget* sender, int count); void dragItem(MyGUI::Widget* sender, int count); @@ -116,7 +123,6 @@ namespace MWGui void onFilterChanged(MyGUI::Widget* _sender); void onAvatarClicked(MyGUI::Widget* _sender); void onPinToggled(); - void onTitleDoubleClicked(); void updateEncumbranceBar(); void notifyContentChanged(); diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 26e2bdc75..f92dfef3c 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -177,7 +177,7 @@ namespace MWGui End of tes3mp addition */ - Misc::StringUtils::replace(message, "%d", std::to_string(mDays).c_str(), 2); + message = Misc::StringUtils::format(message, mDays); for (const int& skill : skills) { @@ -197,8 +197,7 @@ namespace MWGui */ skillMsg = gmst.find("sNotifyMessage39")->mValue.getString(); - Misc::StringUtils::replace(skillMsg, "%s", skillName.c_str(), 2); - Misc::StringUtils::replace(skillMsg, "%d", std::to_string(skillValue).c_str(), 2); + skillMsg = Misc::StringUtils::format(skillMsg, skillName, skillValue); message += "\n" + skillMsg; } diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 43e7edf1e..34c0fa8e1 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -254,7 +254,7 @@ namespace } updateShowingPages(); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(getWidget(CloseBTN)); + MyGUI::InputManager::getInstance().setKeyFocusWidget(getWidget(CloseBTN)); } void onClose() @@ -377,9 +377,9 @@ namespace prevPageBtn->setVisible(prevPageVisible); if (focus == nextPageBtn && !nextPageVisible && prevPageVisible) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(prevPageBtn); + MyGUI::InputManager::getInstance().setKeyFocusWidget(prevPageBtn); else if (focus == prevPageBtn && !prevPageVisible && nextPageVisible) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nextPageBtn); + MyGUI::InputManager::getInstance().setKeyFocusWidget(nextPageBtn); setVisible (PageOneNum, relPages > 0); setVisible (PageTwoNum, relPages > 1); diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index 7355dc1f4..79b9e8457 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -83,7 +83,7 @@ void KeyboardNavigation::restoreFocus(int mode) { MyGUI::Widget* w = found->second; if (w && w->getVisible() && w->getEnabled()) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(found->second); + MyGUI::InputManager::getInstance().setKeyFocusWidget(found->second); } } @@ -130,7 +130,7 @@ void KeyboardNavigation::onFrame() // workaround incorrect key focus resets (fix in MyGUI TBD) if (!shouldAcceptKeyFocus(focus) && shouldAcceptKeyFocus(mCurrentFocus) && (!mModalWindow || isRootParent(mCurrentFocus, mModalWindow))) { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCurrentFocus); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mCurrentFocus); focus = mCurrentFocus; } @@ -154,12 +154,12 @@ void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *d MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (!focus || !shouldAcceptKeyFocus(focus)) { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(defaultFocus); + MyGUI::InputManager::getInstance().setKeyFocusWidget(defaultFocus); } else { if (!isRootParent(focus, window)) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(defaultFocus); + MyGUI::InputManager::getInstance().setKeyFocusWidget(defaultFocus); } } @@ -276,7 +276,7 @@ bool KeyboardNavigation::switchFocus(int direction, bool wrap) else if (direction == D_Up && (vertdiff >= 0 || !isVertical)) return false; - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(keyFocusList[index]); + MyGUI::InputManager::getInstance().setKeyFocusWidget(keyFocusList[index]); return true; } @@ -291,7 +291,7 @@ bool KeyboardNavigation::selectFirstWidget() if (!keyFocusList.empty()) { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(keyFocusList[0]); + MyGUI::InputManager::getInstance().setKeyFocusWidget(keyFocusList[0]); return true; } return false; diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index a9480f261..54382ab4d 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -165,11 +165,6 @@ namespace MWGui if (mMainWidget->getVisible()) return; - if (mViewer->getIncrementalCompileOperation()) - { - mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100); - } - // Assign dummy bounding sphere callback to avoid the bounding sphere of the entire scene being recomputed after each frame of loading // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index b2df52c5a..4b2612604 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -67,12 +68,12 @@ namespace MWGui if (isMainMenu) { if (mButtons["loadgame"]->getVisible()) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mButtons["loadgame"]); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mButtons["loadgame"]); else - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mButtons["newgame"]); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mButtons["newgame"]); } else - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mButtons["return"]); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mButtons["return"]); } Layout::setVisible (visible); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index f49e13ad1..d0bbe7563 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -268,9 +268,6 @@ namespace MWGui { for (int my=0; my tex = mLocalMapRender->getFogOfWarTexture(x, y); - if (tex) - { - entry.mFogTexture.reset(new osgMyGUI::OSGTexture(tex)); - fog->setRenderItemTexture(entry.mFogTexture.get()); - fog->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); - } - else - { - fog->setImageTexture("black"); - entry.mFogTexture.reset(); - } } } @@ -425,31 +409,18 @@ namespace MWGui mInterior = interior; mChanged = false; - applyFogOfWar(); - - // Update the map textures for (int mx=0; mxsetRenderItemTexture(nullptr); + entry.mFogWidget->setRenderItemTexture(nullptr); + entry.mMapTexture.reset(); + entry.mFogTexture.reset(); - osg::ref_ptr texture = mLocalMapRender->getMapTexture(mapX, mapY); - if (texture) - { - entry.mMapTexture.reset(new osgMyGUI::OSGTexture(texture)); - box->setRenderItemTexture(entry.mMapTexture.get()); - box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); - } - else - { - box->setRenderItemTexture(nullptr); - entry.mMapTexture.reset(); - } + entry.mCellX = x + (mx - mCellDistance); + entry.mCellY = y - (my - mCellDistance); } } @@ -463,22 +434,7 @@ namespace MWGui void LocalMapBase::requestMapRender(const MWWorld::CellStore *cell) { - std::set cells; - if (!cell->isExterior()) - cells.insert(cell); - else - { - for (int dX=-mCellDistance; dX<=mCellDistance; ++dX) - { - for (int dY=-mCellDistance; dY<=mCellDistance; ++dY) - { - const MWWorld::CellStore* gridCell = MWBase::Environment::get().getWorld()->getExterior (cell->getCell()->getGridX()+dX, cell->getCell()->getGridY()+dY); - cells.insert(gridCell); - } - } - } - - mLocalMapRender->requestMap(cells); + mLocalMapRender->requestMap(cell); } void LocalMapBase::redraw() @@ -581,6 +537,68 @@ namespace MWGui mMarkerUpdateTimer = 0; updateMagicMarkers(); } + + updateRequiredMaps(); + } + + bool widgetCropped(MyGUI::Widget* widget, MyGUI::Widget* cropTo) + { + MyGUI::IntRect coord = widget->getAbsoluteRect(); + MyGUI::IntRect croppedCoord = cropTo->getAbsoluteRect(); + if (coord.left < croppedCoord.left && coord.right < croppedCoord.left) + return true; + if (coord.left > croppedCoord.right && coord.right > croppedCoord.right) + return true; + if (coord.top < croppedCoord.top && coord.bottom < croppedCoord.top) + return true; + if (coord.top > croppedCoord.bottom && coord.bottom > croppedCoord.bottom) + return true; + return false; + } + + void LocalMapBase::updateRequiredMaps() + { + bool needRedraw = false; + for (MapEntry& entry : mMaps) + { + if (widgetCropped(entry.mMapWidget, mLocalMap)) + continue; + + if (!entry.mMapTexture) + { + if (!mInterior) + requestMapRender(MWBase::Environment::get().getWorld()->getExterior (entry.mCellX, entry.mCellY)); + + osg::ref_ptr texture = mLocalMapRender->getMapTexture(entry.mCellX, entry.mCellY); + if (texture) + { + entry.mMapTexture.reset(new osgMyGUI::OSGTexture(texture)); + entry.mMapWidget->setRenderItemTexture(entry.mMapTexture.get()); + entry.mMapWidget->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); + needRedraw = true; + } + else + entry.mMapTexture.reset(new osgMyGUI::OSGTexture("", nullptr)); + } + if (!entry.mFogTexture && mFogOfWarToggled && mFogOfWarEnabled) + { + osg::ref_ptr tex = mLocalMapRender->getFogOfWarTexture(entry.mCellX, entry.mCellY); + if (tex) + { + entry.mFogTexture.reset(new osgMyGUI::OSGTexture(tex)); + entry.mFogWidget->setRenderItemTexture(entry.mFogTexture.get()); + entry.mFogWidget->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); + } + else + { + entry.mFogWidget->setImageTexture("black"); + entry.mFogTexture.reset(new osgMyGUI::OSGTexture("", nullptr)); + } + needRedraw = true; + } + } + if (needRedraw) + redraw(); } void LocalMapBase::updateDoorMarkers() @@ -1004,7 +1022,9 @@ namespace MWGui void MapWindow::onTitleDoubleClicked() { - if (!mPinned) + if (MyGUI::InputManager::getInstance().isShiftPressed()) + MWBase::Environment::get().getWindowManager()->toggleMaximized(this); + else if (!mPinned) MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map); } @@ -1176,7 +1196,7 @@ namespace MWGui { WindowModal::onOpen(); center(); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); } void EditNoteDialog::onCancelButtonClicked(MyGUI::Widget *sender) diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 0b6bae26a..3c4d3a668 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -151,12 +151,14 @@ namespace MWGui struct MapEntry { MapEntry(MyGUI::ImageBox* mapWidget, MyGUI::ImageBox* fogWidget) - : mMapWidget(mapWidget), mFogWidget(fogWidget) {} + : mMapWidget(mapWidget), mFogWidget(fogWidget), mCellX(0), mCellY(0) {} MyGUI::ImageBox* mMapWidget; MyGUI::ImageBox* mFogWidget; std::shared_ptr mMapTexture; std::shared_ptr mFogTexture; + int mCellX; + int mCellY; }; std::vector mMaps; @@ -197,6 +199,8 @@ namespace MWGui virtual void customMarkerCreated(MyGUI::Widget* marker) {} virtual void doorMarkerCreated(MyGUI::Widget* marker) {} + void updateRequiredMaps(); + void updateMagicMarkers(); void addDetectionMarkers(int type); diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index c452a1f5f..62d739657 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -12,7 +12,6 @@ namespace MWGui GM_Companion, GM_MainMenu, // Main menu mode - GM_Console, // Console mode GM_Journal, // Journal mode GM_Scroll, // Read scroll diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index c85e7eca7..60f90c3af 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -176,7 +177,7 @@ namespace MWGui mHeadRotate->setScrollPosition(initialPos); onHeadRotate(mHeadRotate, initialPos); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mRaceList); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mRaceList); } void RaceDialog::setRaceId(const std::string &raceId) diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 2c8157284..97424a019 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -207,7 +207,7 @@ void Recharge::onItemClicked(MyGUI::Widget *sender, const MWWorld::Ptr& item) if (gem.getRefData().getCount() == 0) { std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage51")->mValue.getString(); - Misc::StringUtils::replace(message, "%s", gem.getClass().getName(gem).c_str(), 2); + message = Misc::StringUtils::format(message, gem.getClass().getName(gem)); MWBase::Environment::get().getWindowManager()->messageBox(message); diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 2027210d7..903fdf69d 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -94,7 +94,7 @@ namespace MWGui MWBase::Environment::get().getStateManager()->deleteGame (mCurrentCharacter, mCurrentSlot); mSaveList->removeItemAt(mSaveList->getIndexSelected()); onSlotSelected(mSaveList, mSaveList->getIndexSelected()); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mSaveList); if (mSaveList->getItemCount() == 0) { @@ -114,7 +114,7 @@ namespace MWGui void SaveGameDialog::onDeleteSlotCancel() { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mSaveList); } void SaveGameDialog::onSaveNameChanged(MyGUI::EditBox *sender) @@ -138,9 +138,9 @@ namespace MWGui mSaveNameEdit->setCaption (""); if (mSaving) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveNameEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mSaveNameEdit); else - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mSaveList); center(); @@ -244,14 +244,11 @@ namespace MWGui void SaveGameDialog::onConfirmationCancel() { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mSaveList); } void SaveGameDialog::accept(bool reallySure) { - // Remove for MyGUI 3.2.2 - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr); - if (mSaving) { // If overwriting an existing slot, ask for confirmation first @@ -334,7 +331,7 @@ namespace MWGui void SaveGameDialog::onCharacterAccept(MyGUI::ComboBox* sender, size_t pos) { // Give key focus to save list so we can confirm the selection with Enter - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mSaveList); } void SaveGameDialog::fillSaveList() diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index f2c967da4..adbbaba15 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -1,6 +1,7 @@ #include "scrollwindow.hpp" #include +#include #include #include @@ -66,7 +67,7 @@ namespace MWGui setTakeButtonShow(showTakeButton); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mCloseButton); } void ScrollWindow::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 707192784..9cb60af1a 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -6,14 +6,18 @@ #include #include #include +#include #include #include +#include + #include #include #include +#include #include #include @@ -142,13 +146,22 @@ namespace MWGui MyGUI::ScrollBar* scroll = current->castType(); std::string valueStr; std::string valueType = getSettingValueType(current); - if (valueType == "Float" || valueType == "Integer") + if (valueType == "Float" || valueType == "Integer" || valueType == "Cell") { // TODO: ScrollBar isn't meant for this. should probably use a dedicated FloatSlider widget float min,max; getSettingMinMax(scroll, min, max); float value = Settings::Manager::getFloat(getSettingName(current), getSettingCategory(current)); - valueStr = MyGUI::utility::toString((int)value); + + if (valueType == "Cell") + { + std::stringstream ss; + ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; + valueStr = ss.str(); + } + else + valueStr = MyGUI::utility::toString(int(value)); + value = std::max(min, std::min(value, max)); value = (value-min)/(max-min); @@ -161,7 +174,8 @@ namespace MWGui scroll->setScrollPosition(value); } scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); - updateSliderLabel(scroll, valueStr); + if (scroll->getVisible()) + updateSliderLabel(scroll, valueStr); } configureWidgets(current); @@ -176,7 +190,7 @@ namespace MWGui MyGUI::TextBox* textBox; getWidget(textBox, labelWidgetName); std::string labelCaption = scroller->getUserString("SettingLabelCaption"); - Misc::StringUtils::replaceAll(labelCaption, "%s", value.c_str(), 2); + labelCaption = Misc::StringUtils::format(labelCaption, value); textBox->setCaptionWithReplacing(labelCaption); } } @@ -185,6 +199,12 @@ namespace MWGui WindowBase("openmw_settings_window.layout"), mKeyboardMode(true) { + bool terrain = Settings::Manager::getBool("distant terrain", "Terrain"); + const std::string widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider"; + MyGUI::Widget* unusedSlider; + getWidget(unusedSlider, widgetName); + unusedSlider->setVisible(false); + configureWidgets(mMainWidget); setTitle("#{sOptions}"); @@ -440,7 +460,7 @@ namespace MWGui { std::string valueStr; std::string valueType = getSettingValueType(scroller); - if (valueType == "Float" || valueType == "Integer") + if (valueType == "Float" || valueType == "Integer" || valueType == "Cell") { float value = pos / float(scroller->getScrollRange()-1); @@ -451,7 +471,15 @@ namespace MWGui Settings::Manager::setFloat(getSettingName(scroller), getSettingCategory(scroller), value); else Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), (int)value); - valueStr = MyGUI::utility::toString(int(value)); + + if (valueType == "Cell") + { + std::stringstream ss; + ss << std::fixed << std::setprecision(2) << value/Constants::CellSizeInUnits; + valueStr = ss.str(); + } + else + valueStr = MyGUI::utility::toString(int(value)); } else { @@ -466,12 +494,13 @@ namespace MWGui void SettingsWindow::apply() { - const Settings::CategorySettingVector changed = Settings::Manager::apply(); + const Settings::CategorySettingVector changed = Settings::Manager::getPendingChanges(); MWBase::Environment::get().getWorld()->processChangedSettings(changed); MWBase::Environment::get().getSoundManager()->processChangedSettings(changed); MWBase::Environment::get().getWindowManager()->processChangedSettings(changed); MWBase::Environment::get().getInputManager()->processChangedSettings(changed); MWBase::Environment::get().getMechanicsManager()->processChangedSettings(changed); + Settings::Manager::resetPendingChanges(); } void SettingsWindow::onKeyboardSwitchClicked(MyGUI::Widget* _sender) @@ -599,9 +628,10 @@ namespace MWGui void SettingsWindow::onOpen() { + highlightCurrentResolution(); updateControlsBox(); resetScrollbars(); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mOkButton); } void SettingsWindow::onWindowResize(MyGUI::Window *_sender) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 5864a5451..018d12c92 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -457,7 +458,7 @@ namespace MWGui void SpellCreationDialog::onOpen() { center(); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mNameEdit); } void SpellCreationDialog::onReferenceUnavailable () diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 6dadebca2..2dd6cdc00 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -148,6 +148,19 @@ namespace MWGui return mSpells.size(); } + SpellModel::ModelIndex SpellModel::getSelectedIndex() const + { + ModelIndex selected = -1; + for (SpellModel::ModelIndex i = 0; i= int(mSpells.size())) diff --git a/apps/openmw/mwgui/spellmodel.hpp b/apps/openmw/mwgui/spellmodel.hpp index 4aa1f9d2a..d191cba0e 100644 --- a/apps/openmw/mwgui/spellmodel.hpp +++ b/apps/openmw/mwgui/spellmodel.hpp @@ -48,6 +48,8 @@ namespace MWGui ///< throws for invalid index size_t getItemCount() const; + ModelIndex getSelectedIndex() const; + ///< returns -1 if nothing is selected private: MWWorld::Ptr mActor; diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 54bda650c..36d914a00 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -1,7 +1,9 @@ #include "spellwindow.hpp" +#include #include #include +#include #include #include @@ -47,6 +49,9 @@ namespace MWGui { mSpellIcons = new SpellIcons(); + MyGUI::Widget* deleteButton; + getWidget(deleteButton, "DeleteSpellButton"); + getWidget(mSpellView, "SpellView"); getWidget(mEffectBox, "EffectsBox"); getWidget(mFilterEdit, "FilterEdit"); @@ -55,8 +60,13 @@ namespace MWGui mSpellView->eventSpellClicked += MyGUI::newDelegate(this, &SpellWindow::onModelIndexSelected); mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &SpellWindow::onFilterChanged); + deleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onDeleteClicked); setCoord(498, 300, 302, 300); + + // Adjust the spell filtering widget size because of MyGUI limitations. + int filterWidth = mSpellView->getSize().width - deleteButton->getSize().width - 3; + mFilterEdit->setSize(filterWidth, mFilterEdit->getSize().height); } SpellWindow::~SpellWindow() @@ -73,7 +83,9 @@ namespace MWGui void SpellWindow::onTitleDoubleClicked() { - if (!mPinned) + if (MyGUI::InputManager::getInstance().isShiftPressed()) + MWBase::Environment::get().getWindowManager()->toggleMaximized(this); + else if (!mPinned) MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Magic); } @@ -82,7 +94,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(nullptr); + MyGUI::InputManager::getInstance().resetKeyFocusWidget(); updateSpells(); } @@ -151,13 +163,17 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); std::string raceId = player.get()->mBase->mRace; - const std::string& signId = - MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(raceId); - const ESM::BirthSign* birthsign = MWBase::Environment::get().getWorld()->getStore().get().find(signId); + // can't delete racial spells, birthsign spells or powers + bool isInherent = race->mPowers.exists(spell->mId) || spell->mData.mType == ESM::Spell::ST_Power; + const std::string& signId = MWBase::Environment::get().getWorld()->getPlayer().getBirthSign(); + if (!isInherent && !signId.empty()) + { + const ESM::BirthSign* sign = MWBase::Environment::get().getWorld()->getStore().get().find(signId); + isInherent = sign->mPowers.exists(spell->mId); + } - // can't delete racial spells, birthsign spells or powers - if (race->mPowers.exists(spell->mId) || birthsign->mPowers.exists(spell->mId) || spell->mData.mType == ESM::Spell::ST_Power) + if (isInherent) { MWBase::Environment::get().getWindowManager()->messageBox("#{sDeleteSpellError}"); } @@ -167,7 +183,7 @@ namespace MWGui mSpellToDelete = spellId; ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); std::string question = MWBase::Environment::get().getWindowManager()->getGameSettingString("sQuestionDeleteSpell", "Delete %s?"); - Misc::StringUtils::replace(question, "%s", spell->mName.c_str(), 2); + question = Misc::StringUtils::format(question, spell->mName); dialog->askForConfirmation(question); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SpellWindow::onDeleteSpellAccept); @@ -196,6 +212,17 @@ namespace MWGui mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), sender->getCaption())); } + void SpellWindow::onDeleteClicked(MyGUI::Widget *widget) + { + SpellModel::ModelIndex selected = mSpellView->getModel()->getSelectedIndex(); + if (selected < 0) + return; + + const Spell& spell = mSpellView->getModel()->getItem(selected); + if (spell.mType != Spell::Type_EnchantedItem) + askDeleteSpell(spell.mId); + } + void SpellWindow::onSpellSelected(const std::string& spellId) { MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -253,12 +280,9 @@ namespace MWGui mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), "")); - SpellModel::ModelIndex selected = 0; - for (SpellModel::ModelIndex i = 0; igetModel()->getItemCount()); ++i) - { - if (mSpellView->getModel()->getItem(i).mSelected) - selected = i; - } + SpellModel::ModelIndex selected = mSpellView->getModel()->getSelectedIndex(); + if (selected < 0) + selected = 0; selected += next ? 1 : -1; int itemcount = mSpellView->getModel()->getItemCount(); diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index ce10770f5..01a9c7392 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -33,6 +33,7 @@ namespace MWGui void onSpellSelected(const std::string& spellId); void onModelIndexSelected(SpellModel::ModelIndex index); void onFilterChanged(MyGUI::EditBox *sender); + void onDeleteClicked(MyGUI::Widget *widget); void onDeleteSpellAccept(); void askDeleteSpell(const std::string& spellId); diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index c3f852c5b..91d414af3 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -652,7 +653,13 @@ namespace MWGui void StatsWindow::onTitleDoubleClicked() { - if (!mPinned) + if (MyGUI::InputManager::getInstance().isShiftPressed()) + { + MWBase::Environment::get().getWindowManager()->toggleMaximized(this); + MyGUI::Window* t = mMainWidget->castType(); + onWindowResize(t); + } + else if (!mPinned) MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Stats); } } diff --git a/apps/openmw/mwgui/textinput.cpp b/apps/openmw/mwgui/textinput.cpp index 54f2d3be9..4cc7a576b 100644 --- a/apps/openmw/mwgui/textinput.cpp +++ b/apps/openmw/mwgui/textinput.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace MWGui { @@ -23,7 +24,7 @@ namespace MWGui okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TextInputDialog::onOkClicked); // Make sure the edit box has focus - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); } void TextInputDialog::setNextButtonShow(bool shown) @@ -46,7 +47,7 @@ namespace MWGui { WindowModal::onOpen(); // Make sure the edit box has focus - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); } // widget controls @@ -56,7 +57,7 @@ namespace MWGui if (mTextEdit->getCaption() == "") { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage37}"); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget (mTextEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); } else eventDone(this); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index f9775253a..dc7a9668d 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -94,17 +94,19 @@ namespace MWGui return; } - bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); + MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); + bool guiMode = winMgr->isGuiMode(); if (guiMode) { - if (!MWBase::Environment::get().getWindowManager()->getCursorVisible()) + if (!winMgr->getCursorVisible()) return; const MyGUI::IntPoint& mousePos = MyGUI::InputManager::getInstance().getMousePosition(); - if (MWBase::Environment::get().getWindowManager()->getWorldMouseOver() && ((MWBase::Environment::get().getWindowManager()->getMode() == GM_Console) - || (MWBase::Environment::get().getWindowManager()->getMode() == GM_Container) - || (MWBase::Environment::get().getWindowManager()->getMode() == GM_Inventory))) + if (winMgr->getWorldMouseOver() && + (winMgr->isConsoleMode() || + (winMgr->getMode() == GM_Container) || + (winMgr->getMode() == GM_Inventory))) { if (mFocusObject.isEmpty ()) return; @@ -112,7 +114,7 @@ namespace MWGui const MWWorld::Class& objectclass = mFocusObject.getClass(); MyGUI::IntSize tooltipSize; - if ((!objectclass.hasToolTip(mFocusObject))&&(MWBase::Environment::get().getWindowManager()->getMode() == GM_Console)) + if (!objectclass.hasToolTip(mFocusObject) && winMgr->isConsoleMode()) { setCoord(0, 0, 300, 300); mDynamicToolTipBox->setVisible(true); @@ -212,7 +214,7 @@ namespace MWGui { MyGUI::IntCoord avatarPos = focus->getAbsoluteCoord(); MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance ().getMousePosition () - MyGUI::IntPoint(avatarPos.left, avatarPos.top); - MWWorld::Ptr item = MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getAvatarSelectedItem (relMousePos.left, relMousePos.top); + MWWorld::Ptr item = winMgr->getInventoryWindow ()->getAvatarSelectedItem (relMousePos.left, relMousePos.top); mFocusObject = item; if (!mFocusObject.isEmpty ()) @@ -486,7 +488,9 @@ namespace MWGui effectsWidget->setEffectList(info.effects); std::vector effectItems; - effectsWidget->createEffectWidgets(effectItems, effectArea, coord, true, info.isPotion ? Widgets::MWEffectList::EF_NoTarget : 0); + int flag = info.isPotion ? Widgets::MWEffectList::EF_NoTarget : 0; + flag |= info.isIngredient ? Widgets::MWEffectList::EF_NoMagnitude : 0; + effectsWidget->createEffectWidgets(effectItems, effectArea, coord, true, flag); totalSize.height += coord.top-6; totalSize.width = std::max(totalSize.width, coord.width); } diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 3b8e8b2cc..43187dc5c 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -22,6 +22,7 @@ namespace MWGui : imageSize(32) , remainingEnchantCharge(-1) , isPotion(false) + , isIngredient(false) , wordWrap(true) {} @@ -41,6 +42,7 @@ namespace MWGui std::vector notes; bool isPotion; // potions do not show target in the tooltip + bool isIngredient; // ingredients have no effect magnitude bool wordWrap; }; diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index b197ecef6..83096140e 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -44,9 +45,6 @@ namespace namespace MWGui { - const float TradeWindow::sBalanceChangeInitialPause = 0.5f; - const float TradeWindow::sBalanceChangeInterval = 0.1f; - TradeWindow::TradeWindow() : WindowBase("openmw_trade_window.layout") , mSortModel(nullptr) @@ -138,7 +136,7 @@ namespace MWGui onFilterChanged(mFilterAll); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTotalBalance); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTotalBalance); } void TradeWindow::onFrame(float dt) @@ -311,7 +309,7 @@ namespace MWGui if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(itemStack.mBase.getCellRef().getRefId(), mPtr)) { std::string msg = gmst.find("sNotifyMessage49")->mValue.getString(); - Misc::StringUtils::replace(msg, "%s", itemStack.mBase.getClass().getName(itemStack.mBase).c_str(), 2); + msg = Misc::StringUtils::format(msg, itemStack.mBase.getClass().getName(itemStack.mBase)); MWBase::Environment::get().getWindowManager()->messageBox(msg); MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, itemStack.mBase, mPtr, itemStack.mCount); @@ -382,10 +380,9 @@ namespace MWGui void TradeWindow::addRepeatController(MyGUI::Widget *widget) { - MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(Controllers::ControllerRepeatEvent::getClassTypeName()); - Controllers::ControllerRepeatEvent* controller = item->castType(); - controller->eventRepeatClick += MyGUI::newDelegate(this, &TradeWindow::onRepeatClick); - controller->setRepeat(sBalanceChangeInitialPause, sBalanceChangeInterval); + MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MyGUI::ControllerRepeatClick::getClassTypeName()); + MyGUI::ControllerRepeatClick* controller = static_cast(item); + controller->eventRepeatClick += newDelegate(this, &TradeWindow::onRepeatClick); MyGUI::ControllerManager::getInstance().addItem(widget, controller); } @@ -468,16 +465,26 @@ namespace MWGui int merchantOffer = 0; + // The offered price must be capped at 75% of the base price to avoid exploits + // connected to buying and selling the same item. + // This value has been determined by researching the limitations of the vanilla formula + // and may not be sufficient if getBarterOffer behavior has been changed. std::vector playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); for (const ItemStack& itemStack : playerBorrowed) { - merchantOffer -= MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(itemStack.mBase, itemStack.mCount), true); + const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); + const int cap = static_cast(std::max(1.f, 0.75f * basePrice)); // Minimum buying price -- 75% of the base + const int buyingPrice = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, basePrice, true); + merchantOffer -= std::max(cap, buyingPrice); } std::vector merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); for (const ItemStack& itemStack : merchantBorrowed) { - merchantOffer += MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(itemStack.mBase, itemStack.mCount), false); + const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); + const int cap = static_cast(std::max(1.f, 0.75f * basePrice)); // Maximum selling price -- 75% of the base + const int sellingPrice = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, basePrice, false); + merchantOffer += mPtr.getClass().isNpc() ? std::min(cap, sellingPrice) : sellingPrice; } int diff = merchantOffer - mCurrentMerchantOffer; diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index ee528ec58..7ec8f814a 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -91,22 +91,22 @@ namespace MWGui mTimeAdvancer.eventInterrupted += MyGUI::newDelegate(this, &WaitDialog::onWaitingInterrupted); mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &WaitDialog::onWaitingFinished); } - - void WaitDialog::onReferenceUnavailable () - { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Rest); - resetReference(); - } void WaitDialog::setPtr(const MWWorld::Ptr &ptr) { - mPtr = ptr; - setCanRest(!mPtr.isEmpty() || MWBase::Environment::get().getWorld ()->canRest () == MWBase::World::Rest_Allowed); + setCanRest(!ptr.isEmpty() || MWBase::Environment::get().getWorld ()->canRest () == MWBase::World::Rest_Allowed); + if (ptr.isEmpty() && MWBase::Environment::get().getWorld ()->canRest() == MWBase::World::Rest_PlayerIsInAir) + { + // Resting in air is not allowed unless you're using a bed + MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage1}"); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Rest); + } + if (mUntilHealedButton->getVisible()) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mUntilHealedButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mUntilHealedButton); else - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mWaitButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mWaitButton); } bool WaitDialog::exit() @@ -118,7 +118,6 @@ namespace MWGui { mSleeping = false; mTimeAdvancer.stop(); - resetReference(); } void WaitDialog::onOpen() @@ -152,12 +151,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage1}"); MWBase::Environment::get().getWindowManager()->popGuiMode (); } - else if (mPtr.isEmpty() && canRest == MWBase::World::Rest_PlayerIsInAir) - { - // Resting in air is not allowed either, unless you're using a bed - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage1}"); - MWBase::Environment::get().getWindowManager()->popGuiMode (); - } /* Start of tes3mp addition @@ -268,7 +261,7 @@ namespace MWGui { mHourText->setCaptionWithReplacing (MyGUI::utility::toString(position+1) + " #{sRestMenu2}"); mManualHours = position+1; - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mWaitButton); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mWaitButton); } void WaitDialog::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character) @@ -351,8 +344,6 @@ namespace MWGui void WaitDialog::onFrame(float dt) { - checkReferenceAvailable(); - mTimeAdvancer.onFrame(dt); if (mFadeTimeRemaining <= 0) diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index b54a9e254..2aecb002f 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -4,7 +4,6 @@ #include "timeadvancer.hpp" #include "windowbase.hpp" -#include "referenceinterface.hpp" namespace MWGui { @@ -23,7 +22,7 @@ namespace MWGui MyGUI::TextBox* mProgressText; }; - class WaitDialog : public WindowBase, public ReferenceInterface + class WaitDialog : public WindowBase { public: WaitDialog(); @@ -64,8 +63,6 @@ namespace MWGui WaitDialogProgressBar mProgressBar; - virtual void onReferenceUnavailable(); - void onUntilHealedButtonClicked(MyGUI::Widget* sender); void onWaitButtonClicked(MyGUI::Widget* sender); void onCancelButtonClicked(MyGUI::Widget* sender); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 54566d896..6e3e081a9 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -235,6 +235,7 @@ namespace MWGui params.mRange = effectInfo.mRange; params.mIsConstant = (flags & MWEffectList::EF_Constant) != 0; params.mNoTarget = (flags & MWEffectList::EF_NoTarget); + params.mNoMagnitude = (flags & MWEffectList::EF_NoMagnitude); effect->setSpellEffect(params); effects.push_back(effect); coord.top += effect->getHeight(); @@ -293,6 +294,7 @@ namespace MWGui effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); effectInfo.mIsConstant = (flags & EF_Constant) || effectInfo.mIsConstant; effectInfo.mNoTarget = (flags & EF_NoTarget) || effectInfo.mNoTarget; + effectInfo.mNoMagnitude = (flags & EF_NoMagnitude) || effectInfo.mNoMagnitude; effect->setSpellEffect(effectInfo); effects.push_back(effect); if (effect->getRequestedWidth() > maxwidth) @@ -408,7 +410,7 @@ namespace MWGui spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString(ESM::Attribute::sGmstAttributeIds[mEffectParams.mAttribute], ""); } - if (mEffectParams.mMagnMin >= 0 || mEffectParams.mMagnMax >= 0) { + if (mEffectParams.mMagnMin || mEffectParams.mMagnMax) { ESM::MagicEffect::MagnitudeDisplayType displayType = magicEffect->getMagnitudeDisplayType(); if ( displayType == ESM::MagicEffect::MDT_TimesInt ) { std::string timesInt = MWBase::Environment::get().getWindowManager()->getGameSettingString("sXTimesINT", ""); @@ -421,7 +423,7 @@ namespace MWGui spellLine += formatter.str(); } - else if ( displayType != ESM::MagicEffect::MDT_None ) { + else if ( displayType != ESM::MagicEffect::MDT_None && !mEffectParams.mNoMagnitude) { spellLine += " " + MyGUI::utility::toString(mEffectParams.mMagnMin); if (mEffectParams.mMagnMin != mEffectParams.mMagnMax) spellLine += to + MyGUI::utility::toString(mEffectParams.mMagnMax); @@ -431,9 +433,9 @@ namespace MWGui else if ( displayType == ESM::MagicEffect::MDT_Feet ) spellLine += " " + ft; else if ( displayType == ESM::MagicEffect::MDT_Level ) - spellLine += " " + ((mEffectParams.mMagnMin == 1 && mEffectParams.mMagnMax == 1) ? lvl : lvls ); + spellLine += " " + ((mEffectParams.mMagnMin == mEffectParams.mMagnMax && std::abs(mEffectParams.mMagnMin) == 1) ? lvl : lvls ); else // ESM::MagicEffect::MDT_Points - spellLine += " " + ((mEffectParams.mMagnMin == 1 && mEffectParams.mMagnMax == 1) ? pt : pts ); + spellLine += " " + ((mEffectParams.mMagnMin == mEffectParams.mMagnMax && std::abs(mEffectParams.mMagnMin) == 1) ? pt : pts ); } } @@ -528,99 +530,5 @@ namespace MWGui assignWidget(mBarWidget, "Bar"); assignWidget(mBarTextWidget, "BarText"); } - - MWScrollBar::MWScrollBar() - : mEnableRepeat(true) - , mRepeatTriggerTime(0.5f) - , mRepeatStepTime(0.1f) - , mIsIncreasing(true) - { - ScrollBar::setRepeatEnabled(false); - } - - MWScrollBar::~MWScrollBar() - { - } - - void MWScrollBar::initialiseOverride() - { - ScrollBar::initialiseOverride(); - - if(mWidgetStart) - { - mWidgetStart->eventMouseButtonPressed += MyGUI::newDelegate(this, &MWScrollBar::onDecreaseButtonPressed); - mWidgetStart->eventMouseButtonReleased += MyGUI::newDelegate(this, &MWScrollBar::onDecreaseButtonReleased); - } - if(mWidgetEnd) - { - mWidgetEnd->eventMouseButtonPressed += MyGUI::newDelegate(this, &MWScrollBar::onIncreaseButtonPressed); - mWidgetEnd->eventMouseButtonReleased += MyGUI::newDelegate(this, &MWScrollBar::onIncreaseButtonReleased); - } - } - - void MWScrollBar::setRepeat(float trigger, float step) - { - mRepeatTriggerTime = trigger; - mRepeatStepTime = step; - } - - void MWScrollBar::repeatClick(MyGUI::Widget* _widget, MyGUI::ControllerItem* _controller) - { - int stepSize = mScrollPage; - - if(mIsIncreasing && mScrollPosition < mScrollRange-1) - { - if(mScrollPosition + stepSize > mScrollRange-1) - mScrollPosition = mScrollRange-1; - else - mScrollPosition += stepSize; - - eventScrollChangePosition(this, mScrollPosition); - updateTrack(); - } - else if(!mIsIncreasing && mScrollPosition > 0) - { - int newPos = mScrollPosition - stepSize; - if(newPos < 0) - mScrollPosition = 0; - else - mScrollPosition -= stepSize; - - eventScrollChangePosition(this, mScrollPosition); - updateTrack(); - } - } - - void MWScrollBar::onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) - { - mIsIncreasing = false; - MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MWGui::Controllers::ControllerRepeatEvent::getClassTypeName()); - MWGui::Controllers::ControllerRepeatEvent* controller = item->castType(); - controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); - controller->setEnabled(mEnableRepeat); - controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); - MyGUI::ControllerManager::getInstance().addItem(this, controller); - } - - void MWScrollBar::onDecreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) - { - MyGUI::ControllerManager::getInstance().removeItem(this); - } - - void MWScrollBar::onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) - { - mIsIncreasing = true; - MyGUI::ControllerItem* item = MyGUI::ControllerManager::getInstance().createItem(MWGui::Controllers::ControllerRepeatEvent::getClassTypeName()); - MWGui::Controllers::ControllerRepeatEvent* controller = item->castType(); - controller->eventRepeatClick += newDelegate(this, &MWScrollBar::repeatClick); - controller->setEnabled(mEnableRepeat); - controller->setRepeat(mRepeatTriggerTime, mRepeatStepTime); - MyGUI::ControllerManager::getInstance().addItem(this, controller); - } - - void MWScrollBar::onIncreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) - { - MyGUI::ControllerManager::getInstance().removeItem(this); - } } } diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 6d9c0a580..6eab431d6 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -38,6 +38,7 @@ namespace MWGui SpellEffectParams() : mNoTarget(false) , mIsConstant(false) + , mNoMagnitude(false) , mKnown(true) , mEffectID(-1) , mSkill(-1) @@ -52,6 +53,7 @@ namespace MWGui bool mNoTarget; // potion effects for example have no target (target is always the player) bool mIsConstant; // constant effect means that duration will not be displayed + bool mNoMagnitude; // effect magnitude will not be displayed (e.g ingredients) bool mKnown; // is this effect known to the player? (If not, will display as a question mark instead) @@ -218,7 +220,9 @@ namespace MWGui enum EffectFlags { EF_NoTarget = 0x01, // potions have no target (target is always the player) - EF_Constant = 0x02 // constant effect means that duration will not be displayed + EF_Constant = 0x02, // constant effect means that duration will not be displayed + EF_NoMagnitude = 0x04 // ingredients have no magnitude + }; void setEffectList(const SpellEffectList& list); @@ -299,33 +303,6 @@ namespace MWGui MyGUI::TextBox* mBarTextWidget; }; typedef MWDynamicStat* MWDynamicStatPtr; - - // Should be removed when upgrading to MyGUI 3.2.2 (current git), it has ScrollBar autorepeat support - class MWScrollBar : public MyGUI::ScrollBar - { - MYGUI_RTTI_DERIVED(MWScrollBar) - - public: - MWScrollBar(); - virtual ~MWScrollBar(); - - void setRepeat(float trigger, float step); - - protected: - virtual void initialiseOverride(); - void repeatClick(MyGUI::Widget* _widget, MyGUI::ControllerItem* _controller); - - bool mEnableRepeat; - float mRepeatTriggerTime; - float mRepeatStepTime; - bool mIsIncreasing; - - private: - void onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); - void onDecreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); - void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); - void onIncreaseButtonReleased(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); - }; } } diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index d483a0b4b..e7c975aa2 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -1,5 +1,6 @@ #include "windowbase.hpp" +#include #include #include @@ -9,6 +10,7 @@ #include #include "draganddrop.hpp" +#include "exposedwindow.hpp" using namespace MWGui; @@ -16,6 +18,32 @@ WindowBase::WindowBase(const std::string& parLayout) : Layout(parLayout) { mMainWidget->setVisible(false); + + Window* window = mMainWidget->castType(false); + if (!window) + return; + + MyGUI::Button* button = nullptr; + MyGUI::VectorWidgetPtr widgets = window->getSkinWidgetsByName("Action"); + for (MyGUI::Widget* widget : widgets) + { + if (widget->isUserString("SupportDoubleClick")) + button = widget->castType(); + } + + if (button) + button->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WindowBase::onDoubleClick); +} + +void WindowBase::onTitleDoubleClicked() +{ + if (MyGUI::InputManager::getInstance().isShiftPressed()) + MWBase::Environment::get().getWindowManager()->toggleMaximized(this); +} + +void WindowBase::onDoubleClick(MyGUI::Widget *_sender) +{ + onTitleDoubleClicked(); } void WindowBase::setVisible(bool visible) @@ -27,18 +55,6 @@ void WindowBase::setVisible(bool visible) onOpen(); else if (wasVisible) onClose(); - - // This is needed as invisible widgets can retain key focus. - // Remove for MyGUI 3.2.2 - if (!visible) - { - MyGUI::Widget* keyFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); - while (keyFocus != mMainWidget && keyFocus != nullptr) - keyFocus = keyFocus->getParent(); - - if (keyFocus == mMainWidget) - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr); - } } bool WindowBase::isVisible() diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 8e7fb86b4..56249d77d 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -20,7 +20,7 @@ namespace MWGui class WindowBase: public Layout { - public: + public: WindowBase(const std::string& parLayout); virtual MyGUI::Widget* getDefaultKeyFocus() { return nullptr; } @@ -52,8 +52,13 @@ namespace MWGui /// Called when GUI viewport changes size virtual void onResChange(int width, int height) {} - }; + protected: + virtual void onTitleDoubleClicked(); + + private: + void onDoubleClick(MyGUI::Widget* _sender); + }; /* * "Modal" windows cause the rest of the interface to be inaccessible while they are visible diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 5d5977828..2f7a4f10c 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -228,8 +228,7 @@ namespace MWGui 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("Layer"); @@ -248,7 +247,6 @@ namespace MWGui 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"); @@ -459,7 +457,6 @@ namespace MWGui mConsole = new Console(w,h, mConsoleOnlyScripts); mWindows.push_back(mConsole); trackWindow(mConsole, "console"); - mGuiModeStates[GM_Console] = GuiModeState(mConsole); bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds"); JournalWindow* journal = JournalWindow::create(JournalViewModel::create (), questList, mEncoding); @@ -721,7 +718,7 @@ namespace MWGui setCursorVisible(!gameMode); if (gameMode) - setKeyFocusWidget (nullptr); + MyGUI::InputManager::getInstance().resetKeyFocusWidget(); // Icons of forced hidden windows are displayed setMinimapVisibility((mAllowed & GW_Map) && (!mMap->pinned() || (mForceHidden & GW_Map))); @@ -734,10 +731,10 @@ namespace MWGui // If in game mode (or interactive messagebox), show the pinned windows if (mGuiModes.empty()) { - mMap->setVisible(mMap->pinned() && !(mForceHidden & GW_Map) && (mAllowed & GW_Map)); - mStatsWindow->setVisible(mStatsWindow->pinned() && !(mForceHidden & GW_Stats) && (mAllowed & GW_Stats)); - mInventoryWindow->setVisible(mInventoryWindow->pinned() && !(mForceHidden & GW_Inventory) && (mAllowed & GW_Inventory)); - mSpellWindow->setVisible(mSpellWindow->pinned() && !(mForceHidden & GW_Magic) && (mAllowed & GW_Magic)); + mMap->setVisible(mMap->pinned() && !isConsoleMode() && !(mForceHidden & GW_Map) && (mAllowed & GW_Map)); + mStatsWindow->setVisible(mStatsWindow->pinned() && !isConsoleMode() && !(mForceHidden & GW_Stats) && (mAllowed & GW_Stats)); + mInventoryWindow->setVisible(mInventoryWindow->pinned() && !isConsoleMode() && !(mForceHidden & GW_Inventory) && (mAllowed & GW_Inventory)); + mSpellWindow->setVisible(mSpellWindow->pinned() && !isConsoleMode() && !(mForceHidden & GW_Magic) && (mAllowed & GW_Magic)); return; } else if (getMode() != GM_Inventory) @@ -1019,6 +1016,12 @@ namespace MWGui void WindowManager::onFrame (float frameDuration) { + bool gameRunning = MWBase::Environment::get().getStateManager()->getState()!= + MWBase::StateManager::State_NoGame; + + if (gameRunning) + updateMap(); + if (!mGuiModes.empty()) { GuiModeState& state = mGuiModeStates[mGuiModes.back()]; @@ -1062,14 +1065,11 @@ namespace MWGui if (mLocalMapRender) mLocalMapRender->cleanupCameras(); - if (MWBase::Environment::get().getStateManager()->getState()== - MWBase::StateManager::State_NoGame) + if (!gameRunning) return; mDragAndDrop->onFrame(); - updateMap(); - mHud->onFrame(frameDuration); mDebugWindow->onFrame(frameDuration); @@ -1254,6 +1254,7 @@ namespace MWGui else if (tag.compare(0, tokenLength, tokenToFind) == 0) { _result = mTranslationDataStorage.translateCellName(tag.substr(tokenLength)); + _result = MyGUI::TextIterator::toTagsString(_result); } else if (Gui::replaceTag(tag, _result)) { @@ -1306,10 +1307,14 @@ namespace MWGui for (std::map::iterator it = mTrackedWindows.begin(); it != mTrackedWindows.end(); ++it) { - MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(it->second + " x", "Windows") * x), - static_cast( Settings::Manager::getFloat(it->second+ " y", "Windows") * y)); - MyGUI::IntSize size(static_cast(Settings::Manager::getFloat(it->second + " w", "Windows") * x), - static_cast(Settings::Manager::getFloat(it->second + " h", "Windows") * y)); + std::string settingName = it->second; + if (Settings::Manager::getBool(settingName + " maximized", "Windows")) + settingName += " maximized"; + + MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(settingName + " x", "Windows") * x), + static_cast(Settings::Manager::getFloat(settingName + " y", "Windows") * y)); + MyGUI::IntSize size(static_cast(Settings::Manager::getFloat(settingName + " w", "Windows") * x), + static_cast(Settings::Manager::getFloat(settingName + " h", "Windows") * y)); it->first->setPosition(pos); it->first->setSize(size); } @@ -1386,6 +1391,10 @@ namespace MWGui } updateVisible(); + + // To make sure that console window get focus again + if (mConsole && mConsole->isVisible()) + mConsole->onOpen(); } void WindowManager::removeGuiMode(GuiMode mode, bool noSound) @@ -1619,14 +1628,15 @@ namespace MWGui bool WindowManager::isGuiMode() const { - return !mGuiModes.empty() || (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); + return + !mGuiModes.empty() || + isConsoleMode() || + (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); } bool WindowManager::isConsoleMode() const { - if (!mGuiModes.empty() && mGuiModes.back()==GM_Console) - return true; - return false; + return mConsole && mConsole->isVisible(); } MWGui::GuiMode WindowManager::getMode() const @@ -1794,16 +1804,6 @@ namespace MWGui } } - // Remove this method for MyGUI 3.2.2 - void WindowManager::setKeyFocusWidget(MyGUI::Widget *widget) - { - if (widget == nullptr) - MyGUI::InputManager::getInstance().resetKeyFocusWidget(); - else - MyGUI::InputManager::getInstance().setKeyFocusWidget(widget); - onKeyFocusChanged(widget); - } - void WindowManager::onKeyFocusChanged(MyGUI::Widget *widget) { if (widget && widget->castType(false)) @@ -1838,11 +1838,16 @@ namespace MWGui void WindowManager::trackWindow(Layout *layout, const std::string &name) { + std::string settingName = name; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(name + " x", "Windows") * viewSize.width), - static_cast(Settings::Manager::getFloat(name + " y", "Windows") * viewSize.height)); - MyGUI::IntSize size (static_cast(Settings::Manager::getFloat(name + " w", "Windows") * viewSize.width), - static_cast(Settings::Manager::getFloat(name + " h", "Windows") * viewSize.height)); + bool isMaximized = Settings::Manager::getBool(name + " maximized", "Windows"); + if (isMaximized) + settingName += " maximized"; + + MyGUI::IntPoint pos(static_cast(Settings::Manager::getFloat(settingName + " x", "Windows") * viewSize.width), + static_cast(Settings::Manager::getFloat(settingName + " y", "Windows") * viewSize.height)); + MyGUI::IntSize size (static_cast(Settings::Manager::getFloat(settingName + " w", "Windows") * viewSize.width), + static_cast(Settings::Manager::getFloat(settingName + " h", "Windows") * viewSize.height)); layout->mMainWidget->setPosition(pos); layout->mMainWidget->setSize(size); @@ -1851,6 +1856,26 @@ namespace MWGui mTrackedWindows[window] = name; } + void WindowManager::toggleMaximized(Layout *layout) + { + MyGUI::Window* window = layout->mMainWidget->castType(); + std::string setting = mTrackedWindows[window]; + if (setting.empty()) + return; + + bool maximized = !Settings::Manager::getBool(setting + " maximized", "Windows"); + if (maximized) + setting += " maximized"; + + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + float x = Settings::Manager::getFloat(setting + " x", "Windows") * float(viewSize.width); + float y = Settings::Manager::getFloat(setting + " y", "Windows") * float(viewSize.height); + float w = Settings::Manager::getFloat(setting + " w", "Windows") * float(viewSize.width); + float h = Settings::Manager::getFloat(setting + " h", "Windows") * float(viewSize.height); + window->setCoord(x, y, w, h); + Settings::Manager::setBool(mTrackedWindows[window] + " maximized", "Windows", maximized); + } + void WindowManager::onWindowChangeCoord(MyGUI::Window *_sender) { std::string setting = mTrackedWindows[_sender]; @@ -1863,6 +1888,9 @@ namespace MWGui Settings::Manager::setFloat(setting + " y", "Windows", y); Settings::Manager::setFloat(setting + " w", "Windows", w); Settings::Manager::setFloat(setting + " h", "Windows", h); + bool maximized = Settings::Manager::getBool(setting + " maximized", "Windows"); + if (maximized) + Settings::Manager::setBool(setting + " maximized", "Windows", false); } void WindowManager::clear() @@ -1942,6 +1970,7 @@ namespace MWGui bool WindowManager::isSavingAllowed() const { return !MyGUI::InputManager::getInstance().isModalAny() + && !isConsoleMode() // TODO: remove this, once we have properly serialized the state of open windows && (!isGuiMode() || (mGuiModes.size() == 1 && (getMode() == GM_MainMenu || getMode() == GM_Rest))); } @@ -1968,7 +1997,7 @@ namespace MWGui sizeVideo(screenSize.width, screenSize.height); MyGUI::Widget* oldKeyFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); - setKeyFocusWidget(mVideoWidget); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mVideoWidget); mVideoBackground->setVisible(true); @@ -2006,7 +2035,7 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->resumeSounds(); - setKeyFocusWidget(oldKeyFocus); + MyGUI::InputManager::getInstance().setKeyFocusWidget(oldKeyFocus); setCursorVisible(cursorWasVisible); @@ -2189,6 +2218,21 @@ namespace MWGui SDL_free(text); } + void WindowManager::toggleConsole() + { + bool visible = mConsole->isVisible(); + + if (!visible && !mGuiModes.empty()) + mKeyboardNavigation->saveFocus(mGuiModes.back()); + + mConsole->setVisible(!visible); + + if (visible && !mGuiModes.empty()) + mKeyboardNavigation->restoreFocus(mGuiModes.back()); + + updateVisible(); + } + void WindowManager::toggleDebugWindow() { mDebugWindow->setVisible(!mDebugWindow->isVisible()); @@ -2358,6 +2402,11 @@ namespace MWGui tex->unlock(); } + void WindowManager::addCell(MWWorld::CellStore* cell) + { + mLocalMapRender->addCell(cell); + } + void WindowManager::removeCell(MWWorld::CellStore *cell) { mLocalMapRender->removeCell(cell); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 5868dbec2..fea01b5b8 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -147,9 +147,6 @@ namespace MWGui /// (and will continually update the window while doing so) virtual void playVideo(const std::string& name, bool allowSkipping); - /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. - virtual void setKeyFocusWidget (MyGUI::Widget* widget); - virtual void setNewGame(bool newgame); virtual void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg); @@ -436,6 +433,7 @@ namespace MWGui virtual void removeCurrentModal(WindowModal* input); virtual void pinWindow (MWGui::GuiWindow window); + virtual void toggleMaximized(Layout *layout); /// Fade the screen in, over \a time seconds virtual void fadeScreenIn(const float time, bool clearQueue, float delay); @@ -449,6 +447,7 @@ namespace MWGui virtual void activateHitOverlay(bool interrupt); virtual void setWerewolfOverlay(bool set); + virtual void toggleConsole(); virtual void toggleDebugWindow(); /// Cycle to next or previous spell @@ -464,6 +463,7 @@ namespace MWGui virtual std::string correctTexturePath(const std::string& path); virtual bool textureExists(const std::string& path); + void addCell(MWWorld::CellStore* cell); void removeCell(MWWorld::CellStore* cell); void writeFog(MWWorld::CellStore* cell); diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp index 88e88b494..6106d6b56 100644 --- a/apps/openmw/mwgui/windowpinnablebase.cpp +++ b/apps/openmw/mwgui/windowpinnablebase.cpp @@ -1,7 +1,5 @@ #include "windowpinnablebase.hpp" -#include - #include "exposedwindow.hpp" namespace MWGui @@ -9,21 +7,10 @@ namespace MWGui WindowPinnableBase::WindowPinnableBase(const std::string& parLayout) : WindowBase(parLayout), mPinned(false) { - ExposedWindow* window = mMainWidget->castType(); + Window* window = mMainWidget->castType(); mPinButton = window->getSkinWidget ("Button"); mPinButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonPressed); - - MyGUI::Button* button = nullptr; - MyGUI::VectorWidgetPtr widgets = window->getSkinWidgetsByName("Action"); - for (MyGUI::Widget* widget : widgets) - { - if (widget->isUserString("HideWindowOnDoubleClick")) - button = widget->castType(); - } - - if (button) - button->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WindowPinnableBase::onDoubleClick); } void WindowPinnableBase::onPinButtonPressed(MyGUI::Widget* _sender, int left, int top, MyGUI::MouseButton id) @@ -41,11 +28,6 @@ namespace MWGui onPinToggled(); } - void WindowPinnableBase::onDoubleClick(MyGUI::Widget *_sender) - { - onTitleDoubleClicked(); - } - void WindowPinnableBase::setPinned(bool pinned) { if (pinned != mPinned) diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp index c085bebf2..a94212819 100644 --- a/apps/openmw/mwgui/windowpinnablebase.hpp +++ b/apps/openmw/mwgui/windowpinnablebase.hpp @@ -17,11 +17,9 @@ namespace MWGui private: void onPinButtonPressed(MyGUI::Widget* _sender, int left, int top, MyGUI::MouseButton id); - void onDoubleClick(MyGUI::Widget* _sender); protected: virtual void onPinToggled() = 0; - virtual void onTitleDoubleClicked() = 0; MyGUI::Widget* mPinButton; bool mPinned; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index c22b9dae8..590895658 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -507,7 +507,7 @@ namespace MWInput void InputManager::updateCursorMode() { bool grab = !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) - && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Console; + && !MWBase::Environment::get().getWindowManager()->isConsoleMode(); bool was_relative = mInputManager->getMouseRelative(); bool is_relative = !MWBase::Environment::get().getWindowManager()->isGuiMode(); @@ -618,6 +618,7 @@ namespace MWInput { bool triedToMove = false; bool isRunning = false; + bool alwaysRunAllowed = false; // joystick movement float xAxis = mInputBinder->getChannel(A_MoveLeftRight)->getValue(); @@ -646,31 +647,23 @@ namespace MWInput isRunning = xAxis > .75 || xAxis < .25 || yAxis > .75 || yAxis < .25; if(triedToMove) resetIdleTime(); - if (actionIsActive(A_MoveLeft) && !actionIsActive(A_MoveRight)) + if (actionIsActive(A_MoveLeft) != actionIsActive(A_MoveRight)) { + alwaysRunAllowed = true; triedToMove = true; - mPlayer->setLeftRight (-1); - } - else if (actionIsActive(A_MoveRight) && !actionIsActive(A_MoveLeft)) - { - triedToMove = true; - mPlayer->setLeftRight (1); + mPlayer->setLeftRight (actionIsActive(A_MoveRight) ? 1 : -1); } - if (actionIsActive(A_MoveForward) && !actionIsActive(A_MoveBackward)) - { - triedToMove = true; - mPlayer->setAutoMove (false); - mPlayer->setForwardBackward (1); - } - else if (actionIsActive(A_MoveBackward) && !actionIsActive(A_MoveForward)) + if (actionIsActive(A_MoveForward) != actionIsActive(A_MoveBackward)) { + alwaysRunAllowed = true; triedToMove = true; mPlayer->setAutoMove (false); - mPlayer->setForwardBackward (-1); + mPlayer->setForwardBackward (actionIsActive(A_MoveForward) ? 1 : -1); } else if(mPlayer->getAutoMove()) { + alwaysRunAllowed = true; triedToMove = true; mPlayer->setForwardBackward (1); } @@ -715,7 +708,7 @@ namespace MWInput mOverencumberedMessageDelay = 0.f; } - if (mAlwaysRunActive || isRunning) + if ((mAlwaysRunActive && alwaysRunAllowed) || isRunning) mPlayer->setRunState(!actionIsActive(A_Run)); else mPlayer->setRunState(actionIsActive(A_Run)); @@ -838,9 +831,6 @@ 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(); } } @@ -880,7 +870,7 @@ namespace MWInput OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(arg.keysym.sym); if (mInputBinder->getKeyBinding(mInputBinder->getControl(A_Console), ICS::Control::INCREASE) == arg.keysym.scancode - && MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Console) + && MWBase::Environment::get().getWindowManager()->isConsoleMode()) SDL_StopTextInput(); bool consumed = false; @@ -1142,10 +1132,17 @@ namespace MWInput void InputManager::windowResized(int x, int y) { + // Note: this is a side effect of resolution change or window resize. + // There is no need to track these changes. Settings::Manager::setInt("resolution x", "Video", x); Settings::Manager::setInt("resolution y", "Video", y); + Settings::Manager::resetPendingChange("resolution x", "Video"); + Settings::Manager::resetPendingChange("resolution y", "Video"); MWBase::Environment::get().getWindowManager()->windowResized(x, y); + + // We should reload TrueType fonts to fit new resolution + MWBase::Environment::get().getWindowManager()->loadUserFonts(); } void InputManager::windowClosed() @@ -1161,6 +1158,9 @@ namespace MWInput return; } + if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) + return; + bool inGame = MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame; MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); @@ -1179,6 +1179,9 @@ namespace MWInput return; } + if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) + return; + MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); bool inGame = MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame; @@ -1327,7 +1330,7 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - + /* Start of tes3mp addition @@ -1339,6 +1342,9 @@ namespace MWInput End of tes3mp addition */ + if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) + return; + // Toggle between game mode and inventory mode if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Inventory); @@ -1368,17 +1374,7 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - // Switch to console mode no matter what mode we are currently - // in, except of course if we are already in console mode - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) - { - if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Console) - MWBase::Environment::get().getWindowManager()->popGuiMode(); - else - MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Console); - } - else - MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Console); + MWBase::Environment::get().getWindowManager()->toggleConsole(); } void InputManager::toggleJournal() @@ -1461,6 +1457,7 @@ namespace MWInput void InputManager::toggleSneaking() { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; + if (!mControlSwitch["playercontrols"]) return; mSneaking = !mSneaking; mPlayer->setSneak(mSneaking); } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 11d6cff45..ce0ab3890 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -501,7 +501,7 @@ namespace MWMechanics { MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); // Also have actor1's allies start combat - for (const MWWorld::Ptr ally1 : allies1) + for (const MWWorld::Ptr& ally1 : allies1) MWBase::Environment::get().getMechanicsManager()->startCombat(ally1, actor2); return; } @@ -2328,7 +2328,7 @@ namespace MWMechanics std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); getObjectsInRange(position, mActorsProcessingRange, neighbors); - for(const MWWorld::Ptr neighbor : neighbors) + for(const MWWorld::Ptr& neighbor : neighbors) { if (neighbor == actor) continue; diff --git a/apps/openmw/mwmechanics/actorutil.cpp b/apps/openmw/mwmechanics/actorutil.cpp index 537f27197..e27c9de49 100644 --- a/apps/openmw/mwmechanics/actorutil.cpp +++ b/apps/openmw/mwmechanics/actorutil.cpp @@ -3,6 +3,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/player.hpp" namespace MWMechanics @@ -16,4 +17,10 @@ namespace MWMechanics { return MWBase::Environment::get().getWorld()->getPlayer().isInCombat(); } + + bool canActorMoveByZAxis(const MWWorld::Ptr& actor) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); + } } diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index 3c6dc940c..82a904799 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -10,6 +10,7 @@ namespace MWMechanics { MWWorld::Ptr getPlayer(); bool isPlayerInCombat(); + bool canActorMoveByZAxis(const MWWorld::Ptr& actor); } #endif diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 111330090..94a502544 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -402,9 +402,3 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: return result; } - -bool MWMechanics::AiPackage::canActorMoveByZAxis(const MWWorld::Ptr& actor) const -{ - MWBase::World* world = MWBase::Environment::get().getWorld(); - return (actor.getClass().canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor) || !world->isActorCollisionEnabled(actor); -} diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 9ccbfed59..6bb12342a 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -130,8 +130,6 @@ namespace MWMechanics DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const; - bool canActorMoveByZAxis(const MWWorld::Ptr& actor) const; - // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index db2057df3..dc225ac23 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -215,11 +215,7 @@ namespace MWMechanics getAllowedNodes(actor, currentCell->getCell(), storage); } - bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) - || MWBase::Environment::get().getWorld()->isFlying(actor) - || !MWBase::Environment::get().getWorld()->isActorCollisionEnabled(actor); - - if(actorCanMoveByZ && mDistance > 0) { + if (canActorMoveByZAxis(actor) && mDistance > 0) { // Typically want to idle for a short time before the next wander if (Misc::Rng::rollDice(100) >= 92 && storage.mState != AiWanderStorage::Wander_Walking) { wanderNearStart(actor, storage, mDistance); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 18e2e10ca..71f829a56 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -565,8 +565,9 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character mCurrentMovement = movementAnimName; if(!mCurrentMovement.empty()) { - bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) - && !MWBase::Environment::get().getWorld()->isFlying(mPtr); + bool isflying = MWBase::Environment::get().getWorld()->isFlying(mPtr); + bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !isflying; + bool issneaking = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !isflying; // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. @@ -600,7 +601,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character // The first person anims don't have any velocity to calculate a speed multiplier from. // We use the third person velocities instead. // FIXME: should be pulled from the actual animation, but it is not presently loaded. - mMovementAnimSpeed = (isrunning ? 222.857f : 154.064f); + mMovementAnimSpeed = (issneaking ? 33.5452f : (isrunning ? 222.857f : 154.064f)); mMovementAnimationControlled = false; } } @@ -1148,41 +1149,36 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr) void CharacterController::updateIdleStormState(bool inwater) { - bool inStormDirection = false; - if (MWBase::Environment::get().getWorld()->isInStorm()) + if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperCharState_Nothing || inwater) { - osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); - osg::Vec3f characterDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); - inStormDirection = std::acos(stormDirection * characterDirection / (stormDirection.length() * characterDirection.length())) - > osg::DegreesToRadians(120.f); + mAnimation->disable("idlestorm"); + return; } - if (inStormDirection && !inwater && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) - { - float complete = 0; - mAnimation->getInfo("idlestorm", &complete); - if (complete == 0) - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, - 1.0f, "start", "loop start", 0.0f, 0); - else if (complete == 1) - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, - 1.0f, "loop start", "loop stop", 0.0f, ~0ul); - } - else + if (MWBase::Environment::get().getWorld()->isInStorm()) { - if (mUpperBodyState == UpperCharState_Nothing) + osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); + osg::Vec3f characterDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); + stormDirection.normalize(); + characterDirection.normalize(); + if (stormDirection * characterDirection < -0.5f) { - if (mAnimation->isPlaying("idlestorm")) + if (!mAnimation->isPlaying("idlestorm")) { - if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) - { - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, true, - 1.0f, "loop stop", "stop", 0.0f, 0); - } + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, true, + 1.0f, "start", "stop", 0.0f, ~0ul); } + else + { + mAnimation->setLoopingEnabled("idlestorm", true); + } + return; } - else - mAnimation->disable("idlestorm"); + } + + if (mAnimation->isPlaying("idlestorm")) + { + mAnimation->setLoopingEnabled("idlestorm", false); } } @@ -2100,24 +2096,24 @@ void CharacterController::update(float duration, bool animationOnly) osg::Vec3f rot = cls.getRotationVector(mPtr); speed = cls.getSpeed(mPtr); + float analogueMult = 1.f; if(isPlayer) { - // Joystick anologue movement. + // Joystick analogue movement. float xAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[0]); float yAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[1]); - float analogueMovement = ((xAxis > yAxis) ? xAxis : yAxis); + analogueMult = ((xAxis > yAxis) ? xAxis : yAxis); // If Strafing, our max speed is slower so multiply by X axis instead. if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) - analogueMovement = xAxis; + analogueMult = xAxis; // Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used. - if(!isrunning && !sneak && !flying && analogueMovement <= 0.5f) - speed *= 2; - - speed *= (analogueMovement); + if(!isrunning && !sneak && !flying && analogueMult <= 0.5f) + analogueMult *= 2.f; } + speed *= analogueMult; vec.x() *= speed; vec.y() *= speed; @@ -2184,6 +2180,7 @@ void CharacterController::update(float duration, bool animationOnly) } } fatigueLoss *= duration; + fatigueLoss *= analogueMult; DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); if (!godmode) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 2fde9d22f..8f0f1a74a 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -590,6 +590,11 @@ namespace MWMechanics { osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3()); osg::Vec3f pos2 (actor2.getRefData().getPosition().asVec3()); + if (canActorMoveByZAxis(actor2)) + { + pos1.z() = 0.f; + pos2.z() = 0.f; + } float d = (pos1 - pos2).length(); diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 805707333..0c5706775 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -61,7 +61,7 @@ namespace MWMechanics std::string msg = "sMagicContractDisease"; msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->mValue.getString(); - Misc::StringUtils::replace(msg, "%s", spell->mName.c_str(), 2); + msg = Misc::StringUtils::format(msg, spell->mName); MWBase::Environment::get().getWindowManager()->messageBox(msg); } } diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index fa896dd86..0d3b1560c 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -193,46 +193,36 @@ namespace MWMechanics * * Formula on UESPWiki is not entirely correct. */ - int Enchanting::getEnchantPoints() const + float Enchanting::getEnchantPoints(bool precise) const { if (mEffectList.mList.empty()) // No effects added, cost = 0 return 0; const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - std::vector mEffects = mEffectList.mList; + const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); + const float fEnchantmentConstantDurationMult = store.get().find("fEnchantmentConstantDurationMult")->mValue.getFloat(); - int enchantmentCost = 0; - float cost = 0; - for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) + float enchantmentCost = 0.f; + float cost = 0.f; + for (const ESM::ENAMstruct& effect : mEffectList.mList) { - float baseCost = (store.get().find(it->mEffectID))->mData.mBaseCost; - int magMin = std::max(1, it->mMagnMin); - int magMax = std::max(1, it->mMagnMax); - int area = std::max(1, it->mArea); - - float magnitudeCost = (magMin + magMax) * baseCost * 0.05f; + float baseCost = (store.get().find(effect.mEffectID))->mData.mBaseCost; + int magMin = std::max(1, effect.mMagnMin); + int magMax = std::max(1, effect.mMagnMax); + int area = std::max(1, effect.mArea); + float duration = static_cast(effect.mDuration); if (mCastStyle == ESM::Enchantment::ConstantEffect) - { - magnitudeCost *= store.get().find("fEnchantmentConstantDurationMult")->mValue.getFloat(); - } - else - { - magnitudeCost *= it->mDuration; - } + duration = fEnchantmentConstantDurationMult; - float areaCost = area * 0.05f * baseCost; - - const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); - - cost += (magnitudeCost + areaCost) * fEffectCostMult; + cost += ((magMin + magMax) * duration + area) * baseCost * fEffectCostMult * 0.05f; cost = std::max(1.f, cost); - if (it->mRange == ESM::RT_Target) - cost *= 1.5; + if (effect.mRange == ESM::RT_Target) + cost *= 1.5f; - enchantmentCost += static_cast(cost); + enchantmentCost += precise ? cost : std::floor(cost); } return enchantmentCost; @@ -244,7 +234,7 @@ namespace MWMechanics if (mCastStyle == ESM::Enchantment::ConstantEffect) return 0; - return getEnchantPoints(); + return static_cast(getEnchantPoints(false)); } int Enchanting::getEffectiveCastCost() const @@ -308,21 +298,21 @@ namespace MWMechanics mEnchanter = enchanter; } - float Enchanting::getEnchantChance() const + int Enchanting::getEnchantChance() const { const CreatureStats& stats = mEnchanter.getClass().getCreatureStats(mEnchanter); - - 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(); + const float a = static_cast(mEnchanter.getClass().getSkill(mEnchanter, ESM::Skill::Enchant)); + const float b = static_cast(stats.getAttribute (ESM::Attribute::Intelligence).getModified()); + const float c = static_cast(stats.getAttribute (ESM::Attribute::Luck).getModified()); + const float fEnchantmentChanceMult = gmst.find("fEnchantmentChanceMult")->mValue.getFloat(); + const float fEnchantmentConstantChanceMult = gmst.find("fEnchantmentConstantChanceMult")->mValue.getFloat(); - float chance2 = 7.5f / (gmst.find("fEnchantmentChanceMult")->mValue.getFloat() * ((mCastStyle == ESM::Enchantment::ConstantEffect) ? - gmst.find("fEnchantmentConstantChanceMult")->mValue.getFloat() : 1.0f )) - * getEnchantPoints(); + float x = (a - getEnchantPoints()*fEnchantmentChanceMult + 0.2f * b + 0.1f * c) * stats.getFatigueTerm(); + if (mCastStyle == ESM::Enchantment::ConstantEffect) + x *= fEnchantmentConstantChanceMult; - return (chance1-chance2); + return static_cast(x); } void Enchanting::payForEnchantment() const diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 275147671..6f72d96a8 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -37,13 +37,13 @@ namespace MWMechanics bool create(); //Return true if created, false if failed. void nextCastStyle(); //Set enchant type to next possible type (for mOldItemPtr object) int getCastStyle() const; - int getEnchantPoints() const; + float getEnchantPoints(bool precise = true) const; int getBaseCastCost() const; // To be saved in the enchantment's record int getEffectiveCastCost() const; // Effective cost taking player Enchant skill into account, used for preview purposes in the UI int getEnchantPrice() const; int getMaxEnchantValue() const; int getGemCharge() const; - float getEnchantChance() const; + int getEnchantChance() const; bool soulEmpty() const; //Return true if empty bool itemEmpty() const; //Return true if empty void payForEnchantment() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 372208a8f..f2771310f 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -689,8 +689,8 @@ namespace MWMechanics float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm(); float npcTerm = (d + e + f) * sellerStats.getFatigueTerm(); - float buyTerm = 0.01f * std::max(75.f, (100 - 0.5f * (pcTerm - npcTerm))); - float sellTerm = 0.01f * std::min(75.f, (50 - 0.5f * (npcTerm - pcTerm))); + float buyTerm = 0.01f * (100 - 0.5f * (pcTerm - npcTerm)); + float sellTerm = 0.01f * (50 - 0.5f * (npcTerm - pcTerm)); int offerPrice = int(basePrice * (buying ? buyTerm : sellTerm)); return std::max(1, offerPrice); } @@ -1011,6 +1011,9 @@ namespace MWMechanics return true; } + if (!target.getClass().canBeActivated(target)) + return true; + // TODO: implement a better check to check if target is owned bed if (target.getClass().isActivator() && target.getClass().getScript(target).compare(0, 3, "Bed") != 0) return true; @@ -1053,7 +1056,7 @@ namespace MWMechanics } if (!cellref.getOwner().empty()) - victim = MWBase::Environment::get().getWorld()->searchPtr(cellref.getOwner(), true); + victim = MWBase::Environment::get().getWorld()->searchPtr(cellref.getOwner(), true, false); return (!isOwned && !isFactionOwned); } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index bd01f5cf8..68821d415 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -66,6 +66,16 @@ const std::map& MWMechanics::NpcStats::getFactionRanks() const return mFactionRank; } +int MWMechanics::NpcStats::getFactionRank(const std::string &faction) const +{ + const std::string lower = Misc::StringUtils::lowerCase(faction); + std::map::const_iterator it = mFactionRank.find(lower); + if (it != mFactionRank.end()) + return it->second; + + return -1; +} + void MWMechanics::NpcStats::raiseRank(const std::string &faction) { const std::string lower = Misc::StringUtils::lowerCase(faction); @@ -85,7 +95,12 @@ void MWMechanics::NpcStats::lowerRank(const std::string &faction) std::map::iterator it = mFactionRank.find(lower); if (it != mFactionRank.end()) { - it->second = std::max(0, it->second-1); + it->second = it->second-1; + if (it->second < 0) + { + mFactionRank.erase(it); + mExpelled.erase(lower); + } } } @@ -250,8 +265,7 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas MWBase::Environment::get().getWindowManager()->playSound("skillraise"); std::string message = MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", ""); - Misc::StringUtils::replace(message, "%s", ("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}").c_str(), 2); - Misc::StringUtils::replace(message, "%d", std::to_string(base).c_str(), 2); + message = Misc::StringUtils::format(message, ("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}"), base); if (readBook) message = "#{sBookSkillMessage}\n" + message; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index c4ffa99e6..dae4b80ba 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -49,7 +49,7 @@ namespace MWMechanics // ----- used by the player only, maybe should be moved at some point ------- int mBounty; int mWerewolfKills; - /// Used for the player only; NPCs have maximum one faction defined in their NPC record + /// Used only for the player and for NPC's with ranks, modified by scripts; other NPCs have maximum one faction defined in their NPC record std::map mFactionRank; std::set mExpelled; std::map mFactionReputation; @@ -81,7 +81,9 @@ namespace MWMechanics SkillValue& getSkill (int index); void setSkill(int index, const SkillValue& value); + int getFactionRank(const std::string &faction) const; const std::map& getFactionRanks() const; + /// Increase the rank in this faction by 1, if such a rank exists. void raiseRank(const std::string& faction); /// Lower the rank in this faction by 1, if such a rank exists. diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 778989b81..b54fc629c 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -113,7 +113,7 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) std::string message = MWBase::Environment::get().getWorld()->getStore().get() .find("sNotifyMessage51")->mValue.getString(); - Misc::StringUtils::replace(message, "%s", mTool.getClass().getName(mTool).c_str(), 2); + message = Misc::StringUtils::format(message, mTool.getClass().getName(mTool)); MWBase::Environment::get().getWindowManager()->messageBox(message); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 2c1e1f341..65b7ad531 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -1142,7 +1142,7 @@ namespace MWMechanics { // "X has no effect on you" std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage50")->mValue.getString(); - Misc::StringUtils::replace(message, "%s", ingredient->mName.c_str(), 2); + message = Misc::StringUtils::format(message, ingredient->mName); MWBase::Environment::get().getWindowManager()->messageBox(message); return false; } diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 125ac7690..b12b58dce 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -185,14 +185,16 @@ namespace MWMechanics bool Spells::isSpellActive(const std::string &id) const { - TContainer::const_iterator found = mSpells.find(getSpell(id)); - if (found != mSpells.end()) - { - const ESM::Spell *spell = found->first; + if (id.empty()) + return false; - return (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || - spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); + if (spell && hasSpell(spell)) + { + auto type = spell->mData.mType; + return (type==ESM::Spell::ST_Ability || type==ESM::Spell::ST_Blight || type==ESM::Spell::ST_Disease || type==ESM::Spell::ST_Curse); } + return false; } diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index c02729341..118c0297f 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -108,12 +108,6 @@ namespace MWMechanics } } - if (enemy.getClass().isNpc()) - { - static const float fCombatArmorMinMult = gmst.find("fCombatArmorMinMult")->mValue.getFloat(); - rating *= std::max(fCombatArmorMinMult, rating / (rating + enemy.getClass().getArmorRating(enemy))); - } - int value = 50.f; if (actor.getClass().isNpc()) { diff --git a/apps/openmw/mwmp/GUI/GUIChat.cpp b/apps/openmw/mwmp/GUI/GUIChat.cpp index 1f186a033..6575746f3 100644 --- a/apps/openmw/mwmp/GUI/GUIChat.cpp +++ b/apps/openmw/mwmp/GUI/GUIChat.cpp @@ -188,7 +188,7 @@ namespace mwmp { editState = state; mCommandLine->setVisible(editState); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(editState ? mCommandLine : nullptr); + MyGUI::InputManager::getInstance().setKeyFocusWidget(editState ? mCommandLine : nullptr); } void GUIChat::pressedSay() diff --git a/apps/openmw/mwmp/GUI/TextInputDialog.cpp b/apps/openmw/mwmp/GUI/TextInputDialog.cpp index eaa687f09..e9cea1cf6 100644 --- a/apps/openmw/mwmp/GUI/TextInputDialog.cpp +++ b/apps/openmw/mwmp/GUI/TextInputDialog.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace mwmp { @@ -26,7 +27,7 @@ namespace mwmp okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TextInputDialog::onOkClicked); // Make sure the edit box has focus - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); } void TextInputDialog::setNextButtonShow(bool shown) @@ -59,7 +60,7 @@ namespace mwmp { WindowModal::onOpen(); // Make sure the edit box has focus - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); } bool TextInputDialog::exit() @@ -74,7 +75,7 @@ namespace mwmp if (mTextEdit->getCaption() == "") { //MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage37}"); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTextEdit); } else eventDone(this); @@ -94,4 +95,4 @@ namespace mwmp { mTextEdit->setCaption(text); } -} \ No newline at end of file +} diff --git a/apps/openmw/mwmp/processors/player/ProcessorGameSettings.hpp b/apps/openmw/mwmp/processors/player/ProcessorGameSettings.hpp index ef65911af..fe3ff8eab 100644 --- a/apps/openmw/mwmp/processors/player/ProcessorGameSettings.hpp +++ b/apps/openmw/mwmp/processors/player/ProcessorGameSettings.hpp @@ -27,7 +27,7 @@ namespace mwmp if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { - if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Console && !player->consoleAllowed) + if (MWBase::Environment::get().getWindowManager()->isConsoleMode() && !player->consoleAllowed) MWBase::Environment::get().getWindowManager()->popGuiMode(); else if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Rest && (!player->bedRestAllowed || !player->wildernessRestAllowed || !player->waitAllowed)) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 1842383b0..d00e0c8cd 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1,4 +1,4 @@ -#include "physicssystem.hpp" +#include "physicssystem.hpp" #include diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1e8eb9f8e..e341778ac 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -131,6 +131,25 @@ namespace } }; + class HarvestVisitor : public osg::NodeVisitor + { + public: + HarvestVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + virtual void apply(osg::Switch& node) + { + if (node.getName() == Constants::HerbalismLabel) + { + node.setSingleChildOn(1); + } + + traverse(node); + } + }; + NifOsg::TextKeyMap::const_iterator findGroupStart(const NifOsg::TextKeyMap &keys, const std::string &groupname) { NifOsg::TextKeyMap::const_iterator iter(keys.begin()); @@ -460,6 +479,46 @@ namespace namespace MWRender { + class TransparencyUpdater : public SceneUtil::StateSetUpdater + { + public: + TransparencyUpdater(const float alpha) + : mAlpha(alpha) + { + } + + void setAlpha(const float alpha) + { + mAlpha = alpha; + } + + protected: + virtual void setDefaults(osg::StateSet* stateset) + { + osg::Material* material = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + + osg::BlendFunc* blendfunc (new osg::BlendFunc); + stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + // FIXME: overriding diffuse/ambient/emissive colors + material = new osg::Material; + material->setColorMode(osg::Material::OFF); + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,mAlpha)); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("colorMode", 0), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + osg::Material* material = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + material->setAlpha(osg::Material::FRONT_AND_BACK, mAlpha); + } + + private: + float mAlpha; + }; + class GlowUpdater : public SceneUtil::StateSetUpdater { public: @@ -1772,32 +1831,21 @@ namespace MWRender return; mAlpha = alpha; + // TODO: we use it to fade actors away too, but it would be nice to have a dithering shader instead. if (alpha != 1.f) { - // If we have an existing material for alpha transparency, just override alpha level - osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet(); - osg::Material* material = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - if (material) + if (mTransparencyUpdater == nullptr) { - material->setAlpha(osg::Material::FRONT_AND_BACK, alpha); + mTransparencyUpdater = new TransparencyUpdater(alpha); + mObjectRoot->addUpdateCallback(mTransparencyUpdater); } else - { - osg::BlendFunc* blendfunc (new osg::BlendFunc); - stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - // FIXME: overriding diffuse/ambient/emissive colors - material = new osg::Material; - material->setColorMode(osg::Material::OFF); - material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha)); - material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("colorMode", 0), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - mObjectRoot->setStateSet(stateset); - } + mTransparencyUpdater->setAlpha(alpha); } else { + mObjectRoot->removeUpdateCallback(mTransparencyUpdater); + mTransparencyUpdater = nullptr; mObjectRoot->setStateSet(nullptr); } @@ -1970,6 +2018,28 @@ namespace MWRender AddSwitchCallbacksVisitor visitor; mObjectRoot->accept(visitor); } + + if (ptr.getRefData().getCustomData() != nullptr && canBeHarvested()) + { + const MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + if (!store.hasVisibleItems()) + { + HarvestVisitor visitor; + mObjectRoot->accept(visitor); + } + } + } + + bool ObjectAnimation::canBeHarvested() const + { + if (mPtr.getTypeName() != typeid(ESM::Container).name()) + return false; + + const MWWorld::LiveCellRef* ref = mPtr.get(); + if (!(ref->mBase->mFlags & ESM::Container::Organic)) + return false; + + return SceneUtil::hasUserDescription(mObjectRoot, Constants::HerbalismLabel); } Animation::AnimState::~AnimState() diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 9b9092146..95007d888 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -35,6 +35,7 @@ namespace MWRender class ResetAccumRootCallback; class RotateController; class GlowUpdater; +class TransparencyUpdater; class EffectAnimationTime : public SceneUtil::ControllerSource { @@ -266,6 +267,7 @@ protected: osg::ref_ptr mGlowLight; osg::ref_ptr mGlowUpdater; + osg::ref_ptr mTransparencyUpdater; float mAlpha; @@ -475,6 +477,7 @@ public: virtual float getHeadPitch() const; virtual float getHeadYaw() const; virtual void setAccurateAiming(bool enabled) {} + virtual bool canBeHarvested() const { return false; } private: Animation(const Animation&); @@ -484,6 +487,8 @@ private: class ObjectAnimation : public Animation { public: ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight); + + bool canBeHarvested() const; }; class UpdateVfxCallback : public osg::NodeCallback diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 6c8dfec60..f4a54eb98 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -256,36 +256,30 @@ bool needUpdate(std::set >& renderedGrid, std::set cells) +void LocalMap::requestMap(const MWWorld::CellStore* cell) { - std::set > grid; - for (const MWWorld::CellStore* cell : cells) + if (cell->isExterior()) { - if (cell->isExterior()) - grid.insert(std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())); - } + int cellX = cell->getCell()->getGridX(); + int cellY = cell->getCell()->getGridY(); - for (const MWWorld::CellStore* cell : cells) - { - if (cell->isExterior()) + MapSegment& segment = mSegments[std::make_pair(cellX, cellY)]; + if (!needUpdate(segment.mGrid, mCurrentGrid, cellX, cellY)) + return; + else { - int cellX = cell->getCell()->getGridX(); - int cellY = cell->getCell()->getGridY(); - - MapSegment& segment = mSegments[std::make_pair(cellX, cellY)]; - if (!needUpdate(segment.mGrid, grid, cellX, cellY)) - { - continue; - } - else - { - segment.mGrid = grid; - requestExteriorMap(cell); - } + segment.mGrid = mCurrentGrid; + requestExteriorMap(cell); } - else - requestInteriorMap(cell); } + else + requestInteriorMap(cell); +} + +void LocalMap::addCell(MWWorld::CellStore *cell) +{ + if (cell->isExterior()) + mCurrentGrid.emplace(cell->getCell()->getGridX(), cell->getCell()->getGridY()); } void LocalMap::removeCell(MWWorld::CellStore *cell) @@ -293,7 +287,11 @@ void LocalMap::removeCell(MWWorld::CellStore *cell) saveFogOfWar(cell); if (cell->isExterior()) - mSegments.erase(std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())); + { + std::pair coords = std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY()); + mSegments.erase(coords); + mCurrentGrid.erase(coords); + } else mSegments.clear(); } diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index febe79d84..83a975aed 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -45,13 +45,12 @@ namespace MWRender void clear(); /** - * Request a map render for the given cells. Render textures will be immediately created and can be retrieved with the getMapTexture function. + * Request a map render for the given cell. Render textures will be immediately created and can be retrieved with the getMapTexture function. */ - void requestMap (std::set cells); + void requestMap (const MWWorld::CellStore* cell); + + void addCell(MWWorld::CellStore* cell); - /** - * Remove map and fog textures for the given cell. - */ void removeCell (MWWorld::CellStore* cell); osg::ref_ptr getMapTexture (int x, int y); @@ -110,6 +109,9 @@ namespace MWRender CameraVector mCamerasPendingRemoval; + typedef std::set > Grid; + Grid mCurrentGrid; + struct MapSegment { MapSegment(); @@ -124,7 +126,7 @@ namespace MWRender osg::ref_ptr mFogOfWarTexture; osg::ref_ptr mFogOfWarImage; - std::set > mGrid; // the grid that was active at the time of rendering this segment + Grid mGrid; // the grid that was active at the time of rendering this segment bool mHasFogState; }; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 964aaa9de..0d9cbd9b0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -268,6 +268,7 @@ namespace MWRender { mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); + mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100); } mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 82d8af241..f7feb267a 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -68,8 +68,8 @@ namespace osg::ref_ptr mat (new osg::Material); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); mat->setColorMode(osg::Material::DIFFUSE); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); @@ -101,12 +101,15 @@ RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* updater->addParticleSystem(mParticleSystem); mParticleNode = new osg::PositionAttitudeTransform; + mParticleNode->setName("Ripple Root"); mParticleNode->addChild(updater); mParticleNode->addChild(mParticleSystem); - mParticleNode->setNodeMask(Mask_Effect); + mParticleNode->setNodeMask(Mask_Water); createWaterRippleStateSet(resourceSystem, mParticleNode); + resourceSystem->getSceneManager()->recreateShaders(mParticleNode); + mParent->addChild(mParticleNode); } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index afa38905a..60e43b850 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -273,6 +273,7 @@ public: attach(osg::Camera::COLOR_BUFFER, mRefractionTexture); mRefractionDepthTexture = new osg::Texture2D; + mRefractionDepthTexture->setTextureSize(rttSize, rttSize); mRefractionDepthTexture->setSourceFormat(GL_DEPTH_COMPONENT); mRefractionDepthTexture->setInternalFormat(GL_DEPTH_COMPONENT24); mRefractionDepthTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); @@ -335,14 +336,7 @@ public: setName("ReflectionCamera"); setCullCallback(new InheritViewPointCallback); - int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - reflectionDetail = std::min(4, std::max(isInterior ? 2 : 0, reflectionDetail)); - unsigned int extraMask = 0; - if(reflectionDetail >= 1) extraMask |= Mask_Terrain; - if(reflectionDetail >= 2) extraMask |= Mask_Static; - if(reflectionDetail >= 3) extraMask |= Mask_Effect|Mask_ParticleSystem|Mask_Object; - if(reflectionDetail >= 4) extraMask |= Mask_Player|Mask_Actor; - setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask); + setInterior(isInterior); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); @@ -353,6 +347,7 @@ public: setUpdateCallback(new NoTraverseCallback); mReflectionTexture = new osg::Texture2D; + mReflectionTexture->setTextureSize(rttSize, rttSize); mReflectionTexture->setInternalFormat(GL_RGB); mReflectionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mReflectionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); @@ -372,6 +367,18 @@ public: SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } + void setInterior(bool isInterior) + { + int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); + reflectionDetail = std::min(4, std::max(isInterior ? 2 : 0, reflectionDetail)); + unsigned int extraMask = 0; + if(reflectionDetail >= 1) extraMask |= Mask_Terrain; + if(reflectionDetail >= 2) extraMask |= Mask_Static; + if(reflectionDetail >= 3) extraMask |= Mask_Effect|Mask_ParticleSystem|Mask_Object; + if(reflectionDetail >= 4) extraMask |= Mask_Player|Mask_Actor; + setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask); + } + void setWaterLevel(float waterLevel) { setViewMatrix(osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,2 * waterLevel)); @@ -430,15 +437,12 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem , mTop(0) , mInterior(false) { - mSimulation.reset(new RippleSimulation(parent, resourceSystem)); + mSimulation.reset(new RippleSimulation(mSceneRoot, resourceSystem)); mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits*150, 40, 900); mWaterGeom->setDrawCallback(new DepthClampCallback); mWaterGeom->setNodeMask(Mask_Water); - if (ico) - ico->add(mWaterGeom); - mWaterNode = new osg::PositionAttitudeTransform; mWaterNode->setName("Water Root"); mWaterNode->addChild(mWaterGeom); @@ -457,6 +461,9 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem mRainIntensityUniform = new osg::Uniform("rainIntensity",(float) 0.0); updateWaterMaterial(); + + if (ico) + ico->add(mWaterNode); } osg::Uniform *Water::getRainIntensityUniform() @@ -666,8 +673,8 @@ void Water::changeCell(const MWWorld::CellStore* store) mWaterNode->setPosition(osg::Vec3f(0,0,mTop)); mInterior = true; } - if(mInterior != wasInterior) - updateWaterMaterial(); + if(mInterior != wasInterior && mReflection) + mReflection->setInterior(mInterior); // create a new StateSet to prevent threading issues osg::ref_ptr nodeStateSet (new osg::StateSet); diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 2e4c300f3..4aac46948 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -119,13 +119,13 @@ namespace MWScript if (count == 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}"); + msgBox = ::Misc::StringUtils::format(msgBox, itemName); } else { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage61}"); - ::Misc::StringUtils::replace(msgBox, "%d", std::to_string(count).c_str(), 2); + msgBox = ::Misc::StringUtils::format(msgBox, count, itemName); } - ::Misc::StringUtils::replace(msgBox, "%s", itemName.c_str(), 2); MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); } /* @@ -254,13 +254,13 @@ namespace MWScript if (numRemoved > 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}"); - ::Misc::StringUtils::replace(msgBox, "%d", std::to_string(numRemoved).c_str(), 2); + msgBox = ::Misc::StringUtils::format(msgBox, numRemoved, itemName); } else { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage62}"); + msgBox = ::Misc::StringUtils::format(msgBox, itemName); } - ::Misc::StringUtils::replace(msgBox, "%s", itemName.c_str(), 2); MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); } /* @@ -469,11 +469,27 @@ namespace MWScript const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); MWWorld::ConstContainerStoreIterator it = invStore.getSlot (MWWorld::InventoryStore::Slot_CarriedRight); - if (it == invStore.end() || it->getTypeName () != typeid(ESM::Weapon).name()) + if (it == invStore.end()) { runtime.push(-1); return; } + else if (it->getTypeName() != typeid(ESM::Weapon).name()) + { + if (it->getTypeName() == typeid(ESM::Lockpick).name()) + { + runtime.push(-2); + } + else if (it->getTypeName() == typeid(ESM::Probe).name()) + { + runtime.push(-3); + } + else + { + runtime.push(-1); + } + return; + } runtime.push(it->get()->mBase->mData.mType); } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index d97f2a134..b3029f417 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -459,5 +459,7 @@ op 0x2000308: ToggleNavMesh op 0x2000309: ToggleActorsPaths op 0x200030a: SetNavMeshNumber op 0x200030b: Journal, explicit +op 0x200030c: RepairedOnMe +op 0x200030d: RepairedOnMe, explicit opcodes 0x200030c-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index f3b076fa1..6d37949f9 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -29,6 +29,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" @@ -37,6 +38,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/manualref.hpp" #include "../mwmechanics/aicast.hpp" #include "../mwmechanics/npcstats.hpp" @@ -555,6 +557,9 @@ namespace MWScript std::string gem = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); + if (!ptr.getClass().hasInventoryStore(ptr)) + return; + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); store.get().find(creature); // This line throws an exception if it can't find the creature @@ -585,7 +590,10 @@ namespace MWScript for (unsigned int i=0; igetCellRef().getSoul(), soul)) @@ -620,16 +628,18 @@ namespace MWScript if (amount == 0) return; - // Prefer dropping unequipped items first; re-stack if possible by unequipping items before dropping them. - MWWorld::InventoryStore *invStorePtr = 0; - if (ptr.getClass().hasInventoryStore(ptr)) { - invStorePtr = &ptr.getClass().getInventoryStore(ptr); + if (!ptr.getClass().isActor()) + return; - int numNotEquipped = invStorePtr->count(item); + if (ptr.getClass().hasInventoryStore(ptr)) + { + // Prefer dropping unequipped items first; re-stack if possible by unequipping items before dropping them. + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + int numNotEquipped = store.count(item); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { - MWWorld::ConstContainerStoreIterator it = invStorePtr->getSlot (slot); - if (it != invStorePtr->end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) + MWWorld::ConstContainerStoreIterator it = store.getSlot (slot); + if (it != store.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) { numNotEquipped -= it->getRefData().getCount(); } @@ -637,37 +647,48 @@ namespace MWScript for (int slot = 0; slot < MWWorld::InventoryStore::Slots && amount > numNotEquipped; ++slot) { - MWWorld::ContainerStoreIterator it = invStorePtr->getSlot (slot); - if (it != invStorePtr->end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) + MWWorld::ContainerStoreIterator it = store.getSlot (slot); + if (it != store.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) { - int numToRemove = it->getRefData().getCount(); - if (numToRemove > amount - numNotEquipped) - { - numToRemove = amount - numNotEquipped; - } - invStorePtr->unequipItemQuantity(*it, ptr, numToRemove); + int numToRemove = std::min(amount - numNotEquipped, it->getRefData().getCount()); + store.unequipItemQuantity(*it, ptr, numToRemove); numNotEquipped += numToRemove; } } - } - int toRemove = amount; - MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); - for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) - { - if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item) - && (!invStorePtr || !invStorePtr->isEquipped(*iter))) + for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { - int removed = store.remove(*iter, toRemove, ptr); - MWWorld::Ptr dropped = MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); - dropped.getCellRef().setOwner(""); + if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item) && !store.isEquipped(*iter)) + { + int removed = store.remove(*iter, amount, ptr); + MWWorld::Ptr dropped = MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); + dropped.getCellRef().setOwner(""); - toRemove -= removed; + amount -= removed; - if (toRemove <= 0) - break; + if (amount <= 0) + break; + } } } + + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), item, 1); + MWWorld::Ptr itemPtr(ref.getPtr()); + if (amount > 0) + { + if (itemPtr.getClass().getScript(itemPtr).empty()) + { + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, itemPtr, amount); + } + else + { + // Dropping one item per time to prevent making stacks of scripted items + for (int i = 0; i < amount; i++) + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, itemPtr, 1); + } + } + + MWBase::Environment::get().getSoundManager()->playSound3D(ptr, itemPtr.getClass().getDownSoundId(itemPtr), 1.f, 1.f); } }; @@ -684,8 +705,10 @@ namespace MWScript std::string soul = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); + if (!ptr.getClass().hasInventoryStore(ptr)) + return; + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { @@ -1494,6 +1517,18 @@ namespace MWScript } }; + template + class OpRepairedOnMe : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + // Broken in vanilla and deliberately no-op. + runtime.push(0); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); @@ -1597,6 +1632,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeToggleNavMesh, new OpToggleNavMesh); interpreter.installSegment5 (Compiler::Misc::opcodeToggleActorsPaths, new OpToggleActorsPaths); interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender); + interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMe, new OpRepairedOnMe); + interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMeExplicit, new OpRepairedOnMe); } } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 5d2609637..ea8e479a7 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1143,7 +1143,19 @@ namespace MWScript if (ptr == player) return; - ptr.getClass().getNpcStats(ptr).raiseRank(factionID); + // If we already changed rank for this NPC, modify current rank in the NPC stats. + // Otherwise take rank from base NPC record, increase it and put it to NPC data. + int currentRank = ptr.getClass().getNpcStats(ptr).getFactionRank(factionID); + if (currentRank >= 0) + ptr.getClass().getNpcStats(ptr).raiseRank(factionID); + else + { + int rank = ptr.getClass().getPrimaryFactionRank(ptr); + rank++; + ptr.getClass().getNpcStats(ptr).joinFaction(factionID); + for (int i=0; i 0) + ptr.getClass().getNpcStats(ptr).lowerRank(factionID); + else + { + int rank = ptr.getClass().getPrimaryFactionRank(ptr); + rank--; + ptr.getClass().getNpcStats(ptr).joinFaction(factionID); + for (int i=0; irotateObject(ptr,ax,angle,az); else if (axis == "z") MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle); - else - throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -196,8 +194,6 @@ namespace MWScript { runtime.push(osg::RadiansToDegrees(ptr.getCellRef().getPosition().rot[2])); } - else - throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -225,8 +221,6 @@ namespace MWScript { runtime.push(osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[2])); } - else - throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -254,8 +248,6 @@ namespace MWScript { runtime.push(ptr.getRefData().getPosition().pos[2]); } - else - throw std::runtime_error ("invalid axis: " + axis); } }; @@ -307,7 +299,9 @@ namespace MWScript updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos,true); } else - throw std::runtime_error ("invalid axis: " + axis); + { + return; + } dynamic_cast(runtime.getContext()).updatePtr(ptr,updated); } @@ -337,8 +331,6 @@ namespace MWScript { runtime.push(ptr.getCellRef().getPosition().pos[2]); } - else - throw std::runtime_error ("invalid axis: " + axis); } }; @@ -743,8 +735,6 @@ namespace MWScript MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay+rotation,az); else if (axis == "z") MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,az+rotation); - else - throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -777,7 +767,7 @@ namespace MWScript else if (axis == "z") rot = osg::Quat(rotation, -osg::Z_AXIS); else - throw std::runtime_error ("invalid rotation axis: " + axis); + return; osg::Quat attitude = ptr.getRefData().getBaseNode()->getAttitude(); MWBase::Environment::get().getWorld()->rotateWorldObject(ptr, attitude * rot); @@ -839,7 +829,7 @@ namespace MWScript posChange=osg::Vec3f(0, 0, movement); } else - throw std::runtime_error ("invalid movement axis: " + axis); + return; // is it correct that disabled objects can't be Move-d? if (!ptr.getRefData().getBaseNode()) @@ -883,7 +873,7 @@ namespace MWScript else if (axis == "z") diff.z() += movement; else - throw std::runtime_error ("invalid movement axis: " + axis); + return; // We should move actors, standing on moving object, too. // This approach can be used to create elevators. diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 19bbc17d3..77f25f326 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -520,7 +520,7 @@ namespace MWSound Stream *sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); if(!sound) return; - mActiveSaySounds.insert(std::make_pair(ptr, sound)); + mSaySoundsQueue.emplace(ptr, sound); } float SoundManager::getSaySoundLoudness(const MWWorld::ConstPtr &ptr) const @@ -568,7 +568,15 @@ namespace MWSound void SoundManager::stopSay(const MWWorld::ConstPtr &ptr) { - SaySoundMap::iterator snditer = mActiveSaySounds.find(ptr); + SaySoundMap::iterator snditer = mSaySoundsQueue.find(ptr); + if(snditer != mSaySoundsQueue.end()) + { + mOutput->finishStream(snditer->second); + mUnusedStreams.push_back(snditer->second); + mSaySoundsQueue.erase(snditer); + } + + snditer = mActiveSaySounds.find(ptr); if(snditer != mActiveSaySounds.end()) { mOutput->finishStream(snditer->second); @@ -761,7 +769,10 @@ namespace MWSound for(SoundBufferRefPair &snd : snditer->second) mOutput->finishSound(snd.first); } - SaySoundMap::iterator sayiter = mActiveSaySounds.find(ptr); + SaySoundMap::iterator sayiter = mSaySoundsQueue.find(ptr); + if(sayiter != mSaySoundsQueue.end()) + mOutput->finishStream(sayiter->second); + sayiter = mActiveSaySounds.find(ptr); if(sayiter != mActiveSaySounds.end()) mOutput->finishStream(sayiter->second); } @@ -777,6 +788,12 @@ namespace MWSound } } + for(SaySoundMap::value_type &snd : mSaySoundsQueue) + { + if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell) + mOutput->finishStream(snd.second); + } + for(SaySoundMap::value_type &snd : mActiveSaySounds) { if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell) @@ -986,6 +1003,15 @@ namespace MWSound void SoundManager::updateSounds(float duration) { + // We update active say sounds map for specific actors here + // because for vanilla compatibility we can't do it immediately. + SaySoundMap::iterator queuesayiter = mSaySoundsQueue.begin(); + while (queuesayiter != mSaySoundsQueue.end()) + { + mActiveSaySounds[queuesayiter->first] = queuesayiter->second; + mSaySoundsQueue.erase(queuesayiter++); + } + static float timePassed = 0.0; timePassed += duration; @@ -1187,6 +1213,12 @@ namespace MWSound sound->setBaseVolume(volumeFromType(sound->getPlayType())); mOutput->updateStream(sound); } + for(SaySoundMap::value_type &snd : mSaySoundsQueue) + { + Stream *sound = snd.second; + sound->setBaseVolume(volumeFromType(sound->getPlayType())); + mOutput->updateStream(sound); + } for(Stream *sound : mActiveTracks) { sound->setBaseVolume(volumeFromType(sound->getPlayType())); @@ -1218,7 +1250,16 @@ namespace MWSound mActiveSounds.erase(snditer); mActiveSounds.emplace(updated, std::move(sndlist)); } - SaySoundMap::iterator sayiter = mActiveSaySounds.find(old); + + SaySoundMap::iterator sayiter = mSaySoundsQueue.find(old); + if(sayiter != mSaySoundsQueue.end()) + { + Stream *stream = sayiter->second; + mSaySoundsQueue.erase(sayiter); + mSaySoundsQueue.emplace(updated, stream); + } + + sayiter = mActiveSaySounds.find(old); if(sayiter != mActiveSaySounds.end()) { Stream *stream = sayiter->second; @@ -1311,6 +1352,13 @@ namespace MWSound mUnderwaterSound = nullptr; mNearWaterSound = nullptr; + for(SaySoundMap::value_type &snd : mSaySoundsQueue) + { + mOutput->finishStream(snd.second); + mUnusedStreams.push_back(snd.second); + } + mSaySoundsQueue.clear(); + for(SaySoundMap::value_type &snd : mActiveSaySounds) { mOutput->finishStream(snd.second); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index d9cf22c32..d0b98ffe4 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -93,6 +93,7 @@ namespace MWSound SoundMap mActiveSounds; typedef std::map SaySoundMap; + SaySoundMap mSaySoundsQueue; SaySoundMap mActiveSaySounds; typedef std::vector TrackList; diff --git a/apps/openmw/mwworld/actionharvest.cpp b/apps/openmw/mwworld/actionharvest.cpp new file mode 100644 index 000000000..ff35938b2 --- /dev/null +++ b/apps/openmw/mwworld/actionharvest.cpp @@ -0,0 +1,92 @@ +#include "actionharvest.hpp" + +#include + +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + +#include "class.hpp" +#include "containerstore.hpp" + +namespace MWWorld +{ + ActionHarvest::ActionHarvest (const MWWorld::Ptr& container) + : Action (true, container) + { + setSound("Item Ingredient Up"); + } + + void ActionHarvest::executeImp (const MWWorld::Ptr& actor) + { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return; + + MWWorld::Ptr target = getTarget(); + MWWorld::ContainerStore& store = target.getClass().getContainerStore (target); + MWWorld::ContainerStore& actorStore = actor.getClass().getContainerStore(actor); + std::map takenMap; + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + if (!it->getClass().showsInInventory(*it)) + continue; + + int itemCount = it->getRefData().getCount(); + // Note: it is important to check for crime before move an item from container. Otherwise owner check will not work + // for a last item in the container - empty harvested containers are considered as "allowed to use". + MWBase::Environment::get().getMechanicsManager()->itemTaken(actor, *it, target, itemCount); + actorStore.add(*it, itemCount, actor); + store.remove(*it, itemCount, getTarget()); + takenMap[it->getClass().getName(*it)]+=itemCount; + } + + // Spawn a messagebox (only for items added to player's inventory) + if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + std::ostringstream stream; + int lineCount = 0; + const static int maxLines = 10; + for (auto & pair : takenMap) + { + std::string itemName = pair.first; + int itemCount = pair.second; + lineCount++; + if (lineCount == maxLines) + stream << "\n..."; + else if (lineCount > maxLines) + break; + + // The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory + std::string msgBox; + if (itemCount == 1) + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("\n#{sNotifyMessage60}"); + msgBox = Misc::StringUtils::format(msgBox, itemName); + } + else + { + msgBox = MyGUI::LanguageManager::getInstance().replaceTags("\n#{sNotifyMessage61}"); + msgBox = Misc::StringUtils::format(msgBox, itemCount, itemName); + } + + stream << msgBox; + } + std::string tooltip = stream.str(); + // remove the first newline (easier this way) + if (tooltip.size() > 0 && tooltip[0] == '\n') + tooltip.erase(0, 1); + + if (tooltip.size() > 0) + MWBase::Environment::get().getWindowManager()->messageBox(tooltip); + } + + // Update animation object + MWBase::Environment::get().getWorld()->disable(target); + MWBase::Environment::get().getWorld()->enable(target); + } +} diff --git a/apps/openmw/mwworld/actionharvest.hpp b/apps/openmw/mwworld/actionharvest.hpp new file mode 100644 index 000000000..b93ff7f10 --- /dev/null +++ b/apps/openmw/mwworld/actionharvest.hpp @@ -0,0 +1,19 @@ +#ifndef GAME_MWWORLD_ACTIONHARVEST_H +#define GAME_MWWORLD_ACTIONHARVEST_H + +#include "action.hpp" +#include "ptr.hpp" + +namespace MWWorld +{ + class ActionHarvest : public Action + { + virtual void executeImp (const MWWorld::Ptr& actor); + + public: + ActionHarvest (const Ptr& container); + ///< \param container The Container the Player has activated. + }; +} + +#endif // ACTIONOPEN_H diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index d0a2f4cd3..d3d9843cd 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -50,6 +50,9 @@ namespace { MWWorld::Ptr container (&*iter, 0); + if (container.getRefData().getCustomData() == nullptr) + continue; + MWWorld::Ptr ptr = container.getClass().getContainerStore (container).search (id); diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 8cf6f2755..5a053f6d8 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -477,6 +477,17 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const return count - toRemove; } +bool MWWorld::ContainerStore::hasVisibleItems() const +{ + for (auto iter(begin()); iter != end(); ++iter) + { + if (iter->getClass().showsInInventory(*iter)) + return true; + } + + return false; +} + int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor) { assert(this == item.getContainerStore()); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 4564d2fa3..b06e8b8ce 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -128,6 +128,8 @@ namespace MWWorld ContainerStoreIterator begin (int mask = Type_All); ContainerStoreIterator end(); + bool hasVisibleItems() const; + virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false); ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) /// diff --git a/apps/openmw/mwworld/customdata.cpp b/apps/openmw/mwworld/customdata.cpp index a63123bcf..5080c0923 100644 --- a/apps/openmw/mwworld/customdata.cpp +++ b/apps/openmw/mwworld/customdata.cpp @@ -42,6 +42,13 @@ MWClass::ContainerCustomData &CustomData::asContainerCustomData() throw std::logic_error(error.str()); } +const MWClass::ContainerCustomData &CustomData::asContainerCustomData() const +{ + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to ContainerCustomData"; + throw std::logic_error(error.str()); +} + MWClass::DoorCustomData &CustomData::asDoorCustomData() { std::stringstream error; diff --git a/apps/openmw/mwworld/customdata.hpp b/apps/openmw/mwworld/customdata.hpp index 11932e690..8af45e36a 100644 --- a/apps/openmw/mwworld/customdata.hpp +++ b/apps/openmw/mwworld/customdata.hpp @@ -30,6 +30,7 @@ namespace MWWorld virtual const MWClass::NpcCustomData& asNpcCustomData() const; virtual MWClass::ContainerCustomData& asContainerCustomData(); + virtual const MWClass::ContainerCustomData& asContainerCustomData() const; virtual MWClass::DoorCustomData& asDoorCustomData(); virtual const MWClass::DoorCustomData& asDoorCustomData() const; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 1bbe73d0e..56b46d856 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -205,40 +205,48 @@ void ESMStore::validate() continue; bool changed = false; - for (ESM::ENAMstruct& effect : spell.mEffects.mList) + auto iter = spell.mEffects.mList.begin(); + while (iter != spell.mEffects.mList.end()) { - const ESM::MagicEffect* mgef = mMagicEffects.search(effect.mEffectID); - if (!mgef) // Do nothing for now + const ESM::MagicEffect* mgef = mMagicEffects.search(iter->mEffectID); + if (!mgef) + { + Log(Debug::Verbose) << "Spell '" << spell.mId << "' has an an invalid effect (index " << iter->mEffectID << ") present, dropping it."; + iter = spell.mEffects.mList.erase(iter); + changed = true; continue; + } if (mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) { - if (effect.mAttribute != -1) + if (iter->mAttribute != -1) { - effect.mAttribute = -1; - Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(effect.mEffectID) << + iter->mAttribute = -1; + Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) << " effect of spell '" << spell.mId << "' has an attribute argument present, dropping it."; changed = true; } } else if (mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) { - if (effect.mSkill != -1) + if (iter->mSkill != -1) { - effect.mSkill = -1; - Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(effect.mEffectID) << + iter->mSkill = -1; + Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) << " effect of spell '" << spell.mId << "' has a skill argument present, dropping it."; changed = true; } } - else if (effect.mSkill != -1 || effect.mAttribute != -1) + else if (iter->mSkill != -1 || iter->mAttribute != -1) { - effect.mSkill = -1; - effect.mAttribute = -1; - Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(effect.mEffectID) << + iter->mSkill = -1; + iter->mAttribute = -1; + Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) << " effect of spell '" << spell.mId << "' has argument(s) present, dropping them."; changed = true; } + + ++iter; } if (changed) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index fee6d84cb..e5b4c29e0 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -608,8 +608,8 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) // Autoequip clothing, armor and weapons. // Equipping lights is handled in Actors::updateEquippedLight based on environment light. - // Note: creatures do not use the armor mitigation and can equip only shields - // Use a custom logic for them - select shield based on its health instead of armor rating (since it useless for creatures) + // Note: creatures ignore equipment armor rating and only equip shields + // Use custom logic for them - select shield based on its health instead of armor rating autoEquipWeapon(actor, slots_); autoEquipArmor(actor, slots_); diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index ff47d3e56..a727b4b3a 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -42,6 +42,11 @@ namespace bool operator()(const MWWorld::Ptr& containerPtr) { + // Ignore containers without generated content + if (containerPtr.getTypeName() == typeid(ESM::Container).name() && + containerPtr.getRefData().getCustomData() == nullptr) + return false; + MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr); for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) { diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 521be50be..31d321ab8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -472,6 +472,7 @@ namespace MWWorld mRendering.addCell(cell); + MWBase::Environment::get().getWindowManager()->addCell(cell); bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); float waterLevel = cell->getWaterLevel(); mRendering.setWaterEnabled(waterEnabled); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 2a0c39466..444650217 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -500,7 +500,12 @@ namespace MWWorld } void Store::setUp() { + // The land is static for given game session, there is no need to refresh it every load. + if (mBuilt) + return; + std::sort(mStatic.begin(), mStatic.end(), Compare()); + mBuilt = true; } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 2ed81af48..c6ef401ed 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -247,6 +247,8 @@ namespace MWWorld RecordId load(ESM::ESMReader &esm); void setUp(); + private: + bool mBuilt = false; }; template <> diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index e3b05426d..ec6781327 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -794,10 +794,9 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, if (mIsStorm) { osg::Vec3f playerPos (player.getRefData().getPosition().asVec3()); - osg::Vec3f redMountainPos (19950, 72032, 27831); - + playerPos.z() = 0; + osg::Vec3f redMountainPos (25000, 70000, 0); mStormDirection = (playerPos - redMountainPos); - mStormDirection.z() = 0; mStormDirection.normalize(); mRendering.getSkyManager()->setStormDirection(mStormDirection); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b47324e9d..c17c69229 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -722,7 +722,7 @@ namespace MWWorld mLocalScripts.remove (ref); } - Ptr World::searchPtr (const std::string& name, bool activeOnly) + Ptr World::searchPtr (const std::string& name, bool activeOnly, bool searchInContainers) { Ptr ret; // the player is always in an active cell. @@ -749,11 +749,14 @@ namespace MWWorld return ret; } - for (CellStore* cellstore : mWorldScene->getActiveCells()) + if (searchInContainers) { - Ptr ptr = cellstore->searchInContainer(lowerCaseName); - if (!ptr.isEmpty()) - return ptr; + for (CellStore* cellstore : mWorldScene->getActiveCells()) + { + Ptr ptr = cellstore->searchInContainer(lowerCaseName); + if (!ptr.isEmpty()) + return ptr; + } } Ptr ptr = mPlayer->getPlayer().getClass() @@ -1508,7 +1511,7 @@ namespace MWWorld CellStore* cell = ptr.getCell(); CellStore* newCell = getExterior(cellX, cellY); - bool isCellActive = getPlayerPtr().getCell()->isExterior() && mWorldScene->isCellActive(*newCell); + bool isCellActive = getPlayerPtr().isInCell() && getPlayerPtr().getCell()->isExterior() && mWorldScene->isCellActive(*newCell); if (cell->isExterior() || (moveToActive && isCellActive && ptr.getClass().isActor())) cell = newCell; @@ -3750,9 +3753,13 @@ namespace MWWorld return true; // Consider references inside containers as well (except if we are looking for a Creature, they cannot be in containers) - if (mType != World::Detect_Creature && - (ptr.getClass().isActor() || ptr.getClass().getTypeName() == typeid(ESM::Container).name())) + bool isContainer = ptr.getClass().getTypeName() == typeid(ESM::Container).name(); + if (mType != World::Detect_Creature && (ptr.getClass().isActor() || isContainer)) { + // but ignore containers without resolved content + if (isContainer && ptr.getRefData().getCustomData() == nullptr) + return true; + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); { for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index e7bdc2366..406c6b5b8 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -152,10 +152,9 @@ namespace MWWorld */ public: // FIXME + void addContainerScripts(const Ptr& reference, CellStore* cell) override; void removeContainerScripts(const Ptr& reference) override; private: - void addContainerScripts(const Ptr& reference, CellStore* cell); - void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. @@ -305,7 +304,7 @@ namespace MWWorld ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. - Ptr searchPtr (const std::string& name, bool activeOnly) override; + Ptr searchPtr (const std::string& name, bool activeOnly, bool searchInContainers = true) override; ///< Return a pointer to a liveCellRef with the given name. /// \param activeOnly do non search inactive cells. diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 516495da6..e66f051d7 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -33,7 +33,7 @@ namespace Compiler { extensions.registerInstruction ("aiactivate", "c/l", opcodeAIActivate, opcodeAIActivateExplicit); - extensions.registerInstruction ("aitravel", "fff/l", opcodeAiTravel, + extensions.registerInstruction ("aitravel", "fff/lx", opcodeAiTravel, opcodeAiTravelExplicit); extensions.registerInstruction ("aiescort", "cffff/l", opcodeAiEscort, opcodeAiEscortExplicit); @@ -180,7 +180,7 @@ namespace Compiler extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); extensions.registerInstruction ("choice", "j/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); - extensions.registerInstruction("forcegreeting","",opcodeForceGreeting, + extensions.registerInstruction("forcegreeting","X",opcodeForceGreeting, opcodeForceGreetingExplicit); extensions.registerInstruction("goodbye", "", opcodeGoodbye); extensions.registerInstruction("setreputation", "l", opcodeSetReputation, @@ -324,6 +324,7 @@ namespace Compiler extensions.registerInstruction ("tap", "", opcodeToggleActorsPaths); extensions.registerInstruction ("toggleactorspaths", "", opcodeToggleActorsPaths); extensions.registerInstruction ("setnavmeshnumber", "l", opcodeSetNavMeshNumberToRender); + extensions.registerFunction ("repairedonme", 'l', "S", opcodeRepairedOnMe, opcodeRepairedOnMeExplicit); } } @@ -422,7 +423,7 @@ namespace Compiler for (int i=0; ipeek()) { // Skip the following zero byte mCtx.leftRec--; diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 1e23569b5..c8d31c41c 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -352,12 +352,7 @@ namespace ESMTerrain std::string Storage::getTextureName(UniqueTextureId id) { - // Goes under used terrain blend transitions - static const std::string baseTexture = "textures\\tx_black_01.dds"; - if (id.first == -1) - return baseTexture; - - static const std::string defaultTexture = "textures\\_land_default.dds"; + static constexpr char defaultTexture[] = "textures\\_land_default.dds"; if (id.first == 0) return defaultTexture; // Not sure if the default texture really is hardcoded? @@ -385,72 +380,61 @@ namespace ESMTerrain int rowStart = (origin.x() - cellX) * realTextureSize; int colStart = (origin.y() - cellY) * realTextureSize; - int rowEnd = rowStart + chunkSize * (realTextureSize-1) + 1; - int colEnd = colStart + chunkSize * (realTextureSize-1) + 1; - - // Save the used texture indices so we know the total number of textures - // and number of required blend maps - std::set textureIndices; - // Due to the way the blending works, the base layer will bleed between texture transitions so we want it to be a black texture - // The subsequent passes are added instead of blended, so this gives the correct result - textureIndices.insert(std::make_pair(-1,0)); // -1 goes to tx_black_01 - - LandCache cache; - - for (int y=colStart; y textureIndicesMap; - for (std::set::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it) - { - int size = textureIndicesMap.size(); - textureIndicesMap[*it] = size; - layerList.push_back(getLayerInfo(getTextureName(*it))); - } - - // size-1 since the base layer doesn't need blending - int numBlendmaps = textureIndices.size() - 1; - // Second iteration - create and fill in the blend maps const int blendmapSize = (realTextureSize-1) * chunkSize + 1; // We need to upscale the blendmap 2x with nearest neighbor sampling to look like Vanilla const int imageScaleFactor = 2; const int blendmapImageSize = blendmapSize * imageScaleFactor; - for (int i=0; i image (new osg::Image); - image->allocateImage(blendmapImageSize, blendmapImageSize, 1, GL_ALPHA, GL_UNSIGNED_BYTE); - unsigned char* pData = image->data(); + LandCache cache; + std::map textureIndicesMap; - for (int y=0; y::iterator found = textureIndicesMap.find(id); + if (found == textureIndicesMap.end()) { - UniqueTextureId id = getVtexIndexAt(cellX, cellY, x+rowStart, y+colStart, cache); - assert(textureIndicesMap.find(id) != textureIndicesMap.end()); - int layerIndex = textureIndicesMap.find(id)->second; + unsigned int layerIndex = layerList.size(); + Terrain::LayerInfo info = getLayerInfo(getTextureName(id)); - int alpha = (layerIndex == i+1) ? 255 : 0; + // look for existing diffuse map, which may be present when several plugins use the same texture + for (unsigned int i=0; i= layerList.size()) + { + osg::ref_ptr image (new osg::Image); + image->allocateImage(blendmapImageSize, blendmapImageSize, 1, GL_ALPHA, GL_UNSIGNED_BYTE); + unsigned char* pData = image->data(); + memset(pData, 0, image->getTotalDataSize()); + blendmaps.emplace_back(image); + layerList.emplace_back(info); + } } + unsigned int layerIndex = found->second; + unsigned char* pData = blendmaps[layerIndex]->data(); + int realY = (blendmapSize - y - 1)*imageScaleFactor; + int realX = x*imageScaleFactor; + pData[((realY+0)*blendmapImageSize + realX + 0)] = 255; + pData[((realY+1)*blendmapImageSize + realX + 0)] = 255; + pData[((realY+0)*blendmapImageSize + realX + 1)] = 255; + pData[((realY+1)*blendmapImageSize + realX + 1)] = 255; } - blendmaps.push_back(image); } + + if (blendmaps.size() == 1) + blendmaps.clear(); // If a single texture fills the whole terrain, there is no need to blend } float Storage::getHeightAt(const osg::Vec3f &worldPos) diff --git a/components/files/androidpath.cpp b/components/files/androidpath.cpp index bf5c98577..a35b2f75b 100644 --- a/components/files/androidpath.cpp +++ b/components/files/androidpath.cpp @@ -1,110 +1,58 @@ #include "androidpath.hpp" -#if defined(__ANDROID__) +#if defined(__ANDROID__) +#include #include #include #include -#include "androidpath.h" #include #include +static const char *g_path_global; //< Path to global directory root, e.g. /data/data/com.libopenmw.openmw +static const char *g_path_user; //< Path to user root, e.g. /sdcard/Android/data/com.libopenmw.openmw -class Buffer { - public: - static void setData(char const *data); - static char const * getData(); -}; -static char const *path; - -void Buffer::setData(char const *data) -{ - path=data; -} -char const * Buffer::getData() -{ - return path; -} - - -JNIEXPORT void JNICALL Java_ui_activity_GameActivity_getPathToJni(JNIEnv *env, jobject obj, jstring prompt) -{ - jboolean iscopy; - Buffer::setData((env)->GetStringUTFChars(prompt, &iscopy)); - (env)->DeleteLocalRef(prompt); -} - -namespace +/** + * \brief Called by java code to set up directory paths + */ +extern "C" JNIEXPORT void JNICALL Java_ui_activity_GameActivity_getPathToJni(JNIEnv *env, jobject obj, jstring global, jstring user) { - - boost::filesystem::path getUserHome() - { - const char* dir = getenv("HOME"); - if (dir == nullptr) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != nullptr) - { - dir = pwd->pw_dir; - } - } - if (dir == nullptr) - return boost::filesystem::path(); - else - return boost::filesystem::path(dir); - } - - boost::filesystem::path getEnv(const std::string& envVariable, const boost::filesystem::path& fallback) - { - const char* result = getenv(envVariable.c_str()); - if (!result) - return fallback; - boost::filesystem::path dir(result); - if (dir.empty()) - return fallback; - else - return dir; - } + g_path_global = env->GetStringUTFChars(global, nullptr); + g_path_user = env->GetStringUTFChars(user, nullptr); } -/** - * \namespace Files - */ namespace Files { AndroidPath::AndroidPath(const std::string& application_name) - : mName(application_name) { } +// /sdcard/Android/data/com.libopenmw.openmw/config boost::filesystem::path AndroidPath::getUserConfigPath() const { - std::string buffer = ""; - buffer = buffer + Buffer::getData() +"/config"; - return getEnv("XDG_CONFIG_HOME", buffer) / mName; + return boost::filesystem::path(g_path_user) / "config"; } +// /sdcard/Android/data/com.libopenmw.openmw/ +// (so that saves are placed at /sdcard/Android/data/com.libopenmw.openmw/saves) boost::filesystem::path AndroidPath::getUserDataPath() const { - std::string buffer = ""; - buffer = buffer + Buffer::getData() +"/share"; - return getEnv("XDG_DATA_HOME", buffer) / mName; + return boost::filesystem::path(g_path_user); } +// /data/data/com.libopenmw.openmw/cache +// (supposed to be "official" android cache location) boost::filesystem::path AndroidPath::getCachePath() const { - std::string buffer = ""; - buffer = buffer + Buffer::getData() +"/cache"; - return getEnv("XDG_CACHE_HOME", buffer) / mName; + return boost::filesystem::path(g_path_global) / "cache"; } +// /data/data/com.libopenmw.openmw/files/config +// (note the addition of "files") boost::filesystem::path AndroidPath::getGlobalConfigPath() const { - std::string buffer = ""; - buffer = buffer + Buffer::getData() +"/"; - boost::filesystem::path globalPath(buffer); - return globalPath / mName; + return boost::filesystem::path(g_path_global) / "files" / "config"; } boost::filesystem::path AndroidPath::getLocalPath() const @@ -112,12 +60,11 @@ boost::filesystem::path AndroidPath::getLocalPath() const return boost::filesystem::path("./"); } +// /sdcard/Android/data/com.libopenmw.openmw +// (so that the data is at /sdcard/Android/data/com.libopenmw.openmw/data) boost::filesystem::path AndroidPath::getGlobalDataPath() const { - std::string buffer = ""; - buffer = buffer + Buffer::getData() +"/data"; - boost::filesystem::path globalDataPath(buffer); - return globalDataPath / mName; + return boost::filesystem::path(g_path_user); } boost::filesystem::path AndroidPath::getInstallPath() const diff --git a/components/files/androidpath.h b/components/files/androidpath.h deleted file mode 100644 index a93a160e0..000000000 --- a/components/files/androidpath.h +++ /dev/null @@ -1,19 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include - -#ifndef _Included_ui_activity_GameActivity_getPathToJni -#define _Included_ui_activity_GameActivity_getPathToJni -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: Java_org_libsdl_app_SDLActivity_getPathToJni - * Method: getPathToJni - * Signature: (I)I - */ -JNIEXPORT void JNICALL Java_ui_activity_GameActivity_getPathToJni(JNIEnv *env, jobject obj, jstring prompt); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/components/files/androidpath.hpp b/components/files/androidpath.hpp index a8124e6db..cca77858c 100644 --- a/components/files/androidpath.hpp +++ b/components/files/androidpath.hpp @@ -46,8 +46,6 @@ struct AndroidPath boost::filesystem::path getCachePath() const; boost::filesystem::path getInstallPath() const; - - std::string mName; }; } /* namespace Files */ diff --git a/components/misc/constants.hpp b/components/misc/constants.hpp index 01aeb2fc1..af43eb414 100644 --- a/components/misc/constants.hpp +++ b/components/misc/constants.hpp @@ -27,6 +27,9 @@ const int CellSizeInUnits = 8192; // A label to mark night/day visual switches const std::string NightDayLabel = "NightDaySwitch"; +// A label to mark visual switches for herbalism feature +const std::string HerbalismLabel = "HerbalismSwitch"; + } #endif diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index f0b488f28..ba27d4775 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -1,7 +1,6 @@ #ifndef MISC_STRINGOPS_H #define MISC_STRINGOPS_H -#include #include #include @@ -18,6 +17,19 @@ class StringUtils } }; + // Allow to convert complex arguments to C-style strings for format() function + template + static T argument(T value) noexcept + { + return value; + } + + template + static T const * argument(std::basic_string const & value) noexcept + { + return value.c_str(); + } + public: /// Plain and simple locale-unaware toLower. Anything from A to Z is lower-cased, multibyte characters are unchanged. @@ -212,30 +224,26 @@ public: return str; } - /** @brief Replaces the first occurrence of a string in another string. - * - * @param str The string to operate on. - * @param what The string to replace. - * @param with The replacement string. - * @param whatLen The length of the string to replace. - * @param withLen The length of the replacement string. - * - * @return A reference to the string passed in @p str. - */ - static std::string &replace(std::string &str, const char *what, const char *with, - std::size_t whatLen=std::string::npos, std::size_t withLen=std::string::npos) + // Requires some C++11 features: + // 1. std::string needs to be contiguous + // 2. std::snprintf with zero size (second argument) returns an output string size + // 3. variadic templates support + template + static std::string format(const char* fmt, Args const & ... args) { - if (whatLen == std::string::npos) - whatLen = strlen(what); - - if (withLen == std::string::npos) - withLen = strlen(with); + auto size = std::snprintf(nullptr, 0, fmt, argument(args) ...); + // Note: sprintf also writes a trailing null character. We should remove it. + std::string ret(size+1, '\0'); + std::sprintf(&ret[0], fmt, argument(args) ...); + ret.erase(size); - std::size_t found; - if ((found = str.find(what)) != std::string::npos) - str.replace(found, whatLen, with, withLen); + return ret; + } - return str; + template + static std::string format(const std::string& fmt, Args const & ... args) + { + return format(fmt.c_str(), args ...); } }; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 1bc8d1c5f..8f98174ab 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -209,7 +209,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n // We encountered a RootCollisionNode inside autogenerated mesh. It is not right. if (node->recType == Nif::RC_RootCollisionNode && autogenerated) - Log(Debug::Info) << "Found RootCollisionNode attached to non-root node in " << fileName << ". Treat it as a common NiTriShape."; + Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << fileName << ". Treating it as a common NiTriShape."; // Check for extra data Nif::Extra const *e = node; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a1aa74cab..d6a459b1b 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -635,6 +635,8 @@ namespace NifOsg const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); if (niSwitchNode->name == Constants::NightDayLabel && !SceneUtil::hasUserDescription(rootNode, Constants::NightDayLabel)) rootNode->getOrCreateUserDataContainer()->addDescription(Constants::NightDayLabel); + else if (niSwitchNode->name == Constants::HerbalismLabel && !SceneUtil::hasUserDescription(rootNode, Constants::HerbalismLabel)) + rootNode->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel); } return node; diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index c657e66fd..415bdae3a 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -165,6 +165,8 @@ namespace SceneUtil , mLightingMask(~0u) { setUpdateCallback(new LightManagerUpdateCallback); + for (unsigned int i=0; i<8; ++i) + mDummies.push_back(new LightStateAttribute(i, std::vector >())); } LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) @@ -219,7 +221,6 @@ namespace SceneUtil osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList, unsigned int frameNum) { - // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) size_t hash = 0; for (unsigned int i=0; i > lights; lights.reserve(lightList.size()); for (unsigned int i=0; imLightSource->getLight(frameNum)); - } // the first light state attribute handles the actual state setting for all lights // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary - osg::ref_ptr attr = new LightStateAttribute(mStartLight, lights); // don't use setAttributeAndModes, that does not support light indices! - stateset->setAttribute(attr, osg::StateAttribute::ON); + stateset->setAttribute(new LightStateAttribute(mStartLight, std::move(lights)), osg::StateAttribute::ON); + for (unsigned int i=0; isetMode(GL_LIGHT0 + mStartLight + i, osg::StateAttribute::ON); // need to push some dummy attributes to ensure proper state tracking // lights need to reset to their default when the StateSet is popped for (unsigned int i=1; i dummy = new LightStateAttribute(mStartLight+i, std::vector >()); - stateset->setAttribute(dummy, osg::StateAttribute::ON); - } + stateset->setAttribute(mDummies[i+mStartLight].get(), osg::StateAttribute::ON); - stateSetCache.insert(std::make_pair(hash, stateset)); + stateSetCache.emplace(hash, stateset); return stateset; } } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index d35aa6058..ea6c640c4 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -140,6 +140,8 @@ namespace SceneUtil typedef std::map > LightStateSetMap; LightStateSetMap mStateSetCache[2]; + std::vector> mDummies; + int mStartLight; unsigned int mLightingMask; diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 27463ea88..59e75dc76 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -414,11 +414,20 @@ void Manager::setBool(const std::string &setting, const std::string &category, c setString(setting, category, value ? "true" : "false"); } -const CategorySettingVector Manager::apply() +void Manager::resetPendingChange(const std::string &setting, const std::string &category) +{ + CategorySettingValueMap::key_type key = std::make_pair(category, setting); + mChangedSettings.erase(key); +} + +const CategorySettingVector Manager::getPendingChanges() +{ + return mChangedSettings; +} + +void Manager::resetPendingChanges() { - CategorySettingVector vec = mChangedSettings; mChangedSettings.clear(); - return vec; } } diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 7adcb9b39..3d1cabede 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -35,7 +35,11 @@ namespace Settings void saveUser (const std::string& file); ///< save user settings to file - static const CategorySettingVector apply(); + static void resetPendingChange(const std::string &setting, const std::string &category); + + static void resetPendingChanges(); + + static const CategorySettingVector getPendingChanges(); ///< returns the list of changed settings and then clears it static int getInt (const std::string& setting, const std::string& category); diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 41b1fdbe1..881397936 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -30,7 +30,6 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T , mCompositeMapSize(512) , mCompositeMapLevel(1.f) , mMaxCompGeometrySize(1.f) - , mCullingActive(true) { } @@ -208,7 +207,8 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve mCompositeMapRenderer->addCompositeMap(compositeMap.get(), false); - transform->getOrCreateUserDataContainer()->setUserData(compositeMap); + geometry->setCompositeMap(compositeMap); + geometry->setCompositeMapRenderer(mCompositeMapRenderer); TextureLayer layer; layer.mDiffuseMap = compositeMap->mTexture; @@ -222,14 +222,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve } transform->addChild(geometry); - - if (!mCullingActive) - { - transform->setCullingActive(false); - geometry->setCullingActive(false); - } - else - transform->getBound(); + transform->getBound(); if (mSceneManager->getIncrementalCompileOperation()) { diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 1c989cc2a..fd385e793 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -111,6 +111,24 @@ namespace } }; + class BlendFuncFirst + { + public: + static const osg::ref_ptr& value() + { + static BlendFuncFirst instance; + return instance.mValue; + } + + private: + osg::ref_ptr mValue; + + BlendFuncFirst() + : mValue(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ZERO)) + { + } + }; + class BlendFunc { public: @@ -124,9 +142,8 @@ namespace osg::ref_ptr mValue; BlendFunc() - : mValue(new osg::BlendFunc) + : mValue(new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE)) { - mValue->setFunction(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE); } }; @@ -166,19 +183,16 @@ namespace Terrain osg::ref_ptr stateset (new osg::StateSet); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + if (!firstLayer) { - stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON); } - // disable fog if we're the first layer of several - supposed to be completely black - if (firstLayer && blendmaps.size() > 0) + else { - osg::ref_ptr fog (new osg::Fog); - fog->setStart(10000000); - fog->setEnd(10000000); - stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + stateset->setAttributeAndModes(BlendFuncFirst::value(), osg::StateAttribute::ON); stateset->setAttributeAndModes(LequalDepth::value(), osg::StateAttribute::ON); } @@ -193,7 +207,7 @@ namespace Terrain stateset->addUniform(new osg::Uniform("diffuseMap", texunit)); - if(!firstLayer) + if (!blendmaps.empty()) { ++texunit; osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); @@ -212,7 +226,7 @@ namespace Terrain Shader::ShaderManager::DefineMap defineMap; defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; - defineMap["blendMap"] = !firstLayer ? "1" : "0"; + defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0"; defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; @@ -229,7 +243,17 @@ namespace Terrain } else { - if(!firstLayer) + // Add the actual layer texture + osg::ref_ptr tex = it->mDiffuseMap; + stateset->setTextureAttributeAndModes(texunit, tex.get()); + + if (layerTileSize != 1.f) + stateset->setTextureAttributeAndModes(texunit, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); + + ++texunit; + + // Multiply by the alpha map + if (!blendmaps.empty()) { osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); @@ -242,12 +266,6 @@ namespace Terrain ++texunit; } - // Add the actual layer texture multiplied by the alpha map. - osg::ref_ptr tex = it->mDiffuseMap; - stateset->setTextureAttributeAndModes(texunit, tex.get()); - - if (layerTileSize != 1.f) - stateset->setTextureAttributeAndModes(texunit, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); } stateset->setRenderBinDetails(passIndex++, "RenderBin"); diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 141803cf7..69f9b3fa4 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -61,7 +61,6 @@ QuadTreeNode::QuadTreeNode(QuadTreeNode* parent, ChildDirection direction, float , mValidBounds(false) , mSize(size) , mCenter(center) - , mViewDataMap(nullptr) { for (unsigned int i=0; i<4; ++i) mNeighbours[i] = 0; @@ -109,56 +108,63 @@ void QuadTreeNode::initNeighbours() getChild(i)->initNeighbours(); } -void QuadTreeNode::traverse(osg::NodeVisitor &nv) +void QuadTreeNode::traverse(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback, float maxDist) { if (!hasValidBounds()) return; - bool needsUpdate = true; - ViewData* vd = getView(nv, needsUpdate); + float dist = distance(viewPoint); + if (dist > maxDist) + return; + + bool stopTraversal = (lodCallback->isSufficientDetail(this, dist)) || !getNumChildren(); - if ((mLodCallback && mLodCallback->isSufficientDetail(this, distance(vd->getViewPoint()))) || !getNumChildren()) - vd->add(this, true); + if (stopTraversal) + vd->add(this); else - osg::Group::traverse(nv); + { + for (unsigned int i=0; itraverse(vd, viewPoint, lodCallback, maxDist); + } } -void QuadTreeNode::setLodCallback(LodCallback *lodCallback) +void QuadTreeNode::traverseTo(ViewData* vd, float size, const osg::Vec2f& center) { - mLodCallback = lodCallback; -} + if (!hasValidBounds()) + return; -LodCallback *QuadTreeNode::getLodCallback() -{ - return mLodCallback; -} + if (getCenter().x() + getSize()/2.f <= center.x() - size/2.f + || getCenter().x() - getSize()/2.f >= center.x() + size/2.f + || getCenter().y() + getSize()/2.f <= center.y() - size/2.f + || getCenter().y() - getSize()/2.f >= center.y() + size/2.f) + return; -void QuadTreeNode::setViewDataMap(ViewDataMap *map) -{ - mViewDataMap = map; -} + bool stopTraversal = (getSize() == size); -ViewDataMap *QuadTreeNode::getViewDataMap() -{ - return mViewDataMap; + if (stopTraversal) + vd->add(this); + else + { + for (unsigned int i=0; itraverseTo(vd, size, center); + } } -ViewData* QuadTreeNode::getView(osg::NodeVisitor &nv, bool& needsUpdate) +void QuadTreeNode::intersect(ViewData* vd, TerrainLineIntersector* intersector) { - ViewData* vd = NULL; - if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) - { - osgUtil::CullVisitor* cv = static_cast(&nv); - vd = mViewDataMap->getViewData(cv->getCurrentCamera(), nv.getViewPoint(), needsUpdate); - } - else // INTERSECTION_VISITOR + if (!hasValidBounds()) + return; + + if (!intersector->intersectAndClip(getBoundingBox())) + return; + + if (getNumChildren() == 0) + vd->add(this); + else { - osg::Vec3f viewPoint = nv.getViewPoint(); - static osg::ref_ptr dummyObj = new osg::DummyObject; - vd = mViewDataMap->getViewData(dummyObj.get(), viewPoint, needsUpdate); - needsUpdate = true; + for (unsigned int i=0; iintersect(vd, intersector); } - return vd; } void QuadTreeNode::setBoundingBox(const osg::BoundingBox &boundingBox) diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 9f7c7bbb7..26eecd109 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -2,12 +2,31 @@ #define OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H #include +#include #include "defs.hpp" namespace Terrain { + class TerrainLineIntersector : public osgUtil::LineSegmentIntersector + { + public: + TerrainLineIntersector(osgUtil::LineSegmentIntersector* intersector, osg::Matrix& matrix) : + osgUtil::LineSegmentIntersector(intersector->getStart() * matrix, intersector->getEnd() * matrix) + { + setPrecisionHint(intersector->getPrecisionHint()); + _intersectionLimit = intersector->getIntersectionLimit(); + _parent = intersector; + } + + bool intersectAndClip(const osg::BoundingBox& bbInput) + { + osg::Vec3d s(_start), e(_end); + return osgUtil::LineSegmentIntersector::intersectAndClip(s, e, bbInput); + } + }; + enum ChildDirection { NW = 0, @@ -72,20 +91,14 @@ namespace Terrain /// center in cell coordinates const osg::Vec2f& getCenter() const; - virtual void traverse(osg::NodeVisitor& nv); - - /// Set the Lod callback to use for determining when to stop traversing further down the quad tree. - void setLodCallback(LodCallback* lodCallback); - - LodCallback* getLodCallback(); + /// Traverse nodes according to LOD selection. + void traverse(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback, float maxDist); - /// Set the view data map that the finally used nodes for a given camera/intersection are pushed onto. - void setViewDataMap(ViewDataMap* map); + /// Traverse to a specific node and add only that node. + void traverseTo(ViewData* vd, float size, const osg::Vec2f& center); - ViewDataMap* getViewDataMap(); - - /// Create or retrieve a view for the given traversal. - ViewData* getView(osg::NodeVisitor& nv, bool& needsUpdate); + /// Adds all leaf nodes which intersect the line from start to end + void intersect(ViewData* vd, TerrainLineIntersector* intersector); private: QuadTreeNode* mParent; @@ -98,10 +111,6 @@ namespace Terrain bool mValidBounds; float mSize; osg::Vec2f mCenter; - - osg::ref_ptr mLodCallback; - - ViewDataMap* mViewDataMap; }; } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 499e268dc..b2f119b88 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -97,12 +97,10 @@ private: class QuadTreeBuilder { public: - QuadTreeBuilder(Terrain::Storage* storage, ViewDataMap* viewDataMap, float lodFactor, float minSize) + QuadTreeBuilder(Terrain::Storage* storage, float minSize) : mStorage(storage) - , mLodFactor(lodFactor) , mMinX(0.f), mMaxX(0.f), mMinY(0.f), mMaxY(0.f) , mMinSize(minSize) - , mViewDataMap(viewDataMap) { } @@ -120,8 +118,6 @@ public: float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f; mRootNode = new RootNode(size, osg::Vec2f(centerX, centerY)); - mRootNode->setViewDataMap(mViewDataMap); - mRootNode->setLodCallback(new DefaultLodCallback(mLodFactor, mMinSize)); addChildren(mRootNode); mRootNode->initNeighbours(); @@ -170,8 +166,6 @@ public: } osg::ref_ptr node = new QuadTreeNode(parent, direction, size, center); - node->setLodCallback(parent->getLodCallback()); - node->setViewDataMap(mViewDataMap); if (center.x() - halfSize > mMaxX || center.x() + halfSize < mMinX @@ -191,15 +185,14 @@ public: if (node->getSize() <= mMinSize) { - // We arrived at a leaf - float minZ,maxZ; - if (mStorage->getMinMaxHeights(size, center, minZ, maxZ)) - { - float cellWorldSize = mStorage->getCellWorldSize(); - osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ), - osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ)); - node->setBoundingBox(boundingBox); - } + // We arrived at a leaf. + // Since the tree is used for LOD level selection instead of culling, we do not need to load the actual height data here. + float minZ = -std::numeric_limits::max(); + float maxZ = std::numeric_limits::max(); + float cellWorldSize = mStorage->getCellWorldSize(); + osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ), + osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ)); + node->setBoundingBox(boundingBox); return node; } else @@ -220,9 +213,9 @@ private: float mLodFactor; float mMinX, mMaxX, mMinY, mMaxY; float mMinSize; - ViewDataMap* mViewDataMap; osg::ref_ptr mRootNode; + osg::ref_ptr mLodCallback; }; QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize) @@ -233,9 +226,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) { - // No need for culling on the Drawable / Transform level as the quad tree performs the culling already. - mChunkManager->setCullingActive(false); - mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); @@ -246,52 +236,6 @@ QuadTreeWorld::~QuadTreeWorld() mViewDataMap->clear(); } - -void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallback* lodCallback, const osg::Vec3f& viewPoint, bool visible, float maxDist) -{ - if (!node->hasValidBounds()) - return; - - if (nv && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) - visible = visible && !static_cast(nv)->isCulled(node->getBoundingBox()); - - float dist = node->distance(viewPoint); - if (dist > maxDist) - return; - - bool stopTraversal = (lodCallback && lodCallback->isSufficientDetail(node, dist)) || !node->getNumChildren(); - - if (stopTraversal) - vd->add(node, visible); - else - { - for (unsigned int i=0; igetNumChildren(); ++i) - traverse(node->getChild(i), vd, nv, lodCallback, viewPoint, visible, maxDist); - } -} - -void traverseToCell(QuadTreeNode* node, ViewData* vd, int cellX, int cellY) -{ - if (!node->hasValidBounds()) - return; - - if (node->getCenter().x() + node->getSize()/2.f <= cellX - || node->getCenter().x() - node->getSize()/2.f >= cellX+1 - || node->getCenter().y() + node->getSize()/2.f <= cellY - || node->getCenter().y() - node->getSize()/2.f >= cellY+1) - return; - - bool stopTraversal = !node->getNumChildren(); - - if (stopTraversal) - vd->add(node, true); - else - { - for (unsigned int i=0; igetNumChildren(); ++i) - traverseToCell(node->getChild(i), vd, cellX, cellY); - } -} - /// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set. unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) { @@ -383,8 +327,15 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) return; } - bool needsUpdate = false; - ViewData* vd = mRootNode->getView(nv, needsUpdate); + bool needsUpdate = true; + ViewData* vd = nullptr; + if (isCullVisitor) + vd = mViewDataMap->getViewData(static_cast(&nv)->getCurrentCamera(), nv.getViewPoint(), needsUpdate); + else + { + static ViewData sIntersectionViewData; + vd = &sIntersectionViewData; + } if (needsUpdate) { @@ -400,21 +351,23 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) int x,y; stream >> x; stream >> y; - traverseToCell(mRootNode.get(), vd, x,y); + mRootNode->traverseTo(vd, 1, osg::Vec2f(x+0.5,y+0.5)); } else - traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getViewPoint(), true, mViewDistance); + mRootNode->traverse(vd, cv->getViewPoint(), mLodCallback, mViewDistance); } else - mRootNode->traverse(nv); - } - else if (isCullVisitor) - { - // view point is the same, but must still update visible status in case the camera has rotated - for (unsigned int i=0; igetNumEntries(); ++i) { - ViewData::Entry& entry = vd->getEntry(i); - entry.set(entry.mNode, !static_cast(&nv)->isCulled(entry.mNode->getBoundingBox())); + osgUtil::IntersectionVisitor* iv = static_cast(&nv); + osgUtil::LineSegmentIntersector* lineIntersector = dynamic_cast(iv->getIntersector()); + if (!lineIntersector) + throw std::runtime_error("Cannot update QuadTreeWorld: node visitor is not LineSegmentIntersector"); + + osg::Matrix matrix = osg::Matrix::identity(); + if (lineIntersector->getCoordinateFrame() == osgUtil::Intersector::CoordinateFrame::MODEL && iv->getModelMatrix() == 0) + matrix = lineIntersector->getTransformation(*iv, osgUtil::Intersector::CoordinateFrame::MODEL); + osg::ref_ptr terrainIntersector (new TerrainLineIntersector(lineIntersector, matrix)); + mRootNode->intersect(vd, terrainIntersector); } } @@ -424,21 +377,11 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); - if (entry.mVisible) - { - osg::UserDataContainer* udc = entry.mRenderingNode->getUserDataContainer(); - if (udc && udc->getUserData()) - { - mCompositeMapRenderer->setImmediate(static_cast(udc->getUserData())); - udc->setUserData(nullptr); - - } - entry.mRenderingNode->accept(nv); - } + entry.mRenderingNode->accept(nv); } if (!isCullVisitor) - vd->reset(); // we can't reuse intersection views in the next frame because they only contain what is touched by the intersection ray. + vd->clear(); // we can't reuse intersection views in the next frame because they only contain what is touched by the intersection ray. vd->markUnchanged(); @@ -457,7 +400,8 @@ void QuadTreeWorld::ensureQuadTreeBuilt() return; const float minSize = 1/8.f; - QuadTreeBuilder builder(mStorage, mViewDataMap.get(), mLodFactor, minSize); + mLodCallback = new DefaultLodCallback(mLodFactor, minSize); + QuadTreeBuilder builder(mStorage, minSize); builder.build(); mRootNode = builder.getRootNode(); @@ -483,7 +427,7 @@ void QuadTreeWorld::cacheCell(View *view, int x, int y) { ensureQuadTreeBuilt(); ViewData* vd = static_cast(view); - traverseToCell(mRootNode.get(), vd, x, y); + mRootNode->traverseTo(vd, 1, osg::Vec2f(x+0.5f,y+0.5f)); for (unsigned int i=0; igetNumEntries(); ++i) { @@ -503,7 +447,7 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, std::atomic ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); - traverse(mRootNode.get(), vd, nullptr, mRootNode->getLodCallback(), viewPoint, false, mViewDistance); + mRootNode->traverse(vd, viewPoint, mLodCallback, mViewDistance); for (unsigned int i=0; igetNumEntries() && !abort; ++i) { diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 349f27b64..bcb671ee1 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -15,6 +15,7 @@ namespace Terrain { class RootNode; class ViewDataMap; + class LodCallback; /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) @@ -48,6 +49,7 @@ namespace Terrain osg::ref_ptr mRootNode; osg::ref_ptr mViewDataMap; + osg::ref_ptr mLodCallback; OpenThreads::Mutex mQuadTreeMutex; bool mQuadTreeBuilt; diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index f216bb33b..151977cd4 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -4,12 +4,10 @@ #include -namespace Terrain -{ +#include "compositemaprenderer.hpp" -TerrainDrawable::TerrainDrawable() +namespace Terrain { -} TerrainDrawable::TerrainDrawable(const TerrainDrawable ©, const osg::CopyOp ©op) : osg::Geometry(copy, copyop) @@ -63,6 +61,12 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) return; } + if (mCompositeMap) + { + mCompositeMapRenderer->setImmediate(mCompositeMap); + mCompositeMap = nullptr; + } + bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv); for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it) diff --git a/components/terrain/terraindrawable.hpp b/components/terrain/terraindrawable.hpp index 6bef60bc7..b77b6b784 100644 --- a/components/terrain/terraindrawable.hpp +++ b/components/terrain/terraindrawable.hpp @@ -16,6 +16,9 @@ namespace SceneUtil namespace Terrain { + class CompositeMap; + class CompositeMapRenderer; + /** * Subclass of Geometry that supports built in multi-pass rendering and built in LightListCallback. */ @@ -28,7 +31,8 @@ namespace Terrain virtual const char* className() const { return "TerrainDrawable"; } virtual const char* libraryName() const { return "Terrain"; } - TerrainDrawable(); + TerrainDrawable() = default; + ~TerrainDrawable() = default; TerrainDrawable(const TerrainDrawable& copy, const osg::CopyOp& copyop); virtual void accept(osg::NodeVisitor &nv); @@ -41,10 +45,15 @@ namespace Terrain virtual void compileGLObjects(osg::RenderInfo& renderInfo) const; + void setCompositeMap(CompositeMap* map) { mCompositeMap = map; } + void setCompositeMapRenderer(CompositeMapRenderer* renderer) { mCompositeMapRenderer = renderer; } + private: PassVector mPasses; osg::ref_ptr mLightListCallback; + osg::ref_ptr mCompositeMap; + osg::ref_ptr mCompositeMapRenderer; }; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index f8ce2019c..c91240334 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -61,11 +61,6 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu return nullptr; if (parent) parent->addChild(node); - - osg::UserDataContainer* udc = node->getUserDataContainer(); - if (udc && udc->getUserData()) - mCompositeMapRenderer->setImmediate(static_cast(udc->getUserData())); - return node; } } diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index d1870abaa..d07a0e356 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -26,7 +26,7 @@ void ViewData::copyFrom(const ViewData& other) mViewPoint = other.mViewPoint; } -void ViewData::add(QuadTreeNode *node, bool visible) +void ViewData::add(QuadTreeNode *node) { unsigned int index = mNumEntries++; @@ -34,7 +34,7 @@ void ViewData::add(QuadTreeNode *node, bool visible) mEntries.resize(index+1); Entry& entry = mEntries[index]; - if (entry.set(node, visible)) + if (entry.set(node)) mChanged = true; } @@ -73,7 +73,7 @@ void ViewData::reset() { // clear any unused entries for (unsigned int i=mNumEntries; i mRenderingNode; diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst new file mode 100644 index 000000000..d7c199b57 --- /dev/null +++ b/docs/source/reference/modding/extended.rst @@ -0,0 +1,210 @@ +Extended modding feature overview +################################# + +OpenMW supports some extended modding features out of the box. +These features can conflict with mods, and such situations should be handled as any other mod conflict by patching the mods themselves. +The engine itself does not provide any kind of blacklisting of incompatible mods. + + +Native herbalism support +------------------------ + +In OpenMW it is possible to add one or more NiSwitchNodes with a ``HerbalismSwitch`` name. +Every switch node should have at least two child nodes (the first one represents the unharvested container, the second one - the harvested container). +If an organic container's mesh has such nodes, it is considered to be a plant. During activation, a window with the plant's content is not shown, +OpenMW transfers the contents directly into the player's inventory, triggers a theft crime event if the plant is owned and toggles the harvested model state. + +It is also possible to use scripts with ``OnActivate`` command for such containers. For example, when player needs a tool to harvest a plant (e.g. a pickaxe for ore). + +Keep in mind that the collision detection system ignores switch nodes, so add a ``RootCollisionNode`` or ``NCO`` NiStringExtraData to harvestable meshes. + +Advantages of described approach over old herbalism mods: + +1. There is no need to spawn separate "harvested" objects + +2. There is no need to attach a script to every container + +3. It supports an ownership check (the original engine without MWSE does not) + +4. It does not alter original respawn mechanics + +An example of mod which uses this feature is `Graphic Herbalism`_. + +Animated containers support +--------------------------- + +It is possible to attach opening/closing animations for containers. To do this, you need to create a KF-file for the container with the following groups: + +1. ``ContainerOpen`` (with ``Start``, ``Loot`` and ``Stop`` keys) + +2. ``ContainerClose`` (with ``Start`` and ``Stop`` keys) + +The ``Loot`` key for ``ContainerOpen`` allows to play a part of opening animation in the background. + +For example, with the following setup, the opening animation has 1.0 sec duration and shows the container window in the middle of opening animation: + +:: + + 0.0: ContainerOpen: start + 0.5: ContainerOpen: loot + 1.0: ContainerOpen: stop + +It is also possible to attach opening/closing sounds to container's animations: + +:: + + 1.0: ContainerClose: open + 1.01: Sound: AC_dw_drawer_close + 2.0: ContainerClose: stop + +The text key approach is the same as the one used for sound playback in animations in general +Note that the sound starting time value is slightly higher than the closing animation start, otherwise sound will be played at the end of opening animation in this example. + +It is important to assign a RootCollisionNode to the container mesh -- the collision shape will depend on the animation state otherwise, and this can have a performance impact. + +Advantages of described approach over old animated containers mods: + +1. There is no need to attach a script to every container + +2. Part of the opening animation can be played in the background, so we do not waste the player's time + +An example of a mod which uses this feature is `OpenMW Containers Animated`_. + + +Day/night state support +----------------------- + +It is possible to add one or more NiSwitchNodes named ``NightDaySwitch``. +Every such switch should have at least two child nodes +(the first node represents the normal node state, the second one represents the node state during night, +the optional third node represents the node state during daytime in interior cells). + +The behavior of such a model: + +1. During the day in exteriors, it is in the "normal" mode (child 0). + +2. During the night in exteriors, it is in the "night" mode (child 1). + +3. During the day in interiors, it is in the "normal" mode or "interior day" mode (child 2) depending on weather. + +4. During the night in interiors, it is in the "normal" mode. + +The actual state toggling time depends on the sunrise/sunset time settings in `openmw.cfg`: + +:: + + fallback=Weather_Sunrise_Time,6 + fallback=Weather_Sunset_Time,18 + fallback=Weather_Sunrise_Duration,2 + fallback=Weather_Sunset_Duration,2 + +These settings lead to the "night" starting at 20:00 and ending at 6:00. + +The engine checks if the weather is bright enough to support the "interior day" mode using the Glare_View setting. If it is >= 0.5, the engine considers the weather bright. + +:: + + fallback=Weather_Clear_Glare_View,1 + fallback=Weather_Foggy_Glare_View,0.25 + +With these settings, the "interior day" mode would be used for Clear weather, but would not be used for Foggy weather. + +Keep in mind that the engine will not update the weather type after a teleportation to a different region if the player did not move to an exterior cell in the new region yet. + +This feature can be used to implement street illumination, glowing windows, etc. + +Advantages of the described approach over old mods with glowing windows: + +1. There is no need to spawn additional objects for day and night mode + +2. There is no need to attach a script to every switchable object + +An example of a mod which uses this feature is `Glow in the Dahrk`_. + + +Per-group animation files support +--------------------------------- + +In the original engine it is possible to add a custom animation file to NPC to override some animations (usually idle ones). +In OpenMW it is possible to override animations via the same file for all actors which use a given basic animation file. + +If you want to override animations for all biped actors (which use the xbase_anim.nif skeleton), you can put your animations in the +``Animations/xbase_anim`` folder in your ``Data Files``. You can also have them in a data folder with a higher priority. +In this case any biped actor without a custom animation will use your animations, but – if he has additional animations – they have a higher priority. + +For example, all biped actors in Morrowind normally use the same spellcasting animations, so overriding xbase_anim spellcasting animations is sufficient. +If you want to override walking animations, you should override ``xbase_anim_female`` and ``xbase_anim_kna`` animations -- these are used for women and beast races, and +– because they have their own walking animations – they override ones which come from ``xbase_anim`` and its loose overrides. + +To enable this feature, you should have this line in your settings.cfg: + +:: + + [Game] + use additional anim sources = true + +An example of a mod which uses this feature is `Almalexia's Cast for Beasts`_. + + +Weapon sheathing support +------------------------ + +In OpenMW it is possible to display equipped, but not currently wielded weapons on the actor's model, including quivers and scabbards. + +This feature conflicts with old mods which use scripted scabbards, arrows with particles or decorative quivers (attached to the left pauldron, for example). + +1. Basics + +The minimum you need is the ``xbase_anim_sh.nif`` file from the `Weapon Sheathing`_ mod and this line in your settings.cfg: + +:: + + [Game] + weapon sheathing = true + +The ``xbase_anim_sh.nif`` contains default placement points for different weapon types. +That way you'll get Gothic-style weapon sheathing for all biped actors (without quivers and scabbards). + +2. Scabbards + +For a scabbard to be displayed, you need a mesh with an ``_sh`` suffix. For example, if the weapon has a model named foo.nif, the scabbard model must be named foo_sh.nif. + +There should be an least two nodes in the sheath file: + +``Bip01 Weapon`` - represents the weapon itself (may be just a grip for sword, for example). It is not shown when the weapon is drawn. + +``Bip01 Sheath`` - represents scabbards, quivers, etc. It is shown always when the weapon is equipped. + +You can move or rotate nodes if the default placement from the ``xbase_anim_sh.nif`` does not look good for your weapon. + +If you want to exempt a specific weapon from using this feature, you can create a stub sheath mesh with just one root node. + +If you want to use the common weapon mesh, but with custom placement, you can create a sheath mesh with an empty ``Bip01 Weapon`` node and move it as you want. + +3. Quivers + +To show the quiver for a ranged weapon, you need these nodes in the sheath file: + +``Bip01 Sheath`` node, as for scabbards + +``Bip01 Ammo`` node to show ammunition in the quiver + +``Bip01 Weapon`` to show the weapon itself (not needed for throwing weapons) + +The ``Bip01 Ammo`` should have some empty child nodes, to which the engine will attach ammunition nodes. + +The appearance and count of shown ammunition depends on type and count of equipped ammunition. If the ammunition has a wrong type (e.g. bolts for bow), it won't be shown. + +It is important to make sure the names of empty nodes start with ``"Bip01 "``, or the engine will optimize them out. + +4. Shields + +Shield holstering is not supported at the moment since it conflicts with any mods which use pseudo-shields as held items (such as Animated Morrowind and Hold It). + +An example of a mod which uses this feature is `Weapon Sheathing`_. + +.. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599 +.. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232 +.. _`Glow in the Dahrk`: https://www.nexusmods.com/morrowind/mods/45886 +.. _`Almalexia's Cast for Beasts`: https://www.nexusmods.com/morrowind/mods/45853 +.. _`Weapon sheathing`: https://www.nexusmods.com/morrowind/mods/46069` diff --git a/docs/source/reference/modding/font.rst b/docs/source/reference/modding/font.rst index d230164c5..fa89b33ea 100644 --- a/docs/source/reference/modding/font.rst +++ b/docs/source/reference/modding/font.rst @@ -9,7 +9,7 @@ To our knowledge, the format is undocumented. OpenMW can load this format and convert it on the fly into something usable (see font loader `source code `_). -In OpenMW 0.32, an --export-fonts command line option was added to write the converted font +You can use --export-fonts command line option to write the converted font (a PNG image and an XML file describing the position of each glyph in the image) to the current directory. TrueType fonts @@ -17,108 +17,27 @@ TrueType fonts Unlike vanilla Morrowind, OpenMW directly supports TrueType (``.ttf``) fonts. -0.45.0+ way ------------ This is the recommended way to install replacement fonts. -- To replace the primary "Magic Cards" font: + 1. Download `TrueType fonts for OpenMW `_ + 2. Place the ``Fonts`` folder from archive to the configuration folder. Use :doc:`paths` article to find the folder. - 1. Download `Pelagiad `_ by Isak Larborn (aka Isaskar). - 2. Create ``Fonts`` folder at the location of your ``openmw.cfg``. - 3. Copy ``openmw_font.xml`` and ``Pelagiad.ttf`` files into the folder. - 4. Either remove the ``MonoFont`` entry from the created ``openmw_font.xml`` or copy the ``DejaVuLGCSansMono.ttf`` to the ``Fonts`` folder (prefer latter option on Windows). - 5. If desired, you can now delete the original ``Magic_Cards.*`` files from your ``Data Files/Fonts`` directory. - 6. Pelagiad glyphs may appear to be pretty small, so open ``settings.cfg`` and adjust the following settings as you deem necessary:: +Now Fonts folder should include ``openmw_font.xml`` file and three ``.ttf`` files. - [GUI] - font size = 17 - ttf resolution = 96 +If desired, you can now delete the ``Data Files/Fonts`` directory. -- You can also replace the Daedric font: +It is also possible to adjust the font size and resolution:: - 1. Download `Ayembedt `_ by Georg Duffner. - 2. Install ``OMWAyembedt.otf`` into the ``Fonts`` folder. - 3. Add the following lines into ``openmw_font.xml``:: - - - - - - - - - - - - + [GUI] + font size = 16 + ttf resolution = 96 - 4. This font is missing a few glyphs (mostly punctuation), but it has all the primary glyphs. If desired, you can now delete the original ``daedric.*`` files from your ``Data Files/Fonts`` directory. +The ``font size`` setting accepts clamped values in range from 12 to 20 while ``ttf resolution`` setting accepts values from 48 to 960. -Any Resolution or Size properties in the file have no effect because the engine settings override them. +Any Resolution or Size properties in the XML file have no effect because the engine settings override them. The engine automatically takes UI scaling factor into account, so don't account for it when tweaking the settings. -Pre-0.45.0 way --------------- - -- To replace the primary "Magic Cards" font: - - 1. Download `Pelagiad `_ by Isak Larborn (aka Isaskar). - 2. Install the ``openmw_font.xml`` file into ``resources/mygui/openmw_font.xml`` in your OpenMW installation. - 3. Copy ``Pelagiad.ttf`` into ``resources/mygui/`` as well. - 4. If desired, you can now delete the original ``Magic_Cards.*`` files from your ``Data Files/Fonts`` directory. -- You can also replace the Daedric font: - - 1. Download `Ayembedt `_ by Georg Duffner. - 2. Install ``OMWAyembedt.otf`` into ``/resources/mygui/`` folder in your OpenMW installation. - 3. Add the following lines to ``openmw_font.xml``:: - - - - - - - - - - - - - - - - 4. This font is missing a few glyphs (mostly punctuation), but it has all the primary glyphs. If desired, you can now delete the original ``daedric.*`` files from your ``Data Files/Fonts`` directory. - -- Another replacement for the Daedric font is `Oblivion `_ by Dongle. - - 1. Install the ``Oblivion.ttf`` file into ``resources/mygui/``. - 2. The ``openmw_font.xml`` entry is:: - - - - - - - - - - - - - - - - - - - - - - - - - - Bitmap fonts ------------ diff --git a/docs/source/reference/modding/index.rst b/docs/source/reference/modding/index.rst index 30793504e..69ec0a56a 100644 --- a/docs/source/reference/modding/index.rst +++ b/docs/source/reference/modding/index.rst @@ -23,4 +23,5 @@ about creating new content for OpenMW, please refer to settings/index texture-modding/index font + extended paths diff --git a/docs/source/reference/modding/paths.rst b/docs/source/reference/modding/paths.rst index 085168189..ea9b6c416 100644 --- a/docs/source/reference/modding/paths.rst +++ b/docs/source/reference/modding/paths.rst @@ -9,50 +9,50 @@ The following describes the locations for the various OpenMW file paths for diff Configuration files and log files --------------------------------- -+--------------+------------------------------------------------------------------+ -| OS | Location | -+==============+==================================================================+ -| Linux | ``$HOME/.config/openmw`` | -+--------------+------------------------------------------------------------------+ -| Mac | ``$HOME/Library/Preferences/openmw`` | -+--------------+---------------+--------------------------------------------------+ -| Windows | File Explorer | ``%USERPROFILE%\Documents\My Games\OpenMW`` | -| | | | -| | PowerShell | ``"$env:USERPROFILE\Documents\My Games\OpenMW"`` | -| | | | -| | Example | ``C:\Users\Username\Documents\My Games\OpenMW`` | -+--------------+---------------+--------------------------------------------------+ ++--------------+-----------------------------------------------------------------------------------------------+ +| OS | Location | ++==============+===============================================================================================+ +| Linux | ``$HOME/.config/openmw`` | ++--------------+-----------------------------------------------------------------------------------------------+ +| Mac | ``$HOME/Library/Preferences/openmw`` | ++--------------+---------------+-------------------------------------------------------------------------------+ +| Windows | File Explorer | ``Documents\My Games\OpenMW`` | +| | | | +| | PowerShell | ``Join-Path ([environment]::GetFolderPath("mydocuments")) "My Games\OpenMW"`` | +| | | | +| | Example | ``C:\Users\Username\Documents\My Games\OpenMW`` | ++--------------+---------------+-------------------------------------------------------------------------------+ Savegames --------- -+--------------+------------------------------------------------------------------------+ -| OS | Location | -+==============+========================================================================+ -| Linux | ``$HOME/.config/openmw/saves`` | -+--------------+------------------------------------------------------------------------+ -| Mac | ``$HOME/Library/Application\ Support/openmw/saves`` | -+--------------+---------------+--------------------------------------------------------+ -| Windows | File Explorer | ``%USERPROFILE%\Documents\My Games\OpenMW\saves`` | -| | | | -| | PowerShell | ``"$env:USERPROFILE\Documents\My Games\OpenMW\saves"`` | -| | | | -| | Example | ``C:\Users\Username\Documents\My Games\OpenMW\saves`` | -+--------------+---------------+--------------------------------------------------------+ ++--------------+-----------------------------------------------------------------------------------------------------+ +| OS | Location | ++==============+=====================================================================================================+ +| Linux | ``$HOME/.config/openmw/saves`` | ++--------------+-----------------------------------------------------------------------------------------------------+ +| Mac | ``$HOME/Library/Application\ Support/openmw/saves`` | ++--------------+---------------+-------------------------------------------------------------------------------------+ +| Windows | File Explorer | ``Documents\My Games\OpenMW\saves`` | +| | | | +| | PowerShell | ``Join-Path ([environment]::GetFolderPath("mydocuments")) "My Games\OpenMW\saves"`` | +| | | | +| | Example | ``C:\Users\Username\Documents\My Games\OpenMW\saves`` | ++--------------+---------------+-------------------------------------------------------------------------------------+ Screenshots ----------- -+--------------+-------------------------------------------------------------------------+ -| OS | Location | -+==============+=========================================================================+ -| Linux | ``$HOME/.local/share/openmw`` | -+--------------+-------------------------------------------------------------------------+ -| Mac | ``$HOME/Library/Application\ Support/openmw`` | -+--------------+---------------+---------------------------------------------------------+ -| Windows | File Explorer | ``%USERPROFILE%\Documents\My Games\OpenMW\OpenMW`` | -| | | | -| | PowerShell | ``"$env:USERPROFILE\Documents\My Games\OpenMW\OpenMW"`` | -| | | | -| | Example | ``C:\Users\Username\Documents\My Games\OpenMW\OpenMW`` | -+--------------+---------------+---------------------------------------------------------+ ++--------------+-----------------------------------------------------------------------------------------------+ +| OS | Location | ++==============+===============================================================================================+ +| Linux | ``$HOME/.local/share/openmw`` | ++--------------+-----------------------------------------------------------------------------------------------+ +| Mac | ``$HOME/Library/Application\ Support/openmw`` | ++--------------+---------------+-------------------------------------------------------------------------------+ +| Windows | File Explorer | ``Documents\My Games\OpenMW`` | +| | | | +| | PowerShell | ``Join-Path ([environment]::GetFolderPath("mydocuments")) "My Games\OpenMW"`` | +| | | | +| | Example | ``C:\Users\Username\Documents\My Games\OpenMW`` | ++--------------+---------------+-------------------------------------------------------------------------------+ diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index 6f13e3305..92267a4a0 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -46,7 +46,7 @@ viewing distance :Type: floating point :Range: > 0 -:Default: 6666.0 +:Default: 6656.0 This value controls the maximum visible distance (also called the far clipping plane). Larger values significantly improve rendering in exterior spaces, @@ -87,7 +87,7 @@ Enabling the distant terrain setting is an alternative to increasing exterior ce Note that the distant land setting does not include rendering of distant static objects, so the resulting visual effect is not the same. -This setting can be adjusted in game from the ridiculously low value of 2000.0 to a maximum of 6666.0 +This setting can be adjusted in game from the ridiculously low value of 2048.0 to a maximum of 81920.0 using the View Distance slider in the Detail tab of the Video panel of the Options menu. field of view diff --git a/docs/source/reference/modding/settings/map.rst b/docs/source/reference/modding/settings/map.rst index 2cf53f704..3ef376bb9 100644 --- a/docs/source/reference/modding/settings/map.rst +++ b/docs/source/reference/modding/settings/map.rst @@ -112,5 +112,5 @@ local map cell distance :Default: 1 Similar to "exterior cell load distance" in the Cells section, controls how many cells are rendered on the local map. -Values higher than the default may result in longer loading times. Please note that only loaded cells can be rendered, +Please note that only loaded cells can be rendered, so this setting must be lower or equal to "exterior cell load distance" to work properly. diff --git a/extern/osg-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp index 6abaa64cd..54519f428 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.hpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp @@ -80,8 +80,8 @@ struct PacketQueue { AVPacketList *first_pkt, *last_pkt; std::atomic flushing; - int nb_packets; - int size; + std::atomic nb_packets; + std::atomic size; OpenThreads::Mutex mutex; OpenThreads::Condition cond; diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index 75cb6a9b0..98123acb9 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(mygui) add_subdirectory(shaders) +add_subdirectory(vfs) diff --git a/files/launcher/images/openmw-plugin.png b/files/launcher/images/openmw-plugin.png index 75ad4be0b..eb673b336 100644 Binary files a/files/launcher/images/openmw-plugin.png and b/files/launcher/images/openmw-plugin.png differ diff --git a/files/mygui/openmw_chargen_race.layout b/files/mygui/openmw_chargen_race.layout index ea7ec6179..71f6dc476 100644 --- a/files/mygui/openmw_chargen_race.layout +++ b/files/mygui/openmw_chargen_race.layout @@ -14,42 +14,42 @@ - + - + - + - + - + - + - + diff --git a/files/mygui/openmw_console.layout b/files/mygui/openmw_console.layout index a58327a48..6ab65d21d 100644 --- a/files/mygui/openmw_console.layout +++ b/files/mygui/openmw_console.layout @@ -1,6 +1,6 @@  - + diff --git a/files/mygui/openmw_count_window.layout b/files/mygui/openmw_count_window.layout index 2e083dcea..d507ad31a 100644 --- a/files/mygui/openmw_count_window.layout +++ b/files/mygui/openmw_count_window.layout @@ -11,7 +11,7 @@ - + diff --git a/files/mygui/openmw_debug_window.skin.xml b/files/mygui/openmw_debug_window.skin.xml index 587101b7f..1421b52a1 100644 --- a/files/mygui/openmw_debug_window.skin.xml +++ b/files/mygui/openmw_debug_window.skin.xml @@ -12,6 +12,6 @@ - + diff --git a/files/mygui/openmw_dialogue_window.layout b/files/mygui/openmw_dialogue_window.layout index 1ed399572..d1f96121e 100644 --- a/files/mygui/openmw_dialogue_window.layout +++ b/files/mygui/openmw_dialogue_window.layout @@ -11,7 +11,7 @@ - + diff --git a/files/mygui/openmw_edit.skin.xml b/files/mygui/openmw_edit.skin.xml index a53becdaa..07c3557e0 100644 --- a/files/mygui/openmw_edit.skin.xml +++ b/files/mygui/openmw_edit.skin.xml @@ -37,7 +37,7 @@ - + @@ -54,7 +54,7 @@ - + diff --git a/files/mygui/openmw_edit_effect.layout b/files/mygui/openmw_edit_effect.layout index c2429d24b..3b26286c4 100644 --- a/files/mygui/openmw_edit_effect.layout +++ b/files/mygui/openmw_edit_effect.layout @@ -37,7 +37,7 @@ - + @@ -46,7 +46,7 @@ - + @@ -67,7 +67,7 @@ - + @@ -87,7 +87,7 @@ - + diff --git a/files/mygui/openmw_inventory_window.layout b/files/mygui/openmw_inventory_window.layout index e935e2f5c..8f5b4c146 100644 --- a/files/mygui/openmw_inventory_window.layout +++ b/files/mygui/openmw_inventory_window.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 5e4a74978..24595881a 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -10,7 +10,6 @@ - diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 9af0e7966..ccaeb0319 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -22,10 +22,10 @@ - + - + @@ -35,10 +35,10 @@ - - - - + + + + @@ -64,10 +64,10 @@ - + - + @@ -80,11 +80,10 @@ - + - - - + + @@ -130,7 +129,7 @@ - + @@ -142,7 +141,7 @@ - + @@ -154,7 +153,7 @@ - + @@ -205,27 +204,27 @@ - - - + + + - - - + + + - - - + + + - - - + + + diff --git a/files/mygui/openmw_map_window.layout b/files/mygui/openmw_map_window.layout index 8d45022f8..d6e85919c 100644 --- a/files/mygui/openmw_map_window.layout +++ b/files/mygui/openmw_map_window.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_map_window.skin.xml b/files/mygui/openmw_map_window.skin.xml index f6c099630..cb964758d 100644 --- a/files/mygui/openmw_map_window.skin.xml +++ b/files/mygui/openmw_map_window.skin.xml @@ -6,7 +6,7 @@ - - + + diff --git a/files/mygui/openmw_scroll.skin.xml b/files/mygui/openmw_scroll.skin.xml index cff0ad7eb..f946a61ac 100644 --- a/files/mygui/openmw_scroll.skin.xml +++ b/files/mygui/openmw_scroll.skin.xml @@ -5,13 +5,13 @@ - + - + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 2341bfa19..ce1fb15ed 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -10,7 +10,7 @@ - + @@ -31,7 +31,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -77,7 +77,7 @@ - + @@ -144,7 +144,7 @@ - + @@ -155,7 +155,7 @@ - + @@ -166,7 +166,7 @@ - + @@ -177,7 +177,7 @@ - + @@ -188,7 +188,7 @@ - + @@ -234,7 +234,7 @@ - + @@ -308,7 +308,7 @@ - + @@ -331,7 +331,7 @@ - + @@ -363,7 +363,7 @@ - + @@ -376,18 +376,30 @@ - - - + + + - - + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_spell_window.layout b/files/mygui/openmw_spell_window.layout index 8a98c47b2..67a9346f4 100644 --- a/files/mygui/openmw_spell_window.layout +++ b/files/mygui/openmw_spell_window.layout @@ -1,7 +1,7 @@ - + @@ -13,6 +13,15 @@ + + + + + + + + + diff --git a/files/mygui/openmw_stats_window.layout b/files/mygui/openmw_stats_window.layout index 098b3c2d8..f26f1d598 100644 --- a/files/mygui/openmw_stats_window.layout +++ b/files/mygui/openmw_stats_window.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_wait_dialog.layout b/files/mygui/openmw_wait_dialog.layout index b02db3764..460002b87 100644 --- a/files/mygui/openmw_wait_dialog.layout +++ b/files/mygui/openmw_wait_dialog.layout @@ -16,7 +16,7 @@ - + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index 426d6dc06..d6d747c4f 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -589,6 +589,7 @@ + @@ -725,6 +726,7 @@ + @@ -860,8 +862,8 @@ + - diff --git a/files/openmw.cfg b/files/openmw.cfg index f2972aef4..d71b6b53d 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -1,6 +1,6 @@ # This is the global openmw.cfg file. Do not modify! # Modifications should be done on the user openmw.cfg file instead -# (see: https://wiki.openmw.org/index.php?title=Paths) +# (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) data=${MORROWIND_DATA_FILES} data-local="?userdata?data" diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index e7de92e84..804d168b3 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -1,6 +1,6 @@ # This is the local openmw.cfg file. Do not modify! # Modifications should be done on the user openmw.cfg file instead -# (see: https://wiki.openmw.org/index.php?title=Paths) +# (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) data="?global?data" data=./data diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 7e7e04b78..d3776e92f 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -21,9 +21,9 @@ small feature culling = true small feature culling pixel size = 2.0 -# Maximum visible distance (e.g. 2000.0 to 6666.0). Caution: this setting +# Maximum visible distance. Caution: this setting # can dramatically affect performance, see documentation for details. -viewing distance = 6666.0 +viewing distance = 6656.0 # Camera field of view in degrees (e.g. 30.0 to 110.0). # Does not affect the player's hands in the first person camera. @@ -484,86 +484,151 @@ stats x = 0.015 stats y = 0.015 stats w = 0.4275 stats h = 0.45 +stats maximized x = 0.0 +stats maximized y = 0.0 +stats maximized w = 1.0 +stats maximized h = 1.0 stats pin = false stats hidden = false +stats maximized = false # Spells window displaying powers, spells, and magical items. spells x = 0.63 spells y = 0.39 spells w = 0.36 spells h = 0.51 +spells maximized x = 0.0 +spells maximized y = 0.0 +spells maximized w = 1.0 +spells maximized h = 1.0 spells pin = false spells hidden = false +spells maximized = false # Local and world map window. map x = 0.63 map y = 0.015 map w = 0.36 map h = 0.37 +map maximized x = 0.0 +map maximized y = 0.0 +map maximized w = 1.0 +map maximized h = 1.0 map pin = false map hidden = false +map maximized = false # Player inventory window when explicitly opened. -inventory x = 0.015 +inventory x = 0.0 inventory y = 0.54 inventory w = 0.45 inventory h = 0.38 +inventory maximized x = 0.0 +inventory maximized y = 0.0 +inventory maximized w = 1.0 +inventory maximized h = 1.0 inventory pin = false inventory hidden = false - -# Dialog window for talking with NPCs. -dialogue x = 0.15 -dialogue y = 0.5 -dialogue w = 0.7 -dialogue h = 0.45 - -# Alchemy window for crafting potions. -alchemy x = 0.25 -alchemy y = 0.25 -alchemy w = 0.5 -alchemy h = 0.5 - -# Console command window for debugging commands. -console x = 0.015 -console y = 0.015 -console w = 1.0 -console h = 0.5 +inventory maximized = false # Player inventory window when searching a container. inventory container x = 0.015 inventory container y = 0.54 inventory container w = 0.45 inventory container h = 0.38 +inventory container maximized x = 0.0 +inventory container maximized y = 0.5 +inventory container maximized w = 1.0 +inventory container maximized h = 0.5 +inventory container maximized = false # Player inventory window when bartering with a shopkeeper. inventory barter x = 0.015 inventory barter y = 0.54 inventory barter w = 0.45 inventory barter h = 0.38 +inventory barter maximized x = 0.0 +inventory barter maximized y = 0.5 +inventory barter maximized w = 1.0 +inventory barter maximized h = 0.5 +inventory barter maximized = false # Player inventory window when trading with a companion. inventory companion x = 0.015 inventory companion y = 0.54 inventory companion w = 0.45 inventory companion h = 0.38 +inventory companion maximized x = 0.0 +inventory companion maximized y = 0.5 +inventory companion maximized w = 1.0 +inventory companion maximized h = 0.5 +inventory companion maximized = false + +# Dialog window for talking with NPCs. +dialogue x = 0.15 +dialogue y = 0.5 +dialogue w = 0.7 +dialogue h = 0.45 +dialogue maximized x = 0.0 +dialogue maximized y = 0.0 +dialogue maximized w = 1.0 +dialogue maximized h = 1.0 +dialogue maximized = false + +# Alchemy window for crafting potions. +alchemy x = 0.25 +alchemy y = 0.25 +alchemy w = 0.5 +alchemy h = 0.5 +alchemy maximized x = 0.0 +alchemy maximized y = 0.0 +alchemy maximized w = 1.0 +alchemy maximized h = 1.0 +alchemy maximized = false + +# Console command window for debugging commands. +console x = 0.015 +console y = 0.015 +console w = 1.0 +console h = 0.5 +console maximized x = 0.0 +console maximized y = 0.0 +console maximized w = 1.0 +console maximized h = 1.0 +console maximized = false # Container inventory when searching a container. container x = 0.49 container y = 0.54 container w = 0.39 container h = 0.38 +container maximized x = 0.0 +container maximized y = 0.0 +container maximized w = 1.0 +container maximized h = 0.5 +container maximized = false # NPC inventory window when bartering with a shopkeeper. barter x = 0.6 barter y = 0.27 barter w = 0.38 barter h = 0.63 +barter maximized x = 0.0 +barter maximized y = 0.0 +barter maximized w = 1.0 +barter maximized h = 0.5 +barter maximized = false # NPC inventory window when trading with a companion. companion x = 0.6 companion y = 0.27 companion w = 0.38 companion h = 0.63 +companion maximized x = 0.0 +companion maximized y = 0.0 +companion maximized w = 1.0 +companion maximized h = 0.5 +companion maximized = false [Navigator] diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index b50e26869..cc211a3d6 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -6,7 +6,7 @@ // tweakables -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -const float VISIBILITY = 2.5; +const float VISIBILITY = 2500.0; const float BIG_WAVES_X = 0.1; // strength of big waves const float BIG_WAVES_Y = 0.1; @@ -36,7 +36,7 @@ const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction const float SPEC_HARDNESS = 256.0; // specular highlights hardness -const float BUMP_SUPPRESS_DEPTH = 0.3; // at what water depth bumpmap will be supressed for reflections and refractions (prevents artifacts at shores) +const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be supressed for reflections and refractions (prevents artifacts at shores) const vec2 WIND_DIR = vec2(0.5f, -0.8f); const float WIND_SPEED = 0.2f; @@ -151,11 +151,11 @@ uniform float rainIntensity; float frustumDepth; -float linearizeDepth(float depth) // takes <0,1> non-linear depth value and returns <0,1> linearized value +float linearizeDepth(float depth) { float z_n = 2.0 * depth - 1.0; depth = 2.0 * near * far / (far + near - z_n * frustumDepth); - return depth / frustumDepth; + return depth; } void main(void) @@ -239,10 +239,9 @@ void main(void) fresnel = clamp(fresnel, 0.0, 1.0); #if REFRACTION - float normalization = frustumDepth / 1000; - float depthSample = linearizeDepth(texture2D(refractionDepthMap,screenCoords).x) * normalization; - float depthSampleDistorted = linearizeDepth(texture2D(refractionDepthMap,screenCoords-(normal.xy*REFR_BUMP)).x) * normalization; - float surfaceDepth = linearizeDepth(gl_FragCoord.z) * normalization; + float depthSample = linearizeDepth(texture2D(refractionDepthMap,screenCoords).x); + float depthSampleDistorted = linearizeDepth(texture2D(refractionDepthMap,screenCoords-(normal.xy*REFR_BUMP)).x); + float surfaceDepth = linearizeDepth(gl_FragCoord.z); float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum float shore = clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); #else diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 5c26e1044..8e42ee7cb 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -2,20 +2,6 @@ DataFilesPage - - - 0 - 0 - 518 - 108 - - - - - 0 - 0 - - Qt::DefaultContextMenu @@ -79,6 +65,19 @@ + + + + Clone Content List + + + Clone Content List + + + true + + + @@ -87,9 +86,6 @@ Delete Content List - - Ctrl+D - true @@ -115,6 +111,22 @@ Ctrl+N + + + + + + + + Clone Content List + + + Clone Content List + + + Ctrl+G + + false @@ -130,6 +142,9 @@ Delete Content List + + Ctrl+D + diff --git a/files/vfs/CMakeLists.txt b/files/vfs/CMakeLists.txt new file mode 100644 index 000000000..a97210d1d --- /dev/null +++ b/files/vfs/CMakeLists.txt @@ -0,0 +1,18 @@ +if (NOT DEFINED OPENMW_MYGUI_FILES_ROOT) + return() +endif() + +# Copy resource files into the build directory +set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(DDIRRELATIVE resources/vfs/textures) + +set(TEXTURE_FILES + textures/omw_menu_scroll_down.dds + textures/omw_menu_scroll_up.dds + textures/omw_menu_scroll_left.dds + textures/omw_menu_scroll_right.dds + textures/omw_menu_scroll_center_h.dds + textures/omw_menu_scroll_center_v.dds +) + +copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_MYGUI_FILES_ROOT} ${DDIRRELATIVE} "${TEXTURE_FILES}") diff --git a/files/vfs/textures/omw_menu_scroll_center_h.dds b/files/vfs/textures/omw_menu_scroll_center_h.dds new file mode 100644 index 000000000..38c09a590 Binary files /dev/null and b/files/vfs/textures/omw_menu_scroll_center_h.dds differ diff --git a/files/vfs/textures/omw_menu_scroll_center_v.dds b/files/vfs/textures/omw_menu_scroll_center_v.dds new file mode 100644 index 000000000..156255614 Binary files /dev/null and b/files/vfs/textures/omw_menu_scroll_center_v.dds differ diff --git a/files/vfs/textures/omw_menu_scroll_down.dds b/files/vfs/textures/omw_menu_scroll_down.dds new file mode 100644 index 000000000..f005fd0b7 Binary files /dev/null and b/files/vfs/textures/omw_menu_scroll_down.dds differ diff --git a/files/vfs/textures/omw_menu_scroll_left.dds b/files/vfs/textures/omw_menu_scroll_left.dds new file mode 100644 index 000000000..fec7c0599 Binary files /dev/null and b/files/vfs/textures/omw_menu_scroll_left.dds differ diff --git a/files/vfs/textures/omw_menu_scroll_right.dds b/files/vfs/textures/omw_menu_scroll_right.dds new file mode 100644 index 000000000..7b5011bbd Binary files /dev/null and b/files/vfs/textures/omw_menu_scroll_right.dds differ diff --git a/files/vfs/textures/omw_menu_scroll_up.dds b/files/vfs/textures/omw_menu_scroll_up.dds new file mode 100644 index 000000000..9261490c1 Binary files /dev/null and b/files/vfs/textures/omw_menu_scroll_up.dds differ