Merge remote-tracking branch 'refs/remotes/upstream/master'

pull/1547/head
AnyOldName3 7 years ago
commit 759e6fb804

@ -6,4 +6,6 @@ DATE=`date +'%d%m%Y'`
SHORT_COMMIT=`git rev-parse --short ${TRAVIS_COMMIT}`
TARGET_FILENAME="OpenMW-${DATE}-${SHORT_COMMIT}.dmg"
curl --ssl --ftp-create-dirs -T *.dmg -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}${TARGET_FILENAME}"
if ! curl --ssl -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}" --silent | grep $SHORT_COMMIT > /dev/null; then
curl --ssl --ftp-create-dirs -T *.dmg -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}${TARGET_FILENAME}"
fi

@ -360,6 +360,8 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter")
endif()
elseif (MSVC)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} /Zi /bigobj")
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF /INCREMENTAL:NO")
# Enable link-time code generation globally for all linking
if (OPENMW_LTO_BUILD)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL")

@ -23,6 +23,7 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
// Expected values are (0, 1, 2, 3)
int showOwnedIndex = mEngineSettings.getInt("show owned", "Game");
@ -61,6 +62,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))

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

@ -81,14 +81,14 @@ opencs_units_noqt (view/world
opencs_units (view/widget
scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton
scenetooltoggle2 completerpopup coloreditor colorpickerpopup droplineedit
scenetooltoggle2 scenetooltexturebrush completerpopup coloreditor colorpickerpopup droplineedit
)
opencs_units (view/render
scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget
previewwidget editmode instancemode instanceselectionmode instancemovemode
orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller
cellwater
cellwater terraintexturemode
)
opencs_units_noqt (view/render

@ -222,7 +222,15 @@ void CSMPrefs::State::declare()
EnumValues insertOutsideVisibleCell;
insertOutsideVisibleCell.add (showAndInsert).add (dontInsert).add (insertAnyway);
declareCategory ("Scene Drops");
EnumValue createAndLandEdit ("Create cell and land, then edit");
EnumValue showAndLandEdit ("Show cell and edit");
EnumValue dontLandEdit ("Discard");
EnumValues landeditOutsideCell;
landeditOutsideCell.add (createAndLandEdit).add (dontLandEdit);
EnumValues landeditOutsideVisibleCell;
landeditOutsideVisibleCell.add (showAndLandEdit).add (dontLandEdit);
declareCategory ("3D Scene Editing");
declareInt ("distance", "Drop Distance", 50).
setTooltip ("If an instance drop can not be placed against another object at the "
"insert point, it will be placed by this distance from the insert point instead");
@ -230,6 +238,12 @@ void CSMPrefs::State::declare()
addValues (insertOutsideCell);
declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert).
addValues (insertOutsideVisibleCell);
declareEnum ("outside-landedit", "Handling land edit outside of cells", createAndLandEdit).
addValues (landeditOutsideCell);
declareEnum ("outside-visible-landedit", "Handling land edit outside of visible cells", showAndLandEdit).
addValues (landeditOutsideVisibleCell);
declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50).
setMin (1);
declareCategory ("Key Bindings");

@ -28,9 +28,13 @@ void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages)
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Class, class_.mId);
// test for empty name and description
// A class should have a name
if (class_.mName.empty())
messages.push_back (std::make_pair (id, class_.mId + " has an empty name"));
messages.push_back (std::make_pair (id, class_.mId + " doesn't have a name"));
// A playable class should have a description
if (class_.mData.mIsPlayable != 0 && class_.mDescription.empty())
messages.push_back (std::make_pair (id, class_.mId + " doesn't have a description and it's playable"));
// test for invalid attributes
for (int i=0; i<2; ++i)

@ -11,11 +11,6 @@ namespace CSMWorld
bool LandTextureTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const
{
int columnIndex = mSourceModel->findColumnIndex(Columns::ColumnId_Modification);
QModelIndex index = mSourceModel->index(sourceRow, columnIndex);
if (mSourceModel->data(index).toInt() != RecordBase::State_ModifiedOnly)
return false;
return IdTableProxyModel::filterAcceptsRow(sourceRow, sourceParent);
}
}

