diff --git a/apps/openmw-mp/Object.cpp b/apps/openmw-mp/Object.cpp index 3968eee37..ae9bab454 100644 --- a/apps/openmw-mp/Object.cpp +++ b/apps/openmw-mp/Object.cpp @@ -58,6 +58,7 @@ void Object::setPosition(float x, float y, float z) object.position.pos[0] = x; object.position.pos[1] = y; object.position.pos[2] = z; + object.droppedByPlayer = false; changedObjectPlace = true; } diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 6ff93cb95..5fc25dc86 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -192,9 +192,8 @@ if (ANDROID) dl z ${OPENSCENEGRAPH_LIBRARIES} - ${OSG_PLUGINS} + freetype jpeg - gif png ) endif (ANDROID) diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c index 8cd69e8f0..3f28afa1b 100644 --- a/apps/openmw/android_main.c +++ b/apps/openmw/android_main.c @@ -1,6 +1,9 @@ +int stderr = 0; // Hack: fix linker error -#ifdef __ANDROID__ #include "SDL_main.h" +#include +#include +#include /******************************************************************************* Functions called by JNI @@ -14,13 +17,37 @@ extern int argcData; extern const char **argvData; void releaseArgv(); + +int Java_org_libsdl_app_SDLActivity_getMouseX(JNIEnv *env, jclass cls, jobject obj) { + int ret = 0; + SDL_GetMouseState(&ret, NULL); + return ret; +} + + +int Java_org_libsdl_app_SDLActivity_getMouseY(JNIEnv *env, jclass cls, jobject obj) { + int ret = 0; + SDL_GetMouseState(NULL, &ret); + return ret; +} + +int Java_org_libsdl_app_SDLActivity_isMouseShown(JNIEnv *env, jclass cls, jobject obj) { + return SDL_ShowCursor(SDL_QUERY); +} + + int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) { + setenv("OPENMW_DECOMPRESS_TEXTURES", "1", 1); + SDL_Android_Init(env, cls); SDL_SetMainReady(); + // On Android, we use a virtual controller with guid="Virtual" + SDL_GameControllerAddMapping("5669727475616c000000000000000000,Virtual,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4"); + /* Run the application code! */ int status; @@ -33,5 +60,3 @@ int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, return status; } -#endif /* __ANDROID__ */ - diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 328cdef55..8076ee04b 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -351,7 +351,11 @@ private: }; #endif +#ifdef ANDROID +extern "C" int SDL_main(int argc, char**argv) +#else int main(int argc, char**argv) +#endif { #if defined(__APPLE__) setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 222199211..bc4beda7c 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -249,6 +249,17 @@ namespace MWBase virtual MWWorld::Ptr getFacedObject() = 0; ///< Return pointer to the object the player is looking at, if it is within activation range + /* + Start of tes3mp addition + + This has been declared here so it can be accessed from places + other than MWWorld::World + */ + virtual void PCDropped(const MWWorld::Ptr& item) = 0; + /* + End of tes3mp addition + */ + virtual float getDistanceToFacedObject() = 0; virtual float getMaxActivationDistance() = 0; diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 0b34141c2..1bf0b7984 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -74,7 +74,7 @@ namespace MWGui */ mwmp::WorldEvent *worldEvent = mwmp::Main::get().getNetworking()->getWorldEvent(); worldEvent->reset(); - worldEvent->addObjectPlace(dropped); + worldEvent->addObjectPlace(dropped, true); worldEvent->sendObjectPlace(); /* End of tes3mp addition diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 7c89c4979..a79112b9f 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -207,8 +207,6 @@ namespace MWGui , mMessageBoxManager(parMessageBoxManager) , mButtonPressed(-1) { - setVisible(true); - int textPadding = 10; // padding between text-widget and main-widget int textButtonPadding = 10; // padding between the text-widget und the button-widget int buttonLeftPadding = 10; // padding between the buttons if horizontal @@ -358,7 +356,11 @@ namespace MWGui mMessageWidget->setCoord(messageWidgetCoord); } - // Set key focus to "Ok" button + setVisible(true); + } + + MyGUI::Widget* InteractiveMessageBox::getDefaultKeyFocus() + { std::vector keywords { "sOk", "sYes" }; for(std::vector::const_iterator button = mButtons.begin(); button != mButtons.end(); ++button) { @@ -366,11 +368,11 @@ namespace MWGui { if(Misc::StringUtils::ciEqual(MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}"), (*button)->getCaption())) { - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(*button); - return; + return *button; } } } + return NULL; } void InteractiveMessageBox::mousePressed (MyGUI::Widget* pressed) diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 53b277416..e0bcbe667 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -28,6 +28,8 @@ namespace MWGui bool createInteractiveMessageBox (const std::string& message, const std::vector& buttons); bool isInteractiveMessageBox (); + const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe; } + /// Remove all message boxes void clear(); @@ -77,6 +79,8 @@ namespace MWGui void mousePressed (MyGUI::Widget* _widget); int readPressedButton (); + MyGUI::Widget* getDefaultKeyFocus() override; + virtual bool exit() { return false; } bool mMarkedToDelete; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index a5c3a3b2a..74122677e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -908,6 +908,22 @@ namespace MWGui window->onFrame(frameDuration); } + // Make sure message boxes are always in front + // This is an awful workaround for a series of awfully interwoven issues that couldn't be worked around + // in a better way because of an impressive number of even more awfully interwoven issues. + if (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox() && mCurrentModals.back() != mMessageBoxManager->getInteractiveMessageBox()) + { + std::vector::iterator found = std::find(mCurrentModals.begin(), mCurrentModals.end(), mMessageBoxManager->getInteractiveMessageBox()); + if (found != mCurrentModals.end()) + { + WindowModal* msgbox = *found; + std::swap(*found, mCurrentModals.back()); + MyGUI::InputManager::getInstance().addWidgetModal(msgbox->mMainWidget); + mKeyboardNavigation->setModalWindow(msgbox->mMainWidget); + mKeyboardNavigation->setDefaultFocus(msgbox->mMainWidget, msgbox->getDefaultKeyFocus()); + } + } + if (!mCurrentModals.empty()) mCurrentModals.back()->onFrame(frameDuration); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 89cfa3603..7d1c80652 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1312,13 +1312,13 @@ namespace MWInput clearAllKeyBindings(control); if (defaultKeyBindings.find(i) != defaultKeyBindings.end() - && !mInputBinder->isKeyBound(defaultKeyBindings[i])) + && (force || !mInputBinder->isKeyBound(defaultKeyBindings[i]))) { control->setInitialValue(0.0f); mInputBinder->addKeyBinding(control, defaultKeyBindings[i], ICS::Control::INCREASE); } else if (defaultMouseButtonBindings.find(i) != defaultMouseButtonBindings.end() - && !mInputBinder->isMouseButtonBound(defaultMouseButtonBindings[i])) + && (force || !mInputBinder->isMouseButtonBound(defaultMouseButtonBindings[i]))) { control->setInitialValue(0.0f); mInputBinder->addMouseButtonBinding (control, defaultMouseButtonBindings[i], ICS::Control::INCREASE); @@ -1392,12 +1392,12 @@ namespace MWInput clearAllControllerBindings(control); if (defaultButtonBindings.find(i) != defaultButtonBindings.end() - && !mInputBinder->isJoystickButtonBound(mFakeDeviceID, defaultButtonBindings[i])) + && (force || !mInputBinder->isJoystickButtonBound(mFakeDeviceID, defaultButtonBindings[i]))) { control->setInitialValue(0.0f); mInputBinder->addJoystickButtonBinding(control, mFakeDeviceID, defaultButtonBindings[i], ICS::Control::INCREASE); } - else if (defaultAxisBindings.find(i) != defaultAxisBindings.end() && !mInputBinder->isJoystickAxisBound(mFakeDeviceID, defaultAxisBindings[i])) + else if (defaultAxisBindings.find(i) != defaultAxisBindings.end() && (force || !mInputBinder->isJoystickAxisBound(mFakeDeviceID, defaultAxisBindings[i]))) { control->setValue(0.5f); control->setInitialValue(0.5f); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 1f4ef6f7c..9ef83459e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1302,14 +1302,12 @@ namespace MWMechanics // AI and magic effects update for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { + float distSqr = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2(); // AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this // (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not) // This distance could be made configurable later, but the setting must be marked with a big warning: // using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876) - bool inProcessingRange = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() - <= sqrAiProcessingDistance; - - iter->second->getCharacterController()->setActive(inProcessingRange); + bool inProcessingRange = distSqr <= sqrAiProcessingDistance; /* Start of tes3mp change (minor) @@ -1480,9 +1478,24 @@ namespace MWMechanics CharacterController* playerCharacter = NULL; for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { - if (iter->first != player && - (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2() - > sqrAiProcessingDistance) + const float animationDistance = aiProcessingDistance + 400; // Slightly larger than AI distance so there is time to switch back to the idle animation. + const float distSqr = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2(); + bool isPlayer = iter->first == player; + bool inAnimationRange = isPlayer || (animationDistance == 0 || distSqr <= animationDistance*animationDistance); + int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower) + if (isPlayer) + activeFlag = 2; + int active = inAnimationRange ? activeFlag : 0; + bool canFly = iter->first.getClass().canFly(iter->first); + if (canFly) + { + // Keep animating flying creatures so they don't just hover in-air + inAnimationRange = true; + active = std::max(1, active); + } + iter->second->getCharacterController()->setActive(active); + + if (!inAnimationRange) continue; if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index df636dd56..0ccd0b6ec 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -182,29 +182,11 @@ namespace MWMechanics } } - float bestArrowRating = 0; MWWorld::Ptr bestArrow; - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Arrow); - if (rating > bestArrowRating) - { - bestArrowRating = rating; - bestArrow = *it; - } - } + float bestArrowRating = rateAmmo(actor, enemy, bestArrow, ESM::Weapon::Arrow); - float bestBoltRating = 0; MWWorld::Ptr bestBolt; - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Bolt); - if (rating > bestBoltRating) - { - bestBoltRating = rating; - bestBolt = *it; - } - } + float bestBoltRating = rateAmmo(actor, enemy, bestBolt, ESM::Weapon::Bolt); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { @@ -277,25 +259,9 @@ namespace MWMechanics } } - float bestArrowRating = 0; - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Arrow); - if (rating > bestArrowRating) - { - bestArrowRating = rating; - } - } + float bestArrowRating = rateAmmo(actor, enemy, ESM::Weapon::Arrow); - float bestBoltRating = 0; - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - float rating = rateWeapon(*it, actor, enemy, ESM::Weapon::Bolt); - if (rating > bestBoltRating) - { - bestBoltRating = rating; - } - } + float bestBoltRating = rateAmmo(actor, enemy, ESM::Weapon::Bolt); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 1f8dfd7c4..fd6dd7d0c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2531,7 +2531,7 @@ float CharacterController::getAttackStrength() const return mAttackStrength; } -void CharacterController::setActive(bool active) +void CharacterController::setActive(int active) { mAnimation->setActive(active); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 016d88289..cab51b82f 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -292,7 +292,7 @@ public: float getAttackStrength() const; /// @see Animation::setActive - void setActive(bool active); + void setActive(int active); /// Make this character turn its head towards \a target. To turn off head tracking, pass an empty Ptr. void setHeadTrackTarget(const MWWorld::ConstPtr& target); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 3a730fbeb..7aef54007 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -1,4 +1,5 @@ #include "spellpriority.hpp" +#include "weaponpriority.hpp" #include #include @@ -335,6 +336,12 @@ namespace MWMechanics return 0.f; break; + case ESM::MagicEffect::BoundLongbow: + // AI should not summon the bow if there is no suitable ammo. + if (rateAmmo(actor, enemy, ESM::Weapon::Arrow) <= 0.f) + return 0.f; + break; + case ESM::MagicEffect::RestoreHealth: case ESM::MagicEffect::RestoreMagicka: case ESM::MagicEffect::RestoreFatigue: diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index d06e73c93..5a0bd9c15 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -8,6 +8,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" #include "npcstats.hpp" #include "combat.hpp" @@ -111,6 +112,33 @@ namespace MWMechanics return rating + bonus; } + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType) + { + float bestAmmoRating = 0.f; + if (!actor.getClass().hasInventoryStore(actor)) + return bestAmmoRating; + + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + float rating = rateWeapon(*it, actor, enemy, ammoType); + if (rating > bestAmmoRating) + { + bestAmmoRating = rating; + bestAmmo = *it; + } + } + + return bestAmmoRating; + } + + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, ESM::Weapon::Type ammoType) + { + MWWorld::Ptr emptyPtr; + return rateAmmo(actor, enemy, emptyPtr, ammoType); + } + float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); diff --git a/apps/openmw/mwmechanics/weaponpriority.hpp b/apps/openmw/mwmechanics/weaponpriority.hpp index 90a767c4a..f5c9a1159 100644 --- a/apps/openmw/mwmechanics/weaponpriority.hpp +++ b/apps/openmw/mwmechanics/weaponpriority.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_WEAPON_PRIORITY_H #define OPENMW_WEAPON_PRIORITY_H +#include + #include "../mwworld/ptr.hpp" namespace MWMechanics @@ -8,6 +10,9 @@ namespace MWMechanics float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type=-1, float arrowRating=0.f, float boltRating=0.f); + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType); + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, ESM::Weapon::Type ammoType); + float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); } diff --git a/apps/openmw/mwmp/LocalPlayer.hpp b/apps/openmw/mwmp/LocalPlayer.hpp index 4f0f750b4..3584e49c8 100644 --- a/apps/openmw/mwmp/LocalPlayer.hpp +++ b/apps/openmw/mwmp/LocalPlayer.hpp @@ -90,8 +90,8 @@ namespace mwmp void clearCellStates(); void clearCurrentContainer(); - void storeCurrentContainer(const MWWorld::Ptr& container); void storeCellState(const ESM::Cell& cell, mwmp::CellState::Type stateType); + void storeCurrentContainer(const MWWorld::Ptr& container); void playAnimation(); void playSpeech(); diff --git a/apps/openmw/mwmp/WorldEvent.cpp b/apps/openmw/mwmp/WorldEvent.cpp index 513aa081e..40fd7b9a6 100644 --- a/apps/openmw/mwmp/WorldEvent.cpp +++ b/apps/openmw/mwmp/WorldEvent.cpp @@ -150,6 +150,8 @@ void WorldEvent::editContainers(MWWorld::CellStore* cellStore) void WorldEvent::placeObjects(MWWorld::CellStore* cellStore) { + MWBase::World *world = MWBase::Environment::get().getWorld(); + for (const auto &worldObject : worldObjects) { LOG_APPEND(Log::LOG_VERBOSE, "- cellRef: %s, %i, %i, count: %i, charge: %i, enchantmentCharge: %i", worldObject.refId.c_str(), @@ -164,7 +166,7 @@ void WorldEvent::placeObjects(MWWorld::CellStore* cellStore) // Only create this object if it doesn't already exist if (!ptrFound) { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), worldObject.refId, 1); + MWWorld::ManualRef ref(world->getStore(), worldObject.refId, 1); MWWorld::Ptr newPtr = ref.getPtr(); if (worldObject.count > 1) @@ -177,10 +179,13 @@ void WorldEvent::placeObjects(MWWorld::CellStore* cellStore) newPtr.getCellRef().setEnchantmentCharge(worldObject.enchantmentCharge); newPtr.getCellRef().setGoldValue(worldObject.goldValue); - newPtr = MWBase::Environment::get().getWorld()->placeObject(newPtr, cellStore, worldObject.position); + newPtr = world->placeObject(newPtr, cellStore, worldObject.position); // Because gold automatically gets replaced with a new object, make sure we set the mpNum at the end newPtr.getCellRef().setMpNum(worldObject.mpNum); + + if (guid == Main::get().getLocalPlayer()->guid && worldObject.droppedByPlayer) + world->PCDropped(newPtr); } else LOG_APPEND(Log::LOG_VERBOSE, "-- Object already existed!"); @@ -600,7 +605,7 @@ void WorldEvent::playVideo() } } -void WorldEvent::addObjectPlace(const MWWorld::Ptr& ptr) +void WorldEvent::addObjectPlace(const MWWorld::Ptr& ptr, bool droppedByPlayer) { if (ptr.getCellRef().getRefId().find("$dynamic") != string::npos) { @@ -616,6 +621,7 @@ void WorldEvent::addObjectPlace(const MWWorld::Ptr& ptr) worldObject.mpNum = 0; worldObject.charge = ptr.getCellRef().getCharge(); worldObject.enchantmentCharge = ptr.getCellRef().getEnchantmentCharge(); + worldObject.droppedByPlayer = droppedByPlayer; // Make sure we send the RefData position instead of the CellRef one, because that's what // we actually see on this client diff --git a/apps/openmw/mwmp/WorldEvent.hpp b/apps/openmw/mwmp/WorldEvent.hpp index c693c928a..1d7be2b36 100644 --- a/apps/openmw/mwmp/WorldEvent.hpp +++ b/apps/openmw/mwmp/WorldEvent.hpp @@ -40,7 +40,7 @@ namespace mwmp void playMusic(); void playVideo(); - void addObjectPlace(const MWWorld::Ptr& ptr); + void addObjectPlace(const MWWorld::Ptr& ptr, bool droppedByPlayer = false); void addObjectSpawn(const MWWorld::Ptr& ptr); void addObjectSpawn(const MWWorld::Ptr& ptr, const MWWorld::Ptr& master); void addObjectDelete(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 16ce9b436..1bd839ead 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -486,10 +486,10 @@ namespace MWRender return mPtr; } - void Animation::setActive(bool active) + void Animation::setActive(int active) { if (mSkeleton) - mSkeleton->setActive(active); + mSkeleton->setActive(static_cast(active)); } void Animation::updatePtr(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 8ac78babc..cff98a4b7 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -347,7 +347,8 @@ public: /// Set active flag on the object skeleton, if one exists. /// @see SceneUtil::Skeleton::setActive - void setActive(bool active); + /// 0 = Inactive, 1 = Active in place, 2 = Active + void setActive(int active); osg::Group* getOrCreateObjectRoot(); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index c4dffb7a4..dbfee05df 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -272,6 +272,12 @@ public: void setWaterLevel(float waterLevel) { + const float refractionScale = std::min(1.0f,std::max(0.0f, + Settings::Manager::getFloat("refraction scale", "Water"))); + + setViewMatrix(osg::Matrix::scale(1,1,refractionScale) * + osg::Matrix::translate(0,0,(1.0 - refractionScale) * waterLevel)); + mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0,0,-1), osg::Vec3d(0,0, waterLevel))); } diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 18f05b403..09bfeb043 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -545,7 +545,7 @@ namespace MWScript const MWWorld::Ptr ref = MWBase::Environment::get().getWorld()->getPtr(name, false); // If the objects are in different worldspaces, return a large value (just like vanilla) - if (ref.getCell()->getCell()->getCellId().mWorldspace != ref2.getCell()->getCell()->getCellId().mWorldspace) + if (!ref.isInCell() || !ref2.isInCell() || ref.getCell()->getCell()->getCellId().mWorldspace != ref2.getCell()->getCell()->getCellId().mWorldspace) return std::numeric_limits::max(); double diff[3]; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index d976d5773..ea0531fea 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -60,8 +60,9 @@ namespace MWScript Send an ID_OBJECT_SCALE every time an object's scale is changed through a script */ - if (ptr.isInCell()) + if (ptr.isInCell() && (ptr.getCellRef().getScale() != scale)) { + mwmp::WorldEvent *worldEvent = mwmp::Main::get().getNetworking()->getWorldEvent(); worldEvent->reset(); worldEvent->addObjectScale(ptr, scale); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index e96c696bf..56c82a569 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -243,6 +243,17 @@ namespace MWWorld std::cout << "Unloading cell\n"; ListAndResetObjectsVisitor visitor; + /* + Start of tes3mp addition + + Set a const pointer to the iterator's ESM::Cell here, because + (*iter)->getCell() can become invalid later down + */ + const ESM::Cell* cell = (*iter)->getCell(); + /* + End of tes3mp addition + */ + (*iter)->forEach(visitor); for (std::vector::const_iterator iter2 (visitor.mObjects.begin()); iter2!=visitor.mObjects.end(); ++iter2) @@ -261,16 +272,6 @@ namespace MWWorld mPhysics->removeHeightField ((*iter)->getCell()->getGridX(), (*iter)->getCell()->getGridY()); } - /* - Start of tes3mp addition - - Store a cell unload for the LocalPlayer - */ - mwmp::Main::get().getLocalPlayer()->storeCellState(*(*iter)->getCell(), mwmp::CellState::Type::Unload); - /* - End of tes3mp addition - */ - MWBase::Environment::get().getMechanicsManager()->drop (*iter); mRendering.removeCell(*iter); @@ -280,6 +281,16 @@ namespace MWWorld MWBase::Environment::get().getSoundManager()->stopSound (*iter); mActiveCells.erase(*iter); + + /* + Start of tes3mp addition + + Store a cell unload for the LocalPlayer + */ + mwmp::Main::get().getLocalPlayer()->storeCellState(*cell, mwmp::CellState::UNLOAD); + /* + End of tes3mp addition + */ } void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 10d7ebec0..640d90e23 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -470,6 +470,7 @@ namespace MWWorld gmst["fNPCHealthBarFade"] = ESM::Variant(1.f); gmst["fFleeDistance"] = ESM::Variant(3000.f); gmst["sMaxSale"] = ESM::Variant("Max Sale"); + gmst["sAnd"] = ESM::Variant("and"); // Werewolf (BM) gmst["fWereWolfRunMult"] = ESM::Variant(1.3f); @@ -1859,19 +1860,26 @@ namespace MWWorld void World::updateWindowManager () { - // inform the GUI about focused object - MWWorld::Ptr object = getFacedObject (); - - // retrieve object dimensions so we know where to place the floating label - if (!object.isEmpty ()) + try { - osg::Vec4f screenBounds = mRendering->getScreenBounds(object); + // inform the GUI about focused object + MWWorld::Ptr object = getFacedObject (); - MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords( - screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w()); - } + // retrieve object dimensions so we know where to place the floating label + if (!object.isEmpty ()) + { + osg::Vec4f screenBounds = mRendering->getScreenBounds(object); - MWBase::Environment::get().getWindowManager()->setFocusObject(object); + MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords( + screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w()); + } + + MWBase::Environment::get().getWindowManager()->setFocusObject(object); + } + catch (std::exception& e) + { + std::cerr << "Error updating window manager: " << e.what() << std::endl; + } } MWWorld::Ptr World::getFacedObject(float maxDistance, bool ignorePlayer) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index f3728a29d..229ad2da8 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -136,11 +136,21 @@ namespace MWWorld MWWorld::Ptr getFacedObject(float maxDistance, bool ignorePlayer=true); + /* + Start of tes3mp change (major) + + This has been turned into a public method so it can be used in + multiplayer's different approach to placing items + */ + void PCDropped(const Ptr& item); + /* + End of tes3mp change (major) + */ + public: // FIXME void removeContainerScripts(const Ptr& reference) override; private: void addContainerScripts(const Ptr& reference, CellStore* cell); - void PCDropped (const Ptr& item); void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index 3a179a2cb..f55794cca 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -20,7 +20,7 @@ Wizard::InstallationTargetPage::InstallationTargetPage(QWidget *parent, const Fi void Wizard::InstallationTargetPage::initializePage() { QString path(QFile::decodeName(mCfgMgr.getUserDataPath().string().c_str())); - path.append(QDir::separator() + QLatin1String("data")); + path.append(QDir::separator() + QLatin1String("basedata")); QDir dir(path); targetLineEdit->setText(QDir::toNativeSeparators(dir.absolutePath())); diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 8711d92fe..52a9a63f1 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -63,6 +63,7 @@ namespace Compiler if (mState==BeginState && keyword==Scanner::K_begin) { mState = NameState; + scanner.enableTolerantNames(); /// \todo disable return true; } diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index c2ec13b01..bb0fb9374 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -27,6 +27,7 @@ namespace Compiler if (c=='\n') { mStrictKeywords = false; + mTolerantNames = false; mLoc.mColumn = 0; ++mLoc.mLine; mLoc.mLiteral.clear(); @@ -363,7 +364,7 @@ namespace Compiler } else if (!(c=='"' && name.empty())) { - if (!isStringCharacter (c)) + if (!isStringCharacter (c) && !(mTolerantNames && (c=='.' || c=='-'))) { putback (c); break; @@ -577,7 +578,7 @@ namespace Compiler const Extensions *extensions) : mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions), mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0), - mStrictKeywords (false) + mStrictKeywords (false), mTolerantNames (false) { } @@ -634,4 +635,9 @@ namespace Compiler { mStrictKeywords = true; } + + void Scanner::enableTolerantNames() + { + mTolerantNames = true; + } } diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 270782c74..49fbaa96a 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -38,6 +38,7 @@ namespace Compiler std::string mPutbackName; TokenLoc mPutbackLoc; bool mStrictKeywords; + bool mTolerantNames; public: @@ -129,6 +130,11 @@ namespace Compiler /// /// \attention This mode lasts only until the next newline is reached. void enableStrictKeywords(); + + /// Continue parsing a name when hitting a '.' or a '-' + /// + /// \attention This mode lasts only until the next newline is reached. + void enableTolerantNames(); }; } diff --git a/components/esm/loadtes3.hpp b/components/esm/loadtes3.hpp index f2dde4d1f..896936cf5 100644 --- a/components/esm/loadtes3.hpp +++ b/components/esm/loadtes3.hpp @@ -13,24 +13,37 @@ namespace ESM #pragma pack(push) #pragma pack(1) + struct Data + { + /* File format version. This is actually a float, the supported + versions are 1.2 and 1.3. These correspond to: + 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 + */ + unsigned int version; + int type; // 0=esp, 1=esm, 32=ess (unused) + NAME32 author; // Author's name + NAME256 desc; // File description + int records; // Number of records + }; + + struct GMDT + { + float mCurrentHealth; + float mMaximumHealth; + float mHour; + unsigned char unknown1[12]; + NAME64 mCurrentCell; + unsigned char unknown2[4]; + NAME32 mPlayerName; + }; + +#pragma pack(pop) + /// \brief File header record struct Header { static const int CurrentFormat = 0; // most recent known format - struct Data - { - /* File format version. This is actually a float, the supported - versions are 1.2 and 1.3. These correspond to: - 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 - */ - unsigned int version; - int type; // 0=esp, 1=esm, 32=ess (unused) - NAME32 author; // Author's name - NAME256 desc; // File description - int records; // Number of records - }; - // Defines another files (esm or esp) that this file depends upon. struct MasterData { @@ -39,16 +52,6 @@ namespace ESM int index; // Position of the parent file in the global list of loaded files }; - struct GMDT - { - float mCurrentHealth; - float mMaximumHealth; - float mHour; - unsigned char unknown1[12]; - NAME64 mCurrentCell; - unsigned char unknown2[4]; - NAME32 mPlayerName; - }; GMDT mGameData; // Used in .ess savegames only std::vector mSCRD; // Used in .ess savegames only, unknown std::vector mSCRS; // Used in .ess savegames only, screenshot @@ -62,7 +65,6 @@ namespace ESM void load (ESMReader &esm); void save (ESMWriter &esm); }; -#pragma pack(pop) } diff --git a/components/openmw-mp/Base/BaseEvent.hpp b/components/openmw-mp/Base/BaseEvent.hpp index 1174d577a..a4d580108 100644 --- a/components/openmw-mp/Base/BaseEvent.hpp +++ b/components/openmw-mp/Base/BaseEvent.hpp @@ -51,6 +51,7 @@ namespace mwmp std::string varName; bool isDisarmed; + bool droppedByPlayer; Target master; bool hasMaster; diff --git a/components/openmw-mp/Packets/World/PacketObjectPlace.cpp b/components/openmw-mp/Packets/World/PacketObjectPlace.cpp index 031913074..17657dfb6 100644 --- a/components/openmw-mp/Packets/World/PacketObjectPlace.cpp +++ b/components/openmw-mp/Packets/World/PacketObjectPlace.cpp @@ -17,4 +17,5 @@ void PacketObjectPlace::Object(WorldObject &worldObject, bool send) RW(worldObject.enchantmentCharge, send); RW(worldObject.goldValue, send); RW(worldObject.position, send); + RW(worldObject.droppedByPlayer, send); } diff --git a/components/sceneutil/skeleton.cpp b/components/sceneutil/skeleton.cpp index 116edfdb4..94ae1f234 100644 --- a/components/sceneutil/skeleton.cpp +++ b/components/sceneutil/skeleton.cpp @@ -36,8 +36,9 @@ private: Skeleton::Skeleton() : mBoneCacheInit(false) , mNeedToUpdateBoneMatrices(true) - , mActive(true) + , mActive(Active) , mLastFrameNumber(0) + , mLastCullFrameNumber(0) { } @@ -48,6 +49,7 @@ Skeleton::Skeleton(const Skeleton ©, const osg::CopyOp ©op) , mNeedToUpdateBoneMatrices(true) , mActive(copy.mActive) , mLastFrameNumber(0) + , mLastCullFrameNumber(0) { } @@ -123,14 +125,14 @@ void Skeleton::updateBoneMatrices(unsigned int traversalNumber) } } -void Skeleton::setActive(bool active) +void Skeleton::setActive(ActiveType active) { mActive = active; } bool Skeleton::getActive() const { - return mActive; + return mActive != Inactive; } void Skeleton::markDirty() @@ -142,8 +144,16 @@ void Skeleton::markDirty() void Skeleton::traverse(osg::NodeVisitor& nv) { - if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR && mLastFrameNumber != 0) - return; + if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) + { + if (mActive == Inactive && mLastFrameNumber != 0) + return; + if (mActive == SemiActive && mLastFrameNumber != 0 && mLastCullFrameNumber+3 <= nv.getTraversalNumber()) + return; + } + else if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + mLastCullFrameNumber = nv.getTraversalNumber(); + osg::Group::traverse(nv); } diff --git a/components/sceneutil/skeleton.hpp b/components/sceneutil/skeleton.hpp index 245e3522c..1d8069f8e 100644 --- a/components/sceneutil/skeleton.hpp +++ b/components/sceneutil/skeleton.hpp @@ -47,9 +47,16 @@ namespace SceneUtil /// Request an update of bone matrices. May be a no-op if already updated in this frame. void updateBoneMatrices(unsigned int traversalNumber); + enum ActiveType + { + Inactive=0, + SemiActive, /// Like Active, but don't bother with Update (including new bounding box) if we're off-screen + Active + }; + /// Set the skinning active flag. Inactive skeletons will not have their child rigs updated. /// You should set this flag to false if you know that bones are not currently moving. - void setActive(bool active); + void setActive(ActiveType active); bool getActive() const; @@ -71,9 +78,10 @@ namespace SceneUtil bool mNeedToUpdateBoneMatrices; - bool mActive; + ActiveType mActive; unsigned int mLastFrameNumber; + unsigned int mLastCullFrameNumber; }; } diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 411fb3694..65aa2106f 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -16,7 +16,7 @@ #include "imagetosurface.hpp" -#ifdef OSG_LIBRARY_STATIC +#if defined(OSG_LIBRARY_STATIC) && !defined(ANDROID) // Sets the default windowing system interface according to the OS. // Necessary for OpenSceneGraph to do some things, like decompression. USE_GRAPHICSWINDOW() @@ -203,24 +203,21 @@ namespace SDLUtil void SDLCursorManager::cursorChanged(const std::string& name) { mCurrentCursor = name; - - CursorMap::const_iterator curs_iter = mCursorMap.find(name); - - if(curs_iter != mCursorMap.end()) - { - //we have this cursor - _setGUICursor(name); - } + _setGUICursor(name); } void SDLCursorManager::_setGUICursor(const std::string &name) { - SDL_SetCursor(mCursorMap.find(name)->second); + auto it = mCursorMap.find(name); + if (it != mCursorMap.end()) + SDL_SetCursor(it->second); } void SDLCursorManager::createCursor(const std::string& name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y) { +#ifndef ANDROID _createCursorFromResource(name, rotDegrees, image, hotspot_x, hotspot_y); +#endif } void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y) diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index c2cf2536e..de82ca850 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -92,9 +92,18 @@ void GraphicsWindowSDL2::init() SDL_GLContext oldCtx = SDL_GL_GetCurrentContext(); #if defined(OPENGL_ES) || defined(ANDROID) - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + int major = 1; + int minor = 1; + char *ver = getenv("OPENMW_GLES_VERSION"); + + if (ver && strcmp(ver, "2") == 0) { + major = 2; + minor = 0; + } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor); #endif mContext = SDL_GL_CreateContext(mWindow); diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index ad5150a44..9738e2a17 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -1,5 +1,7 @@ #include "filesystemarchive.hpp" +#include + #include namespace VFS @@ -38,7 +40,8 @@ namespace VFS std::transform(proper.begin() + prefix, proper.end(), std::back_inserter(searchable), normalize_function); - mIndex.insert (std::make_pair (searchable, file)); + if (!mIndex.insert (std::make_pair (searchable, file)).second) + std::cerr << "Warning: found duplicate file for '" << proper << "', please check your file system for two files with the same name in different cases." << std::endl; } mBuiltIndex = true; diff --git a/components/vfs/registerarchives.cpp b/components/vfs/registerarchives.cpp index b361e3f42..a22e785cd 100644 --- a/components/vfs/registerarchives.cpp +++ b/components/vfs/registerarchives.cpp @@ -1,5 +1,6 @@ #include "registerarchives.hpp" +#include #include #include @@ -33,12 +34,20 @@ namespace VFS } if (useLooseFiles) + { + std::set seen; for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) { - std::cout << "Adding data directory " << iter->string() << std::endl; - // Last data dir has the highest priority - vfs->addArchive(new FileSystemArchive(iter->string())); + if (seen.insert(*iter).second) + { + std::cout << "Adding data directory " << iter->string() << std::endl; + // Last data dir has the highest priority + vfs->addArchive(new FileSystemArchive(iter->string())); + } + else + std::cerr << "Ignoring duplicate data directory " << iter->string() << std::endl; } + } vfs->buildIndex(); } diff --git a/docs/source/manuals/openmw-cs/tour.rst b/docs/source/manuals/openmw-cs/tour.rst index 38915c95b..645c18453 100644 --- a/docs/source/manuals/openmw-cs/tour.rst +++ b/docs/source/manuals/openmw-cs/tour.rst @@ -211,15 +211,11 @@ Adding the ring to the game's world Now that we have defined the ring it is time add it to the game world so the player can find it legitimately. We will add the ring to a merchant, place it -in a chest and put it somewhere in plain sight. To this end we will have to +in a chest, and put it somewhere in plain sight. To this end we will have to actually modify the contents of the game. - -Subsection to come... -===================== - -Adding to an npc -**************** +Adding to an NPC +================ The simplest way is probably to add it to the inventory of a shopkeeper. An obvious candidate is Arrille in Seyda Neen - he's quick to find in a new game @@ -258,6 +254,79 @@ to give the ring to the player - the same as used earlier in the console .. figure:: _static/images/chapter-1/Ring_to_Fargoth_2.png :alt: Editing Fargoth to give ring to player +Placing in a chest +================== + +For this example we will use the small chest intended for lockpick practice, +located in the Census and Excise Office in Seyda Neen. + +First we need the ID of the chest - this can be obtained either by clicking on it in the console +in the game, or by applying a similar process in the CS - + +World/Cells + +Select "Seyda Neen, Census and Excise Office" + +Right-click and select "View" + +Use mouse wheel to zoom in/out, and mouse plus WASD keys to navigate + +Click on the small chest + +Either way, you should find the ID, which is "chest_small_02_lockprac". + +Open the Objects table (World/Objects) and scroll down to find this item. + +Alternatively use the Edit/Search facility, selecting ID rather than text, +enter "lockprac" (without the quotes) into the search box, press "Search", +which should return two rows, then select the "Container" one rather than the "Instance" + +Right-click and "Edit Record". + +Right-click the "Content" section and select "Add a row" + +Set the Item ID of the new row to be your new ring - simplest way is probably to open the Objects +table if it's not already open, sort on the "Modified" column which should bring the ring, +with its status of "Added" to the top, then drag and drop to the chest row. + +Increase the Count to 1. + +Save the addon, then test to ensure it works - e.g. start a new game and lockpick the chest. + +Placing in plain sight +===================== + +Let's hide the Ring of Night vision in the cabin of the [Ancient Shipwreck] +(http://en.uesp.net/wiki/Morrowind:Ancient_Shipwreck), a derelict vessel +southeast of Dagon Fel. Open the list of Cells (*World* → *Cells*) and find +"Ancient Shipwreck, Cabin". + +This will open a visualization of the cabin. You can navigate around the scene +just like you would when playing Morrowind. Use the WASD keys to move forward, +backwards, and sideways. Click and drag with the left mouse button to change the +direction you are looking. Navigate to the table in the cabin. + +If you've closed the Objects table, reopen it via *World* → *Objects*. Navigate +to your Ring of Night Vision (you can find it easily if you sort by the "Modified" +column). Drag the ring from the Objects table onto the table in the Cell view. + +Now let's move the ring to the precise location we want. Hover over the ring and +click the middle mouse button. If you don't have a middle mouse button, you can +select an alternative command by going to *Edit* → *Preferences…* (Windows, Linux) +or *OpenMW* → *Preferences…* (macOS). Go to the Key Bindings section and choose +"Scene" from the dropdown menu. Then click on the button for "Primary Select" and +choose an alternative binding. + +After you have switched to movement mode, you will see several arrows. Clicking +and dragging them with the right mouse button will allow you to move the object +in the direction you want. + +If you'd like an easy way to test this, you can start OpenMW with the [game +arguments](https://wiki.openmw.org/index.php?title=Testing) +`--start="Ancient Shipwreck, Cabin" --skip-menu`. This will place you right in +the cell and allow you to pick up and equip the ring in order to check that it +works. + Navigation in the CS ==================== This is probably a suitable place to start talking about how navigation differs from TESCS @@ -303,43 +372,4 @@ Launch OpenMW and in the launcher under *Data Files* check your addon, if it's n already checked. Load a game and make your way to Seyda Neen - or start a new game. Check whether Arrille has one (or more) for sale, and whether Fargoth give you one -when you return his healing ring. - -Placing in a chest -****************** - -For this example we will use the small chest intended for lockpick practice, -located in the Census and Excise Office in Seyda Neen. - -First we need the ID of the chest - this can be obtained either by clicking on it in the console -in the game, or by applying a similar process in the CS - - -World/Cells - -Select "Seyda Neen, Census and Excise Office" - -Right-click and select "View" - -Use mouse wheel to zoom in/out, and mouse plus WASD keys to navigate - -Click on the small chest - -Either way, you should find the ID, which is "chest_small_02_lockprac". - -Open the Objects table (World/Objects) and scroll down to find this item. - -Alternatively use the Edit/Search facility, selecting ID rather than text, -enter "lockprac" (without the quotes) into the search box, press "Search", -which should return two rows, then select the "Container" one rather than the "Instance" - -Right-click and "Edit Record". - -Right-click the "Content" section and select "Add a row" - -Set the Item ID of the new row to be your new ring - simplest way is probably to open the Objects -table if it's not already open, sort on the "Modified" column which should bring the ring, -with its status of "Added" to the top, then drag and drop to the chest row. - -Increase the Count to 1. - -Save the addon, then test to ensure it works - e.g. start a new game and lockpick the chest. +when you return his healing ring. \ No newline at end of file diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index a1c6c2068..bd0741da9 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -88,3 +88,20 @@ This setting will have no effect if the shader setting is false, or the 'small feature culling' (in the 'Camera' section) is disabled. This setting can only be configured by editing the settings configuration file. + +refraction scale +---------------- + +:Type: floating point +:Range: 0 to 1 +:Default: 1.0 + +Simulates light rays refracting when transitioning from air to water, which causes the space under water look scaled down +in height when viewed from above the water surface. Though adding realism, the setting can cause distortion which can +make for example aiming at enemies in water more challenging, so it is off by default (i.e. set to 1.0). To get a realistic +look of real-life water, set the value to 0.75. + +This setting only applies if water shader is on and refractions are enabled. Note that if refractions are enabled and this +setting if off, there will still be small refractions caused by the water waves, which however do not cause such significant +distortion. + diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index 9fd209d56..35bfca4aa 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -315,9 +315,9 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) vp->pts = pts; vp->data.resize((*this->video_st)->codec->width * (*this->video_st)->codec->height * 4); - uint8_t *dst = &vp->data[0]; + uint8_t *dst[4] = { &vp->data[0], nullptr, nullptr, nullptr }; sws_scale(this->sws_context, pFrame->data, pFrame->linesize, - 0, (*this->video_st)->codec->height, &dst, this->rgbaFrame->linesize); + 0, (*this->video_st)->codec->height, dst, this->rgbaFrame->linesize); // now we inform our display thread that we have a pic ready this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_ARRAY_SIZE; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 253883402..c694d4db2 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -376,6 +376,9 @@ reflect actors = false # Overrides the value in '[Camera] small feature culling pixel size' specifically for water reflection/refraction textures. small feature culling pixel size = 20.0 +# By what factor water downscales objects. Only works with water shader and refractions on. +refraction scale = 1.0 + [Windows] # Location and sizes of windows as a fraction of the OpenMW window or