Merge pull request #374 from OpenMW/master while resolving conflicts

# Conflicts:
#	apps/openmw/CMakeLists.txt
0.6.3
David Cernat 6 years ago
commit 5d4b97645d

@ -192,9 +192,8 @@ if (ANDROID)
dl
z
${OPENSCENEGRAPH_LIBRARIES}
${OSG_PLUGINS}
freetype
jpeg
gif
png
)
endif (ANDROID)

@ -1,3 +1,4 @@
int stderr = 0; // Hack: fix linker error
#ifdef __ANDROID__
#include "SDL_main.h"
@ -17,6 +18,8 @@ void releaseArgv();
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();

@ -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);

@ -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<std::string> keywords { "sOk", "sYes" };
for(std::vector<MyGUI::Button*>::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)

@ -28,6 +28,8 @@ namespace MWGui
bool createInteractiveMessageBox (const std::string& message, const std::vector<std::string>& 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;

@ -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<WindowModal*>::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);

@ -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);

@ -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())

@ -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)
{

@ -2531,7 +2531,7 @@ float CharacterController::getAttackStrength() const
return mAttackStrength;
}
void CharacterController::setActive(bool active)
void CharacterController::setActive(int active)
{
mAnimation->setActive(active);
}

@ -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);

@ -1,4 +1,5 @@
#include "spellpriority.hpp"
#include "weaponpriority.hpp"
#include <components/esm/loadench.hpp>
#include <components/esm/loadmgef.hpp>
@ -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:

@ -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<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();

@ -1,6 +1,8 @@
#ifndef OPENMW_WEAPON_PRIORITY_H
#define OPENMW_WEAPON_PRIORITY_H
#include <components/esm/loadweap.hpp>
#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);
}

@ -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<SceneUtil::Skeleton::ActiveType>(active));
}
void Animation::updatePtr(const MWWorld::Ptr &ptr)

@ -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();

@ -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)));
}

@ -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<float>::max();
double diff[3];

@ -468,6 +468,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);
@ -1857,19 +1858,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)

@ -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()));

@ -63,6 +63,7 @@ namespace Compiler
if (mState==BeginState && keyword==Scanner::K_begin)
{
mState = NameState;
scanner.enableTolerantNames(); /// \todo disable
return true;
}

@ -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;
}
}

@ -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();
};
}

@ -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 &copy, const osg::CopyOp &copyop)
, 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);
}

@ -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;
};
}

@ -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()
@ -220,7 +220,9 @@ namespace SDLUtil
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)

@ -1,5 +1,7 @@
#include "filesystemarchive.hpp"
#include <iostream>
#include <boost/filesystem.hpp>
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;

@ -1,5 +1,6 @@
#include "registerarchives.hpp"
#include <set>
#include <iostream>
#include <sstream>
@ -33,12 +34,20 @@ namespace VFS
}
if (useLooseFiles)
{
std::set<boost::filesystem::path> 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();
}

@ -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.

@ -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.

@ -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

Loading…
Cancel
Save