@ -551,7 +551,7 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event)
if (noCell)
{
std::string mode = CSMPrefs::get()["Scene Drops"]["outside-drop"].toString();
std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-drop"].toString();
// target cell does not exist
if (mode=="Discard")
@ -585,7 +585,7 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event)
{
// target cell exists, but is not shown
std::string mode =
CSMPrefs::get()["Scene Drops"]["outside-visible-drop"].toString();
CSMPrefs::get()["3D Scene Editing"]["outside-visible-drop"].toString();
if (mode=="Discard")
return;

@ -21,6 +21,7 @@
#include "mask.hpp"
#include "cameracontroller.hpp"
#include "cellarrow.hpp"
#include "terraintexturemode.hpp"
bool CSVRender::PagedWorldspaceWidget::adjustCells()
{
@ -136,7 +137,7 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons (
new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain shape editing"),
"terrain-shape");
tool->addButton (
new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain texture editing"),
new TerrainTextureMode (this, tool),
"terrain-texture");
tool->addButton (
new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"),

@ -0,0 +1,542 @@
#include "terraintexturemode.hpp"
#include <string>
#include <sstream>
#include <QWidget>
#include <QIcon>
#include <QEvent>
#include <QDropEvent>
#include <QDragEnterEvent>
#include <QDrag>
#include <components/esm/loadland.hpp>
#include "../widget/modebutton.hpp"
#include "../widget/scenetoolbar.hpp"
#include "../widget/scenetooltexturebrush.hpp"
#include "../../model/doc/document.hpp"
#include "../../model/prefs/state.hpp"
#include "../../model/world/columnbase.hpp"
#include "../../model/world/commandmacro.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/data.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/idtree.hpp"
#include "../../model/world/land.hpp"
#include "../../model/world/landtexture.hpp"
#include "../../model/world/resourcetable.hpp"
#include "../../model/world/tablemimedata.hpp"
#include "../../model/world/universalid.hpp"
#include "editmode.hpp"
#include "pagedworldspacewidget.hpp"
#include "mask.hpp"
#include "object.hpp" // Something small needed regarding pointers from here ()
#include "worldspacewidget.hpp"
CSVRender::TerrainTextureMode::TerrainTextureMode (WorldspaceWidget *worldspaceWidget, QWidget *parent)
: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-texture"}, Mask_Terrain | Mask_Reference, "Terrain texture editing", parent),
mBrushTexture("L0#0"),
mBrushSize(0),
mBrushShape(0),
mTextureBrushScenetool(0)
{
}
void CSVRender::TerrainTextureMode::activate(CSVWidget::SceneToolbar* toolbar)
{
if(!mTextureBrushScenetool)
{
mTextureBrushScenetool = new CSVWidget::SceneToolTextureBrush (toolbar, "scenetooltexturebrush", getWorldspaceWidget().getDocument());
connect(mTextureBrushScenetool, SIGNAL (clicked()), mTextureBrushScenetool, SLOT (activate()));
connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passBrushSize(int)), this, SLOT(setBrushSize(int)));
connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passBrushShape(int)), this, SLOT(setBrushShape(int)));
connect(mTextureBrushScenetool->mTextureBrushWindow->mSizeSliders->mBrushSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBrushSize(int)));
connect(mTextureBrushScenetool, SIGNAL(passTextureId(std::string)), this, SLOT(setBrushTexture(std::string)));
connect(mTextureBrushScenetool->mTextureBrushWindow, SIGNAL(passTextureId(std::string)), this, SLOT(setBrushTexture(std::string)));
connect(mTextureBrushScenetool, SIGNAL(passEvent(QDropEvent*)), this, SLOT(handleDropEvent(QDropEvent*)));
connect(this, SIGNAL(passBrushTexture(std::string)), mTextureBrushScenetool->mTextureBrushWindow, SLOT(setBrushTexture(std::string)));
connect(this, SIGNAL(passBrushTexture(std::string)), mTextureBrushScenetool, SLOT(updateBrushHistory(std::string)));
}
EditMode::activate(toolbar);
toolbar->addTool (mTextureBrushScenetool);
}
void CSVRender::TerrainTextureMode::deactivate(CSVWidget::SceneToolbar* toolbar)
{
if(mTextureBrushScenetool)
{
toolbar->removeTool (mTextureBrushScenetool);
delete mTextureBrushScenetool;
mTextureBrushScenetool = 0;
}
EditMode::deactivate(toolbar);
}
void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult& hit) // Apply changes here
{
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
mCellId = getWorldspaceWidget().getCellId (hit.worldPos);
QUndoStack& undoStack = document.getUndoStack();
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = document.getData().getLandTextures();
int index = landtexturesCollection.searchId(mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit == true)
{
undoStack.beginMacro ("Edit texture records");
if(allowLandTextureEditing(mCellId)==true)
{
undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId));
editTerrainTextureGrid(hit);
}
undoStack.endMacro();
}
}
void CSVRender::TerrainTextureMode::primarySelectPressed(const WorldspaceHitResult& hit)
{
}
void CSVRender::TerrainTextureMode::secondarySelectPressed(const WorldspaceHitResult& hit)
{
}
bool CSVRender::TerrainTextureMode::primaryEditStartDrag (const QPoint& pos)
{
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
mCellId = getWorldspaceWidget().getCellId (hit.worldPos);
QUndoStack& undoStack = document.getUndoStack();
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = document.getData().getLandTextures();
int index = landtexturesCollection.searchId(mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
undoStack.beginMacro ("Edit texture records");
if(allowLandTextureEditing(mCellId)==true && hit.hit == true)
{
undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId));
editTerrainTextureGrid(hit);
}
}
return true;
}
bool CSVRender::TerrainTextureMode::secondaryEditStartDrag (const QPoint& pos)
{
return false;
}
bool CSVRender::TerrainTextureMode::primarySelectStartDrag (const QPoint& pos)
{
return false;
}
bool CSVRender::TerrainTextureMode::secondarySelectStartDrag (const QPoint& pos)
{
return false;
}
void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor)
{
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = document.getData().getLandTextures();
int index = landtexturesCollection.searchId(mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit == true)
{
editTerrainTextureGrid(hit);
}
}
void CSVRender::TerrainTextureMode::dragCompleted(const QPoint& pos) {
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
QUndoStack& undoStack = document.getUndoStack();
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = document.getData().getLandTextures();
int index = landtexturesCollection.searchId(mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
undoStack.endMacro();
}
}
void CSVRender::TerrainTextureMode::dragAborted() {
}
void CSVRender::TerrainTextureMode::dragWheel (int diff, double speedFactor) {}
void CSVRender::TerrainTextureMode::handleDropEvent (QDropEvent *event) {
const CSMWorld::TableMimeData* mime = dynamic_cast<const CSMWorld::TableMimeData*> (event->mimeData());
if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped
return;
if (mime->holdsType (CSMWorld::UniversalId::Type_LandTexture))
{
const std::vector<CSMWorld::UniversalId> ids = mime->getData();
for (const CSMWorld::UniversalId& uid : ids)
{
mBrushTexture = uid.getId();
emit passBrushTexture(mBrushTexture);
}
}
if (mime->holdsType (CSMWorld::UniversalId::Type_Texture))
{
const std::vector<CSMWorld::UniversalId> ids = mime->getData();
for (const CSMWorld::UniversalId& uid : ids)
{
std::string textureFileName = uid.toString();
createTexture(textureFileName);
emit passBrushTexture(mBrushTexture);
}
}
}
void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitResult& hit)
{
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
mCellId = getWorldspaceWidget().getCellId (hit.worldPos);
if(allowLandTextureEditing(mCellId)==true) {}
std::pair<CSMWorld::CellCoordinates, bool> cellCoordinates_pair = CSMWorld::CellCoordinates::fromId (mCellId);
int cellX = cellCoordinates_pair.first.getX();
int cellY = cellCoordinates_pair.first.getY();
// The coordinates of hit in mCellId
int xHitInCell (float(((hit.worldPos.x() - (cellX* cellSize)) * landTextureSize / cellSize) - 0.5));
int yHitInCell (float(((hit.worldPos.y() - (cellY* cellSize)) * landTextureSize / cellSize) + 0.5));
if (xHitInCell < 0)
{
xHitInCell = xHitInCell + landTextureSize;
cellX = cellX - 1;
}
if (yHitInCell > 15)
{
yHitInCell = yHitInCell - landTextureSize;
cellY = cellY + 1;
}
mCellId = "#" + std::to_string(cellX) + " " + std::to_string(cellY);
if(allowLandTextureEditing(mCellId)==true) {}
std::string iteratedCellId;
int textureColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandTexturesIndex);
std::size_t hashlocation = mBrushTexture.find("#");
std::string mBrushTextureInt = mBrushTexture.substr (hashlocation+1);
int brushInt = stoi(mBrushTexture.substr (hashlocation+1))+1; // All indices are offset by +1
float rf = mBrushSize/2;
int r = (mBrushSize/2)+1;
float distance = 0;
if (mBrushShape == 0)
{
CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(mCellId, textureColumn)).value<CSMWorld::LandTexturesColumn::DataType>();
CSMWorld::LandTexturesColumn::DataType mNew(mPointer);
if(allowLandTextureEditing(mCellId)==true)
{
mNew[yHitInCell*landTextureSize+xHitInCell] = brushInt;
pushEditToCommand(mNew, document, landTable, mCellId);
}
}
if (mBrushShape == 1)
{
int upperLeftCellX = cellX - std::floor(r / landTextureSize);
int upperLeftCellY = cellY - std::floor(r / landTextureSize);
if (xHitInCell - (r % landTextureSize) < 0) upperLeftCellX--;
if (yHitInCell - (r % landTextureSize) < 0) upperLeftCellY--;
int lowerrightCellX = cellX + std::floor(r / landTextureSize);
int lowerrightCellY = cellY + std::floor(r / landTextureSize);
if (xHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellX++;
if (yHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellY++;
for(int i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++)
{
for(int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++)
{
iteratedCellId = "#" + std::to_string(i_cell) + " " + std::to_string(j_cell);
if(allowLandTextureEditing(iteratedCellId)==true)
{
CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value<CSMWorld::LandTexturesColumn::DataType>();
CSMWorld::LandTexturesColumn::DataType mNew(mPointer);
for(int i = 0; i < landTextureSize; i++)
{
for(int j = 0; j < landTextureSize; j++)
{
if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r)
{
mNew[j*landTextureSize+i] = brushInt;
}
else
{
int distanceX(0);
int distanceY(0);
if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i;
if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j;
if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize * abs(i_cell-cellX) + i;
if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j;
if (i_cell == cellX) distanceX = abs(i-xHitInCell);
if (j_cell == cellY) distanceY = abs(j-yHitInCell);
if (distanceX < r && distanceY < r) mNew[j*landTextureSize+i] = brushInt;
}
}
}
pushEditToCommand(mNew, document, landTable, iteratedCellId);
}
}
}
}
if (mBrushShape == 2)
{
int upperLeftCellX = cellX - std::floor(r / landTextureSize);
int upperLeftCellY = cellY - std::floor(r / landTextureSize);
if (xHitInCell - (r % landTextureSize) < 0) upperLeftCellX--;
if (yHitInCell - (r % landTextureSize) < 0) upperLeftCellY--;
int lowerrightCellX = cellX + std::floor(r / landTextureSize);
int lowerrightCellY = cellY + std::floor(r / landTextureSize);
if (xHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellX++;
if (yHitInCell + (r % landTextureSize) > landTextureSize - 1) lowerrightCellY++;
for(int i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++)
{
for(int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++)
{
iteratedCellId = "#" + std::to_string(i_cell) + " " + std::to_string(j_cell);
if(allowLandTextureEditing(iteratedCellId)==true)
{
CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value<CSMWorld::LandTexturesColumn::DataType>();
CSMWorld::LandTexturesColumn::DataType mNew(mPointer);
for(int i = 0; i < landTextureSize; i++)
{
for(int j = 0; j < landTextureSize; j++)
{
if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r)
{
int distanceX(0);
int distanceY(0);
if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i;
if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j;
if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize* abs(i_cell-cellX) + i;
if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j;
if (i_cell == cellX) distanceX = abs(i-xHitInCell);
if (j_cell == cellY) distanceY = abs(j-yHitInCell);
distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2)));
if (distance < rf) mNew[j*landTextureSize+i] = brushInt;
}
else
{
int distanceX(0);
int distanceY(0);
if (i_cell < cellX) distanceX = xHitInCell + landTextureSize * abs(i_cell-cellX) - i;
if (j_cell < cellY) distanceY = yHitInCell + landTextureSize * abs(j_cell-cellY) - j;
if (i_cell > cellX) distanceX = -xHitInCell + landTextureSize * abs(i_cell-cellX) + i;
if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j;
if (i_cell == cellX) distanceX = abs(i-xHitInCell);
if (j_cell == cellY) distanceY = abs(j-yHitInCell);
distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2)));
if (distance < rf) mNew[j*landTextureSize+i] = brushInt;
}
}
}
pushEditToCommand(mNew, document, landTable, iteratedCellId);
}
}
}
}
if (mBrushShape == 3)
{
// Not implemented
}
}
void CSVRender::TerrainTextureMode::pushEditToCommand(CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, std::string cellId)
{
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
QVariant changedLand;
changedLand.setValue(newLandGrid);
QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandTexturesIndex)));
QUndoStack& undoStack = document.getUndoStack();
undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId));
undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand));
}
void CSVRender::TerrainTextureMode::createTexture(std::string textureFileName)
{
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
QUndoStack& undoStack = document.getUndoStack();
std::string newId;
int counter=0;
bool freeIndexFound = false;
do {
const size_t maxCounter = std::numeric_limits<uint16_t>::max() - 1;
try
{
newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter);
if (ltexTable.getRecord(newId).isDeleted() == 0) counter = (counter + 1) % maxCounter;
} catch (const std::exception& e)
{
newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter);
freeIndexFound = true;
}
} while (freeIndexFound == false);
std::size_t idlocation = textureFileName.find("Texture: ");
textureFileName = textureFileName.substr (idlocation + 9);
QVariant textureNameVariant;
QVariant textureFileNameVariant;
textureFileNameVariant.setValue(QString::fromStdString(textureFileName));
undoStack.beginMacro ("Add land texture record");
undoStack.push (new CSMWorld::CreateCommand (ltexTable, newId));
QModelIndex index(ltexTable.getModelIndex (newId, ltexTable.findColumnIndex (CSMWorld::Columns::ColumnId_Texture)));
undoStack.push (new CSMWorld::ModifyCommand(ltexTable, index, textureFileNameVariant));
undoStack.endMacro();
mBrushTexture = newId;
}
bool CSVRender::TerrainTextureMode::allowLandTextureEditing(std::string cellId)
{
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
CSMWorld::IdTree& cellTable = dynamic_cast<CSMWorld::IdTree&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells));
bool noCell = document.getData().getCells().searchId (cellId)==-1;
bool noLand = document.getData().getLand().searchId (cellId)==-1;
if (noCell)
{
std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString();
// target cell does not exist
if (mode=="Discard")
return false;
if (mode=="Create cell and land, then edit")
{
std::unique_ptr<CSMWorld::CreateCommand> createCommand (
new CSMWorld::CreateCommand (cellTable, cellId));
int parentIndex = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell);
int index = cellTable.findNestedColumnIndex (parentIndex, CSMWorld::Columns::ColumnId_Interior);
createCommand->addNestedValue (parentIndex, index, false);
document.getUndoStack().push (createCommand.release());
if (CSVRender::PagedWorldspaceWidget *paged =
dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
{
CSMWorld::CellSelection selection = paged->getCellSelection();
selection.add (CSMWorld::CellCoordinates::fromId (cellId).first);
paged->setCellSelection (selection);
}
}
}
else if (CSVRender::PagedWorldspaceWidget *paged =
dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
{
CSMWorld::CellSelection selection = paged->getCellSelection();
if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first))
{
// target cell exists, but is not shown
std::string mode =
CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString();
if (mode=="Discard")
return false;
if (mode=="Show cell and edit")
{
selection.add (CSMWorld::CellCoordinates::fromId (cellId).first);
paged->setCellSelection (selection);
}
}
}
if (noLand)
{
std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString();
// target cell does not exist
if (mode=="Discard")
return false;
if (mode=="Create cell and land, then edit")
{
document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId));
}
}
return true;
}
void CSVRender::TerrainTextureMode::dragMoveEvent (QDragMoveEvent *event) {
}
void CSVRender::TerrainTextureMode::setBrushSize(int brushSize)
{
mBrushSize = brushSize;
}
void CSVRender::TerrainTextureMode::setBrushShape(int brushShape)
{
mBrushShape = brushShape;
}
void CSVRender::TerrainTextureMode::setBrushTexture(std::string brushTexture)
{
mBrushTexture = brushTexture;
}

