Merge pull request #438 from OpenMW/master

Add OpenMW commits up to 24 May 2018
pull/439/head
David Cernat 6 years ago committed by GitHub
commit 74c2a0b311
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,6 +3,7 @@
#include <iostream>
#include <sstream>
#include <components/misc/stringops.hpp>
#include <components/esm/esmreader.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
@ -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<std::pair<std::time_t, std::string> > contentFiles;
void MwIniImporter::dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector<std::string>& result)
{
auto iter = std::find_if(
source.begin(),
source.end(),
[&element](std::pair< std::string, std::vector<std::string> >& 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<std::string> MwIniImporter::dependencySort(MwIniImporter::dependencyList source)
{
std::vector<std::string> result;
while (!source.empty())
{
MwIniImporter::dependencySortStep(source.begin()->first, source, result);
}
return result;
}
std::vector<std::string>::iterator MwIniImporter::findString(std::vector<std::string>& 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<boost::filesystem::path>& output, std::vector<std::string> 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<std::pair<std::time_t, boost::filesystem::path>> contentFiles;
std::string baseGameFile("Game Files:GameFile");
std::string gameFile("");
std::time_t defaultTime = 0;
ToUTF8::Utf8Encoder encoder(mEncoding);
std::vector<boost::filesystem::path> 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<std::string>::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) {
for(std::vector<std::string>::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<std::string>() ) );
// 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<std::pair<std::time_t, std::string> >::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<std::string> 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;
}

@ -14,6 +14,7 @@ class MwIniImporter {
public:
typedef std::map<std::string, std::string> strmap;
typedef std::map<std::string, std::vector<std::string> > multistrmap;
typedef std::vector< std::pair< std::string, std::vector<std::string> > > 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<std::string> dependencySort(MwIniImporter::dependencyList source);
private:
static void dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector<std::string>& result);
static std::vector<std::string>::iterator findString(std::vector<std::string>& 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<boost::filesystem::path>& output, std::vector<std::string> 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

@ -5,6 +5,9 @@
#include <map>
#include <set>
#include <osg/ref_ptr>
#include <osg/Node>
#include <components/esm/cellid.hpp>
#include "../mwworld/ptr.hpp"
@ -434,6 +437,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;
@ -600,7 +604,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,
@ -642,6 +646,8 @@ namespace MWBase
/// Preload VFX associated with this effect list
virtual void preloadEffects(const ESM::EffectList* effectList) = 0;
virtual osg::ref_ptr<osg::Node> getInstance (const std::string& modelName) = 0;
};
}

@ -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<float>(npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified());
float b = 0.0f;

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

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

@ -6,8 +6,12 @@
#include <boost/format.hpp>
#include <osg/BoundingBox>
#include <osg/ComputeBoundsVisitor>
#include <components/misc/rng.hpp>
#include <components/settings/settings.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
/*
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<ESM::Spell>().find(spellid);
std::vector<std::string> addedEffects;
for (std::vector<ESM::ENAMstruct>::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<ESM::Static>().find (effect->mCasting);
else
castStatic = store.get<ESM::Static>().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<ESM::NPC>()->mBase;
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
if (!effect->mCasting.empty())
castStatic = store.get<ESM::Static>().find (effect->mCasting);
else
castStatic = store.get<ESM::Static>().find ("VFX_DefaultCast");
const ESM::Race *race =
esmStore.get<ESM::Race>().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<osg::Node> 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);

@ -18,7 +18,7 @@ namespace MWPhysics
Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape> 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;

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

@ -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<MWWorld::Ptr>(), 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<Object*>::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();

@ -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<SceneUtil::UnrefQueue> mUnrefQueue;
@ -231,6 +233,7 @@ namespace MWPhysics
osg::ref_ptr<osg::Group> mParentNode;
float mPhysicsDt;
float mIdleUpdateTimer;
PhysicsSystem (const PhysicsSystem&);
PhysicsSystem& operator= (const PhysicsSystem&);

@ -8,6 +8,8 @@
#include <osg/MatrixTransform>
#include <osg/BlendFunc>
#include <osg/Material>
#include <osg/ComputeBoundsVisitor>
#include <osg/PositionAttitudeTransform>
#include <osgParticle/ParticleSystem>
#include <osgParticle/ParticleProcessor>
@ -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<osg::PositionAttitudeTransform> trans = new osg::PositionAttitudeTransform;
trans->setScale(scale3f);
trans->addChild(node);
parentNode->removeChild(node);
parentNode->addChild(trans);
mEffects.push_back(params);
}

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

@ -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<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(model);
@ -40,7 +47,7 @@ void EffectManager::addEffect(const std::string &model, const std::string& textu
osg::ref_ptr<osg::PositionAttitudeTransform> 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);

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

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

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

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

@ -205,6 +205,8 @@ namespace MWRender
LandManager* getLandManager() const;
osg::ref_ptr<osg::Node> getInstance(const std::string& modelName);
private:
void updateProjectionMatrix();
void updateTextureFiltering();

@ -26,6 +26,7 @@
#include <components/esm/loadcrea.hpp>
#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);

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

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

@ -1409,8 +1409,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);
@ -1656,6 +1654,11 @@ namespace MWWorld
}
}
osg::ref_ptr<osg::Node> World::getInstance (const std::string& modelName)
{
return mRendering->getInstance(modelName);
}
const ESM::Potion *World::createRecord (const ESM::Potion& record)
{
return mStore.insert(record);
@ -1747,7 +1750,7 @@ namespace MWWorld
if (!paused)
doPhysics (duration);
updatePlayer(paused);
updatePlayer();
mPhysics->debugDraw();
@ -1763,7 +1766,7 @@ namespace MWWorld
}
}
void World::updatePlayer(bool paused)
void World::updatePlayer()
{
MWWorld::Ptr player = getPlayerPtr();
@ -1796,7 +1799,7 @@ namespace MWWorld
bool swimming = isSwimming(player);
static const float i1stPersonSneakDelta = getStore().get<ESM::GameSetting>().find("i1stPersonSneakDelta")->getFloat();
if(!paused && sneaking && !(swimming || inair))
if (sneaking && !(swimming || inair))
mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta);
else
mRendering->getCamera()->setSneakOffset(0.f);
@ -2283,6 +2286,11 @@ namespace MWWorld
return mPhysics->isOnGround(ptr);
}
bool World::isIdle(const MWWorld::Ptr &ptr) const
{
return mPhysics->isIdle(ptr);
}
void World::togglePOV()
{
mRendering->togglePOV();
@ -3539,9 +3547,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,

@ -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();
@ -533,6 +533,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;
@ -704,7 +705,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,
@ -744,6 +745,8 @@ namespace MWWorld
/// Preload VFX associated with this effect list
void preloadEffects(const ESM::EffectList* effectList) override;
osg::ref_ptr<osg::Node> getInstance (const std::string& modelName);
};
}

@ -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("<html><head/><body><p><b>Could not open or create %1 for writing</b></p> \
<p>Please make sure you have the right permissions \
and try again.</p></body></html>").arg(file.fileName()));
msgBox.exec();
return qApp->quit();
}
file.close();
}
// Construct the arguments to run the importer
QStringList arguments;

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

@ -233,7 +233,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour
QuadTreeWorld::~QuadTreeWorld()
{
ensureQuadTreeBuilt();
mViewDataMap->clear();
}

@ -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::
<name>
<name>(<arg1>)
<name>(<arg1>, <arg2>, ..., <argn>)
Where ``<name>`` is the name of the expression, such as ``string`` and the
``<arg>`` 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(<column>, <value>)``
The ``<value>`` is a regular expression pattern. The expressions evaluates
to ``true`` when the value of a record in ``<column>`` 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(<value>, (<lower>, <upper>))``
Match a value type, such as a number, with a range of possible values. The
argument ``<value>`` 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 <expression>``
Logically negates the result of an expression. If ``<expression>`` evaluates
to ``true`` the negation is ``false``, and if ``<expression>`` evaluates to
``false`` the negation is ``true``. Note that there are no parentheses
around the argument.
``or(<expr1>, <expr2>, ..., <exprN>)``
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(<expr1>, <expr2>, ..., <exprN>)``
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.

@ -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 theres 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
characters *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 players 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.

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

@ -21,4 +21,6 @@ few chapters to familiarise yourself with the new interface.
tour
files-and-directories
starting-dialog
tables
record-types
record-filters

Loading…
Cancel
Save