diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 24646b844..6f5c2e2cd 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -653,12 +654,6 @@ void MwIniImporter::setVerbose(bool verbose) { mVerbose = verbose; } -std::string MwIniImporter::numberToString(int n) { - std::stringstream str; - str << n; - return str.str(); -} - MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::path& filename) const { std::cout << "load ini file: " << filename << std::endl; @@ -800,7 +795,7 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con multistrmap::const_iterator it = ini.begin(); for(int i=0; it != ini.end(); i++) { archive = baseArchive; - archive.append(this->numberToString(i)); + archive.append(std::to_string(i)); it = ini.find(archive); if(it == ini.end()) { @@ -824,33 +819,105 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con } } -void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const { - std::vector > contentFiles; +void MwIniImporter::dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector& result) +{ + auto iter = std::find_if( + source.begin(), + source.end(), + [&element](std::pair< std::string, std::vector >& sourceElement) + { + return sourceElement.first == element; + } + ); + if (iter != source.end()) + { + auto foundElement = std::move(*iter); + source.erase(iter); + for (auto name : foundElement.second) + { + MwIniImporter::dependencySortStep(name, source, result); + } + result.push_back(std::move(foundElement.first)); + } +} + +std::vector MwIniImporter::dependencySort(MwIniImporter::dependencyList source) +{ + std::vector result; + while (!source.empty()) + { + MwIniImporter::dependencySortStep(source.begin()->first, source, result); + } + return result; +} + +std::vector::iterator MwIniImporter::findString(std::vector& source, const std::string& string) +{ + return std::find_if(source.begin(), source.end(), [&string](const std::string& sourceString) + { + return Misc::StringUtils::ciEqual(sourceString, string); + }); +} + +void MwIniImporter::addPaths(std::vector& output, std::vector input) { + for (auto& path : input) { + if (path.front() == '"') + { + path.erase(path.begin()); + path.erase(path.end() - 1); + } + output.emplace_back(path); + } +} + +void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const +{ + std::vector> contentFiles; std::string baseGameFile("Game Files:GameFile"); std::string gameFile(""); std::time_t defaultTime = 0; + ToUTF8::Utf8Encoder encoder(mEncoding); + + std::vector dataPaths; + if (cfg.count("data")) + addPaths(dataPaths, cfg["data"]); - // assume the Game Files are all in a "Data Files" directory under the directory holding Morrowind.ini - const boost::filesystem::path gameFilesDir(iniFilename.parent_path() /= "Data Files"); + if (cfg.count("data-local")) + addPaths(dataPaths, cfg["data-local"]); + + dataPaths.push_back(iniFilename.parent_path() /= "Data Files"); multistrmap::const_iterator it = ini.begin(); - for(int i=0; it != ini.end(); i++) { + for (int i=0; it != ini.end(); i++) + { gameFile = baseGameFile; - gameFile.append(this->numberToString(i)); + gameFile.append(std::to_string(i)); it = ini.find(gameFile); - if(it == ini.end()) { + if(it == ini.end()) break; - } - for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { + for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) + { std::string filetype(entry->substr(entry->length()-3)); Misc::StringUtils::lowerCaseInPlace(filetype); - if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { - boost::filesystem::path filepath(gameFilesDir); - filepath /= *entry; - contentFiles.push_back(std::make_pair(lastWriteTime(filepath, defaultTime), *entry)); + if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) + { + bool found = false; + for (auto & dataPath : dataPaths) + { + boost::filesystem::path path = dataPath / *entry; + std::time_t time = lastWriteTime(path, defaultTime); + if (time != defaultTime) + { + contentFiles.push_back({time, path}); + found = true; + break; + } + } + if (!found) + std::cout << "Warning: " << *entry << " not found, ignoring" << std::endl; } } } @@ -858,11 +925,46 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co cfg.erase("content"); cfg.insert( std::make_pair("content", std::vector() ) ); - // this will sort files by time order first, then alphabetical (maybe), I suspect non ASCII filenames will be stuffed. + // sort by timestamp sort(contentFiles.begin(), contentFiles.end()); - for(std::vector >::const_iterator iter=contentFiles.begin(); iter!=contentFiles.end(); ++iter) { - cfg["content"].push_back(iter->second); + + MwIniImporter::dependencyList unsortedFiles; + + ESM::ESMReader reader; + reader.setEncoder(&encoder); + for (auto& file : contentFiles) + { + reader.open(file.second.string()); + std::vector dependencies; + for (auto& gameFile : reader.getGameFiles()) + { + dependencies.push_back(gameFile.name); + } + unsortedFiles.emplace_back(boost::filesystem::path(reader.getName()).filename().string(), dependencies); + reader.close(); + } + + auto sortedFiles = dependencySort(unsortedFiles); + + // hard-coded dependency Morrowind - Tribunal - Bloodmoon + if(findString(sortedFiles, "Morrowind.esm") != sortedFiles.end()) + { + auto tribunalIter = findString(sortedFiles, "Tribunal.esm"); + auto bloodmoonIter = findString(sortedFiles, "Bloodmoon.esm"); + + if (bloodmoonIter != sortedFiles.end() && tribunalIter != sortedFiles.end()) + { + size_t bloodmoonIndex = std::distance(sortedFiles.begin(), bloodmoonIter); + size_t tribunalIndex = std::distance(sortedFiles.begin(), tribunalIter); + if (bloodmoonIndex < tribunalIndex) + tribunalIndex++; + sortedFiles.insert(bloodmoonIter, *tribunalIter); + sortedFiles.erase(sortedFiles.begin() + tribunalIndex); + } } + + for (auto& file : sortedFiles) + cfg["content"].push_back(file); } void MwIniImporter::writeToFile(std::ostream &out, const multistrmap &cfg) { @@ -901,9 +1003,5 @@ std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename std::cout << "content file: " << resolved << " timestamp = (" << writeTime << ") " << timeStrBuffer << std::endl; } - else - { - std::cout << "content file: " << filename << " not found" << std::endl; - } return writeTime; } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index c73cc65b5..7b710a4a4 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -14,6 +14,7 @@ class MwIniImporter { public: typedef std::map strmap; typedef std::map > multistrmap; + typedef std::vector< std::pair< std::string, std::vector > > dependencyList; MwIniImporter(); void setInputEncoding(const ToUTF8::FromType& encoding); @@ -22,14 +23,19 @@ class MwIniImporter { static multistrmap loadCfgFile(const boost::filesystem::path& filename); void merge(multistrmap &cfg, const multistrmap &ini) const; void mergeFallback(multistrmap &cfg, const multistrmap &ini) const; - void importGameFiles(multistrmap &cfg, const multistrmap &ini, + void importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const; void importArchives(multistrmap &cfg, const multistrmap &ini) const; static void writeToFile(std::ostream &out, const multistrmap &cfg); + static std::vector dependencySort(MwIniImporter::dependencyList source); + private: + static void dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector& result); + static std::vector::iterator findString(std::vector& source, const std::string& string); + static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value); - static std::string numberToString(int n); + static void addPaths(std::vector& output, std::vector input); /// \return file's "last modified time", used in original MW to determine plug-in load order static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime); @@ -40,5 +46,4 @@ class MwIniImporter { ToUTF8::FromType mEncoding; }; - #endif diff --git a/apps/openmw-mp/CMakeLists.txt b/apps/openmw-mp/CMakeLists.txt index 59dd2dab2..fdb953bd4 100644 --- a/apps/openmw-mp/CMakeLists.txt +++ b/apps/openmw-mp/CMakeLists.txt @@ -71,25 +71,25 @@ source_group(tes3mp-server\\processors\\actor FILES ${PROCESSORS_ACTOR}) set(PROCESSORS_PLAYER processors/player/ProcessorChatMsg.hpp processors/player/ProcessorGUIMessageBox.hpp + processors/player/ProcessorGUIWindow.hpp - processors/player/ProcessorRecordDynamic.hpp processors/player/ProcessorGameWeather.hpp - processors/player/ProcessorPlayerAnimFlags.hpp processors/player/ProcessorPlayerAnimPlay.hpp - processors/player/ProcessorPlayerAttack.hpp processors/player/ProcessorPlayerAttribute.hpp - processors/player/ProcessorPlayerBook.hpp processors/player/ProcessorPlayerBounty.hpp - processors/player/ProcessorPlayerCellChange.hpp processors/player/ProcessorPlayerCellState.hpp - processors/player/ProcessorPlayerCharClass.hpp processors/player/ProcessorPlayerCharGen.hpp - processors/player/ProcessorPlayerDeath.hpp processors/player/ProcessorPlayerDisposition.hpp - processors/player/ProcessorPlayerEquipment.hpp processors/player/ProcessorPlayerFaction.hpp - processors/player/ProcessorPlayerInteraction.hpp processors/player/ProcessorPlayerInventory.hpp - processors/player/ProcessorPlayerJournal.hpp processors/player/ProcessorPlayerKillCount.hpp - processors/player/ProcessorPlayerLevel.hpp processors/player/ProcessorPlayerMap.hpp - processors/player/ProcessorPlayerMiscellaneous.hpp processors/player/ProcessorPlayerPosition.hpp - processors/player/ProcessorPlayerQuickKeys.hpp processors/player/ProcessorPlayerRest.hpp - processors/player/ProcessorPlayerResurrect.hpp processors/player/ProcessorPlayerShapeshift.hpp - processors/player/ProcessorPlayerSkill.hpp processors/player/ProcessorPlayerSpeech.hpp - processors/player/ProcessorPlayerSpellbook.hpp processors/player/ProcessorPlayerStatsDynamic.hpp - processors/player/ProcessorPlayerTopic.hpp + processors/player/ProcessorGameWeather.hpp processors/player/ProcessorPlayerAnimFlags.hpp + processors/player/ProcessorPlayerAnimPlay.hpp processors/player/ProcessorPlayerAttack.hpp + processors/player/ProcessorPlayerAttribute.hpp processors/player/ProcessorPlayerBook.hpp + processors/player/ProcessorPlayerBounty.hpp processors/player/ProcessorPlayerCellChange.hpp + processors/player/ProcessorPlayerCellState.hpp processors/player/ProcessorPlayerCharClass.hpp + processors/player/ProcessorPlayerCharGen.hpp processors/player/ProcessorPlayerDeath.hpp + processors/player/ProcessorPlayerDisposition.hpp processors/player/ProcessorPlayerEquipment.hpp + processors/player/ProcessorPlayerFaction.hpp processors/player/ProcessorPlayerInteraction.hpp + processors/player/ProcessorPlayerInventory.hpp processors/player/ProcessorPlayerJournal.hpp + processors/player/ProcessorPlayerKillCount.hpp processors/player/ProcessorPlayerLevel.hpp + processors/player/ProcessorPlayerMap.hpp processors/player/ProcessorPlayerMiscellaneous.hpp + processors/player/ProcessorPlayerPosition.hpp processors/player/ProcessorPlayerQuickKeys.hpp + processors/player/ProcessorPlayerRest.hpp processors/player/ProcessorPlayerResurrect.hpp + processors/player/ProcessorPlayerShapeshift.hpp processors/player/ProcessorPlayerSkill.hpp + processors/player/ProcessorPlayerSpeech.hpp processors/player/ProcessorPlayerSpellbook.hpp + processors/player/ProcessorPlayerStatsDynamic.hpp processors/player/ProcessorPlayerTopic.hpp ) source_group(tes3mp-server\\processors\\player FILES ${PROCESSORS_PLAYER}) @@ -109,6 +109,12 @@ set(PROCESSORS_OBJECT source_group(tes3mp-server\\processors\\object FILES ${PROCESSORS_OBJECT}) +set(PROCESSORS_WORLDSTATE + processors/worldstate/ProcessorRecordDynamic.hpp + ) + +source_group(tes3mp-server\\processors\\worldstate FILES ${PROCESSORS_WORLDSTATE}) + set(PROCESSORS processors/ProcessorInitializer.cpp processors/PlayerProcessor.cpp diff --git a/apps/openmw-mp/GUI.cpp b/apps/openmw-mp/GUI.cpp index fd682378d..aea40c864 100644 --- a/apps/openmw-mp/GUI.cpp +++ b/apps/openmw-mp/GUI.cpp @@ -68,12 +68,13 @@ void GUI::customMessageBox(sol::function fn, const char *label, const char *butt setChanged(); } -void GUI::inputDialog(sol::function fn, const char *label, sol::this_environment te) +void GUI::inputDialog(sol::function fn, const char *label, const char *note, sol::this_environment te) { mwmp::BasePlayer::GUIMessageBox mbox; mbox.id = generateGuiId(); mbox.label = label; + mbox.note = note; mbox.type = Player::GUIMessageBox::Type::InputDialog; guiQueue.emplace(std::move(mbox), std::move(fn)); diff --git a/apps/openmw-mp/GUI.hpp b/apps/openmw-mp/GUI.hpp index a9146fdf3..36dfb6f05 100644 --- a/apps/openmw-mp/GUI.hpp +++ b/apps/openmw-mp/GUI.hpp @@ -1,7 +1,3 @@ -// -// Created by koncord on 15.08.17. -// - #pragma once #include @@ -21,7 +17,7 @@ public: void messageBox(sol::function fn, const char *label, sol::this_environment te); void customMessageBox(sol::function fn, const char *label, const char *buttons, sol::this_environment te); - void inputDialog(sol::function fn, const char *label, sol::this_environment te); + void inputDialog(sol::function fn, const char *label, const char *note, sol::this_environment te); void passwordDialog(sol::function fn, const char *label, const char *note, sol::this_environment te); void listBox(sol::function fn, const char *label, const char *items, sol::this_environment te); diff --git a/apps/openmw-mp/Script/LuaState.cpp b/apps/openmw-mp/Script/LuaState.cpp index a57372cc6..e4315b774 100644 --- a/apps/openmw-mp/Script/LuaState.cpp +++ b/apps/openmw-mp/Script/LuaState.cpp @@ -1,7 +1,3 @@ -// -// Created by koncord on 01.08.17. -// - #include #include @@ -264,8 +260,6 @@ LuaState::LuaState() auto packet = mwmp::Networking::get().getWorldstatePacketController()->GetPacket(ID_WORLD_TIME); tempWorldstate.hour = hour; - tempWorldstate.month = -1; - tempWorldstate.day = -1; Players::for_each([&hour, &packet](Player *player){ @@ -279,9 +273,7 @@ LuaState::LuaState() auto packet = mwmp::Networking::get().getWorldstatePacketController()->GetPacket(ID_WORLD_TIME); - tempWorldstate.hour = -1; tempWorldstate.month = month; - tempWorldstate.day = -1; Players::for_each([&month, &packet](Player *player){ @@ -294,8 +286,6 @@ LuaState::LuaState() lua->set_function("setDay", [](int day) { auto packet = mwmp::Networking::get().getWorldstatePacketController()->GetPacket(ID_WORLD_TIME); - tempWorldstate.hour = -1; - tempWorldstate.month = -1; tempWorldstate.day = day; Players::for_each([&day, &packet](Player *player){ @@ -306,6 +296,48 @@ LuaState::LuaState() }); }); + lua->set_function("setYear", [](int year) { + + auto packet = mwmp::Networking::get().getWorldstatePacketController()->GetPacket(ID_WORLD_TIME); + + tempWorldstate.year = year; + + Players::for_each([&year, &packet](Player *player) { + + tempWorldstate.guid = player->guid; + packet->setWorldstate(&tempWorldstate); + packet->Send(false); + }); + }); + + lua->set_function("setDaysPassed", [](int daysPassed) { + + auto packet = mwmp::Networking::get().getWorldstatePacketController()->GetPacket(ID_WORLD_TIME); + + tempWorldstate.daysPassed = daysPassed; + + Players::for_each([&daysPassed, &packet](Player *player) { + + tempWorldstate.guid = player->guid; + packet->setWorldstate(&tempWorldstate); + packet->Send(false); + }); + }); + + lua->set_function("setDaysPassed", [](float timeScale) { + + auto packet = mwmp::Networking::get().getWorldstatePacketController()->GetPacket(ID_WORLD_TIME); + + tempWorldstate.timeScale = timeScale; + + Players::for_each([&timeScale, &packet](Player *player) { + + tempWorldstate.guid = player->guid; + packet->setWorldstate(&tempWorldstate); + packet->Send(false); + }); + }); + lua->set_function("createChannel", [](){ return mwmp::Networking::get().createChannel(); }); diff --git a/apps/openmw-mp/processors/ProcessorInitializer.cpp b/apps/openmw-mp/processors/ProcessorInitializer.cpp index 6e82bcfcb..a7f0fb441 100644 --- a/apps/openmw-mp/processors/ProcessorInitializer.cpp +++ b/apps/openmw-mp/processors/ProcessorInitializer.cpp @@ -1,7 +1,3 @@ -// -// Created by koncord on 31.03.17. -// - #include "ProcessorInitializer.hpp" #include "Networking.hpp" @@ -11,7 +7,6 @@ #include "player/ProcessorGUIMessageBox.hpp" #include "player/ProcessorGUIWindow.hpp" #include "player/ProcessorGameWeather.hpp" -#include "player/ProcessorRecordDynamic.hpp" #include "player/ProcessorPlayerCharGen.hpp" #include "player/ProcessorPlayerAnimFlags.hpp" #include "player/ProcessorPlayerAnimPlay.hpp" @@ -79,7 +74,8 @@ #include "object/ProcessorScriptGlobalShort.hpp" #include "object/ProcessorScriptGlobalFloat.hpp" #include "object/ProcessorVideoPlay.hpp" - +#include "WorldstateProcessor.hpp" +#include "worldstate/ProcessorRecordDynamic.hpp" using namespace mwmp; @@ -89,7 +85,6 @@ void ProcessorInitializer() PlayerProcessor::AddProcessor(new ProcessorGUIMessageBox()); PlayerProcessor::AddProcessor(new ProcessorGUIWindow()); PlayerProcessor::AddProcessor(new ProcessorGameWeather()); - PlayerProcessor::AddProcessor(new ProcessorRecordDynamic()); PlayerProcessor::AddProcessor(new ProcessorPlayerCharGen()); PlayerProcessor::AddProcessor(new ProcessorPlayerAnimFlags()); PlayerProcessor::AddProcessor(new ProcessorPlayerAnimPlay()); @@ -157,4 +152,6 @@ void ProcessorInitializer() ObjectProcessor::AddProcessor(new ProcessorScriptGlobalShort()); ObjectProcessor::AddProcessor(new ProcessorScriptGlobalFloat()); ObjectProcessor::AddProcessor(new ProcessorVideoPlay()); + + WorldstateProcessor::AddProcessor(new ProcessorRecordDynamic()); } diff --git a/apps/openmw-mp/processors/WorldstateProcessor.cpp b/apps/openmw-mp/processors/WorldstateProcessor.cpp index a8b06ccb3..bd9759c4d 100644 --- a/apps/openmw-mp/processors/WorldstateProcessor.cpp +++ b/apps/openmw-mp/processors/WorldstateProcessor.cpp @@ -6,7 +6,7 @@ using namespace mwmp; template typename BasePacketProcessor::processors_t BasePacketProcessor::processors; -void WorldstateProcessor::Do(WorldstatePacket &packet, Player &player, BaseWorldstate &worldstate) +void WorldstateProcessor::Do(WorldstatePacket &packet, const std::shared_ptr &player, BaseWorldstate &worldstate) { packet.Send(true); } @@ -29,7 +29,7 @@ bool WorldstateProcessor::Process(RakNet::Packet &packet, BaseWorldstate &worlds myPacket->Read(); if (worldstate.isValid) - processor.second->Do(*myPacket, *player, worldstate); + processor.second->Do(*myPacket, player, worldstate); else LOG_MESSAGE_SIMPLE(Log::LOG_ERROR, "Received %s that failed integrity check and was ignored!", processor.second->strPacketID.c_str()); diff --git a/apps/openmw-mp/processors/WorldstateProcessor.hpp b/apps/openmw-mp/processors/WorldstateProcessor.hpp index 3a6b3b88b..2dfbab241 100644 --- a/apps/openmw-mp/processors/WorldstateProcessor.hpp +++ b/apps/openmw-mp/processors/WorldstateProcessor.hpp @@ -13,7 +13,7 @@ namespace mwmp { public: - virtual void Do(WorldstatePacket &packet, Player &player, BaseWorldstate &worldstate); + virtual void Do(WorldstatePacket &packet, const std::shared_ptr &player, BaseWorldstate &worldstate); static bool Process(RakNet::Packet &packet, BaseWorldstate &worldstate) noexcept; }; diff --git a/apps/openmw-mp/processors/player/ProcessorRecordDynamic.hpp b/apps/openmw-mp/processors/worldstate/ProcessorRecordDynamic.hpp similarity index 66% rename from apps/openmw-mp/processors/player/ProcessorRecordDynamic.hpp rename to apps/openmw-mp/processors/worldstate/ProcessorRecordDynamic.hpp index 14838fe06..49b6173bd 100644 --- a/apps/openmw-mp/processors/player/ProcessorRecordDynamic.hpp +++ b/apps/openmw-mp/processors/worldstate/ProcessorRecordDynamic.hpp @@ -1,11 +1,11 @@ #ifndef OPENMW_PROCESSORRECORDDYNAMIC_HPP #define OPENMW_PROCESSORRECORDDYNAMIC_HPP -#include "../PlayerProcessor.hpp" +#include "../WorldstateProcessor.hpp" namespace mwmp { - class ProcessorRecordDynamic final : public PlayerProcessor + class ProcessorRecordDynamic final : public WorldstateProcessor { public: ProcessorRecordDynamic() @@ -13,7 +13,7 @@ namespace mwmp BPP_INIT(ID_RECORD_DYNAMIC) } - void Do(PlayerPacket &packet, const std::shared_ptr &player) override + void Do(WorldstatePacket &packet, const std::shared_ptr &player, BaseWorldstate &worldstate) override { DEBUG_PRINTF(strPacketID.c_str()); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 25e638a63..7cd604857 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -114,15 +114,14 @@ add_openmw_dir (mwmp/processors/actor ProcessorActorAI ProcessorActorAnimFlags P ) add_openmw_dir (mwmp/processors/player ProcessorChatMessage ProcessorGUIMessageBox ProcessorHandshake - ProcessorUserDisconnected ProcessorUserMyID ProcessorCellCreate ProcessorRecordDynamic ProcessorGameSettings - ProcessorGameWeather ProcessorPlayerAnimFlags ProcessorPlayerAnimPlay ProcessorPlayerAttack ProcessorPlayerAttribute - ProcessorPlayerBaseInfo ProcessorPlayerBehavior ProcessorPlayerBook ProcessorPlayerBounty ProcessorPlayerCellChange - ProcessorPlayerCellState ProcessorPlayerCharClass ProcessorPlayerCharGen ProcessorPlayerDeath ProcessorPlayerDisposition - ProcessorPlayerEquipment ProcessorPlayerFaction ProcessorPlayerInteraction ProcessorPlayerInventory ProcessorPlayerJail - ProcessorPlayerJournal ProcessorPlayerKillCount ProcessorPlayerLevel ProcessorPlayerMap ProcessorPlayerMiscellaneous - ProcessorPlayerMomentum ProcessorPlayerPosition ProcessorPlayerQuickKeys ProcessorPlayerReputation ProcessorPlayerResurrect - ProcessorPlayerShapeshift ProcessorPlayerSkill ProcessorPlayerSpeech ProcessorPlayerSpellbook ProcessorPlayerStatsDynamic - ProcessorPlayerTopic + ProcessorUserDisconnected ProcessorUserMyID ProcessorCellCreate ProcessorGameSettings ProcessorGameWeather + ProcessorPlayerAnimFlags ProcessorPlayerAnimPlay ProcessorPlayerAttack ProcessorPlayerAttribute ProcessorPlayerBaseInfo + ProcessorPlayerBehavior ProcessorPlayerBook ProcessorPlayerBounty ProcessorPlayerCellChange ProcessorPlayerCellState + ProcessorPlayerCharClass ProcessorPlayerCharGen ProcessorPlayerDeath ProcessorPlayerDisposition ProcessorPlayerEquipment + ProcessorPlayerFaction ProcessorPlayerInteraction ProcessorPlayerInventory ProcessorPlayerJail ProcessorPlayerJournal + ProcessorPlayerKillCount ProcessorPlayerLevel ProcessorPlayerMap ProcessorPlayerMiscellaneous ProcessorPlayerMomentum + ProcessorPlayerPosition ProcessorPlayerQuickKeys ProcessorPlayerReputation ProcessorPlayerResurrect ProcessorPlayerShapeshift + ProcessorPlayerSkill ProcessorPlayerSpeech ProcessorPlayerSpellbook ProcessorPlayerStatsDynamic ProcessorPlayerTopic ) add_openmw_dir (mwmp/processors/object BaseObjectProcessor ProcessorConsoleCommand ProcessorContainer ProcessorDoorDestination @@ -133,7 +132,7 @@ add_openmw_dir (mwmp/processors/object BaseObjectProcessor ProcessorConsoleComma ProcessorScriptMemberFloat ProcessorScriptGlobalShort ProcessorScriptGlobalFloat ) -add_openmw_dir (mwmp/processors/worldstate ProcessorWorldTime +add_openmw_dir (mwmp/processors/worldstate ProcessorRecordDynamic ProcessorWorldTime ) # Main executable diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 1ad1b37e7..53a530216 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -5,6 +5,9 @@ #include #include +#include +#include + #include #include "../mwworld/ptr.hpp" @@ -203,6 +206,36 @@ namespace MWBase virtual void setDay (int day) = 0; ///< Set in-game time day. + /* + Start of tes3mp addition + + Make it possible to set the year from elsewhere + */ + virtual void setYear(int year) = 0; + /* + End of tes3mp addition + */ + + /* + Start of tes3mp addition + + Make it possible to set the number of days passed from elsewhere + */ + virtual void setDaysPassed(int daysPassed) = 0; + /* + End of tes3mp addition + */ + + /* + Start of tes3mp addition + + Make it possible to set a custom timeScale from elsewhere + */ + virtual void setTimeScale(float timeScale) = 0; + /* + End of tes3mp addition + */ + virtual int getDay() const = 0; virtual int getMonth() const = 0; virtual int getYear() const = 0; @@ -424,6 +457,7 @@ namespace MWBase virtual bool isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const = 0; virtual bool isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const = 0; virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0; + virtual bool isIdle(const MWWorld::Ptr &ptr) const = 0; virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0; @@ -590,7 +624,7 @@ namespace MWBase /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0; - virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) = 0; + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) = 0; virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, @@ -632,6 +666,8 @@ namespace MWBase /// Preload VFX associated with this effect list virtual void preloadEffects(const ESM::EffectList* effectList) = 0; + + virtual osg::ref_ptr getInstance (const std::string& modelName) = 0; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index f4ea08349..c91bab33f 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1137,7 +1137,7 @@ namespace MWClass const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); const float encumbranceTerm = gmst.fJumpEncumbranceBase->getFloat() + gmst.fJumpEncumbranceMultiplier->getFloat() * - (1.0f - Npc::getEncumbrance(ptr)/Npc::getCapacity(ptr)); + (1.0f - Npc::getNormalizedEncumbrance(ptr)); float a = static_cast(npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified()); float b = 0.0f; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 8199170dc..511a47a49 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -866,8 +866,7 @@ namespace MWMechanics } // place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be underground. - // Adding 20 in adjustPosition() is not enough. - dest.mZ += 60; + dest.mZ += 80; ToWorldCoordinates(dest, actor.getCell()->getCell()); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 730457205..019c663d5 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -863,6 +863,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); mAnimation->runAnimation(0.f); + mAnimation->updateEffects(0.f); unpersistAnimationState(); } @@ -1869,8 +1870,7 @@ void CharacterController::update(float duration) if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr)) { - const float encumbrance = cls.getEncumbrance(mPtr) / cls.getCapacity(mPtr); - + const float encumbrance = cls.getNormalizedEncumbrance(mPtr); if (sneak) fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult; else @@ -2118,6 +2118,13 @@ void CharacterController::update(float duration) } osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); + + // treat player specifically since he is not in rendering mObjects + if (mPtr == getPlayer()) + { + mAnimation->updateEffects(mSkipAnim ? 0.f : duration); + } + if(duration > 0.0f) moved /= duration; else @@ -2150,7 +2157,8 @@ void CharacterController::update(float duration) moved.z() = 1.0; // Update movement - if(mMovementAnimationControlled && mPtr.getClass().isActor()) + // We should not apply movement for standing actors + if(mMovementAnimationControlled && mPtr.getClass().isActor() && (movement.length2() > 0.f || !world->isIdle(mPtr))) world->queueMovement(mPtr, moved); mSkipAnim = false; @@ -2331,6 +2339,7 @@ void CharacterController::forceStateUpdate() } mAnimation->runAnimation(0.f); + mAnimation->updateEffects(0.f); } CharacterController::KillResult CharacterController::kill() diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 202394d8f..31721e8a8 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -6,8 +6,12 @@ #include +#include +#include + #include #include +#include /* Start of tes3mp addition @@ -40,6 +44,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" +#include "../mwrender/vismask.hpp" #include "npcstats.hpp" #include "actorutil.hpp" @@ -1066,11 +1071,13 @@ namespace MWMechanics return true; } - void CastSpell::playSpellCastingEffects(const std::string &spellid){ - + void CastSpell::playSpellCastingEffects(const std::string &spellid) + { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell *spell = store.get().find(spellid); + std::vector addedEffects; + for (std::vector::const_iterator iter = spell->mEffects.mList.begin(); iter != spell->mEffects.mList.end(); ++iter) { @@ -1079,18 +1086,72 @@ namespace MWMechanics MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); - if (animation && mCaster.getClass().isActor()) // TODO: Non-actors should also create a spell cast vfx even if they are disabled (animation == NULL) + const ESM::Static* castStatic; + + if (!effect->mCasting.empty()) + castStatic = store.get().find (effect->mCasting); + else + castStatic = store.get().find ("VFX_DefaultCast"); + + // check if the effect was already added + if (std::find(addedEffects.begin(), addedEffects.end(), "meshes\\" + castStatic->mModel) != addedEffects.end()) + continue; + + std::string texture = effect->mParticle; + + float scale = 1.0f; + osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3()); + + if (animation && mCaster.getClass().isNpc()) { - const ESM::Static* castStatic; + // For NOC we should take race height as scaling factor + const ESM::NPC *npc = mCaster.get()->mBase; + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); - if (!effect->mCasting.empty()) - castStatic = store.get().find (effect->mCasting); - else - castStatic = store.get().find ("VFX_DefaultCast"); + const ESM::Race *race = + esmStore.get().find(npc->mRace); - std::string texture = effect->mParticle; + scale = npc->isMale() ? race->mData.mHeight.mMale : race->mData.mHeight.mFemale; + } + else + { + std::string casterModel = mCaster.getClass().getModel(mCaster); + osg::ref_ptr model = MWBase::Environment::get().getWorld()->getInstance(casterModel); - animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture); + osg::ComputeBoundsVisitor computeBoundsVisitor; + computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect)); + model->accept(computeBoundsVisitor); + osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); + + if (bounds.valid()) + { + float meshSizeX = std::abs(bounds.xMax() - bounds.xMin()); + float meshSizeY = std::abs(bounds.yMax() - bounds.yMin()); + float meshSizeZ = std::abs(bounds.zMax() - bounds.zMin()); + + // TODO: take a size of particle or NPC with height and weight = 1.0 as scale = 1.0 + float scaleX = meshSizeX/60.f; + float scaleY = meshSizeY/60.f; + float scaleZ = meshSizeZ/120.f; + + scale = std::max({ scaleX, scaleY, scaleZ }); + + //pos = bounds.center(); + //pos[2] = bounds.zMin(); + } + } + + // If the caster has no animation, add the effect directly to the effectManager + if (animation) + { + animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture, scale); + } + else + { + // We should set scale for effect manager manually + float meshScale = !mCaster.getClass().isActor() ? mCaster.getCellRef().getScale() : 1.0f; + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + castStatic->mModel, effect->mParticle, pos, scale * meshScale); } if (animation && !mCaster.getClass().isActor()) @@ -1100,6 +1161,8 @@ namespace MWMechanics "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; + addedEffects.push_back("meshes\\" + castStatic->mModel); + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!effect->mCastSound.empty()) sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); diff --git a/apps/openmw/mwmp/processors/ProcessorInitializer.cpp b/apps/openmw/mwmp/processors/ProcessorInitializer.cpp index 57984d67a..30e2950d6 100644 --- a/apps/openmw/mwmp/processors/ProcessorInitializer.cpp +++ b/apps/openmw/mwmp/processors/ProcessorInitializer.cpp @@ -7,7 +7,6 @@ #include "player/ProcessorHandshake.hpp" #include "player/ProcessorUserDisconnected.hpp" #include "player/ProcessorCellCreate.hpp" -#include "player/ProcessorRecordDynamic.hpp" #include "player/ProcessorGameSettings.hpp" #include "player/ProcessorGameWeather.hpp" #include "player/ProcessorPlayerAnimFlags.hpp" @@ -92,6 +91,7 @@ #include "actor/ProcessorActorTest.hpp" #include "WorldstateProcessor.hpp" +#include "worldstate/ProcessorRecordDynamic.hpp" #include "worldstate/ProcessorWorldTime.hpp" using namespace mwmp; @@ -104,7 +104,6 @@ void ProcessorInitializer() PlayerProcessor::AddProcessor(new ProcessorHandshake()); PlayerProcessor::AddProcessor(new ProcessorUserDisconnected()); PlayerProcessor::AddProcessor(new ProcessorCellCreate()); - PlayerProcessor::AddProcessor(new ProcessorRecordDynamic()); PlayerProcessor::AddProcessor(new ProcessorGameSettings()); PlayerProcessor::AddProcessor(new ProcessorGameWeather()); PlayerProcessor::AddProcessor(new ProcessorPlayerAnimFlags()); @@ -186,5 +185,6 @@ void ProcessorInitializer() ActorProcessor::AddProcessor(new ProcessorActorStatsDynamic()); ActorProcessor::AddProcessor(new ProcessorActorTest()); + WorldstateProcessor::AddProcessor(new ProcessorRecordDynamic()); WorldstateProcessor::AddProcessor(new ProcessorWorldTime()); } diff --git a/apps/openmw/mwmp/processors/player/ProcessorRecordDynamic.hpp b/apps/openmw/mwmp/processors/worldstate/ProcessorRecordDynamic.hpp similarity index 63% rename from apps/openmw/mwmp/processors/player/ProcessorRecordDynamic.hpp rename to apps/openmw/mwmp/processors/worldstate/ProcessorRecordDynamic.hpp index 17d1a0650..591a6d66c 100644 --- a/apps/openmw/mwmp/processors/player/ProcessorRecordDynamic.hpp +++ b/apps/openmw/mwmp/processors/worldstate/ProcessorRecordDynamic.hpp @@ -1,11 +1,11 @@ #ifndef OPENMW_PROCESSORRECORDDYNAMIC_HPP #define OPENMW_PROCESSORRECORDDYNAMIC_HPP -#include "../PlayerProcessor.hpp" +#include "../WorldstateProcessor.hpp" namespace mwmp { - class ProcessorRecordDynamic final: public PlayerProcessor + class ProcessorRecordDynamic final : public WorldstateProcessor { public: ProcessorRecordDynamic() @@ -13,7 +13,7 @@ namespace mwmp BPP_INIT(ID_RECORD_DYNAMIC) } - virtual void Do(PlayerPacket &packet, BasePlayer *player) + virtual void Do(WorldstatePacket &packet, BaseWorldstate &worldstate) { // Placeholder } diff --git a/apps/openmw/mwmp/processors/worldstate/ProcessorWorldTime.hpp b/apps/openmw/mwmp/processors/worldstate/ProcessorWorldTime.hpp index 9cc7d902b..8b5d3ee2d 100644 --- a/apps/openmw/mwmp/processors/worldstate/ProcessorWorldTime.hpp +++ b/apps/openmw/mwmp/processors/worldstate/ProcessorWorldTime.hpp @@ -21,12 +21,24 @@ namespace mwmp if (isLocal()) { MWBase::World *world = MWBase::Environment::get().getWorld(); + if (worldstate.hour != -1) world->setHour(worldstate.hour); - else if (worldstate.day != -1) + + if (worldstate.day != -1) world->setDay(worldstate.day); - else if (worldstate.month != -1) + + if (worldstate.month != -1) world->setMonth(worldstate.month); + + if (worldstate.year != -1) + world->setYear(worldstate.year); + + if (worldstate.daysPassed != -1) + world->setDaysPassed(worldstate.daysPassed); + + if (worldstate.timeScale != -1) + world->setTimeScale(worldstate.timeScale); } } }; diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 79c6dcabf..6de0d1984 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -18,7 +18,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) : mCanWaterWalk(false), mWalkingOnWater(false) - , mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) + , mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false), mIdle(true) , mInternalCollisionMode(true) , mExternalCollisionMode(true) , mCollisionWorld(world) @@ -195,6 +195,11 @@ void Actor::setOnSlope(bool slope) mOnSlope = slope; } +void Actor::setIdle(bool idle) +{ + mIdle = idle; +} + bool Actor::isWalkingOnWater() const { return mWalkingOnWater; diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 8ec94200f..bdafc1235 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -139,6 +139,13 @@ namespace MWPhysics return mInternalCollisionMode && mOnSlope; } + void setIdle(bool idle); + + bool getIdle() const + { + return mIdle; + } + btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); @@ -179,6 +186,7 @@ namespace MWPhysics osg::Vec3f mForce; bool mOnGround; bool mOnSlope; + bool mIdle; bool mInternalCollisionMode; bool mExternalCollisionMode; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5863897b9..cc40a96f4 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -249,7 +249,7 @@ namespace MWPhysics // Check if we actually found a valid spawn point (use an infinitely thin ray this time). // Required for some broken door destinations in Morrowind.esm, where the spawn point // intersects with other geometry if the actor's base is taken into account - btVector3 from = toBullet(position); + btVector3 from = toBullet(position + offset); btVector3 to = from - btVector3(0,0,maxHeight); btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); @@ -683,6 +683,7 @@ namespace MWPhysics , mWaterEnabled(false) , mParentNode(parentNode) , mPhysicsDt(1.f / 60.f) + , mIdleUpdateTimer(0) { mResourceSystem->addResourceManager(mShapeManager.get()); @@ -739,6 +740,18 @@ namespace MWPhysics delete mBroadphase; } + void PhysicsSystem::updateIdle() + { + for (ActorMap::iterator it = mActors.begin(); it != mActors.end(); ++it) + { + osg::Vec3f pos(it->second->getCollisionObjectPosition()); + + RayResult result = castRay(pos, pos - osg::Vec3f(0, 0, it->second->getHalfExtents().z() + 2), it->second->getPtr(), std::vector(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door); + + it->second->setIdle(result.mHit); + } + } + void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) { mUnrefQueue = unrefQueue; @@ -1067,6 +1080,11 @@ namespace MWPhysics return physactor && physactor->getOnGround(); } + bool PhysicsSystem::isIdle(const MWWorld::Ptr &actor) + { + Actor* physactor = getActor(actor); + return physactor && physactor->getIdle(); + } bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) { const Actor* physicActor = getActor(actor); @@ -1359,6 +1377,10 @@ namespace MWPhysics cmode = !cmode; found->second->enableCollisionMode(cmode); found->second->enableCollisionBody(cmode); + + if (cmode) + queueObjectMovement(MWMechanics::getPlayer(), osg::Vec3f(0, 0, -0.1f)); + return cmode; } @@ -1478,6 +1500,13 @@ namespace MWPhysics for (std::set::iterator it = mAnimatedObjects.begin(); it != mAnimatedObjects.end(); ++it) (*it)->animateCollisionShapes(mCollisionWorld); + mIdleUpdateTimer -= dt; + if (mIdleUpdateTimer <= 0.f) + { + mIdleUpdateTimer = 0.5f; + updateIdle(); + } + #ifndef BT_NO_PROFILE CProfileManager::Reset(); CProfileManager::Increment_Frame_Counter(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index b458f936e..fb95e3173 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -124,6 +124,7 @@ namespace MWPhysics bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const; bool isOnGround (const MWWorld::Ptr& actor); + bool isIdle (const MWWorld::Ptr& actor); bool canMoveToWaterSurface (const MWWorld::ConstPtr &actor, const float waterlevel); @@ -183,6 +184,7 @@ namespace MWPhysics private: void updateWater(); + void updateIdle(); osg::ref_ptr mUnrefQueue; @@ -231,6 +233,7 @@ namespace MWPhysics osg::ref_ptr mParentNode; float mPhysicsDt; + float mIdleUpdateTimer; PhysicsSystem (const PhysicsSystem&); PhysicsSystem& operator= (const PhysicsSystem&); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1bd839ead..1b508e593 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -1115,8 +1117,6 @@ namespace MWRender ++stateiter; } - updateEffects(duration); - if (mHeadController) { const float epsilon = 0.001f; @@ -1366,7 +1366,7 @@ namespace MWRender useQuadratic, quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); } - void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) + void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture, float scale) { if (!mObjectRoot.get()) return; @@ -1417,7 +1417,13 @@ namespace MWRender overrideFirstRootTexture(texture, mResourceSystem, node); - // TODO: in vanilla morrowind the effect is scaled based on the host object's bounding box. + osg::Vec3f scale3f (scale, scale, scale); + + osg::ref_ptr trans = new osg::PositionAttitudeTransform; + trans->setScale(scale3f); + trans->addChild(node); + parentNode->removeChild(node); + parentNode->addChild(trans); mEffects.push_back(params); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index cff98a4b7..74224c6bd 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -364,7 +364,7 @@ public: * @param texture override the texture specified in the model's materials - if empty, do not override * @note Will not add an effect twice. */ - void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = ""); + void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = "", float scale = 1.0f); void removeEffect (int effectId); void getLoopingEffects (std::vector& out) const; @@ -446,7 +446,6 @@ public: void setLoopingEnabled(const std::string &groupname, bool enabled); - /// This is typically called as part of runAnimation, but may be called manually if needed. void updateEffects(float duration); /// Return a node with the specified name, or NULL if not existing. diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 3e785a769..c44c5428a 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -26,6 +26,13 @@ EffectManager::~EffectManager() } void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) +{ + osg::Vec3f scale3f (scale, scale, scale); + + addEffect(model, textureOverride, worldPosition, scale3f, isMagicVFX); +} + +void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, const osg::Vec3f &scale, bool isMagicVFX) { osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model); @@ -40,7 +47,7 @@ void EffectManager::addEffect(const std::string &model, const std::string& textu osg::ref_ptr trans = new osg::PositionAttitudeTransform; trans->setPosition(worldPosition); - trans->setScale(osg::Vec3f(scale, scale, scale)); + trans->setScale(scale); trans->addChild(node); SceneUtil::AssignControllerSourcesVisitor assignVisitor(effect.mAnimTime); diff --git a/apps/openmw/mwrender/effectmanager.hpp b/apps/openmw/mwrender/effectmanager.hpp index 5873c00dd..cc1c1b42e 100644 --- a/apps/openmw/mwrender/effectmanager.hpp +++ b/apps/openmw/mwrender/effectmanager.hpp @@ -33,6 +33,7 @@ namespace MWRender /// Add an effect. When it's finished playing, it will be removed automatically. void addEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPosition, float scale, bool isMagicVFX = true); + void addEffect (const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, const osg::Vec3f &scale3f, bool isMagicVFX); void update(float dt); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index f0a3d2e38..523f1774c 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -138,6 +138,14 @@ bool Objects::removeObject (const MWWorld::Ptr& ptr) return false; } +void Objects::updateEffects(float duration) +{ + for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();) + { + iter->second->updateEffects(duration); + ++iter; + } +} void Objects::removeCell(const MWWorld::CellStore* store) { diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 659853763..e97395213 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -88,6 +88,8 @@ public: bool removeObject (const MWWorld::Ptr& ptr); ///< \return found? + void updateEffects(float duration); + void removeCell(const MWWorld::CellStore* store); /// Updates containing cell for object rendering data diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6f0ddba3a..e5933a72a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -557,6 +557,7 @@ namespace MWRender mEffectManager->update(dt); mSky->update(dt); mWater->update(dt); + mObjects->updateEffects(dt); } mCamera->update(dt, paused); @@ -841,9 +842,11 @@ namespace MWRender mObjects->updatePtr(old, updated); } - void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) + void RenderingManager::spawnEffect(const std::string &model, const std::string& texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) { - mEffectManager->addEffect(model, texture, worldPosition, scale, isMagicVFX); + osg::Vec3f scale3f (scale, scale, scale); + + mEffectManager->addEffect(model, texture, worldPosition, scale3f, isMagicVFX); } void RenderingManager::notifyWorldSpaceChanged() @@ -1122,6 +1125,12 @@ namespace MWRender updateProjectionMatrix(); } } + + osg::ref_ptr RenderingManager::getInstance(const std::string& modelName) + { + return mResourceSystem->getSceneManager()->getInstance(modelName); + } + void RenderingManager::exportSceneGraph(const MWWorld::Ptr &ptr, const std::string &filename, const std::string &format) { osg::Node* node = mViewer->getSceneData(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 1c689d29f..7a570f249 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -205,6 +205,8 @@ namespace MWRender LandManager* getLandManager() const; + osg::ref_ptr getInstance(const std::string& modelName); + private: void updateProjectionMatrix(); void updateTextureFiltering(); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 382f6012e..fcac037e3 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -26,6 +26,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/world.hpp" @@ -770,6 +771,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { + MWWorld::Ptr ptr = R()(runtime); + MWBase::Environment::get().getWorld()->queueMovement(ptr, osg::Vec3f(0, 0, -0.1f)); } }; @@ -1121,6 +1124,7 @@ namespace MWScript MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); MWMechanics::CastSpell cast(ptr, target); + cast.playSpellCastingEffects(spell); cast.mHitPosition = target.getRefData().getPosition().asVec3(); cast.mAlwaysSucceed = true; cast.cast(spell); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index c1bb589e8..911e0ebdc 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -506,7 +506,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str if (firstPersonCam != MWBase::Environment::get().getWorld()->isFirstPerson()) MWBase::Environment::get().getWorld()->togglePOV(); - MWWorld::ConstPtr ptr = MWMechanics::getPlayer(); + const MWWorld::Ptr ptr = MWMechanics::getPlayer(); if (ptr.isInCell()) { @@ -538,6 +538,9 @@ void MWState::StateManager::loadGame (const Character *character, const std::str // Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag. // But make sure the flag is cleared anyway in case it was set from an earlier game. MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + + // Workaround to fix camera position upon game load + MWBase::Environment::get().getWorld()->queueMovement(ptr, osg::Vec3f(0, 0, 0)); } catch (const std::exception& e) { diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index a8d4ee014..2ddfb1e55 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -475,10 +475,15 @@ namespace MWWorld float Class::getNormalizedEncumbrance(const Ptr &ptr) const { float capacity = getCapacity(ptr); + float encumbrance = getEncumbrance(ptr); + + if (encumbrance == 0) + return 0.f; + if (capacity == 0) return 1.f; - return getEncumbrance(ptr) / capacity; + return encumbrance / capacity; } std::string Class::getSound(const MWWorld::ConstPtr&) const diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8077c1086..22a9ac47e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -935,6 +935,45 @@ namespace MWWorld mRendering->skySetDate (mDay->getInteger(), month); } + /* + Start of tes3mp addition + + Make it possible to set the year from elsewhere + */ + void World::setYear(int year) + { + mYear->setInteger(year); + } + /* + End of tes3mp addition + */ + + /* + Start of tes3mp addition + + Make it possible to set the number of days passed from elsewhere + */ + void World::setDaysPassed(int days) + { + mDaysPassed->setInteger(days); + } + /* + End of tes3mp addition + */ + + /* + Start of tes3mp addition + + Make it possible to set a custom timeScale from elsewhere + */ + void World::setTimeScale(float timeScale) + { + mTimeScale->setFloat(timeScale); + } + /* + End of tes3mp addition + */ + int World::getDay() const { return mDay->getInteger(); @@ -1398,8 +1437,6 @@ namespace MWWorld if (pos.z() < terrainHeight) pos.z() = terrainHeight; - pos.z() += 20; // place slightly above. will snap down to ground with code below - if (force || !isFlying(ptr)) { osg::Vec3f traced = mPhysics->traceDown(ptr, pos, 500); @@ -1645,6 +1682,11 @@ namespace MWWorld } } + osg::ref_ptr World::getInstance (const std::string& modelName) + { + return mRendering->getInstance(modelName); + } + const ESM::Potion *World::createRecord (const ESM::Potion& record) { return mStore.insert(record); @@ -1736,7 +1778,7 @@ namespace MWWorld if (!paused) doPhysics (duration); - updatePlayer(paused); + updatePlayer(); mPhysics->debugDraw(); @@ -1752,7 +1794,7 @@ namespace MWWorld } } - void World::updatePlayer(bool paused) + void World::updatePlayer() { MWWorld::Ptr player = getPlayerPtr(); @@ -1785,7 +1827,7 @@ namespace MWWorld bool swimming = isSwimming(player); static const float i1stPersonSneakDelta = getStore().get().find("i1stPersonSneakDelta")->getFloat(); - if(!paused && sneaking && !(swimming || inair)) + if (sneaking && !(swimming || inair)) mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); else mRendering->getCamera()->setSneakOffset(0.f); @@ -2272,6 +2314,11 @@ namespace MWWorld return mPhysics->isOnGround(ptr); } + bool World::isIdle(const MWWorld::Ptr &ptr) const + { + return mPhysics->isIdle(ptr); + } + void World::togglePOV() { mRendering->togglePOV(); @@ -3528,9 +3575,9 @@ namespace MWWorld mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); } - void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos) + void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos, float scale, bool isMagicVFX) { - mRendering->spawnEffect(model, textureOverride, worldPos); + mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX); } void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType, diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index a8deea784..b639e23bd 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -129,7 +129,7 @@ namespace MWWorld Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); - void updatePlayer(bool paused); + void updatePlayer(); void preloadSpells(); @@ -315,6 +315,36 @@ namespace MWWorld void setDay (int day) override; ///< Set in-game time day. + /* + Start of tes3mp addition + + Make it possible to set the year from elsewhere + */ + void setYear(int year) override; + /* + End of tes3mp addition + */ + + /* + Start of tes3mp addition + + Make it possible to set the number of days passed from elsewhere + */ + void setDaysPassed(int daysPassed) override; + /* + End of tes3mp addition + */ + + /* + Start of tes3mp addition + + Make it possible to set a custom timeScale from elsewhere + */ + void setTimeScale(float timeScale) override; + /* + End of tes3mp addition + */ + int getDay() const override; int getMonth() const override; int getYear() const override; @@ -523,6 +553,7 @@ namespace MWWorld bool isWading(const MWWorld::ConstPtr &object) const override; bool isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const override; bool isOnGround(const MWWorld::Ptr &ptr) const override; + bool isIdle(const MWWorld::Ptr &ptr) const override; osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const override; @@ -694,7 +725,7 @@ namespace MWWorld /// Spawn a blood effect for \a ptr at \a worldPosition void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override; - void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) override; + void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) override; void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName, @@ -734,6 +765,8 @@ namespace MWWorld /// Preload VFX associated with this effect list void preloadEffects(const ESM::EffectList* effectList) override; + + osg::ref_ptr getInstance (const std::string& modelName); }; } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 0f8fb0c49..57d080cf8 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -231,29 +231,13 @@ void Wizard::MainWizard::setupInstallations() void Wizard::MainWizard::runSettingsImporter() { + writeSettings(); + QString path(field(QLatin1String("installation.path")).toString()); - // Create the file if it doesn't already exist, else the importer will fail QString userPath(toQString(mCfgMgr.getUserConfigPath())); QFile file(userPath + QLatin1String("openmw.cfg")); - if (!file.exists()) { - if (!file.open(QIODevice::ReadWrite)) { - // File cannot be created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("

Could not open or create %1 for writing

\ -

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

").arg(file.fileName())); - msgBox.exec(); - return qApp->quit(); - } - - file.close(); - } - // Construct the arguments to run the importer QStringList arguments; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index d41ec2d83..638bde6c8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -167,6 +167,7 @@ add_component_dir (openmw-mp/Packets add_component_dir (openmw-mp/Packets/Actor ActorPacket + PacketActorList PacketActorAuthority PacketActorTest PacketActorAI PacketActorAnimFlags PacketActorAnimPlay PacketActorAttack PacketActorCellChange PacketActorDeath PacketActorEquipment PacketActorInteraction PacketActorPosition PacketActorSpeech PacketActorStatsDynamic @@ -174,9 +175,10 @@ add_component_dir (openmw-mp/Packets/Actor add_component_dir (openmw-mp/Packets/Player PlayerPacket + PacketHandshake PacketChatMessage PacketGUIBoxes PacketGUIWindow PacketGameSettings PacketGameWeather - PacketCellCreate PacketRecordDynamic + PacketCellCreate PacketPlayerBaseInfo PacketPlayerCharGen PacketPlayerActiveSkills PacketPlayerAnimFlags PacketPlayerAnimPlay PacketPlayerAttack PacketPlayerAttribute PacketPlayerBehavior PacketPlayerBook PacketPlayerBounty @@ -189,6 +191,7 @@ add_component_dir (openmw-mp/Packets/Player add_component_dir (openmw-mp/Packets/Object ObjectPacket + PacketConsoleCommand PacketContainer PacketObjectAnimPlay PacketObjectAttach PacketObjectCollision PacketObjectDelete PacketDoorDestination PacketDoorState PacketObjectLock PacketObjectMove PacketObjectPlace PacketObjectReset PacketObjectRotate PacketObjectScale PacketObjectSpawn PacketObjectState PacketObjectTrap @@ -198,7 +201,8 @@ add_component_dir (openmw-mp/Packets/Object add_component_dir (openmw-mp/Packets/Worldstate WorldstatePacket - PacketWorldTime + + PacketRecordDynamic PacketWorldTime ) add_component_dir (fallback diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 2a716427e..67b9d6a38 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -57,6 +57,7 @@ void ESMReader::close() mCtx.subCached = false; mCtx.recName.clear(); mCtx.subName.clear(); + mHeader.blank(); } void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) diff --git a/components/openmw-mp/Base/BaseWorldstate.hpp b/components/openmw-mp/Base/BaseWorldstate.hpp index ba108a9e1..f8549b74e 100644 --- a/components/openmw-mp/Base/BaseWorldstate.hpp +++ b/components/openmw-mp/Base/BaseWorldstate.hpp @@ -14,14 +14,23 @@ namespace mwmp BaseWorldstate() { + month = -1; + day = -1; + hour = -1; + daysPassed = -1; + timeScale = -1; } RakNet::RakNetGUID guid; - int month; - int day; double hour; + int day; + int month; + int year; + + int daysPassed; + float timeScale; bool isValid; }; diff --git a/components/openmw-mp/Controllers/PlayerPacketController.cpp b/components/openmw-mp/Controllers/PlayerPacketController.cpp index 7500ebb2c..41729c946 100644 --- a/components/openmw-mp/Controllers/PlayerPacketController.cpp +++ b/components/openmw-mp/Controllers/PlayerPacketController.cpp @@ -6,7 +6,6 @@ #include "../Packets/Player/PacketGUIWindow.hpp" #include "../Packets/Player/PacketLoaded.hpp" #include "../Packets/Player/PacketCellCreate.hpp" -#include "../Packets/Player/PacketRecordDynamic.hpp" #include "../Packets/Player/PacketGameSettings.hpp" #include "../Packets/Player/PacketGameWeather.hpp" #include "../Packets/Player/PacketPlayerActiveSkills.hpp" @@ -57,7 +56,6 @@ mwmp::PlayerPacketController::PlayerPacketController(RakNet::RakPeerInterface *p AddPacket(&packets, peer); AddPacket(&packets, peer); AddPacket(&packets, peer); - AddPacket(&packets, peer); AddPacket(&packets, peer); AddPacket(&packets, peer); AddPacket(&packets, peer); diff --git a/components/openmw-mp/Controllers/WorldstatePacketController.cpp b/components/openmw-mp/Controllers/WorldstatePacketController.cpp index f5f28f16a..29dd300bc 100644 --- a/components/openmw-mp/Controllers/WorldstatePacketController.cpp +++ b/components/openmw-mp/Controllers/WorldstatePacketController.cpp @@ -1,8 +1,10 @@ +#include "../Packets/Worldstate/PacketRecordDynamic.hpp" #include "../Packets/Worldstate/PacketWorldTime.hpp" #include "WorldstatePacketController.hpp" mwmp::WorldstatePacketController::WorldstatePacketController(RakNet::RakPeerInterface *peer) { + AddPacket(&packets, peer); AddPacket(&packets, peer); } diff --git a/components/openmw-mp/Packets/Player/PacketGUIBoxes.cpp b/components/openmw-mp/Packets/Player/PacketGUIBoxes.cpp index 67154dc94..5f092bc6e 100644 --- a/components/openmw-mp/Packets/Player/PacketGUIBoxes.cpp +++ b/components/openmw-mp/Packets/Player/PacketGUIBoxes.cpp @@ -1,7 +1,3 @@ -// -// Created by koncord on 23.07.16. -// - #include "PacketGUIBoxes.hpp" #include @@ -25,7 +21,8 @@ void PacketGUIBoxes::Packet(RakNet::BitStream *bs, bool send) if (player->guiMessageBox.type == BasePlayer::GUIMessageBox::Type::CustomMessageBox) RW(player->guiMessageBox.buttons, send); - else if (player->guiMessageBox.type == BasePlayer::GUIMessageBox::Type::PasswordDialog) + else if (player->guiMessageBox.type == BasePlayer::GUIMessageBox::Type::InputDialog || + player->guiMessageBox.type == BasePlayer::GUIMessageBox::Type::PasswordDialog) RW(player->guiMessageBox.note, send); } diff --git a/components/openmw-mp/Packets/Player/PacketRecordDynamic.cpp b/components/openmw-mp/Packets/Worldstate/PacketRecordDynamic.cpp similarity index 72% rename from components/openmw-mp/Packets/Player/PacketRecordDynamic.cpp rename to components/openmw-mp/Packets/Worldstate/PacketRecordDynamic.cpp index 4ed8364eb..ef13d8cc2 100644 --- a/components/openmw-mp/Packets/Player/PacketRecordDynamic.cpp +++ b/components/openmw-mp/Packets/Worldstate/PacketRecordDynamic.cpp @@ -3,15 +3,15 @@ using namespace mwmp; -PacketRecordDynamic::PacketRecordDynamic(RakNet::RakPeerInterface *peer) : PlayerPacket(peer) +PacketRecordDynamic::PacketRecordDynamic(RakNet::RakPeerInterface *peer) : WorldstatePacket(peer) { packetID = ID_RECORD_DYNAMIC; - orderChannel = CHANNEL_SYSTEM; + orderChannel = CHANNEL_WORLDSTATE; } void PacketRecordDynamic::Packet(RakNet::BitStream *bs, bool send) { - PlayerPacket::Packet(bs, send); + WorldstatePacket::Packet(bs, send); // Placeholder } diff --git a/components/openmw-mp/Packets/Player/PacketRecordDynamic.hpp b/components/openmw-mp/Packets/Worldstate/PacketRecordDynamic.hpp similarity index 72% rename from components/openmw-mp/Packets/Player/PacketRecordDynamic.hpp rename to components/openmw-mp/Packets/Worldstate/PacketRecordDynamic.hpp index d58f936c3..3c9658673 100644 --- a/components/openmw-mp/Packets/Player/PacketRecordDynamic.hpp +++ b/components/openmw-mp/Packets/Worldstate/PacketRecordDynamic.hpp @@ -1,12 +1,12 @@ #ifndef OPENMW_PACKETRECORDDYNAMIC_HPP #define OPENMW_PACKETRECORDDYNAMIC_HPP -#include +#include #include namespace mwmp { - class PacketRecordDynamic: public PlayerPacket + class PacketRecordDynamic: public WorldstatePacket { public: PacketRecordDynamic(RakNet::RakPeerInterface *peer); diff --git a/components/openmw-mp/Packets/Worldstate/PacketWorldTime.cpp b/components/openmw-mp/Packets/Worldstate/PacketWorldTime.cpp index af270be62..d19831d90 100644 --- a/components/openmw-mp/Packets/Worldstate/PacketWorldTime.cpp +++ b/components/openmw-mp/Packets/Worldstate/PacketWorldTime.cpp @@ -6,14 +6,18 @@ using namespace mwmp; PacketWorldTime::PacketWorldTime(RakNet::RakPeerInterface *peer) : WorldstatePacket(peer) { packetID = ID_WORLD_TIME; - orderChannel = CHANNEL_SYSTEM; + orderChannel = CHANNEL_WORLDSTATE; } void PacketWorldTime::Packet(RakNet::BitStream *bs, bool send) { WorldstatePacket::Packet(bs, send); - RW(worldstate->month, send); - RW(worldstate->day, send); RW(worldstate->hour, send); + RW(worldstate->day, send); + RW(worldstate->month, send); + RW(worldstate->year, send); + + RW(worldstate->daysPassed, send); + RW(worldstate->timeScale, send); } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index bc26e084e..052090dc8 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -233,7 +233,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour QuadTreeWorld::~QuadTreeWorld() { - ensureQuadTreeBuilt(); mViewDataMap->clear(); } diff --git a/docs/cs-manual/source/record-filters.rst b/docs/cs-manual/source/record-filters.rst new file mode 100644 index 000000000..3379f557f --- /dev/null +++ b/docs/cs-manual/source/record-filters.rst @@ -0,0 +1,293 @@ +Record Filters +############## + +Filters are a key element of the OpenMW CS user interface, they allow rapid and +easy access to records presented in all tables. In order to use this +application effectively you need to familiarise yourself with all the concepts +and instructions explained in this chapter. The filter system is somewhat +unusual at first glance, but once you understand the basics it will be fairly +intuitive and easy to use + +Filters are a key element to using the OpenMW CS efficiently by allowing you to +narrow down the table entries very quickly and find what you are looking for. +The filter system might appear unusual at first, you don't just type in a word +and get all instances where it occurs, instead filters are first-class objects +in the CS with their own table. This allows you to define very specific filters +for your project and store them on disc to use in the next session. The CS +allows you fine-grained control, you can choose whether to make a filter +persistent between session, only for one session or use a one-off filter by +typing it directly into the filter field. + + + +Terms used +********** + +Filter + A Filter is generally speaking a tool able to filter the elements of a + table, that is select some elements while discarding others, according to + some criteria. These criteria are written using their own syntax. + +Criterion + A criterion describes some condition a record needs to satisfy in order to + be selected. They are written using a special syntax which is explained + below. We can logically combine multiple criteria in a filter for finer + control. + +Expression + Expressions are how we perform filtering. They look like functions in a + programming language: they have a name and accept a number of arguments. + The expression evaluates to either ``true`` or ``false`` for every record in + the table. The arguments are expressions themselves. + +Arity + The arity of an expression tells us how many arguments it takes. Expressions + taking no arguments are called *nullary*, those taking one argument are + known as *unary* expressions and those taking two arguments are called + *binary*. + + + +Interface +********* + +Above each table there is a text field which is used to enter a filter: either +one predefined by the OpenMW CS developers or one made by you. Another +important element is the filter table found under *View* → *Filters*. You +should see the default filters made by the OpenMW team in the table. The table +has the columns *Filter*, *Description* and *Modified*. + +ID + A unique name used to refer to this filter. Note that every ID has a + scope prefix, we will explain these soon. + +Modified + This is the same as for all the other records, it tells us whether the + filter is *added* or *removed*. Filters are specific to a project instead of + a content file, they have no effect on the game itself. + +Filter + The actual contents of the filter are given here using the filter syntax. + Change the expressions to modify what the filter returns. + +Description + A textual description of what the filter does. + + + +Using predefined filters +************************ + +To use a filter you have to type its ID into the filter field above a table. + +For instance, try to opening the objects table (under the world menu) and type +into the filters field ``project::weapons``. As soon as you complete the text +the table will show only the weapons. The string ``project::weapons`` is the ID +of one of the predefined filters. This means that in order to use the filter +inside the table you type its name inside the filter field. + +Filter IDs follow these general conventions: + +- IDs of filters for a specific record type contain usually the name of a + specific group. For instance the ``project::weapons`` filter contains the + term ``weapons``. Plural form is always used. + +- When filtering a specific subgroup the ID is prefixed with the name of the + more general filter. For instance ``project::weaponssilver`` will filter only + silver weapons and ``project::weaponsmagical`` will filter only magical + weapons. + +- There are few exceptions from the above rule. For instance there are + ``project::added``, ``project::removed``, ``project::modified`` and + ``project::base``. You might except something more like + ``project::statusadded`` but in this case requiring these extra characters + would not improve readability. + +We strongly recommend you take a look at the filters table right now to see +what you can filter with the defaults. Try using the default filters first +before writing you own. + + + +Writing your own filters +************************ + +As mentioned before, filters are just another type of record in the OpenMW CS. +To create a new filter you will have to add a new record to the *Filters* table +and set its properties to your liking. Filters are created by combining +existing filters into more complex ones. + + +Scopes +====== + +Every default filter has the prefix ``project``. This is a *scpoe*, a mechanism +that determines the lifetime of the filter. These are the supported scopes: + +``project::`` + Indicates that the filter is to be used throughout the project in multiple + sessions. You can restart the CS and the filter will still be there. + +``session::`` + Indicates that the filter is not stored between multiple sessions and once + you quit the OpenMW CS application the filter will be gone. Until then it + can be found inside the filters table. + +Project-filters are stored in an internal project file, not final content file +meant for the player. Keep in mind when collaborating with other modders that +you need to share the same project file. + + + +Writing expressions +=================== + +The syntax for expressions is as follows: + +.. code-block:: + + + () + (, , ..., ) + +Where ```` is the name of the expression, such as ``string`` and the +```` are expressions themselves. A nullary expression consists only of its +name. A unary expression contains its argument within a pair of parentheses +following the name. If there is more than one argument they are separated by +commas inside the parentheses. + +An example of a binary expression is ``string("Record Type", weapon)``; the +name is ``string``, and it takes two arguments which are strings of string +type. The meaning of arguments depends on the expression itself. In this case +the first argument is the name of a record column and the second field is the +values we want to test it against. + +Strings are sequences of characters and are case-insensitive. If a string +contains spaces it must be quoted, otherwise the quotes are optional and +ignored. + + +Constant Expressions +-------------------- + +These expressions take no arguments and always return the same result. + +``true`` + Always evaluates to ``true``. + +``false`` + Always evaluates to ``false``. + + +Comparison Expressions +---------------------- + +``string(, )`` + The ```` is a regular expression pattern. The expressions evaluates + to ``true`` when the value of a record in ```` matches the pattern. + Since the majority of the columns contain string values, ``string`` is among + the most often used expressions. Examples: + + ``string("Record Type", "Weapon")`` + Will evaluate to ``true`` for all records containing ``Weapon`` in the + *Record Type* column cell. + + ``string("Portable", "true")`` + Will evaluate to ``true`` [#]_ for all records containing word ``true`` inside + *Portable* column cell. + +.. [#] There is no Boolean (``true`` or ``false``) value in the OpenMW CS. You + should use a string for those. + + +``value(, (, ))`` + Match a value type, such as a number, with a range of possible values. The + argument ```` is the string name of the value we want to compare, the + second argument is a pair of lower and upper bounds for the range interval. + + One can use either parentheses ``()`` or brackets ``[]`` to surround the + pair. Brackets are inclusive and parentheses are exclusive. We can also mix + both styles: + + .. code:: + + value("Weight", [20, 50)) + + This will match any objects with a weight greater or equal to 20 and + strictly less than 50. + + +Logical Expressions +------------------- + +``not `` + Logically negates the result of an expression. If ```` evaluates + to ``true`` the negation is ``false``, and if ```` evaluates to + ``false`` the negation is ``true``. Note that there are no parentheses + around the argument. + +``or(, , ..., )`` + Logical disjunction, evaluates to ``true`` if at least one argument + evaluates to ``true`` as well, otherwise the expression evaluates to + ``false``. + + As an example assume we want to filter for both NPCs and creatures; the + expression for that use-case is + + .. code:: + + or(string("record type", "npc"), string("record type", "creature")) + + In this particular case only one argument can evaluate to ``true``, but one + can write expressions where multiple arguments can be ``true`` at a time. + +``or(, , ..., )`` + Logical conjunction, evaluates to ``true`` if and only if all arguments + evaluate to ``true`` as well, otherwise the expression evaluates to + ``false``. + + As an example assume we want to filter for weapons weighting less than a hundred + units The expression for that use-case is + + .. code:: + + and(string("record type", "weapon"), value("weight", (0, 100))) + + +Anonymous filters +================= + +Creating a whole new filter when you only intend to use it once can be +cumbersome. For that reason the OpenMW CS supports *anonymous* filters which +can be typed directly into the filters field of a table. They are not stored +anywhere, when you clear the field the filter is gone forever. + +In order to define an anonymous filter you type an exclamation mark as the +first character into the field followed by the filter definition (e.g. +``!string("Record Type", weapon)`` to filter only for weapons). + + + +Creating and saving filters +*************************** + +Filters are managed the same way as other records: go to the filters table, +right click and select the option *Add Record* from the context menu. You are +given a choice between project- or session scope. Choose the scope from the +dropdown and type in your desired ID for the filter. A newly created filter +does nothing since it still lacks expressions. In order to add your queries you +have to edit the filter record. + + +Replacing the default filters set +================================= + +OpenMW CS allows you to substitute the default filter set for the entire +application. This will affect the default filters for all content files that +have not been edited on this computer and user account. + +Create a new content file, add the desired filters, remove the undesired ones +and save. Now rename the *project* file to ``defaultfilters`` and make sure the +``.omwaddon.project`` file extension is removed. This file will act as a +template for all new files from now on. If you wish to go back to the +old default set rename or remove this custom file. diff --git a/docs/cs-manual/source/record-types.rst b/docs/cs-manual/source/record-types.rst new file mode 100644 index 000000000..3742cc9e8 --- /dev/null +++ b/docs/cs-manual/source/record-types.rst @@ -0,0 +1,62 @@ +Record Types +############ + +A game world contains many items, such as chests, weapons and monsters. All +these items are merely instances of templates we call *Objects*. The OpenMW CS +*Objects* table contains information about each of these template objects, such +as its value and weight in the case of items, or an aggression level in the +case of NPCs. + +The following is a list of all Record Types and what you can tell OpenMW CS +about each of them. + +Activator + Activators can have a script attached to them. As long as the cell this + object is in is active the script will be run once per frame. + +Potion + This is a potion which is not self-made. It has an Icon for your inventory, + weight, coin value, and an attribute called *Auto Calc* set to ``False``. + This means that the effects of this potion are pre-configured. This does not + happen when the player makes their own potion. + +Apparatus + This is a tool to make potions. Again there’s an icon for your inventory as + well as a weight and a coin value. It also has a *Quality* value attached to + it: the higher the number, the better the effect on your potions will be. + The *Apparatus Type* describes if the item is a *Calcinator*, *Retort*, + *Alembic* or *Mortar & Pestle*. + +Armor + This type of item adds *Enchantment Points* to the mix. Every piece of + clothing or armor has a "pool" of potential *Magicka* that gets unlocked + when the player enchants it. Strong enchantments consume more magicka from + this pool: the stronger the enchantment, the more *Enchantment Points* each + cast will take up. *Health* means the amount of hit points this piece of + armor has. If it sustains enough damage, the armor will be destroyed. + Finally, *Armor Value* tells the game how much points to add to the player + character’s *Armor Rating*. + +Book + This includes scrolls and notes. For the game to make the distinction + between books and scrolls, an extra property, *Scroll*, has been added. + Under the *Skill* column a scroll or book can have an in-game skill listed. + Reading this item will raise the player’s level in that specific skill. + +Clothing + These items work just like armors, but confer no protective properties. + Rather than *Armor Type*, these items have a *Clothing Type*. + +Container + This is all the stuff that stores items, from chests to sacks to plants. Its + *Capacity* shows how much stuff you can put in the container. You can + compare it to the maximum allowed load a player character can carry. A + container, however, will just refuse to take the item in question when it + gets "over-encumbered". Organic Containers are containers such as plants. + Containers that respawn are not safe to store stuff in. After a certain + amount of time they will reset to their default contents, meaning that + everything in them is gone forever. + +Creature + These can be monsters, animals and the like. + diff --git a/docs/cs-manual/source/tables.rst b/docs/cs-manual/source/tables.rst new file mode 100644 index 000000000..43da03f07 --- /dev/null +++ b/docs/cs-manual/source/tables.rst @@ -0,0 +1,168 @@ +Tables +###### + +If you have launched OpenMW CS already and played around with it for a bit, you +will have noticed that the interface is made entirely of tables. This does not +mean it works just like a spreadsheet application though, it would be more +accurate to think of databases instead. Due to the vast amounts of information +involved with Morrowind tables made the most sense. You have to be able to spot +information quickly and be able to change them on the fly. + + +Used Terms +********** + +Record + An entry in OpenMW CS representing an item, location, sound, NPC or anything + else. + +Instance, Object + When an item is placed in the world, it does not create a whole new record + each time, but an *instance* of the *object*. + + For example, the game world might contain a lot of exquisite belts on + different NPCs and in many crates, but they all refer to one specific + instance: the Exquisite Belt record. In this case, all those belts in crates + and on NPCs are instances. The central Exquisite Belt instance is called an + *object*. This allows modders to make changes to all items of the same type + in one place. + + If you wanted all exquisite belts to have 4000 enchantment points rather + than 400, you would only need to change the object Exquisite Belt rather + than all exquisite belt instances individually. + +Some columns are recurring throughout OpenMW CS, they show up in (nearly) every +table. + +ID + Each item, location, sound, etc. gets the same unique identifier in both + OpenMW CS and Morrowind. This is usually a very self-explanatory name. For + example, the ID for the (unique) black pants of Caius Cosades is + ``Caius_pants``. This allows players to manipulate the game in many ways. + For example, they could add these pants to their inventory by opening the + console and entering: ``player- >addItem Caius_pants``. In both Morrowind + and OpenMW CS the ID is the primary way to identify all these different + parts of the game. + +Modified + This column shows what has happened (if anything) to this record. There are + four possible states in which it can exist: + + Base + The record is unmodified and from a content file other than the one + currently being edited. + + Added + This record has been added in the currently content file. + + Modified + Similar to *base*, but has been changed in some way. + + Deleted + Similar to *base*, but has been removed as an entry. This does not mean, + however, that the occurrences in the game itself have been removed! For + example, if you were to remove the ``CharGen_Bed`` entry from + ``morrowind.esm``, it does not mean the bedroll in the basement of the + Census and Excise Office in Seyda Neen will be gone. You will have to + delete that instance yourself or make sure that that object is replaced + by something that still exists otherwise the player will get crashes in + the worst case scenario. + + + +World Screens +************* + +The contents of the game world can be changed by choosing one of the options in +the appropriate menu at the top of the screen. + + +Regions +======= + +This describes the general areas of Vvardenfell. Each of these areas has +different rules about things such as encounters and weather. + +Name + This is how the game will show the player's location in-game. + +MapColour + This is a six-digit hexadecimal representation of the colour used to + identify the region on the map available in *World* → *Region Map*. + +Sleep Encounter + These are the rules for what kinds of enemies the player might encounter + when sleeping outside in the wilderness. + + +Cells +===== + +Expansive worlds such as Vvardenfell, with all its items, NPCs, etc. have a lot +going on simultaneously. But if the player is in Balmora, why would the +computer need to keep track the exact locations of NPCs walking through the +corridors in a Vivec canton? All that work would be quite useless and bring +the player's system down to its knees! So the world has been divided up into +squares we call *cells*. Once your character enters a cell, the game will load +everything that is going on in that cell so the player can interact with it. + +In the original Morrowind this could be seen when a small loading bar would +appear near the bottom of the screen while travelling; the player had just +entered a new cell and the game had to load all the items and NPCs. The *Cells* +screen in OpenMW CS provides you with a list of cells in the game, both the +interior cells (houses, dungeons, mines, etc.) and the exterior cells (the +outside world). + +Sleep Forbidden + Can the player sleep on the floor? In most cities it is forbidden to sleep + outside. Sleeping in the wilderness carries its own risks of attack, though, + and this entry lets you decide if a player should be allowed to sleep on the + floor in this cell or not. + +Interior Water + Should water be rendered in this interior cell? The game world consists of + an endless ocean at height 0, then the landscape is added. If part of the + landscape goes below height 0, the player will see water. + + Setting the cell’s Interior Water to true tells the game that this cell that + there needs to be water at height 0. This is useful for dungeons or mines + that have water in them. + + Setting the cell’s Interior Water to ``false`` tells the game that the water + at height 0 should not be used. This flag is useless for outside cells. + +Interior Sky + Should this interior cell have a sky? This is a rather unique case. The + Tribunal expansion took place in a city on the mainland. Normally this would + require the city to be composed of exterior cells so it has a sky, weather + and the like. But if the player is in an exterior cell and were to look at + their in-game map, they would see Vvardenfell with an overview of all + exterior cells. The player would have to see the city’s very own map, as if + they were walking around in an interior cell. + + So the developers decided to create a workaround and take a bit of both: The + whole city would technically work exactly like an interior cell, but it + would need a sky as if it was an exterior cell. That is what this is. This + is why the vast majority of the cells you will find in this screen will have + this option set to false: It is only meant for these "fake exteriors". + +Region + To which Region does this cell belong? This has an impact on the way the + game handles weather and encounters in this area. It is also possible for a + cell not to belong to any region. + + +Objects +======= + +This is a library of all the items, triggers, containers, NPCs, etc. in the +game. There are several kinds of Record Types. Depending on which type a record +is, it will need specific information to function. For example, an NPC needs a +value attached to its aggression level. A chest, of course, does not. All +Record Types contain at least a 3D model or else the player would not see them. +Usually they also have a *Name*, which is what the players sees when they hover +their reticle over the object during the game. + +Please refer to the Record Types chapter for an overview of what each type of +object does and what you can tell OpenMW CS about these objects. + diff --git a/docs/source/manuals/openmw-cs/index.rst b/docs/source/manuals/openmw-cs/index.rst index c9f682f17..f124b526f 100644 --- a/docs/source/manuals/openmw-cs/index.rst +++ b/docs/source/manuals/openmw-cs/index.rst @@ -21,4 +21,6 @@ few chapters to familiarise yourself with the new interface. tour files-and-directories starting-dialog - + tables + record-types + record-filters