@ -0,0 +1,100 @@
#ifndef CSV_RENDER_TERRAINTEXTUREMODE_H
#define CSV_RENDER_TERRAINTEXTUREMODE_H
#include "editmode.hpp"
#include <string>
#include <QWidget>
#include <QEvent>
#include "../../model/world/data.hpp"
#include "../../model/world/land.hpp"
#include "../../model/doc/document.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/landtexture.hpp"
namespace CSVWidget
{
class SceneToolTextureBrush;
}
namespace CSVRender
{
class TerrainTextureMode : public EditMode
{
Q_OBJECT
public:
/// \brief Editmode for terrain texture grid
TerrainTextureMode(WorldspaceWidget*, QWidget* parent = nullptr);
/// \brief Create single command for one-click texture editing
void primaryEditPressed (const WorldspaceHitResult& hit);
/// \brief Open brush settings window
void primarySelectPressed(const WorldspaceHitResult&);
void secondarySelectPressed(const WorldspaceHitResult&);
void activate(CSVWidget::SceneToolbar*);
void deactivate(CSVWidget::SceneToolbar*);
/// \brief Start texture editing command macro
virtual bool primaryEditStartDrag (const QPoint& pos);
virtual bool secondaryEditStartDrag (const QPoint& pos);
virtual bool primarySelectStartDrag (const QPoint& pos);
virtual bool secondarySelectStartDrag (const QPoint& pos);
/// \brief Handle texture edit behavior during dragging
virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor);
/// \brief End texture editing command macro
virtual void dragCompleted(const QPoint& pos);
virtual void dragAborted();
virtual void dragWheel (int diff, double speedFactor);
virtual void dragMoveEvent (QDragMoveEvent *event);
/// \brief Handle brush mechanics, maths regarding worldspace hit etc.
void editTerrainTextureGrid (const WorldspaceHitResult& hit);
/// \brief Push texture edits to command macro
void pushEditToCommand (CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, std::string cellId);
/// \brief Create new land texture record from texture asset
void createTexture(std::string textureFileName);
/// \brief Create new cell and land if needed
bool allowLandTextureEditing(std::string textureFileName);
private:
std::string mCellId;
std::string mBrushTexture;
int mBrushSize;
int mBrushShape;
CSVWidget::SceneToolTextureBrush *mTextureBrushScenetool;
const int cellSize {ESM::Land::REAL_SIZE};
const int landSize {ESM::Land::LAND_SIZE};
const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE};
signals:
void passBrushTexture(std::string brushTexture);
public slots:
void handleDropEvent(QDropEvent *event);
void setBrushSize(int brushSize);
void setBrushShape(int brushShape);
void setBrushTexture(std::string brushShape);
};
}
#endif

@ -445,7 +445,7 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPo
// Default placement
direction.normalize();
direction *= CSMPrefs::get()["Scene Drops"]["distance"].toInt();
direction *= CSMPrefs::get()["3D Scene Editing"]["distance"].toInt();
WorldspaceHitResult hit = { false, 0, 0, 0, 0, start + direction };
return hit;
@ -648,6 +648,12 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event)
mDragX = event->posF().x();
mDragY = height() - event->posF().y();
#endif
if (mDragMode == InteractionType_PrimaryEdit)
{
EditMode& editMode = dynamic_cast<CSVRender::EditMode&> (*mEditMode->getCurrent());
editMode.drag (event->pos(), mDragX, mDragY, mDragFactor); // note: terraintexturemode only uses pos
}
}
}
else

@ -0,0 +1,379 @@
#include "scenetooltexturebrush.hpp"
#include <QFrame>
#include <QIcon>
#include <QTableWidget>
#include <QHBoxLayout>
#include <QWidget>
#include <QSpinBox>
#include <QGroupBox>
#include <QSlider>
#include <QEvent>
#include <QDropEvent>
#include <QButtonGroup>
#include <QVBoxLayout>
#include <QDragEnterEvent>
#include <QDrag>
#include <QTableWidget>
#include <QHeaderView>
#include <QApplication>
#include <QSizePolicy>
#include "scenetool.hpp"
#include "../../model/doc/document.hpp"
#include "../../model/prefs/state.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/data.hpp"
#include "../../model/world/idcollection.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/landtexture.hpp"
#include "../../model/world/universalid.hpp"
CSVWidget::BrushSizeControls::BrushSizeControls(const QString &title, QWidget *parent)
: QGroupBox(title, parent)
{
mBrushSizeSlider = new QSlider(Qt::Horizontal);
mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides);
mBrushSizeSlider->setTickInterval(10);
mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt());
mBrushSizeSlider->setSingleStep(1);
mBrushSizeSpinBox = new QSpinBox;
mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt());
mBrushSizeSpinBox->setSingleStep(1);
mLayoutSliderSize = new QHBoxLayout;
mLayoutSliderSize->addWidget(mBrushSizeSlider);
mLayoutSliderSize->addWidget(mBrushSizeSpinBox);
connect(mBrushSizeSlider, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int)));
connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int)));
setLayout(mLayoutSliderSize);
}
CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QWidget *parent)
: QFrame(parent, Qt::Popup),
mBrushShape(0),
mBrushSize(0),
mBrushTexture("L0#0"),
mDocument(document)
{
mBrushTextureLabel = "Selected texture: " + mBrushTexture + " ";
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = mDocument.getData().getLandTextures();
int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture);
int index = landtexturesCollection.searchId(mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value<QString>());
} else
{
mBrushTextureLabel = "No selected texture or invalid texture";
mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel));
}
mButtonPoint = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-point")), "", this);
mButtonSquare = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-square")), "", this);
mButtonCircle = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-circle")), "", this);
mButtonCustom = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-custom")), "", this);
mSizeSliders = new BrushSizeControls("Brush size", this);
QVBoxLayout *layoutMain = new QVBoxLayout;
layoutMain->setSpacing(0);
layoutMain->setContentsMargins(4,0,4,4);
QHBoxLayout *layoutHorizontal = new QHBoxLayout;
layoutHorizontal->setSpacing(0);
layoutHorizontal->setContentsMargins (QMargins (0, 0, 0, 0));
configureButtonInitialSettings(mButtonPoint);
configureButtonInitialSettings(mButtonSquare);
configureButtonInitialSettings(mButtonCircle);
configureButtonInitialSettings(mButtonCustom);
mButtonPoint->setToolTip (toolTipPoint);
mButtonSquare->setToolTip (toolTipSquare);
mButtonCircle->setToolTip (toolTipCircle);
mButtonCustom->setToolTip (toolTipCustom);
QButtonGroup* brushButtonGroup = new QButtonGroup(this);
brushButtonGroup->addButton(mButtonPoint);
brushButtonGroup->addButton(mButtonSquare);
brushButtonGroup->addButton(mButtonCircle);
brushButtonGroup->addButton(mButtonCustom);
brushButtonGroup->setExclusive(true);
layoutHorizontal->addWidget(mButtonPoint, 0, Qt::AlignTop);
layoutHorizontal->addWidget(mButtonSquare, 0, Qt::AlignTop);
layoutHorizontal->addWidget(mButtonCircle, 0, Qt::AlignTop);
layoutHorizontal->addWidget(mButtonCustom, 0, Qt::AlignTop);
mHorizontalGroupBox = new QGroupBox(tr(""));
mHorizontalGroupBox->setLayout(layoutHorizontal);
layoutMain->addWidget(mHorizontalGroupBox);
layoutMain->addWidget(mSizeSliders);
layoutMain->addWidget(mSelectedBrush);
setLayout(layoutMain);
connect(mButtonPoint, SIGNAL(clicked()), this, SLOT(setBrushShape()));
connect(mButtonSquare, SIGNAL(clicked()), this, SLOT(setBrushShape()));
connect(mButtonCircle, SIGNAL(clicked()), this, SLOT(setBrushShape()));
connect(mButtonCustom, SIGNAL(clicked()), this, SLOT(setBrushShape()));
}
void CSVWidget::TextureBrushWindow::configureButtonInitialSettings(QPushButton *button)
{
button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed));
button->setContentsMargins (QMargins (0, 0, 0, 0));
button->setIconSize (QSize (48-6, 48-6));
button->setFixedSize (48, 48);
button->setCheckable(true);
}
void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture)
{
mBrushTexture = brushTexture;
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = mDocument.getData().getLandTextures();
int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture);
int columnModification = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Modification);
int index = landtexturesCollection.searchId(mBrushTexture);
// Check if texture exists in current plugin
if(landtexturesCollection.getData(index, columnModification).value<int>() == 0)
{
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
QUndoStack& undoStack = mDocument.getUndoStack();
QVariant textureFileNameVariant;
textureFileNameVariant.setValue(landtexturesCollection.getData(index, landTextureFilename).value<QString>());
std::size_t hashlocation = mBrushTexture.find("#");
std::string mBrushTexturePlugin = "L0#" + mBrushTexture.substr (hashlocation+1);
int indexPlugin = landtexturesCollection.searchId(mBrushTexturePlugin);
// Reindex texture if needed
if (indexPlugin != -1 && !landtexturesCollection.getRecord(indexPlugin).isDeleted())
{
int counter=0;
bool freeIndexFound = false;
do {
const size_t maxCounter = std::numeric_limits<uint16_t>::max() - 1;
mBrushTexturePlugin = CSMWorld::LandTexture::createUniqueRecordId(0, counter);
if (landtexturesCollection.searchId(mBrushTexturePlugin) != -1 && landtexturesCollection.getRecord(mBrushTexturePlugin).isDeleted() == 0) counter = (counter + 1) % maxCounter;
else freeIndexFound = true;
} while (freeIndexFound == false);
}
undoStack.beginMacro ("Add land texture record");
undoStack.push (new CSMWorld::CloneCommand (ltexTable, mBrushTexture, mBrushTexturePlugin, CSMWorld::UniversalId::Type_LandTexture));
undoStack.endMacro();
mBrushTexture = mBrushTexturePlugin;
emit passTextureId(mBrushTexture);
}
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
mBrushTextureLabel = "Selected texture: " + mBrushTexture + " ";
mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value<QString>());
} else
{
mBrushTextureLabel = "No selected texture or invalid texture";
mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel));
}
emit passBrushShape(mBrushShape); // update icon
}
void CSVWidget::TextureBrushWindow::setBrushSize(int brushSize)
{
mBrushSize = brushSize;
emit passBrushSize(mBrushSize);
}
void CSVWidget::TextureBrushWindow::setBrushShape()
{
if(mButtonPoint->isChecked()) mBrushShape = 0;
if(mButtonSquare->isChecked()) mBrushShape = 1;
if(mButtonCircle->isChecked()) mBrushShape = 2;
if(mButtonCustom->isChecked()) mBrushShape = 3;
emit passBrushShape(mBrushShape);
}
void CSVWidget::SceneToolTextureBrush::adjustToolTips()
{
}
CSVWidget::SceneToolTextureBrush::SceneToolTextureBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document)
: SceneTool (parent, Type_TopAction),
mToolTip (toolTip),
mDocument (document),
mTextureBrushWindow(new TextureBrushWindow(document, this))
{
mBrushHistory.resize(1);
mBrushHistory[0] = "L0#0";
setAcceptDrops(true);
connect(mTextureBrushWindow, SIGNAL(passBrushShape(int)), this, SLOT(setButtonIcon(int)));
setButtonIcon(mTextureBrushWindow->mBrushShape);
mPanel = new QFrame (this, Qt::Popup);
QHBoxLayout *layout = new QHBoxLayout (mPanel);
layout->setContentsMargins (QMargins (0, 0, 0, 0));
mTable = new QTableWidget (0, 2, this);
mTable->setShowGrid (true);
mTable->verticalHeader()->hide();
mTable->horizontalHeader()->hide();
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch);
mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch);
#else
mTable->horizontalHeader()->setResizeMode (0, QHeaderView::Stretch);
mTable->horizontalHeader()->setResizeMode (1, QHeaderView::Stretch);
#endif
mTable->setSelectionMode (QAbstractItemView::NoSelection);
layout->addWidget (mTable);
connect (mTable, SIGNAL (clicked (const QModelIndex&)),
this, SLOT (clicked (const QModelIndex&)));
}
void CSVWidget::SceneToolTextureBrush::setButtonIcon (int brushShape)
{
QString tooltip = "Change brush settings <p>Currently selected: ";
switch (brushShape)
{
case 0:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-point")));
tooltip += mTextureBrushWindow->toolTipPoint;
break;
case 1:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-square")));
tooltip += mTextureBrushWindow->toolTipSquare;
break;
case 2:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle")));
tooltip += mTextureBrushWindow->toolTipCircle;
break;
case 3:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-custom")));
tooltip += mTextureBrushWindow->toolTipCustom;
break;
}
tooltip += "<p>(right click to access of previously used brush settings)";
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = mDocument.getData().getLandTextures();
int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture);
int index = landtexturesCollection.searchId(mTextureBrushWindow->mBrushTexture);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
tooltip += "<p>Selected texture: " + QString::fromStdString(mTextureBrushWindow->mBrushTexture) + " ";
tooltip += landtexturesCollection.getData(index, landTextureFilename).value<QString>();
} else
{
tooltip += "<p>No selected texture or invalid texture";
}
tooltip += "<br>(drop texture here to change)";
setToolTip (tooltip);
}
void CSVWidget::SceneToolTextureBrush::showPanel (const QPoint& position)
{
updatePanel();
mPanel->move (position);
mPanel->show();
}
void CSVWidget::SceneToolTextureBrush::updatePanel()
{
mTable->setRowCount (mBrushHistory.size());
for (int i = mBrushHistory.size()-1; i >= 0; --i)
{
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = mDocument.getData().getLandTextures();
int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture);
int index = landtexturesCollection.searchId(mBrushHistory[i]);
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
mTable->setItem (i, 1, new QTableWidgetItem (landtexturesCollection.getData(index, landTextureFilename).value<QString>()));
mTable->setItem (i, 0, new QTableWidgetItem (QString::fromStdString(mBrushHistory[i])));
} else
{
mTable->setItem (i, 1, new QTableWidgetItem ("Invalid/deleted texture"));
mTable->setItem (i, 0, new QTableWidgetItem (QString::fromStdString(mBrushHistory[i])));
}
}
}
void CSVWidget::SceneToolTextureBrush::updateBrushHistory (const std::string& brushTexture)
{
mBrushHistory.insert(mBrushHistory.begin(), brushTexture);
if(mBrushHistory.size() > 5) mBrushHistory.pop_back();
}
void CSVWidget::SceneToolTextureBrush::clicked (const QModelIndex& index)
{
if (index.column()==0 || index.column()==1)
{
std::string brushTexture = mBrushHistory[index.row()];
std::swap(mBrushHistory[index.row()], mBrushHistory[0]);
mTextureBrushWindow->setBrushTexture(brushTexture);
emit passTextureId(brushTexture);
updatePanel();
mPanel->hide();
}
}
void CSVWidget::SceneToolTextureBrush::activate ()
{
QPoint position = QCursor::pos();
mTextureBrushWindow->mSizeSliders->mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt());
mTextureBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt());
mTextureBrushWindow->move (position);
mTextureBrushWindow->show();
}
void CSVWidget::SceneToolTextureBrush::dragEnterEvent (QDragEnterEvent *event)
{
emit passEvent(event);
event->accept();
}
void CSVWidget::SceneToolTextureBrush::dropEvent (QDropEvent *event)
{
emit passEvent(event);
event->accept();
}

@ -0,0 +1,133 @@
#ifndef CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H
#define CSV_WIDGET_SCENETOOLTEXTUREBRUSH_H
#include <QIcon>
#include <QFrame>
#include <QModelIndex>
#include <QWidget>
#include <QLabel>
#include <QSpinBox>
#include <QGroupBox>
#include <QSlider>
#include <QEvent>
#include <QHBoxLayout>
#include <QPushButton>
#include "scenetool.hpp"
#include "../../model/doc/document.hpp"
class QTableWidget;
namespace CSVRender
{
class TerrainTextureMode;
}
namespace CSVWidget
{
class SceneToolTextureBrush;
/// \brief Layout-box for some brush button settings
class BrushSizeControls : public QGroupBox
{
Q_OBJECT
public:
BrushSizeControls(const QString &title, QWidget *parent);
private:
QHBoxLayout *mLayoutSliderSize;
QSlider *mBrushSizeSlider;
QSpinBox *mBrushSizeSpinBox;
friend class SceneToolTextureBrush;
friend class CSVRender::TerrainTextureMode;
};
class SceneToolTextureBrush;
/// \brief Brush settings window
class TextureBrushWindow : public QFrame
{
Q_OBJECT
public:
TextureBrushWindow(CSMDoc::Document& document, QWidget *parent = 0);
void configureButtonInitialSettings(QPushButton *button);
const QString toolTipPoint = "Paint single point";
const QString toolTipSquare = "Paint with square brush";
const QString toolTipCircle = "Paint with circle brush";
const QString toolTipCustom = "Paint custom selection (not implemented yet)";
private:
int mBrushShape;
int mBrushSize;
std::string mBrushTexture;
CSMDoc::Document& mDocument;
QLabel *mSelectedBrush;
QGroupBox *mHorizontalGroupBox;
std::string mBrushTextureLabel;
QPushButton *mButtonPoint;
QPushButton *mButtonSquare;
QPushButton *mButtonCircle;
QPushButton *mButtonCustom;
BrushSizeControls* mSizeSliders;
friend class SceneToolTextureBrush;
friend class CSVRender::TerrainTextureMode;
public slots:
void setBrushTexture(std::string brushTexture);
void setBrushShape();
void setBrushSize(int brushSize);
signals:
void passBrushSize (int brushSize);
void passBrushShape(int brushShape);
void passTextureId(std::string brushTexture);
};
class SceneToolTextureBrush : public SceneTool
{
Q_OBJECT
QString mToolTip;
CSMDoc::Document& mDocument;
QFrame *mPanel;
QTableWidget *mTable;
std::vector<std::string> mBrushHistory;
TextureBrushWindow *mTextureBrushWindow;
private:
void adjustToolTips();
public:
SceneToolTextureBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document);
virtual void showPanel (const QPoint& position);
void updatePanel ();
void dropEvent (QDropEvent *event);
void dragEnterEvent (QDragEnterEvent *event);
friend class CSVRender::TerrainTextureMode;
public slots:
void setButtonIcon(int brushShape);
void updateBrushHistory (const std::string& mBrushTexture);
void clicked (const QModelIndex& index);
virtual void activate();
signals:
void passEvent(QDropEvent *event);
void passEvent(QDragEnterEvent *event);
void passTextureId(std::string brushTexture);
};
}
#endif

@ -1,6 +1,7 @@
#include "misc.hpp"
#include <components/esm/loadmisc.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -85,7 +86,22 @@ namespace MWClass
{
const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().search(ref->mRef.getSoul());
if (creature)
value *= creature->mData.mSoul;
{
int soul = creature->mData.mSoul;
if (Settings::Manager::getBool("rebalance soul gem values", "Game"))
{
// use the 'soul gem value rebalance' formula from the Morrowind Code Patch
float soulValue = 0.0001 * pow(soul, 3) + 2 * soul;
// for Azura's star add the unfilled value
if (Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "Misc_SoulGem_Azura"))
value += soulValue;
else
value = soulValue;
}
else
value *= soul;
}
}
return value;

@ -988,7 +988,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;

@ -244,7 +244,7 @@ BookTypesetter::Ptr JournalBooks::createLatinJournalIndex ()
const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours();
BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic,
textColours.journalTopicOver,
textColours.journalTopicPressed, (uint32_t) ch);
textColours.journalTopicPressed, (Utf8Stream::UnicodeChar) ch);
if (i == 13)
typesetter->sectionBreak ();
@ -274,7 +274,7 @@ BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex ()
sprintf(buffer, "( %c%c )", ch[0], ch[1]);
Utf8Stream stream ((char*) ch);
uint32_t first = stream.peek();
Utf8Stream::UnicodeChar first = stream.peek();
const MWGui::TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours();
BookTypesetter::Style* style = typesetter->createHotStyle (body, textColours.journalTopic,

@ -7,7 +7,6 @@
#include <components/translation/translation.hpp>
#include <components/misc/stringops.hpp>
#include <components/misc/utf8stream.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/journal.hpp"
@ -307,39 +306,22 @@ struct JournalViewModelImpl : JournalViewModel
visitor (toUtf8Span (topic.getName()));
}
void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const
void visitTopicNamesStartingWith (Utf8Stream::UnicodeChar character, std::function < void (const std::string&) > visitor) const
{
MWBase::Journal * journal = MWBase::Environment::get().getJournal();
for (MWBase::Journal::TTopicIter i = journal->topicBegin (); i != journal->topicEnd (); ++i)
{
Utf8Stream stream (i->first.c_str());
uint32_t first = toUpper(stream.peek());
Utf8Stream::UnicodeChar first = Misc::StringUtils::toLowerUtf8(stream.peek());
if (first != character)
if (first != Misc::StringUtils::toLowerUtf8(character))
continue;
visitor (i->second.getName());
}
}
static uint32_t toUpper(uint32_t ch)
{
// Russian alphabet
if (ch >= 0x0430 && ch < 0x0450)
ch -= 0x20;
// Cyrillic IO character
if (ch == 0x0451)
ch -= 0x50;
// Latin alphabet
if (ch >= 0x61 && ch < 0x80)
ch -= 0x20;
return ch;
}
struct TopicEntryImpl : BaseEntry <MWDialogue::Topic::TEntryIter, TopicEntry>
{
MWDialogue::Topic const & mTopic;

@ -6,6 +6,8 @@
#include <functional>
#include <stdint.h>
#include <components/misc/utf8stream.hpp>
namespace MWGui
{
/// View-Model for the journal GUI
@ -76,7 +78,7 @@ namespace MWGui
virtual void visitTopicName (TopicId topicId, std::function <void (Utf8Span)> visitor) const = 0;
/// walks over the topics whose names start with the character
virtual void visitTopicNamesStartingWith (uint32_t character, std::function < void (const std::string&) > visitor) const = 0;
virtual void visitTopicNamesStartingWith (Utf8Stream::UnicodeChar character, std::function < void (const std::string&) > visitor) const = 0;
/// walks over the topic entries for the topic specified by its identifier
virtual void visitTopicEntries (TopicId topicId, std::function <void (TopicEntry const &)> visitor) const = 0;

@ -14,6 +14,9 @@ namespace MWGui
bool shouldAcceptKeyFocus(MyGUI::Widget* w)
{
if (w && w->getUserString("IgnoreTabKey") == "y")
return false;
return w && !w->castType<MyGUI::Window>(false) && w->getInheritedEnabled() && w->getInheritedVisible() && w->getVisible() && w->getEnabled();
}

@ -32,10 +32,14 @@ namespace
namespace MWGui
{
SpellModel::SpellModel(const MWWorld::Ptr &actor, const std::string& filter)
: mActor(actor), mFilter(filter)
{
}
SpellModel::SpellModel(const MWWorld::Ptr &actor)
: mActor(actor)
{
}
void SpellModel::update()
@ -48,12 +52,19 @@ namespace MWGui
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter);
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
{
const ESM::Spell* spell = it->first;
if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell)
continue;
std::string name = Misc::StringUtils::lowerCaseUtf8(spell->mName);
if (name.find(filter) == std::string::npos)
continue;
Spell newSpell;
newSpell.mName = spell->mName;
if (spell->mData.mType == ESM::Spell::ST_Spell)
@ -89,6 +100,11 @@ namespace MWGui
if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce)
continue;
std::string name = Misc::StringUtils::lowerCaseUtf8(item.getClass().getName(item));
if (name.find(filter) == std::string::npos)
continue;
Spell newSpell;
newSpell.mItem = item;
newSpell.mId = item.getCellRef().getRefId();

@ -35,6 +35,7 @@ namespace MWGui
class SpellModel
{
public:
SpellModel(const MWWorld::Ptr& actor, const std::string& filter);
SpellModel(const MWWorld::Ptr& actor);
typedef int ModelIndex;
@ -50,6 +51,8 @@ namespace MWGui
MWWorld::Ptr mActor;
std::vector<Spell> mSpells;
std::string mFilter;
};
}

@ -2,6 +2,7 @@
#include <boost/format.hpp>
#include <MyGUI_EditBox.h>
#include <MyGUI_InputManager.h>
#include <components/settings/settings.hpp>
@ -38,8 +39,12 @@ namespace MWGui
getWidget(mSpellView, "SpellView");
getWidget(mEffectBox, "EffectsBox");
getWidget(mFilterEdit, "FilterEdit");
mFilterEdit->setUserString("IgnoreTabKey", "y");
mSpellView->eventSpellClicked += MyGUI::newDelegate(this, &SpellWindow::onModelIndexSelected);
mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &SpellWindow::onFilterChanged);
setCoord(498, 300, 302, 300);
}
@ -64,6 +69,11 @@ namespace MWGui
void SpellWindow::onOpen()
{
// Reset the filter focus when opening the window
MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget();
if (focus == mFilterEdit)
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL);
updateSpells();
}
@ -82,7 +92,7 @@ namespace MWGui
{
mSpellIcons->updateWidgets(mEffectBox, false);
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer()));
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), mFilterEdit->getCaption()));
}
void SpellWindow::onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped)
@ -167,6 +177,11 @@ namespace MWGui
}
}
void SpellWindow::onFilterChanged(MyGUI::EditBox *sender)
{
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), sender->getCaption()));
}
void SpellWindow::onSpellSelected(const std::string& spellId)
{
MWWorld::Ptr player = MWMechanics::getPlayer();
@ -202,7 +217,7 @@ namespace MWGui
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery())
return;
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer()));
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer(), ""));
SpellModel::ModelIndex selected = 0;
for (SpellModel::ModelIndex i = 0; i<int(mSpellView->getModel()->getItemCount()); ++i)

@ -32,6 +32,7 @@ namespace MWGui
void onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped);
void onSpellSelected(const std::string& spellId);
void onModelIndexSelected(SpellModel::ModelIndex index);
void onFilterChanged(MyGUI::EditBox *sender);
void onDeleteSpellAccept();
void askDeleteSpell(const std::string& spellId);
@ -41,6 +42,7 @@ namespace MWGui
SpellView* mSpellView;
SpellIcons* mSpellIcons;
MyGUI::EditBox* mFilterEdit;
private:
float mUpdateTimer;

@ -1751,8 +1751,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

@ -222,45 +222,42 @@ namespace MWMechanics
magicEffects = effects;
float resisted = 0;
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
{
// Effects with no resistance attribute belonging to them can not be resisted
if (ESM::MagicEffect::getResistanceEffect(effectId) == -1)
return 0.f;
// Effects with no resistance attribute belonging to them can not be resisted
if (ESM::MagicEffect::getResistanceEffect(effectId) == -1)
return 0.f;
float resistance = getEffectResistanceAttribute(effectId, magicEffects);
float resistance = getEffectResistanceAttribute(effectId, magicEffects);
int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
float luck = static_cast<float>(stats.getAttribute(ESM::Attribute::Luck).getModified());
float x = (willpower + 0.1f * luck) * stats.getFatigueTerm();
int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
float luck = static_cast<float>(stats.getAttribute(ESM::Attribute::Luck).getModified());
float x = (willpower + 0.1f * luck) * stats.getFatigueTerm();
// This makes spells that are easy to cast harder to resist and vice versa
float castChance = 100.f;
if (spell != NULL && !caster.isEmpty() && caster.getClass().isActor())
{
castChance = getSpellSuccessChance(spell, caster, NULL, false); // Uncapped casting chance
}
if (castChance > 0)
x *= 50 / castChance;
// This makes spells that are easy to cast harder to resist and vice versa
float castChance = 100.f;
if (spell != NULL && !caster.isEmpty() && caster.getClass().isActor())
{
castChance = getSpellSuccessChance(spell, caster, NULL, false); // Uncapped casting chance
}
if (castChance > 0)
x *= 50 / castChance;
float roll = Misc::Rng::rollClosedProbability() * 100;
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
roll -= resistance;
float roll = Misc::Rng::rollClosedProbability() * 100;
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
roll -= resistance;
if (x <= roll)
x = 0;
if (x <= roll)
x = 0;
else
{
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
x = 100;
else
{
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
x = 100;
else
x = roll / std::min(x, 100.f);
}
x = roll / std::min(x, 100.f);
}
x = std::min(x + resistance, 100.f);
x = std::min(x + resistance, 100.f);
resisted = x;
}
resisted = x;
return resisted;
}
@ -458,13 +455,11 @@ namespace MWMechanics
}
float magnitudeMult = 1;
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor())
{
if (absorbed)
continue;
// Try reflecting
if (!reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable))
if (!absorbed)
{
// Reflect harmful effects
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && !reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable))
{
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
bool isReflected = (Misc::Rng::roll0to99() < reflect);
@ -488,16 +483,17 @@ namespace MWMechanics
else if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
}
else if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && castByPlayer && target != caster)
{
// If player is attempting to cast a harmful spell and it wasn't fully resisted, show the target's HP bar
MWBase::Environment::get().getWindowManager()->setEnemy(target);
}
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState())
magnitudeMult = 0;
// If player is attempting to cast a harmful spell, show the target's HP bar
if (castByPlayer && target != caster)
MWBase::Environment::get().getWindowManager()->setEnemy(target);
// Notify the target actor they've been hit
if (target != caster && !caster.isEmpty())
if (target != caster && !caster.isEmpty() && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
}

@ -462,10 +462,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

@ -1621,7 +1621,7 @@ namespace MWWorld
if (!paused)
doPhysics (duration);
updatePlayer(paused);
updatePlayer();
mPhysics->debugDraw();
@ -1637,7 +1637,7 @@ namespace MWWorld
}
}
void World::updatePlayer(bool paused)
void World::updatePlayer()
{
MWWorld::Ptr player = getPlayerPtr();
@ -1670,7 +1670,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);

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

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

@ -18,8 +18,8 @@ platform:
- x64
configuration:
- Debug
# - Release
# - Debug
- Release
# For the Qt, Boost, CMake, etc installs
#os: Visual Studio 2017
@ -55,6 +55,10 @@ build_script:
- cmd: if %PLATFORM%==x64 set build=MSVC%msvc%_64
- cmd: msbuild %build%\OpenMW.sln /t:Build /p:Configuration=%configuration% /m:2 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
after_build:
- cmd: if %PLATFORM%==x64 7z a OpenMW_MSVC%msvc%_x64.zip %APPVEYOR_BUILD_FOLDER%\MSVC%msvc%_64\Release\ -xr"!*.pdb"
- cmd: if %PLATFORM%==x64 7z a OpenMW_MSVC%msvc%_x64_pdb.zip %APPVEYOR_BUILD_FOLDER%\MSVC%msvc%_64\Release\*.pdb
test: off
#notifications:
@ -63,3 +67,9 @@ test: off
# -
# on_build_failure: true
# on_build_status_changed: true
artifacts:
- path: OpenMW_MSVC%msvc%_x64.zip
name: OpenMW_MSVC%msvc%_x64
- path: OpenMW_MSVC%msvc%_x64_pdb.zip
name: OpenMW_MSVC%msvc%_x64_pdb

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

@ -6,6 +6,8 @@
#include <string>
#include <algorithm>
#include "utf8stream.hpp"
namespace Misc
{
class StringUtils
@ -56,6 +58,70 @@ public:
};
}
static Utf8Stream::UnicodeChar toLowerUtf8(Utf8Stream::UnicodeChar ch)
{
// Russian alphabet
if (ch >= 0x0410 && ch < 0x0430)
return ch += 0x20;
// Cyrillic IO character
if (ch == 0x0401)
return ch += 0x50;
// Latin alphabet
if (ch >= 0x41 && ch < 0x60)
return ch += 0x20;
// Deutch characters
if (ch == 0xc4 || ch == 0xd6 || ch == 0xdc)
return ch += 0x20;
if (ch == 0x1e9e)
return 0xdf;
// TODO: probably we will need to support characters from other languages
return ch;
}
static std::string lowerCaseUtf8(const std::string str)
{
if (str.empty())
return str;
// Decode string as utf8 characters, convert to lower case and pack them to string
std::string out;
Utf8Stream stream (str.c_str());
while (!stream.eof ())
{
Utf8Stream::UnicodeChar character = toLowerUtf8(stream.peek());
if (character <= 0x7f)
out.append(1, static_cast<char>(character));
else if (character <= 0x7ff)
{
out.append(1, static_cast<char>(0xc0 | ((character >> 6) & 0x1f)));
out.append(1, static_cast<char>(0x80 | (character & 0x3f)));
}
else if (character <= 0xffff)
{
out.append(1, static_cast<char>(0xe0 | ((character >> 12) & 0x0f)));
out.append(1, static_cast<char>(0x80 | ((character >> 6) & 0x3f)));
out.append(1, static_cast<char>(0x80 | (character & 0x3f)));
}
else
{
out.append(1, static_cast<char>(0xf0 | ((character >> 18) & 0x07)));
out.append(1, static_cast<char>(0x80 | ((character >> 12) & 0x3f)));
out.append(1, static_cast<char>(0x80 | ((character >> 6) & 0x3f)));
out.append(1, static_cast<char>(0x80 | (character & 0x3f)));
}
stream.consume();
}
return out;
}
static bool ciLess(const std::string &x, const std::string &y) {
return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), ci());
}

@ -1,6 +1,7 @@
#ifndef MISC_UTF8ITER_HPP
#define MISC_UTF8ITER_HPP
#include <cstring>
#include <tuple>
class Utf8Stream

@ -191,35 +191,22 @@ public:
node->setViewDataMap(mViewDataMap);
parent->addChild(node);
if (center.x() - size > mMaxX
|| center.x() + size < mMinX
|| center.y() - size > mMaxY
|| center.y() + size < mMinY )
// Out of bounds of the actual terrain - this will happen because
// we rounded the size up to the next power of two
{
// Still create and return an empty node so as to not break the assumption that each QuadTreeNode has either 4 or 0 children.
return node;
}
if (node->getSize() <= mMinSize)
{
// We arrived at a leaf
float minZ,maxZ;
if (mStorage->getMinMaxHeights(size, center, minZ, maxZ))
{
float cellWorldSize = mStorage->getCellWorldSize();
osg::BoundingBox boundingBox(osg::Vec3f((center.x()-size)*cellWorldSize, (center.y()-size)*cellWorldSize, minZ),
osg::Vec3f((center.x()+size)*cellWorldSize, (center.y()+size)*cellWorldSize, maxZ));
node->setBoundingBox(boundingBox);
}
return node;
}
else
if (node->getSize() > mMinSize)
{
addChildren(node);
return node;
}
// We arrived at a leaf
float minZ, maxZ;
mStorage->getMinMaxHeights(size, center, minZ, maxZ);
float cellWorldSize = mStorage->getCellWorldSize();
osg::BoundingBox boundingBox(osg::Vec3f((center.x()-size)*cellWorldSize, (center.y()-size)*cellWorldSize, minZ),
osg::Vec3f((center.x()+size)*cellWorldSize, (center.y()+size)*cellWorldSize, maxZ));
node->setBoundingBox(boundingBox);
return node;
}
osg::ref_ptr<RootNode> getRootNode()
@ -248,7 +235,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

@ -10,7 +10,11 @@
</Widget>
<!-- Spell list -->
<Widget type="SpellView" skin="MW_SpellView" position="8 38 268 518" align="Left Top Stretch" name="SpellView">
<Widget type="SpellView" skin="MW_SpellView" position="8 38 268 490" align="Left Top Stretch" name="SpellView">
</Widget>
<!-- Search box-->
<Widget type="EditBox" skin="MW_TextBoxEditWithBorder" position="8 535 268 23" align="Left Bottom HStretch" name="FilterEdit">
</Widget>
</Widget>

@ -68,7 +68,7 @@
<file alias="edit-preview">record-preview.png</file>
<file alias="edit-clone">record-clone.png</file>
<file alias="edit-add">record-add.png</file>
<file alias="resources-icon">resources-icon.png</file>
<file alias="resources-icon">resources-icon.png</file>
<file alias="resources-mesh">resources-mesh.png</file>
<file alias="resources-music">resources-music.png</file>
<file alias="resources-sound">resources-sound.png</file>
@ -149,6 +149,10 @@
<file alias="transform-scale">transform-scale.png</file>
<file alias="selection-mode-cube">selection-mode-cube.png</file>
<file alias="selection-mode-cube-corner">selection-mode-cube-corner.png</file>
<file alias="selection-mode-cube-sphere">selection-mode-cube-sphere.png</file>
<file alias="selection-mode-cube-sphere">selection-mode-cube-sphere.png</file>
<file alias="brush-point">brush-point.png</file>
<file alias="brush-square">brush-square.png</file>
<file alias="brush-circle">brush-circle.png</file>
<file alias="brush-custom">brush-custom.png</file>
</qresource>
</RCC>

@ -216,6 +216,9 @@ followers attack on sight = false
# Can loot non-fighting actors during death animation
can loot during death animation = true
# Makes the value of filled soul gems dependent only on soul magnitude (with formula from the Morrowind Code Patch)
rebalance soul gem values = false
[General]
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).

@ -109,6 +109,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="rebalanceSoulGemValuesCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, the value of filled soul gems is dependent only on soul magnitude.&lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Rebalance soul gem values</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignLeft">
<widget class="QWidget" name="showOwnedGroup" native="true">
<property name="toolTip">

Loading…
Cancel
Save