mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-07-13 09:21:42 +00:00
Add OpenMW commits up to 4 Aug 2020
This commit is contained in:
commit
baa9446cd1
66 changed files with 1785 additions and 1025 deletions
|
@ -10,7 +10,10 @@ If you feel your name is missing from this list, please notify a developer.
|
|||
Programmers
|
||||
-----------
|
||||
|
||||
Marc Zinnschlag (Zini) - Lead Programmer/Project Manager
|
||||
Bret Curtis (psi29a) - Project leader 2019-present
|
||||
Marc Zinnschlag (Zini) - Project leader 2010-2018
|
||||
Nicolay Korslund - Project leader 2008-2010
|
||||
scrawl - Top contributor
|
||||
|
||||
Adam Hogan (aurix)
|
||||
Aesylwinn
|
||||
|
@ -39,7 +42,6 @@ Programmers
|
|||
Austin Salgat (Salgat)
|
||||
Ben Shealy (bentsherman)
|
||||
Berulacks
|
||||
Bret Curtis (psi29a)
|
||||
Britt Mathis (galdor557)
|
||||
Capostrophic
|
||||
Carl Maxwell
|
||||
|
@ -146,7 +148,6 @@ Programmers
|
|||
Nathan Jeffords (blunted2night)
|
||||
NeveHanter
|
||||
Nialsy
|
||||
Nicolay Korslund
|
||||
Nikolay Kasyanov (corristo)
|
||||
nobrakal
|
||||
Nolan Poe (nopoe)
|
||||
|
@ -175,7 +176,6 @@ Programmers
|
|||
Roman Siromakha (elsid)
|
||||
Sandy Carter (bwrsandman)
|
||||
Scott Howard (maqifrnswa)
|
||||
scrawl
|
||||
Sebastian Wick (swick)
|
||||
Sergey Fukanchik
|
||||
Sergey Shambir (sergey-shambir)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
Bug #3676: NiParticleColorModifier isn't applied properly
|
||||
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
|
||||
Bug #4021: Attributes and skills are not stored as floats
|
||||
Bug #4055: Local scripts don't inherit variables from their base record
|
||||
Bug #4623: Corprus implementation is incorrect
|
||||
Bug #4764: Data race in osg ParticleSystem
|
||||
Bug #4774: Guards are ignorant of an invisible player that tries to attack them
|
||||
|
@ -41,6 +42,7 @@
|
|||
Bug #5502: Dead zone for analogue stick movement is too small
|
||||
Bug #5507: Sound volume is not clamped on ingame settings update
|
||||
Bug #5531: Actors flee using current rotation by axis x
|
||||
Bug #5548: Certain exhausted topics can be highlighted again even though there's no new dialogue
|
||||
Feature #390: 3rd person look "over the shoulder"
|
||||
Feature #2386: Distant Statics in the form of Object Paging
|
||||
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
|
||||
|
@ -48,8 +50,10 @@
|
|||
Feature #5445: Handle NiLines
|
||||
Feature #5457: Realistic diagonal movement
|
||||
Feature #5486: Fixes trainers to choose their training skills based on their base skill points
|
||||
Feature #5519: Code Patch tab in launcher
|
||||
Feature #5524: Resume failed script execution after reload
|
||||
Feature #5525: Search fields tweaks (utf-8)
|
||||
Feature #5545: Option to allow stealing from an unconscious NPC during combat
|
||||
Task #5480: Drop Qt4 support
|
||||
Task #5520: Improve cell name autocompleter implementation
|
||||
|
||||
|
|
|
@ -258,10 +258,10 @@ download() {
|
|||
|
||||
if [ -z $VERBOSE ]; then
|
||||
RET=0
|
||||
curl --silent --retry 10 -kLy 5 -o $FILE $URL || RET=$?
|
||||
curl --silent --retry 10 -Ly 5 -o $FILE $URL || RET=$?
|
||||
else
|
||||
RET=0
|
||||
curl --retry 10 -kLy 5 -o $FILE $URL || RET=$?
|
||||
curl --retry 10 -Ly 5 -o $FILE $URL || RET=$?
|
||||
fi
|
||||
|
||||
if [ $RET -ne 0 ]; then
|
||||
|
|
|
@ -525,9 +525,7 @@ if(WIN32)
|
|||
INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt")
|
||||
INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt")
|
||||
INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt")
|
||||
INSTALL(FILES
|
||||
"${OpenMW_SOURCE_DIR}/files/mygui/DejaVu Font License.txt"
|
||||
DESTINATION ".")
|
||||
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".")
|
||||
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/settings-default.cfg" DESTINATION "." CONFIGURATIONS Debug)
|
||||
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/settings-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
|
||||
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/tes3mp-client-default.cfg" DESTINATION "." CONFIGURATIONS Debug)
|
||||
|
|
|
@ -13,7 +13,7 @@ TES3MP is a project adding multiplayer functionality to [OpenMW](https://github.
|
|||
* License: GPLv3 (see [LICENSE](https://github.com/TES3MP/openmw-tes3mp/blob/master/LICENSE) for more information)
|
||||
|
||||
Font Licenses:
|
||||
* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVu Font License.txt](https://github.com/TES3MP/openmw-tes3mp/blob/master/files/mygui/DejaVu%20Font%20License.txt) for more information)
|
||||
* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVuFontLicense.txt](https://github.com/TES3MP/openmw-tes3mp/blob/master/files/mygui/DejaVuFontLicense.txt) for more information)
|
||||
|
||||
Project status
|
||||
--------------
|
||||
|
|
|
@ -4,9 +4,43 @@
|
|||
#include <components/config/launchersettings.hpp>
|
||||
#include <QFileDialog>
|
||||
#include <QCompleter>
|
||||
#include <QProxyStyle>
|
||||
#include <components/contentselector/view/contentselector.hpp>
|
||||
#include <components/contentselector/model/esmfile.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
||||
class HorizontalTextWestTabStyle : public QProxyStyle
|
||||
{
|
||||
public:
|
||||
QSize sizeFromContents(ContentsType type, const QStyleOption* option, const QSize& size, const QWidget* widget) const
|
||||
{
|
||||
QSize s = QProxyStyle::sizeFromContents(type, option, size, widget);
|
||||
if (type == QStyle::CT_TabBarTab)
|
||||
{
|
||||
s.transpose();
|
||||
s.setHeight(s.height() + 20);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const
|
||||
{
|
||||
if (element == CE_TabBarTabLabel)
|
||||
{
|
||||
if (const QStyleOptionTab* tab = qstyleoption_cast<const QStyleOptionTab*>(option))
|
||||
{
|
||||
QStyleOptionTab opt(*tab);
|
||||
opt.shape = QTabBar::RoundedNorth;
|
||||
QProxyStyle::drawControl(element, &opt, painter, widget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
QProxyStyle::drawControl(element, option, painter, widget);
|
||||
}
|
||||
};
|
||||
|
||||
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
|
||||
Config::GameSettings &gameSettings,
|
||||
Settings::Manager &engineSettings, QWidget *parent)
|
||||
|
@ -19,6 +53,7 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
|
|||
setupUi(this);
|
||||
|
||||
loadSettings();
|
||||
AdvancedTabWidget->tabBar()->setStyle(new HorizontalTextWestTabStyle);
|
||||
mCellNameCompleter.setModel(&mCellNameCompleterModel);
|
||||
startDefaultCharacterAtField->setCompleter(&mCellNameCompleter);
|
||||
}
|
||||
|
@ -55,33 +90,45 @@ void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
|
|||
runScriptAfterStartupField->setText(path);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr double CellSizeInUnits = 8192;
|
||||
|
||||
double convertToCells(double unitRadius)
|
||||
{
|
||||
return std::round((unitRadius / 0.93 + 1024) / CellSizeInUnits);
|
||||
}
|
||||
|
||||
double convertToUnits(double CellGridRadius)
|
||||
{
|
||||
return (CellSizeInUnits * CellGridRadius - 1024) * 0.93;
|
||||
}
|
||||
}
|
||||
|
||||
bool Launcher::AdvancedPage::loadSettings()
|
||||
{
|
||||
// Testing
|
||||
bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1;
|
||||
if (skipMenu) {
|
||||
skipMenuCheckBox->setCheckState(Qt::Checked);
|
||||
}
|
||||
startDefaultCharacterAtLabel->setEnabled(skipMenu);
|
||||
startDefaultCharacterAtField->setEnabled(skipMenu);
|
||||
|
||||
startDefaultCharacterAtField->setText(mGameSettings.value("start"));
|
||||
runScriptAfterStartupField->setText(mGameSettings.value("script-run"));
|
||||
|
||||
// Game Settings
|
||||
// Game mechanics
|
||||
{
|
||||
loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
|
||||
loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
|
||||
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
|
||||
loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
||||
loadSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game");
|
||||
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
|
||||
loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
|
||||
loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
|
||||
loadSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game");
|
||||
loadSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game");
|
||||
loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
|
||||
loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
|
||||
int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game");
|
||||
if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2)
|
||||
unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex);
|
||||
loadSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game");
|
||||
loadSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
|
||||
}
|
||||
|
||||
// Visuals
|
||||
{
|
||||
loadSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders");
|
||||
loadSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
|
||||
loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
|
||||
connect(animSourcesCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotAnimSourcesToggled(bool)));
|
||||
loadSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
|
||||
if (animSourcesCheckBox->checkState())
|
||||
|
@ -89,18 +136,21 @@ bool Launcher::AdvancedPage::loadSettings()
|
|||
loadSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
|
||||
loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
|
||||
}
|
||||
loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
|
||||
loadSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game");
|
||||
loadSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
|
||||
loadSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
|
||||
|
||||
// Input Settings
|
||||
loadSettingBool(grabCursorCheckBox, "grab cursor", "Input");
|
||||
loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
|
||||
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
|
||||
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
|
||||
if (distantTerrain && objectPaging) {
|
||||
distantLandCheckBox->setCheckState(Qt::Checked);
|
||||
}
|
||||
|
||||
// Saves Settings
|
||||
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
|
||||
maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves"));
|
||||
loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain");
|
||||
viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera")));
|
||||
}
|
||||
|
||||
// User Interface Settings
|
||||
// Interface Changes
|
||||
{
|
||||
loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
|
||||
loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
|
||||
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
|
||||
|
@ -110,66 +160,93 @@ bool Launcher::AdvancedPage::loadSettings()
|
|||
// Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid.
|
||||
if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
|
||||
showOwnedComboBox->setCurrentIndex(showOwnedIndex);
|
||||
}
|
||||
|
||||
// Bug fixes
|
||||
{
|
||||
loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
||||
loadSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game");
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
{
|
||||
// Saves
|
||||
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
|
||||
maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves"));
|
||||
|
||||
// Other Settings
|
||||
QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper();
|
||||
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
|
||||
screenshotFormatComboBox->addItem(screenshotFormatString);
|
||||
screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString));
|
||||
}
|
||||
|
||||
// Testing
|
||||
{
|
||||
loadSettingBool(grabCursorCheckBox, "grab cursor", "Input");
|
||||
|
||||
bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1;
|
||||
if (skipMenu)
|
||||
{
|
||||
skipMenuCheckBox->setCheckState(Qt::Checked);
|
||||
}
|
||||
startDefaultCharacterAtLabel->setEnabled(skipMenu);
|
||||
startDefaultCharacterAtField->setEnabled(skipMenu);
|
||||
|
||||
startDefaultCharacterAtField->setText(mGameSettings.value("start"));
|
||||
runScriptAfterStartupField->setText(mGameSettings.value("script-run"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::saveSettings()
|
||||
{
|
||||
// Ensure we only set the new settings if they changed. This is to avoid cluttering the
|
||||
// user settings file (which by definition should only contain settings the user has touched)
|
||||
|
||||
// Testing
|
||||
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
|
||||
if (skipMenu != mGameSettings.value("skip-menu").toInt())
|
||||
mGameSettings.setValue("skip-menu", QString::number(skipMenu));
|
||||
|
||||
QString startCell = startDefaultCharacterAtField->text();
|
||||
if (startCell != mGameSettings.value("start")) {
|
||||
mGameSettings.setValue("start", startCell);
|
||||
}
|
||||
QString scriptRun = runScriptAfterStartupField->text();
|
||||
if (scriptRun != mGameSettings.value("script-run"))
|
||||
mGameSettings.setValue("script-run", scriptRun);
|
||||
|
||||
// Game Settings
|
||||
// Game mechanics
|
||||
{
|
||||
saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
|
||||
saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
|
||||
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
|
||||
saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
||||
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
|
||||
saveSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game");
|
||||
saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
|
||||
saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
|
||||
saveSettingBool(classicReflectedAbsorbSpellsCheckBox, "classic reflected absorb spells behavior", "Game");
|
||||
saveSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game");
|
||||
saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
|
||||
saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
|
||||
int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex();
|
||||
if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game"))
|
||||
mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex);
|
||||
saveSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game");
|
||||
saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game");
|
||||
}
|
||||
|
||||
// Visuals
|
||||
{
|
||||
saveSettingBool(bumpMapLocalLightingCheckBox, "apply lighting to environment maps", "Shaders");
|
||||
saveSettingBool(magicItemAnimationsCheckBox, "use magic item animations", "Game");
|
||||
saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
|
||||
saveSettingBool(animSourcesCheckBox, "use additional anim sources", "Game");
|
||||
saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
|
||||
saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
|
||||
saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
|
||||
saveSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game");
|
||||
saveSettingBool(viewOverShoulderCheckBox, "view over shoulder", "Camera");
|
||||
saveSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
|
||||
|
||||
// Input Settings
|
||||
saveSettingBool(grabCursorCheckBox, "grab cursor", "Input");
|
||||
saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
|
||||
|
||||
// Saves Settings
|
||||
saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
|
||||
int maximumQuicksaves = maximumQuicksavesComboBox->value();
|
||||
if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves")) {
|
||||
mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves);
|
||||
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
|
||||
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
|
||||
const bool wantDistantLand = distantLandCheckBox->checkState();
|
||||
if (wantDistantLand != (distantTerrain && objectPaging)) {
|
||||
mEngineSettings.setBool("distant terrain", "Terrain", wantDistantLand);
|
||||
mEngineSettings.setBool("object paging", "Terrain", wantDistantLand);
|
||||
}
|
||||
|
||||
// User Interface Settings
|
||||
saveSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain");
|
||||
double viewingDistance = viewingDistanceComboBox->value();
|
||||
if (viewingDistance != convertToCells(mEngineSettings.getInt("viewing distance", "Camera")))
|
||||
{
|
||||
mEngineSettings.setInt("viewing distance", "Camera", convertToUnits(viewingDistance));
|
||||
}
|
||||
}
|
||||
|
||||
// Interface Changes
|
||||
{
|
||||
saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
|
||||
saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
|
||||
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
|
||||
|
@ -178,6 +255,23 @@ void Launcher::AdvancedPage::saveSettings()
|
|||
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
|
||||
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
|
||||
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
|
||||
}
|
||||
|
||||
// Bug fixes
|
||||
{
|
||||
saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
||||
saveSettingBool(trainersTrainingSkillsBasedOnBaseSkillCheckBox, "trainers training skills based on base skill", "Game");
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
{
|
||||
// Saves Settings
|
||||
saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
|
||||
int maximumQuicksaves = maximumQuicksavesComboBox->value();
|
||||
if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves"))
|
||||
{
|
||||
mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves);
|
||||
}
|
||||
|
||||
// Other Settings
|
||||
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
|
||||
|
@ -185,12 +279,33 @@ void Launcher::AdvancedPage::saveSettings()
|
|||
mEngineSettings.setString("screenshot format", "General", screenshotFormatString);
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) {
|
||||
// Testing
|
||||
{
|
||||
saveSettingBool(grabCursorCheckBox, "grab cursor", "Input");
|
||||
|
||||
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
|
||||
if (skipMenu != mGameSettings.value("skip-menu").toInt())
|
||||
mGameSettings.setValue("skip-menu", QString::number(skipMenu));
|
||||
|
||||
QString startCell = startDefaultCharacterAtField->text();
|
||||
if (startCell != mGameSettings.value("start"))
|
||||
{
|
||||
mGameSettings.setValue("start", startCell);
|
||||
}
|
||||
QString scriptRun = runScriptAfterStartupField->text();
|
||||
if (scriptRun != mGameSettings.value("script-run"))
|
||||
mGameSettings.setValue("script-run", scriptRun);
|
||||
}
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group)
|
||||
{
|
||||
if (mEngineSettings.getBool(setting, group))
|
||||
checkbox->setCheckState(Qt::Checked);
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) {
|
||||
void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group)
|
||||
{
|
||||
bool cValue = checkbox->checkState();
|
||||
if (cValue != mEngineSettings.getBool(setting, group))
|
||||
mEngineSettings.setBool(setting, group, cValue);
|
||||
|
|
|
@ -82,7 +82,7 @@ add_openmw_dir (mwclass
|
|||
)
|
||||
|
||||
add_openmw_dir (mwmechanics
|
||||
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil
|
||||
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil spelllist
|
||||
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
|
||||
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
|
||||
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
|
||||
|
|
|
@ -537,6 +537,7 @@ namespace MWBase
|
|||
|
||||
virtual void togglePOV(bool force = false) = 0;
|
||||
virtual bool isFirstPerson() const = 0;
|
||||
virtual bool isPreviewModeEnabled() const = 0;
|
||||
virtual void togglePreviewMode(bool enable) = 0;
|
||||
virtual bool toggleVanityMode(bool enable) = 0;
|
||||
virtual void allowVanityMode(bool allow) = 0;
|
||||
|
|
|
@ -158,14 +158,9 @@ namespace MWClass
|
|||
data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr));
|
||||
|
||||
// spells
|
||||
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
|
||||
iter!=ref->mBase->mSpells.mList.end(); ++iter)
|
||||
{
|
||||
if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
|
||||
data->mCreatureStats.getSpells().add (spell);
|
||||
else /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility
|
||||
Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << *iter << "' on creature '" << ref->mBase->mId << "'";
|
||||
}
|
||||
bool spellsInitialised = data->mCreatureStats.getSpells().setSpells(ref->mBase->mId);
|
||||
if (!spellsInitialised)
|
||||
data->mCreatureStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList);
|
||||
|
||||
// inventory
|
||||
bool hasInventory = hasInventoryStore(ptr);
|
||||
|
@ -969,6 +964,9 @@ namespace MWClass
|
|||
CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
||||
const ESM::CreatureState& creatureState = state.asCreatureState();
|
||||
customData.mContainerStore->readState (creatureState.mInventory);
|
||||
bool spellsInitialised = customData.mCreatureStats.getSpells().setSpells(ptr.get<ESM::Creature>()->mBase->mId);
|
||||
if(spellsInitialised)
|
||||
customData.mCreatureStats.getSpells().clear();
|
||||
customData.mCreatureStats.readState (creatureState.mCreatureStats);
|
||||
}
|
||||
|
||||
|
@ -1062,6 +1060,11 @@ namespace MWClass
|
|||
MWMechanics::setBaseAISetting<ESM::Creature>(id, setting, value);
|
||||
}
|
||||
|
||||
void Creature::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const
|
||||
{
|
||||
MWMechanics::modifyBaseInventory<ESM::Creature>(actorId, itemId, amount);
|
||||
}
|
||||
|
||||
float Creature::getWalkSpeed(const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
|
|
|
@ -142,6 +142,8 @@ namespace MWClass
|
|||
|
||||
virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const;
|
||||
|
||||
virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const;
|
||||
|
||||
float getWalkSpeed(const MWWorld::Ptr& ptr) const final;
|
||||
|
||||
float getRunSpeed(const MWWorld::Ptr& ptr) const final;
|
||||
|
|
|
@ -175,7 +175,7 @@ namespace
|
|||
*
|
||||
* and by adding class, race, specialization bonus.
|
||||
*/
|
||||
void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr)
|
||||
void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr, bool spellsInitialised)
|
||||
{
|
||||
const ESM::Class *class_ =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
|
||||
|
@ -252,9 +252,11 @@ namespace
|
|||
for (int i=0; i<ESM::Attribute::Length; ++i)
|
||||
attributes[i] = npcStats.getAttribute(i).getBase();
|
||||
|
||||
if (!spellsInitialised)
|
||||
{
|
||||
std::vector<std::string> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race);
|
||||
for (std::vector<std::string>::iterator it = spells.begin(); it != spells.end(); ++it)
|
||||
npcStats.getSpells().add(*it);
|
||||
npcStats.getSpells().addAllToInstance(spells);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,6 +330,8 @@ namespace MWClass
|
|||
|
||||
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
|
||||
|
||||
bool spellsInitialised = data->mNpcStats.getSpells().setSpells(ref->mBase->mId);
|
||||
|
||||
// creature stats
|
||||
int gold=0;
|
||||
if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||
|
@ -368,7 +372,7 @@ namespace MWClass
|
|||
data->mNpcStats.setReputation(ref->mBase->mNpdt.mReputation);
|
||||
|
||||
autoCalculateAttributes(ref->mBase, data->mNpcStats);
|
||||
autoCalculateSkills(ref->mBase, data->mNpcStats, ptr);
|
||||
autoCalculateSkills(ref->mBase, data->mNpcStats, ptr, spellsInitialised);
|
||||
|
||||
data->mNpcStats.setNeedRecalcDynamicStats(true);
|
||||
}
|
||||
|
@ -379,14 +383,7 @@ namespace MWClass
|
|||
|
||||
// race powers
|
||||
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
|
||||
for (std::vector<std::string>::const_iterator iter (race->mPowers.mList.begin());
|
||||
iter!=race->mPowers.mList.end(); ++iter)
|
||||
{
|
||||
if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
|
||||
data->mNpcStats.getSpells().add (spell);
|
||||
else
|
||||
Log(Debug::Warning) << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'";
|
||||
}
|
||||
data->mNpcStats.getSpells().addAllToInstance(race->mPowers.mList);
|
||||
|
||||
if (!ref->mBase->mFaction.empty())
|
||||
{
|
||||
|
@ -407,17 +404,8 @@ namespace MWClass
|
|||
data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
|
||||
|
||||
// spells
|
||||
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
|
||||
iter!=ref->mBase->mSpells.mList.end(); ++iter)
|
||||
{
|
||||
if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(*iter))
|
||||
data->mNpcStats.getSpells().add (spell);
|
||||
else
|
||||
{
|
||||
/// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility
|
||||
Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << *iter << "' on NPC '" << ref->mBase->mId << "'";
|
||||
}
|
||||
}
|
||||
if (!spellsInitialised)
|
||||
data->mNpcStats.getSpells().addAllToInstance(ref->mBase->mSpells.mList);
|
||||
|
||||
// inventory
|
||||
// setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items
|
||||
|
@ -1127,13 +1115,19 @@ namespace MWClass
|
|||
}
|
||||
else if (!stats.getAiSequence().isInCombat())
|
||||
{
|
||||
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) || stats.getKnockedDown())
|
||||
if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor))
|
||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
|
||||
|
||||
// Can't talk to werewolves
|
||||
if (!getNpcStats(ptr).isWerewolf())
|
||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
|
||||
}
|
||||
else // In combat
|
||||
{
|
||||
const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game");
|
||||
if (stealingInCombat && stats.getKnockedDown())
|
||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
|
||||
}
|
||||
|
||||
// Tribunal and some mod companions oddly enough must use open action as fallback
|
||||
if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion"))
|
||||
|
@ -1285,7 +1279,14 @@ namespace MWClass
|
|||
if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished())
|
||||
return true;
|
||||
|
||||
return !customData.mNpcStats.getAiSequence().isInCombat();
|
||||
if (!customData.mNpcStats.getAiSequence().isInCombat())
|
||||
return true;
|
||||
|
||||
const bool stealingInCombat = Settings::Manager::getBool ("always allow stealing from knocked out actors", "Game");
|
||||
if (stealingInCombat && customData.mNpcStats.getKnockedDown())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
|
||||
|
@ -1536,6 +1537,9 @@ namespace MWClass
|
|||
const ESM::NpcState& npcState = state.asNpcState();
|
||||
customData.mInventoryStore.readState (npcState.mInventory);
|
||||
customData.mNpcStats.readState (npcState.mNpcStats);
|
||||
bool spellsInitialised = customData.mNpcStats.getSpells().setSpells(ptr.get<ESM::NPC>()->mBase->mId);
|
||||
if(spellsInitialised)
|
||||
customData.mNpcStats.getSpells().clear();
|
||||
customData.mNpcStats.readState (npcState.mCreatureStats);
|
||||
}
|
||||
|
||||
|
@ -1670,6 +1674,11 @@ namespace MWClass
|
|||
MWMechanics::setBaseAISetting<ESM::NPC>(id, setting, value);
|
||||
}
|
||||
|
||||
void Npc::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const
|
||||
{
|
||||
MWMechanics::modifyBaseInventory<ESM::NPC>(actorId, itemId, amount);
|
||||
}
|
||||
|
||||
float Npc::getWalkSpeed(const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
const GMST& gmst = getGmst();
|
||||
|
|
|
@ -177,6 +177,8 @@ namespace MWClass
|
|||
|
||||
virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const;
|
||||
|
||||
virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const;
|
||||
|
||||
float getWalkSpeed(const MWWorld::Ptr& ptr) const final;
|
||||
|
||||
float getRunSpeed(const MWWorld::Ptr& ptr) const final;
|
||||
|
|
|
@ -158,6 +158,7 @@ namespace MWDialogue
|
|||
mTalkedTo = creatureStats.hasTalkedToPlayer();
|
||||
|
||||
mActorKnownTopics.clear();
|
||||
mActorKnownTopicsFlag.clear();
|
||||
|
||||
//greeting
|
||||
const MWWorld::Store<ESM::Dialogue> &dialogs =
|
||||
|
@ -350,11 +351,11 @@ namespace MWDialogue
|
|||
}
|
||||
}
|
||||
|
||||
mLastTopic = topic;
|
||||
|
||||
executeScript (info->mResultScript, mActor);
|
||||
|
||||
parseText (info->mResponse);
|
||||
|
||||
mLastTopic = topic;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -373,6 +374,7 @@ namespace MWDialogue
|
|||
updateGlobals();
|
||||
|
||||
mActorKnownTopics.clear();
|
||||
mActorKnownTopicsFlag.clear();
|
||||
|
||||
const auto& dialogs = MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>();
|
||||
|
||||
|
|
|
@ -706,7 +706,6 @@ namespace MWGui
|
|||
{
|
||||
mHistoryContents.push_back(new Response(text, title, needMargin));
|
||||
updateHistory();
|
||||
updateTopics();
|
||||
}
|
||||
|
||||
void DialogueWindow::addMessageBox(const std::string& text)
|
||||
|
|
|
@ -179,6 +179,11 @@ namespace MWInput
|
|||
mAttemptJump = false;
|
||||
}
|
||||
|
||||
bool ActionManager::isPreviewModeEnabled()
|
||||
{
|
||||
return MWBase::Environment::get().getWorld()->isPreviewModeEnabled();
|
||||
}
|
||||
|
||||
void ActionManager::resetIdleTime()
|
||||
{
|
||||
if (mTimeIdle < 0)
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace MWInput
|
|||
|
||||
void setAttemptJump(bool enabled) { mAttemptJump = enabled; }
|
||||
|
||||
float getPreviewDelay() const { return mPreviewPOVDelay; };
|
||||
bool isPreviewModeEnabled();
|
||||
|
||||
private:
|
||||
void handleGuiArrowKey(int action);
|
||||
|
|
|
@ -89,7 +89,7 @@ namespace MWInput
|
|||
|
||||
bool ControllerManager::update(float dt)
|
||||
{
|
||||
mGamepadPreviewMode = mActionManager->getPreviewDelay() == 1.f;
|
||||
mGamepadPreviewMode = mActionManager->isPreviewModeEnabled();
|
||||
|
||||
if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled))
|
||||
{
|
||||
|
@ -287,7 +287,7 @@ namespace MWInput
|
|||
}
|
||||
else
|
||||
{
|
||||
if (mGamepadPreviewMode && arg.value) // Preview Mode Gamepad Zooming
|
||||
if (mGamepadPreviewMode) // Preview Mode Gamepad Zooming
|
||||
{
|
||||
if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT)
|
||||
{
|
||||
|
|
|
@ -2167,10 +2167,6 @@ namespace MWMechanics
|
|||
// One case where we need this is to make sure bound items are removed upon death
|
||||
stats.modifyMagicEffects(MWMechanics::MagicEffects());
|
||||
stats.getActiveSpells().clear();
|
||||
|
||||
if (!isPlayer)
|
||||
stats.getSpells().clear();
|
||||
|
||||
// Make sure spell effects are removed
|
||||
purgeSpellEffects(stats.getActorId());
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef OPENMW_MWMECHANICS_ACTORUTIL_H
|
||||
#define OPENMW_MWMECHANICS_ACTORUTIL_H
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <components/esm/loadcrea.hpp>
|
||||
#include <components/esm/loadnpc.hpp>
|
||||
|
||||
|
@ -53,8 +55,34 @@ namespace MWMechanics
|
|||
MWBase::Environment::get().getWorld()->createOverrideRecord(copy);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount)
|
||||
{
|
||||
ESM::NPC copy = *MWBase::Environment::get().getWorld()->getStore().get<ESM::NPC>().find(actorId);
|
||||
for(auto& it : copy.mInventory.mList)
|
||||
{
|
||||
if(Misc::StringUtils::ciEqual(it.mItem, itemId))
|
||||
{
|
||||
int sign = it.mCount < 1 ? -1 : 1;
|
||||
it.mCount = sign * std::max(it.mCount * sign + amount, 0);
|
||||
MWBase::Environment::get().getWorld()->createOverrideRecord(copy);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(amount > 0)
|
||||
{
|
||||
ESM::ContItem cont;
|
||||
cont.mItem = itemId;
|
||||
cont.mCount = amount;
|
||||
copy.mInventory.mList.push_back(cont);
|
||||
MWBase::Environment::get().getWorld()->createOverrideRecord(copy);
|
||||
}
|
||||
}
|
||||
|
||||
template void setBaseAISetting<ESM::Creature>(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value);
|
||||
template void setBaseAISetting<ESM::NPC>(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value);
|
||||
template void modifyBaseInventory<ESM::Creature>(const std::string& actorId, const std::string& itemId, int amount);
|
||||
template void modifyBaseInventory<ESM::NPC>(const std::string& actorId, const std::string& itemId, int amount);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace MWMechanics
|
|||
continue;
|
||||
|
||||
float resist = 0.f;
|
||||
if (spells.hasCorprusEffect(spell))
|
||||
if (Spells::hasCorprusEffect(spell))
|
||||
resist = 1.f - 0.01f * (actorEffects.get(ESM::MagicEffect::ResistCorprusDisease).getMagnitude()
|
||||
- actorEffects.get(ESM::MagicEffect::WeaknessToCorprusDisease).getMagnitude());
|
||||
else if (spell->mData.mType == ESM::Spell::ST_Disease)
|
||||
|
|
|
@ -97,7 +97,7 @@ namespace MWMechanics
|
|||
|
||||
// reset
|
||||
creatureStats.setLevel(player->mNpdt.mLevel);
|
||||
creatureStats.getSpells().clear();
|
||||
creatureStats.getSpells().clear(true);
|
||||
creatureStats.modifyMagicEffects(MagicEffects());
|
||||
|
||||
for (int i=0; i<27; ++i)
|
||||
|
|
175
apps/openmw/mwmechanics/spelllist.cpp
Normal file
175
apps/openmw/mwmechanics/spelllist.cpp
Normal file
|
@ -0,0 +1,175 @@
|
|||
#include "spelllist.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <components/esm/loadspel.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include "spells.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
template<class T>
|
||||
const std::vector<std::string> getSpellList(const std::string& id)
|
||||
{
|
||||
return MWBase::Environment::get().getWorld()->getStore().get<T>().find(id)->mSpells.mList;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
bool withBaseRecord(const std::string& id, const std::function<bool(std::vector<std::string>&)>& function)
|
||||
{
|
||||
T copy = *MWBase::Environment::get().getWorld()->getStore().get<T>().find(id);
|
||||
bool changed = function(copy.mSpells.mList);
|
||||
if(changed)
|
||||
MWBase::Environment::get().getWorld()->createOverrideRecord(copy);
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
SpellList::SpellList(const std::string& id, int type) : mId(id), mType(type) {}
|
||||
|
||||
bool SpellList::withBaseRecord(const std::function<bool(std::vector<std::string>&)>& function)
|
||||
{
|
||||
switch(mType)
|
||||
{
|
||||
case ESM::REC_CREA:
|
||||
return ::withBaseRecord<ESM::Creature>(mId, function);
|
||||
case ESM::REC_NPC_:
|
||||
return ::withBaseRecord<ESM::NPC>(mId, function);
|
||||
default:
|
||||
throw std::logic_error("failed to update base record for " + mId);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string> SpellList::getSpells() const
|
||||
{
|
||||
switch(mType)
|
||||
{
|
||||
case ESM::REC_CREA:
|
||||
return getSpellList<ESM::Creature>(mId);
|
||||
case ESM::REC_NPC_:
|
||||
return getSpellList<ESM::NPC>(mId);
|
||||
default:
|
||||
throw std::logic_error("failed to get spell list for " + mId);
|
||||
}
|
||||
}
|
||||
|
||||
const ESM::Spell* SpellList::getSpell(const std::string& id)
|
||||
{
|
||||
return MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(id);
|
||||
}
|
||||
|
||||
void SpellList::add (const ESM::Spell* spell)
|
||||
{
|
||||
auto& id = spell->mId;
|
||||
bool changed = withBaseRecord([&] (auto& spells)
|
||||
{
|
||||
for(auto it : spells)
|
||||
{
|
||||
if(Misc::StringUtils::ciEqual(id, it))
|
||||
return false;
|
||||
}
|
||||
spells.push_back(id);
|
||||
return true;
|
||||
});
|
||||
if(changed)
|
||||
{
|
||||
for(auto listener : mListeners)
|
||||
listener->addSpell(spell);
|
||||
}
|
||||
}
|
||||
|
||||
void SpellList::remove (const ESM::Spell* spell)
|
||||
{
|
||||
auto& id = spell->mId;
|
||||
bool changed = withBaseRecord([&] (auto& spells)
|
||||
{
|
||||
for(auto it = spells.begin(); it != spells.end(); it++)
|
||||
{
|
||||
if(Misc::StringUtils::ciEqual(id, *it))
|
||||
{
|
||||
spells.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if(changed)
|
||||
{
|
||||
for(auto listener : mListeners)
|
||||
listener->removeSpell(spell);
|
||||
}
|
||||
}
|
||||
|
||||
void SpellList::removeAll (const std::vector<std::string>& ids)
|
||||
{
|
||||
bool changed = withBaseRecord([&] (auto& spells)
|
||||
{
|
||||
const auto it = std::remove_if(spells.begin(), spells.end(), [&] (const auto& spell)
|
||||
{
|
||||
const auto isSpell = [&] (const auto& id) { return Misc::StringUtils::ciEqual(spell, id); };
|
||||
return ids.end() != std::find_if(ids.begin(), ids.end(), isSpell);
|
||||
});
|
||||
if (it == spells.end())
|
||||
return false;
|
||||
spells.erase(it, spells.end());
|
||||
return true;
|
||||
});
|
||||
if(changed)
|
||||
{
|
||||
for(auto listener : mListeners)
|
||||
{
|
||||
for(auto& id : ids)
|
||||
{
|
||||
const auto spell = getSpell(id);
|
||||
listener->removeSpell(spell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpellList::clear()
|
||||
{
|
||||
bool changed = withBaseRecord([] (auto& spells)
|
||||
{
|
||||
if(spells.empty())
|
||||
return false;
|
||||
spells.clear();
|
||||
return true;
|
||||
});
|
||||
if(changed)
|
||||
{
|
||||
for(auto listener : mListeners)
|
||||
listener->removeAllSpells();
|
||||
}
|
||||
}
|
||||
|
||||
void SpellList::addListener(Spells* spells)
|
||||
{
|
||||
for(const auto ptr : mListeners)
|
||||
{
|
||||
if(ptr == spells)
|
||||
return;
|
||||
}
|
||||
mListeners.push_back(spells);
|
||||
}
|
||||
|
||||
void SpellList::removeListener(Spells* spells)
|
||||
{
|
||||
for(auto it = mListeners.begin(); it != mListeners.end(); it++)
|
||||
{
|
||||
if(*it == spells)
|
||||
{
|
||||
mListeners.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
apps/openmw/mwmechanics/spelllist.hpp
Normal file
60
apps/openmw/mwmechanics/spelllist.hpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
#ifndef GAME_MWMECHANICS_SPELLLIST_H
|
||||
#define GAME_MWMECHANICS_SPELLLIST_H
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <components/esm/loadspel.hpp>
|
||||
|
||||
#include "magiceffects.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct SpellState;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
struct SpellParams
|
||||
{
|
||||
std::map<int, float> mEffectRands; // <effect index, normalised random magnitude>
|
||||
std::set<int> mPurgedEffects; // indices of purged effects
|
||||
};
|
||||
|
||||
class Spells;
|
||||
|
||||
class SpellList
|
||||
{
|
||||
const std::string mId;
|
||||
const int mType;
|
||||
std::vector<Spells*> mListeners;
|
||||
|
||||
bool withBaseRecord(const std::function<bool(std::vector<std::string>&)>& function);
|
||||
public:
|
||||
SpellList(const std::string& id, int type);
|
||||
|
||||
/// Get spell from ID, throws exception if not found
|
||||
static const ESM::Spell* getSpell(const std::string& id);
|
||||
|
||||
void add (const ESM::Spell* spell);
|
||||
///< Adding a spell that is already listed in *this is a no-op.
|
||||
|
||||
void remove (const ESM::Spell* spell);
|
||||
|
||||
void removeAll(const std::vector<std::string>& spells);
|
||||
|
||||
void clear();
|
||||
///< Remove all spells of all types.
|
||||
|
||||
void addListener(Spells* spells);
|
||||
|
||||
void removeListener(Spells* spells);
|
||||
|
||||
const std::vector<std::string> getSpells() const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,8 +1,10 @@
|
|||
#include "spells.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm/loadspel.hpp>
|
||||
#include <components/esm/spellstate.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
@ -33,46 +35,41 @@ namespace MWMechanics
|
|||
{
|
||||
}
|
||||
|
||||
Spells::TIterator Spells::begin() const
|
||||
std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::begin() const
|
||||
{
|
||||
return mSpells.begin();
|
||||
}
|
||||
|
||||
Spells::TIterator Spells::end() const
|
||||
std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::end() const
|
||||
{
|
||||
return mSpells.end();
|
||||
}
|
||||
|
||||
const ESM::Spell* Spells::getSpell(const std::string& id) const
|
||||
{
|
||||
return MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(id);
|
||||
}
|
||||
|
||||
void Spells::rebuildEffects() const
|
||||
{
|
||||
mEffects = MagicEffects();
|
||||
mSourcedEffects.clear();
|
||||
|
||||
for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter)
|
||||
for (const auto& iter : mSpells)
|
||||
{
|
||||
const ESM::Spell *spell = iter->first;
|
||||
const ESM::Spell *spell = iter.first;
|
||||
|
||||
if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight ||
|
||||
spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse)
|
||||
{
|
||||
int i=0;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it)
|
||||
for (const auto& effect : spell->mEffects.mList)
|
||||
{
|
||||
if (iter->second.mPurgedEffects.find(i) != iter->second.mPurgedEffects.end())
|
||||
if (iter.second.mPurgedEffects.find(i) != iter.second.mPurgedEffects.end())
|
||||
continue; // effect was purged
|
||||
|
||||
float random = 1.f;
|
||||
if (iter->second.mEffectRands.find(i) != iter->second.mEffectRands.end())
|
||||
random = iter->second.mEffectRands.at(i);
|
||||
if (iter.second.mEffectRands.find(i) != iter.second.mEffectRands.end())
|
||||
random = iter.second.mEffectRands.at(i);
|
||||
|
||||
float magnitude = it->mMagnMin + (it->mMagnMax - it->mMagnMin) * random;
|
||||
mEffects.add (*it, magnitude);
|
||||
mSourcedEffects[spell].add(MWMechanics::EffectKey(*it), magnitude);
|
||||
float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * random;
|
||||
mEffects.add (effect, magnitude);
|
||||
mSourcedEffects[spell].add(MWMechanics::EffectKey(effect), magnitude);
|
||||
|
||||
++i;
|
||||
}
|
||||
|
@ -82,7 +79,7 @@ namespace MWMechanics
|
|||
|
||||
bool Spells::hasSpell(const std::string &spell) const
|
||||
{
|
||||
return hasSpell(getSpell(spell));
|
||||
return hasSpell(SpellList::getSpell(spell));
|
||||
}
|
||||
|
||||
bool Spells::hasSpell(const ESM::Spell *spell) const
|
||||
|
@ -91,6 +88,16 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
void Spells::add (const ESM::Spell* spell)
|
||||
{
|
||||
mSpellList->add(spell);
|
||||
}
|
||||
|
||||
void Spells::add (const std::string& spellId)
|
||||
{
|
||||
add(SpellList::getSpell(spellId));
|
||||
}
|
||||
|
||||
void Spells::addSpell(const ESM::Spell* spell)
|
||||
{
|
||||
if (mSpells.find (spell)==mSpells.end())
|
||||
{
|
||||
|
@ -117,26 +124,26 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
void Spells::add (const std::string& spellId)
|
||||
{
|
||||
add(getSpell(spellId));
|
||||
}
|
||||
|
||||
void Spells::remove (const std::string& spellId)
|
||||
{
|
||||
const ESM::Spell* spell = getSpell(spellId);
|
||||
TContainer::iterator iter = mSpells.find (spell);
|
||||
|
||||
if (iter!=mSpells.end())
|
||||
{
|
||||
mSpells.erase (iter);
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
const auto spell = SpellList::getSpell(spellId);
|
||||
removeSpell(spell);
|
||||
mSpellList->remove(spell);
|
||||
|
||||
if (spellId==mSelectedSpell)
|
||||
mSelectedSpell.clear();
|
||||
}
|
||||
|
||||
void Spells::removeSpell(const ESM::Spell* spell)
|
||||
{
|
||||
const auto it = mSpells.find(spell);
|
||||
if(it != mSpells.end())
|
||||
{
|
||||
mSpells.erase(it);
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
MagicEffects Spells::getMagicEffects() const
|
||||
{
|
||||
if (mSpellsChanged) {
|
||||
|
@ -146,12 +153,19 @@ namespace MWMechanics
|
|||
return mEffects;
|
||||
}
|
||||
|
||||
void Spells::clear()
|
||||
void Spells::removeAllSpells()
|
||||
{
|
||||
mSpells.clear();
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
|
||||
void Spells::clear(bool modifyBase)
|
||||
{
|
||||
removeAllSpells();
|
||||
if(modifyBase)
|
||||
mSpellList->clear();
|
||||
}
|
||||
|
||||
void Spells::setSelectedSpell (const std::string& spellId)
|
||||
{
|
||||
mSelectedSpell = spellId;
|
||||
|
@ -177,141 +191,88 @@ namespace MWMechanics
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Spells::hasCommonDisease() const
|
||||
bool Spells::hasDisease(const ESM::Spell::SpellType type) const
|
||||
{
|
||||
for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter)
|
||||
for (const auto& iter : mSpells)
|
||||
{
|
||||
const ESM::Spell *spell = iter->first;
|
||||
if (spell->mData.mType == ESM::Spell::ST_Disease)
|
||||
const ESM::Spell *spell = iter.first;
|
||||
if (spell->mData.mType == type)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Spells::hasCommonDisease() const
|
||||
{
|
||||
return hasDisease(ESM::Spell::ST_Disease);
|
||||
}
|
||||
|
||||
bool Spells::hasBlightDisease() const
|
||||
{
|
||||
for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter)
|
||||
{
|
||||
const ESM::Spell *spell = iter->first;
|
||||
if (spell->mData.mType == ESM::Spell::ST_Blight)
|
||||
return true;
|
||||
return hasDisease(ESM::Spell::ST_Blight);
|
||||
}
|
||||
|
||||
return false;
|
||||
void Spells::purge(const SpellFilter& filter)
|
||||
{
|
||||
std::vector<std::string> purged;
|
||||
for (auto iter = mSpells.begin(); iter!=mSpells.end();)
|
||||
{
|
||||
const ESM::Spell *spell = iter->first;
|
||||
if (filter(spell))
|
||||
{
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
||||
Send an ID_PLAYER_SPELLBOOK packet every time a spell is purged here
|
||||
*/
|
||||
mwmp::Main::get().getLocalPlayer()->sendSpellChange(spell->mId, mwmp::SpellbookChanges::REMOVE);
|
||||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
mSpells.erase(iter++);
|
||||
purged.push_back(spell->mId);
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
if(!purged.empty())
|
||||
mSpellList->removeAll(purged);
|
||||
}
|
||||
|
||||
void Spells::purgeCommonDisease()
|
||||
{
|
||||
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
|
||||
{
|
||||
const ESM::Spell *spell = iter->first;
|
||||
if (spell->mData.mType == ESM::Spell::ST_Disease)
|
||||
{
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
||||
Send an ID_PLAYER_SPELLBOOK packet every time a player's common disease is removed
|
||||
*/
|
||||
mwmp::Main::get().getLocalPlayer()->sendSpellChange(spell->mId, mwmp::SpellbookChanges::REMOVE);
|
||||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
mSpells.erase(iter++);
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Disease; });
|
||||
}
|
||||
|
||||
void Spells::purgeBlightDisease()
|
||||
{
|
||||
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
|
||||
{
|
||||
const ESM::Spell *spell = iter->first;
|
||||
if (spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell))
|
||||
{
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
||||
Send an ID_PLAYER_SPELLBOOK packet every time a player's blight disease is removed
|
||||
*/
|
||||
mwmp::Main::get().getLocalPlayer()->sendSpellChange(spell->mId, mwmp::SpellbookChanges::REMOVE);
|
||||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
mSpells.erase(iter++);
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Blight && !hasCorprusEffect(spell); });
|
||||
}
|
||||
|
||||
void Spells::purgeCorprusDisease()
|
||||
{
|
||||
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
|
||||
{
|
||||
const ESM::Spell *spell = iter->first;
|
||||
if (hasCorprusEffect(spell))
|
||||
{
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
||||
Send an ID_PLAYER_SPELLBOOK packet every time a player's corprus disease is removed
|
||||
*/
|
||||
mwmp::Main::get().getLocalPlayer()->sendSpellChange(spell->mId, mwmp::SpellbookChanges::REMOVE);
|
||||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
mSpells.erase(iter++);
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
purge(&hasCorprusEffect);
|
||||
}
|
||||
|
||||
void Spells::purgeCurses()
|
||||
{
|
||||
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
|
||||
{
|
||||
const ESM::Spell *spell = iter->first;
|
||||
if (spell->mData.mType == ESM::Spell::ST_Curse)
|
||||
{
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
||||
Send an ID_PLAYER_SPELLBOOK packet every time a player's curse is removed
|
||||
*/
|
||||
mwmp::Main::get().getLocalPlayer()->sendSpellChange(spell->mId, mwmp::SpellbookChanges::REMOVE);
|
||||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
mSpells.erase(iter++);
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Curse; });
|
||||
}
|
||||
|
||||
void Spells::removeEffects(const std::string &id)
|
||||
{
|
||||
if (isSpellActive(id))
|
||||
{
|
||||
for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell)
|
||||
for (auto& spell : mSpells)
|
||||
{
|
||||
if (spell->first == getSpell(id))
|
||||
if (spell.first == SpellList::getSpell(id))
|
||||
{
|
||||
for (long unsigned int i = 0; i != spell->first->mEffects.mList.size(); i++)
|
||||
for (long unsigned int i = 0; i != spell.first->mEffects.mList.size(); i++)
|
||||
{
|
||||
spell->second.mPurgedEffects.insert(i);
|
||||
spell.second.mPurgedEffects.insert(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -327,23 +288,21 @@ namespace MWMechanics
|
|||
mSpellsChanged = false;
|
||||
}
|
||||
|
||||
for (std::map<SpellKey, MagicEffects>::const_iterator it = mSourcedEffects.begin();
|
||||
it != mSourcedEffects.end(); ++it)
|
||||
for (const auto& it : mSourcedEffects)
|
||||
{
|
||||
const ESM::Spell * spell = it->first;
|
||||
for (MagicEffects::Collection::const_iterator effectIt = it->second.begin();
|
||||
effectIt != it->second.end(); ++effectIt)
|
||||
const ESM::Spell * spell = it.first;
|
||||
for (const auto& effectIt : it.second)
|
||||
{
|
||||
visitor.visit(effectIt->first, spell->mName, spell->mId, -1, effectIt->second.getMagnitude());
|
||||
visitor.visit(effectIt.first, spell->mName, spell->mId, -1, effectIt.second.getMagnitude());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Spells::hasCorprusEffect(const ESM::Spell *spell)
|
||||
{
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt)
|
||||
for (const auto& effectIt : spell->mEffects.mList)
|
||||
{
|
||||
if (effectIt->mEffectID == ESM::MagicEffect::Corprus)
|
||||
if (effectIt.mEffectID == ESM::MagicEffect::Corprus)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -353,14 +312,14 @@ namespace MWMechanics
|
|||
|
||||
void Spells::purgeEffect(int effectId)
|
||||
{
|
||||
for (TContainer::iterator spellIt = mSpells.begin(); spellIt != mSpells.end(); ++spellIt)
|
||||
for (auto& spellIt : mSpells)
|
||||
{
|
||||
int i = 0;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spellIt->first->mEffects.mList.begin(); effectIt != spellIt->first->mEffects.mList.end(); ++effectIt)
|
||||
for (auto& effectIt : spellIt.first->mEffects.mList)
|
||||
{
|
||||
if (effectIt->mEffectID == effectId)
|
||||
if (effectIt.mEffectID == effectId)
|
||||
{
|
||||
spellIt->second.mPurgedEffects.insert(i);
|
||||
spellIt.second.mPurgedEffects.insert(i);
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
++i;
|
||||
|
@ -370,15 +329,15 @@ namespace MWMechanics
|
|||
|
||||
void Spells::purgeEffect(int effectId, const std::string & sourceId)
|
||||
{
|
||||
const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(sourceId);
|
||||
TContainer::iterator spellIt = mSpells.find(spell);
|
||||
const ESM::Spell * spell = SpellList::getSpell(sourceId);
|
||||
auto spellIt = mSpells.find(spell);
|
||||
if (spellIt == mSpells.end())
|
||||
return;
|
||||
|
||||
int i = 0;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spellIt->first->mEffects.mList.begin(); effectIt != spellIt->first->mEffects.mList.end(); ++effectIt)
|
||||
for (auto& effectIt : spellIt->first->mEffects.mList)
|
||||
{
|
||||
if (effectIt->mEffectID == effectId)
|
||||
if (effectIt.mEffectID == effectId)
|
||||
{
|
||||
spellIt->second.mPurgedEffects.insert(i);
|
||||
mSpellsChanged = true;
|
||||
|
@ -389,11 +348,8 @@ namespace MWMechanics
|
|||
|
||||
bool Spells::canUsePower(const ESM::Spell* spell) const
|
||||
{
|
||||
std::map<SpellKey, MWWorld::TimeStamp>::const_iterator it = mUsedPowers.find(spell);
|
||||
if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
const auto it = mUsedPowers.find(spell);
|
||||
return it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp();
|
||||
}
|
||||
|
||||
void Spells::usePower(const ESM::Spell* spell)
|
||||
|
@ -403,6 +359,8 @@ namespace MWMechanics
|
|||
|
||||
void Spells::readState(const ESM::SpellState &state, CreatureStats* creatureStats)
|
||||
{
|
||||
const auto& baseSpells = mSpellList->getSpells();
|
||||
|
||||
for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it)
|
||||
{
|
||||
// Discard spells that are no longer available due to changed content files
|
||||
|
@ -416,6 +374,13 @@ namespace MWMechanics
|
|||
mSelectedSpell = it->first;
|
||||
}
|
||||
}
|
||||
// Add spells from the base record
|
||||
for(const std::string& id : baseSpells)
|
||||
{
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(id);
|
||||
if(spell)
|
||||
addSpell(spell);
|
||||
}
|
||||
|
||||
for (std::map<std::string, ESM::TimeStamp>::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it)
|
||||
{
|
||||
|
@ -487,17 +452,50 @@ namespace MWMechanics
|
|||
|
||||
void Spells::writeState(ESM::SpellState &state) const
|
||||
{
|
||||
for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
||||
const auto& baseSpells = mSpellList->getSpells();
|
||||
for (const auto& it : mSpells)
|
||||
{
|
||||
//Don't save spells stored in the base record
|
||||
if(std::find(baseSpells.begin(), baseSpells.end(), it.first->mId) == baseSpells.end())
|
||||
{
|
||||
ESM::SpellState::SpellParams params;
|
||||
params.mEffectRands = it->second.mEffectRands;
|
||||
params.mPurgedEffects = it->second.mPurgedEffects;
|
||||
state.mSpells.insert(std::make_pair(it->first->mId, params));
|
||||
params.mEffectRands = it.second.mEffectRands;
|
||||
params.mPurgedEffects = it.second.mPurgedEffects;
|
||||
state.mSpells.insert(std::make_pair(it.first->mId, params));
|
||||
}
|
||||
}
|
||||
|
||||
state.mSelectedSpell = mSelectedSpell;
|
||||
|
||||
for (std::map<SpellKey, MWWorld::TimeStamp>::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it)
|
||||
state.mUsedPowers[it->first->mId] = it->second.toEsm();
|
||||
for (const auto& it : mUsedPowers)
|
||||
state.mUsedPowers[it.first->mId] = it.second.toEsm();
|
||||
}
|
||||
|
||||
bool Spells::setSpells(const std::string& actorId)
|
||||
{
|
||||
bool result;
|
||||
std::tie(mSpellList, result) = MWBase::Environment::get().getWorld()->getStore().getSpellList(actorId);
|
||||
mSpellList->addListener(this);
|
||||
for(const auto& id : mSpellList->getSpells())
|
||||
addSpell(SpellList::getSpell(id));
|
||||
return result;
|
||||
}
|
||||
|
||||
void Spells::addAllToInstance(const std::vector<std::string>& spells)
|
||||
{
|
||||
for(const std::string& id : spells)
|
||||
{
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(id);
|
||||
if(spell)
|
||||
addSpell(spell);
|
||||
else
|
||||
Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << id << "'";
|
||||
}
|
||||
}
|
||||
|
||||
Spells::~Spells()
|
||||
{
|
||||
if(mSpellList)
|
||||
mSpellList->removeListener(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
#ifndef GAME_MWMECHANICS_SPELLS_H
|
||||
#define GAME_MWMECHANICS_SPELLS_H
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/timestamp.hpp"
|
||||
|
||||
#include "magiceffects.hpp"
|
||||
|
||||
#include "spelllist.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct Spell;
|
||||
|
||||
struct SpellState;
|
||||
}
|
||||
|
||||
|
@ -32,37 +29,36 @@ namespace MWMechanics
|
|||
/// diseases. It also keeps track of used powers (which can only be used every 24h).
|
||||
class Spells
|
||||
{
|
||||
public:
|
||||
|
||||
typedef const ESM::Spell* SpellKey;
|
||||
struct SpellParams
|
||||
{
|
||||
std::map<int, float> mEffectRands; // <effect index, normalised random magnitude>
|
||||
std::set<int> mPurgedEffects; // indices of purged effects
|
||||
};
|
||||
|
||||
typedef std::map<SpellKey, SpellParams> TContainer;
|
||||
typedef TContainer::const_iterator TIterator;
|
||||
|
||||
private:
|
||||
TContainer mSpells;
|
||||
std::shared_ptr<SpellList> mSpellList;
|
||||
std::map<const ESM::Spell*, SpellParams> mSpells;
|
||||
|
||||
// Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different)
|
||||
std::string mSelectedSpell;
|
||||
|
||||
std::map<SpellKey, MWWorld::TimeStamp> mUsedPowers;
|
||||
std::map<const ESM::Spell*, MWWorld::TimeStamp> mUsedPowers;
|
||||
|
||||
mutable bool mSpellsChanged;
|
||||
mutable MagicEffects mEffects;
|
||||
mutable std::map<SpellKey, MagicEffects> mSourcedEffects;
|
||||
mutable std::map<const ESM::Spell*, MagicEffects> mSourcedEffects;
|
||||
void rebuildEffects() const;
|
||||
|
||||
/// Get spell from ID, throws exception if not found
|
||||
const ESM::Spell* getSpell(const std::string& id) const;
|
||||
bool hasDisease(const ESM::Spell::SpellType type) const;
|
||||
|
||||
using SpellFilter = bool (*)(const ESM::Spell*);
|
||||
void purge(const SpellFilter& filter);
|
||||
|
||||
void addSpell(const ESM::Spell* spell);
|
||||
void removeSpell(const ESM::Spell* spell);
|
||||
void removeAllSpells();
|
||||
|
||||
friend class SpellList;
|
||||
public:
|
||||
using TIterator = std::map<const ESM::Spell*, SpellParams>::const_iterator;
|
||||
|
||||
Spells();
|
||||
|
||||
~Spells();
|
||||
|
||||
static bool hasCorprusEffect(const ESM::Spell *spell);
|
||||
|
||||
void purgeEffect(int effectId);
|
||||
|
@ -96,7 +92,7 @@ namespace MWMechanics
|
|||
MagicEffects getMagicEffects() const;
|
||||
///< Return sum of magic effects resulting from abilities, blights, deseases and curses.
|
||||
|
||||
void clear();
|
||||
void clear(bool modifyBase = false);
|
||||
///< Remove all spells of al types.
|
||||
|
||||
void setSelectedSpell (const std::string& spellId);
|
||||
|
@ -118,6 +114,10 @@ namespace MWMechanics
|
|||
|
||||
void readState (const ESM::SpellState& state, CreatureStats* creatureStats);
|
||||
void writeState (ESM::SpellState& state) const;
|
||||
|
||||
bool setSpells(const std::string& id);
|
||||
|
||||
void addAllToInstance(const std::vector<std::string>& spells);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -372,6 +372,7 @@ namespace MWRender
|
|||
|
||||
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip));
|
||||
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance));
|
||||
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false));
|
||||
|
||||
mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near");
|
||||
mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far");
|
||||
|
|
|
@ -76,6 +76,13 @@ namespace MWScript
|
|||
|| ::Misc::StringUtils::ciEqual(item, "gold_100"))
|
||||
item = "gold_001";
|
||||
|
||||
// Explicit calls to non-unique actors affect the base record
|
||||
if(!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getWorld()->getStore().getRefCount(ptr.getCellRef().getRefId()) > 1)
|
||||
{
|
||||
ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, count);
|
||||
return;
|
||||
}
|
||||
|
||||
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
|
||||
|
||||
/*
|
||||
|
@ -207,6 +214,13 @@ namespace MWScript
|
|||
|| ::Misc::StringUtils::ciEqual(item, "gold_100"))
|
||||
item = "gold_001";
|
||||
|
||||
// Explicit calls to non-unique actors affect the base record
|
||||
if(!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getWorld()->getStore().getRefCount(ptr.getCellRef().getRefId()) > 1)
|
||||
{
|
||||
ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count);
|
||||
return;
|
||||
}
|
||||
|
||||
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr);
|
||||
|
||||
std::string itemName;
|
||||
|
|
|
@ -314,6 +314,15 @@ namespace MWScript
|
|||
return iter->second->mLocals;
|
||||
}
|
||||
|
||||
const Locals* GlobalScripts::getLocalsIfPresent (const std::string& name) const
|
||||
{
|
||||
std::string name2 = ::Misc::StringUtils::lowerCase (name);
|
||||
auto iter = mScripts.find (name2);
|
||||
if (iter==mScripts.end())
|
||||
return nullptr;
|
||||
return &iter->second->mLocals;
|
||||
}
|
||||
|
||||
void GlobalScripts::updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated)
|
||||
{
|
||||
MatchPtrVisitor visitor(base);
|
||||
|
|
|
@ -82,6 +82,8 @@ namespace MWScript
|
|||
///< If the script \a name has not been added as a global script yet, it is added
|
||||
/// automatically, but is not set to running state.
|
||||
|
||||
const Locals* getLocalsIfPresent (const std::string& name) const;
|
||||
|
||||
void updatePtrs(const MWWorld::Ptr& base, const MWWorld::Ptr& updated);
|
||||
///< Update the Ptrs stored in mTarget. Should be called after the reference has been moved to a new cell.
|
||||
};
|
||||
|
|
|
@ -442,15 +442,13 @@ namespace MWScript
|
|||
|
||||
std::vector<std::string> InterpreterContext::getGlobals() const
|
||||
{
|
||||
std::vector<std::string> ids;
|
||||
|
||||
const MWWorld::Store<ESM::Global>& globals =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Global>();
|
||||
|
||||
for (MWWorld::Store<ESM::Global>::iterator iter = globals.begin(); iter!=globals.end();
|
||||
++iter)
|
||||
std::vector<std::string> ids;
|
||||
for (auto& globalVariable : globals)
|
||||
{
|
||||
ids.push_back (iter->mId);
|
||||
ids.emplace_back(globalVariable.mId);
|
||||
}
|
||||
|
||||
return ids;
|
||||
|
@ -462,22 +460,22 @@ namespace MWScript
|
|||
return world->getGlobalVariableType(name);
|
||||
}
|
||||
|
||||
std::string InterpreterContext::getActionBinding(const std::string& action) const
|
||||
std::string InterpreterContext::getActionBinding(const std::string& targetAction) const
|
||||
{
|
||||
MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
|
||||
std::vector<int> actions = input->getActionKeySorting ();
|
||||
for (std::vector<int>::const_iterator it = actions.begin(); it != actions.end(); ++it)
|
||||
for (const int action : actions)
|
||||
{
|
||||
std::string desc = input->getActionDescription (*it);
|
||||
std::string desc = input->getActionDescription (action);
|
||||
if(desc == "")
|
||||
continue;
|
||||
|
||||
if(desc == action)
|
||||
if(desc == targetAction)
|
||||
{
|
||||
if(input->joystickLastUsed())
|
||||
return input->getActionControllerBindingName(*it);
|
||||
return input->getActionControllerBindingName(action);
|
||||
else
|
||||
return input->getActionKeyBindingName (*it);
|
||||
return input->getActionKeyBindingName(action);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "locals.hpp"
|
||||
#include "globalscripts.hpp"
|
||||
|
||||
#include <components/esm/loadscpt.hpp>
|
||||
#include <components/esm/variant.hpp>
|
||||
|
@ -33,6 +34,15 @@ namespace MWScript
|
|||
if (mInitialised)
|
||||
return false;
|
||||
|
||||
const Locals* global = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocalsIfPresent(script.mId);
|
||||
if(global)
|
||||
{
|
||||
mShorts = global->mShorts;
|
||||
mLongs = global->mLongs;
|
||||
mFloats = global->mFloats;
|
||||
}
|
||||
else
|
||||
{
|
||||
const Compiler::Locals& locals =
|
||||
MWBase::Environment::get().getScriptManager()->getLocals (script.mId);
|
||||
|
||||
|
@ -42,6 +52,7 @@ namespace MWScript
|
|||
mLongs.resize (locals.get ('l').size(), 0);
|
||||
mFloats.clear();
|
||||
mFloats.resize (locals.get ('f').size(), 0);
|
||||
}
|
||||
|
||||
mInitialised = true;
|
||||
return true;
|
||||
|
|
|
@ -59,9 +59,9 @@ namespace
|
|||
|
||||
void addToLevList(ESM::LevelledListBase* list, const std::string& itemId, int level)
|
||||
{
|
||||
for (std::vector<ESM::LevelledListBase::LevelItem>::iterator it = list->mList.begin(); it != list->mList.end(); ++it)
|
||||
for (auto& levelItem : list->mList)
|
||||
{
|
||||
if (it->mLevel == level && itemId == it->mId)
|
||||
if (levelItem.mLevel == level && itemId == levelItem.mId)
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -735,9 +735,9 @@ namespace MWScript
|
|||
effects += store.getMagicEffects();
|
||||
}
|
||||
|
||||
for (MWMechanics::MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it)
|
||||
for (const auto& effect : effects)
|
||||
{
|
||||
if (it->first.mId == key && it->second.getModifier() > 0)
|
||||
if (effect.first.mId == key && effect.second.getModifier() > 0)
|
||||
{
|
||||
runtime.push(1);
|
||||
return;
|
||||
|
|
|
@ -151,16 +151,17 @@ namespace MWScript
|
|||
|
||||
const MWWorld::Store<ESM::Script>& scripts = mStore.get<ESM::Script>();
|
||||
|
||||
for (MWWorld::Store<ESM::Script>::iterator iter = scripts.begin();
|
||||
iter != scripts.end(); ++iter)
|
||||
for (auto& script : mStore.get<ESM::Script>())
|
||||
{
|
||||
if (!std::binary_search (mScriptBlacklist.begin(), mScriptBlacklist.end(),
|
||||
Misc::StringUtils::lowerCase (iter->mId)))
|
||||
Misc::StringUtils::lowerCase(script.mId)))
|
||||
{
|
||||
++count;
|
||||
|
||||
if (compile (iter->mId))
|
||||
if (compile(script.mId))
|
||||
++success;
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair (count, success);
|
||||
}
|
||||
|
@ -195,7 +196,7 @@ namespace MWScript
|
|||
scanner.scan (parser);
|
||||
|
||||
std::map<std::string, Compiler::Locals>::iterator iter =
|
||||
mOtherLocals.insert (std::make_pair (name2, locals)).first;
|
||||
mOtherLocals.emplace(name2, locals).first;
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
|
|
@ -535,6 +535,11 @@ namespace MWWorld
|
|||
throw std::runtime_error ("class does not have creature stats");
|
||||
}
|
||||
|
||||
void Class::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const
|
||||
{
|
||||
throw std::runtime_error ("class does not have an inventory store");
|
||||
}
|
||||
|
||||
float Class::getWalkSpeed(const Ptr& /*ptr*/) const
|
||||
{
|
||||
return 0;
|
||||
|
|
|
@ -374,6 +374,8 @@ namespace MWWorld
|
|||
|
||||
virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const;
|
||||
|
||||
virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const;
|
||||
|
||||
virtual float getWalkSpeed(const Ptr& ptr) const;
|
||||
|
||||
virtual float getRunSpeed(const Ptr& ptr) const;
|
||||
|
|
|
@ -9,6 +9,47 @@
|
|||
#include <components/esm/esmreader.hpp>
|
||||
#include <components/esm/esmwriter.hpp>
|
||||
|
||||
#include "../mwmechanics/spelllist.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
void readRefs(const ESM::Cell& cell, std::map<ESM::RefNum, std::string>& refs, std::vector<ESM::ESMReader>& readers)
|
||||
{
|
||||
for (size_t i = 0; i < cell.mContextList.size(); i++)
|
||||
{
|
||||
size_t index = cell.mContextList[i].index;
|
||||
if (readers.size() <= index)
|
||||
readers.resize(index + 1);
|
||||
cell.restore(readers[index], i);
|
||||
ESM::CellRef ref;
|
||||
ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
|
||||
bool deleted = false;
|
||||
while(cell.getNextRef(readers[index], ref, deleted))
|
||||
{
|
||||
if(deleted)
|
||||
refs.erase(ref.mRefNum);
|
||||
else if (std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum) == cell.mMovedRefs.end())
|
||||
{
|
||||
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
|
||||
refs[ref.mRefNum] = ref.mRefID;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(const auto& it : cell.mLeasedRefs)
|
||||
{
|
||||
bool deleted = it.second;
|
||||
if(deleted)
|
||||
refs.erase(it.first.mRefNum);
|
||||
else
|
||||
{
|
||||
ESM::CellRef ref = it.first;
|
||||
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
|
||||
refs[ref.mRefNum] = ref.mRefID;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
|
||||
|
@ -146,7 +187,33 @@ void ESMStore::setUp(bool validateRecords)
|
|||
mDialogs.setUp();
|
||||
|
||||
if (validateRecords)
|
||||
{
|
||||
validate();
|
||||
countRecords();
|
||||
}
|
||||
}
|
||||
|
||||
void ESMStore::countRecords()
|
||||
{
|
||||
if(!mRefCount.empty())
|
||||
return;
|
||||
std::map<ESM::RefNum, std::string> refs;
|
||||
std::vector<ESM::ESMReader> readers;
|
||||
for(auto it = mCells.intBegin(); it != mCells.intEnd(); it++)
|
||||
readRefs(*it, refs, readers);
|
||||
for(auto it = mCells.extBegin(); it != mCells.extEnd(); it++)
|
||||
readRefs(*it, refs, readers);
|
||||
for(const auto& pair : refs)
|
||||
mRefCount[pair.second]++;
|
||||
}
|
||||
|
||||
int ESMStore::getRefCount(const std::string& id) const
|
||||
{
|
||||
const std::string lowerId = Misc::StringUtils::lowerCase(id);
|
||||
auto it = mRefCount.find(lowerId);
|
||||
if(it == mRefCount.end())
|
||||
return 0;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void ESMStore::validate()
|
||||
|
@ -344,4 +411,23 @@ void ESMStore::validate()
|
|||
throw std::runtime_error ("Invalid player record (race or class unavailable");
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<MWMechanics::SpellList>, bool> ESMStore::getSpellList(const std::string& originalId) const
|
||||
{
|
||||
const std::string id = Misc::StringUtils::lowerCase(originalId);
|
||||
auto result = mSpellListCache.find(id);
|
||||
std::shared_ptr<MWMechanics::SpellList> ptr;
|
||||
if (result != mSpellListCache.end())
|
||||
ptr = result->second.lock();
|
||||
if (!ptr)
|
||||
{
|
||||
int type = find(id);
|
||||
ptr = std::make_shared<MWMechanics::SpellList>(id, type);
|
||||
if (result != mSpellListCache.end())
|
||||
result->second = ptr;
|
||||
else
|
||||
mSpellListCache.insert({id, ptr});
|
||||
return {ptr, false};
|
||||
}
|
||||
return {ptr, true};
|
||||
}
|
||||
} // end namespace
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef OPENMW_MWWORLD_ESMSTORE_H
|
||||
#define OPENMW_MWWORLD_ESMSTORE_H
|
||||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
|
@ -12,6 +13,11 @@ namespace Loading
|
|||
class Listener;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class SpellList;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class ESMStore
|
||||
|
@ -70,15 +76,20 @@ namespace MWWorld
|
|||
std::map<std::string, int> mIds;
|
||||
std::map<std::string, int> mStaticIds;
|
||||
|
||||
std::map<std::string, int> mRefCount;
|
||||
|
||||
std::map<int, StoreBase *> mStores;
|
||||
|
||||
ESM::NPC mPlayerTemplate;
|
||||
|
||||
unsigned int mDynamicCount;
|
||||
|
||||
mutable std::map<std::string, std::weak_ptr<MWMechanics::SpellList> > mSpellListCache;
|
||||
|
||||
/// Validate entries in store after setup
|
||||
void validate();
|
||||
|
||||
void countRecords();
|
||||
public:
|
||||
/// \todo replace with SharedIterator<StoreBase>
|
||||
typedef std::map<int, StoreBase *>::const_iterator iterator;
|
||||
|
@ -252,6 +263,10 @@ namespace MWWorld
|
|||
|
||||
// To be called when we are done with dynamic record loading
|
||||
void checkPlayer();
|
||||
|
||||
int getRefCount(const std::string& id) const;
|
||||
|
||||
std::pair<std::shared_ptr<MWMechanics::SpellList>, bool> getSpellList(const std::string& id) const;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -2650,6 +2650,11 @@ namespace MWWorld
|
|||
return mRendering->getCamera()->isFirstPerson();
|
||||
}
|
||||
|
||||
bool World::isPreviewModeEnabled() const
|
||||
{
|
||||
return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::Preview;
|
||||
}
|
||||
|
||||
void World::togglePreviewMode(bool enable)
|
||||
{
|
||||
mRendering->togglePreviewMode(enable);
|
||||
|
|
|
@ -634,6 +634,7 @@ namespace MWWorld
|
|||
void togglePOV(bool force = false) override;
|
||||
|
||||
bool isFirstPerson() const override;
|
||||
bool isPreviewModeEnabled() const override;
|
||||
|
||||
void togglePreviewMode(bool enable) override;
|
||||
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
#include <components/loadinglistener/loadinglistener.hpp>
|
||||
|
||||
#include "apps/openmw/mwworld/esmstore.hpp"
|
||||
#include "apps/openmw/mwmechanics/spelllist.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
SpellList::SpellList(const std::string& id, int type) : mId(id), mType(type) {}
|
||||
}
|
||||
|
||||
static Loading::Listener dummyListener;
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ add_component_dir (nif
|
|||
)
|
||||
|
||||
add_component_dir (nifosg
|
||||
nifloader controller particle userdata
|
||||
nifloader controller particle matrixtransform
|
||||
)
|
||||
|
||||
add_component_dir (nifbullet
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "esmwriter.hpp"
|
||||
|
||||
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
|
||||
int ESM::SavedGame::sCurrentFormat = 12;
|
||||
int ESM::SavedGame::sCurrentFormat = 13;
|
||||
|
||||
void ESM::SavedGame::load (ESMReader &esm)
|
||||
{
|
||||
|
|
|
@ -4,14 +4,13 @@
|
|||
#include <osg/TexMat>
|
||||
#include <osg/Material>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/UserDataContainer>
|
||||
|
||||
#include <osgParticle/Emitter>
|
||||
|
||||
#include <components/nif/data.hpp>
|
||||
#include <components/sceneutil/morphgeometry.hpp>
|
||||
|
||||
#include "userdata.hpp"
|
||||
#include "matrixtransform.hpp"
|
||||
|
||||
namespace NifOsg
|
||||
{
|
||||
|
@ -119,13 +118,12 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv)
|
|||
{
|
||||
if (hasInput())
|
||||
{
|
||||
osg::MatrixTransform* trans = static_cast<osg::MatrixTransform*>(node);
|
||||
NifOsg::MatrixTransform* trans = static_cast<NifOsg::MatrixTransform*>(node);
|
||||
osg::Matrix mat = trans->getMatrix();
|
||||
|
||||
float time = getInputValue(nv);
|
||||
|
||||
NodeUserData* userdata = static_cast<NodeUserData*>(trans->getUserDataContainer()->getUserObject(0));
|
||||
Nif::Matrix3& rot = userdata->mRotationScale;
|
||||
Nif::Matrix3& rot = trans->mRotationScale;
|
||||
|
||||
bool setRot = false;
|
||||
if(!mRotations.empty())
|
||||
|
@ -140,18 +138,18 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv)
|
|||
}
|
||||
else
|
||||
{
|
||||
// no rotation specified, use the previous value from the UserData
|
||||
// no rotation specified, use the previous value
|
||||
for (int i=0;i<3;++i)
|
||||
for (int j=0;j<3;++j)
|
||||
mat(j,i) = rot.mValues[i][j]; // NB column/row major difference
|
||||
}
|
||||
|
||||
if (setRot) // copy the new values back to the UserData
|
||||
if (setRot) // copy the new values back
|
||||
for (int i=0;i<3;++i)
|
||||
for (int j=0;j<3;++j)
|
||||
rot.mValues[i][j] = mat(j,i); // NB column/row major difference
|
||||
|
||||
float& scale = userdata->mScale;
|
||||
float& scale = trans->mScale;
|
||||
if(!mScales.empty())
|
||||
scale = mScales.interpKey(time);
|
||||
|
||||
|
|
|
@ -9,11 +9,9 @@
|
|||
#include <components/sceneutil/controller.hpp>
|
||||
#include <components/sceneutil/statesetupdater.hpp>
|
||||
|
||||
#include <set> //UVController
|
||||
#include <set>
|
||||
|
||||
// FlipController
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
#include <osg/StateSet>
|
||||
#include <osg/NodeCallback>
|
||||
|
@ -22,8 +20,6 @@
|
|||
|
||||
namespace osg
|
||||
{
|
||||
class Node;
|
||||
class StateSet;
|
||||
class Material;
|
||||
}
|
||||
|
||||
|
|
23
components/nifosg/matrixtransform.cpp
Normal file
23
components/nifosg/matrixtransform.cpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#include "matrixtransform.hpp"
|
||||
|
||||
namespace NifOsg
|
||||
{
|
||||
MatrixTransform::MatrixTransform()
|
||||
: osg::MatrixTransform()
|
||||
{
|
||||
}
|
||||
|
||||
MatrixTransform::MatrixTransform(const Nif::Transformation &trafo)
|
||||
: osg::MatrixTransform(trafo.toMatrix())
|
||||
, mScale(trafo.scale)
|
||||
, mRotationScale(trafo.rotation)
|
||||
{
|
||||
}
|
||||
|
||||
MatrixTransform::MatrixTransform(const MatrixTransform ©, const osg::CopyOp ©op)
|
||||
: osg::MatrixTransform(copy, copyop)
|
||||
, mScale(copy.mScale)
|
||||
, mRotationScale(copy.mRotationScale)
|
||||
{
|
||||
}
|
||||
}
|
31
components/nifosg/matrixtransform.hpp
Normal file
31
components/nifosg/matrixtransform.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#ifndef OPENMW_COMPONENTS_NIFOSG_MATRIXTRANSFORM_H
|
||||
#define OPENMW_COMPONENTS_NIFOSG_MATRIXTRANSFORM_H
|
||||
|
||||
#include <components/nif/niftypes.hpp>
|
||||
|
||||
#include <osg/MatrixTransform>
|
||||
|
||||
namespace NifOsg
|
||||
{
|
||||
|
||||
class MatrixTransform : public osg::MatrixTransform
|
||||
{
|
||||
public:
|
||||
MatrixTransform();
|
||||
MatrixTransform(const Nif::Transformation &trafo);
|
||||
MatrixTransform(const MatrixTransform ©, const osg::CopyOp ©op);
|
||||
|
||||
META_Node(NifOsg, MatrixTransform)
|
||||
|
||||
// Hack: account for Transform differences between OSG and NIFs.
|
||||
// OSG uses a 4x4 matrix, NIF's use a 3x3 rotationScale, float scale, and vec3 position.
|
||||
// Decomposing the original components from the 4x4 matrix isn't possible, which causes
|
||||
// problems when a KeyframeController wants to change only one of these components. So
|
||||
// we store the scale and rotation components separately here.
|
||||
float mScale{0.f};
|
||||
Nif::Matrix3 mRotationScale;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -3,7 +3,6 @@
|
|||
#include <mutex>
|
||||
|
||||
#include <osg/Matrixf>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/Array>
|
||||
#include <osg/LOD>
|
||||
|
@ -43,8 +42,9 @@
|
|||
#include <components/sceneutil/riggeometry.hpp>
|
||||
#include <components/sceneutil/morphgeometry.hpp>
|
||||
|
||||
#include "matrixtransform.hpp"
|
||||
#include "nodeindexholder.hpp"
|
||||
#include "particle.hpp"
|
||||
#include "userdata.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -170,31 +170,6 @@ namespace
|
|||
|
||||
namespace NifOsg
|
||||
{
|
||||
class CollisionSwitch : public osg::MatrixTransform
|
||||
{
|
||||
public:
|
||||
CollisionSwitch() : osg::MatrixTransform()
|
||||
{
|
||||
}
|
||||
|
||||
CollisionSwitch(const CollisionSwitch& copy, const osg::CopyOp& copyop)
|
||||
: osg::MatrixTransform(copy, copyop)
|
||||
{
|
||||
}
|
||||
|
||||
META_Node(NifOsg, CollisionSwitch)
|
||||
|
||||
CollisionSwitch(const osg::Matrixf& transformations, bool enabled) : osg::MatrixTransform(transformations)
|
||||
{
|
||||
setEnabled(enabled);
|
||||
}
|
||||
|
||||
void setEnabled(bool enabled)
|
||||
{
|
||||
setNodeMask(enabled ? ~0 : Loader::getIntersectionDisabledNodeMask());
|
||||
}
|
||||
};
|
||||
|
||||
bool Loader::sShowMarkers = false;
|
||||
|
||||
void Loader::setShowMarkers(bool show)
|
||||
|
@ -501,14 +476,6 @@ namespace NifOsg
|
|||
case Nif::RC_NiBillboardNode:
|
||||
dataVariance = osg::Object::DYNAMIC;
|
||||
break;
|
||||
case Nif::RC_NiCollisionSwitch:
|
||||
{
|
||||
bool enabled = nifNode->flags & Nif::NiNode::Flag_ActiveCollision;
|
||||
node = new CollisionSwitch(nifNode->trafo.toMatrix(), enabled);
|
||||
// This matrix transform must not be combined with another matrix transform.
|
||||
dataVariance = osg::Object::DYNAMIC;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// The Root node can be created as a Group if no transformation is required.
|
||||
// This takes advantage of the fact root nodes can't have additional controllers
|
||||
|
@ -521,7 +488,14 @@ namespace NifOsg
|
|||
break;
|
||||
}
|
||||
if (!node)
|
||||
node = new osg::MatrixTransform(nifNode->trafo.toMatrix());
|
||||
node = new NifOsg::MatrixTransform(nifNode->trafo);
|
||||
|
||||
if (nifNode->recType == Nif::RC_NiCollisionSwitch && !(nifNode->flags & Nif::NiNode::Flag_ActiveCollision))
|
||||
{
|
||||
node->setNodeMask(Loader::getIntersectionDisabledNodeMask());
|
||||
// This node must not be combined with another node.
|
||||
dataVariance = osg::Object::DYNAMIC;
|
||||
}
|
||||
|
||||
node->setDataVariance(dataVariance);
|
||||
|
||||
|
@ -549,14 +523,11 @@ namespace NifOsg
|
|||
if (!rootNode)
|
||||
rootNode = node;
|
||||
|
||||
// UserData used for a variety of features:
|
||||
// The original NIF record index is used for a variety of features:
|
||||
// - finding the correct emitter node for a particle system
|
||||
// - establishing connections to the animated collision shapes, which are handled in a separate loader
|
||||
// - finding a random child NiNode in NiBspArrayController
|
||||
// - storing the previous 3x3 rotation and scale values for when a KeyframeController wants to
|
||||
// change only certain elements of the 4x4 transform
|
||||
node->getOrCreateUserDataContainer()->addUserObject(
|
||||
new NodeUserData(nifNode->recIndex, nifNode->trafo.scale, nifNode->trafo.rotation));
|
||||
node->getOrCreateUserDataContainer()->addUserObject(new NodeIndexHolder(nifNode->recIndex));
|
||||
|
||||
for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next)
|
||||
{
|
||||
|
|
35
components/nifosg/nodeindexholder.hpp
Normal file
35
components/nifosg/nodeindexholder.hpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef OPENMW_COMPONENTS_NIFOSG_NODEINDEXHOLDER_H
|
||||
#define OPENMW_COMPONENTS_NIFOSG_NODEINDEXHOLDER_H
|
||||
|
||||
#include <osg/Object>
|
||||
|
||||
namespace NifOsg
|
||||
{
|
||||
|
||||
class NodeIndexHolder : public osg::Object
|
||||
{
|
||||
public:
|
||||
NodeIndexHolder() = default;
|
||||
NodeIndexHolder(int index)
|
||||
: mIndex(index)
|
||||
{
|
||||
}
|
||||
NodeIndexHolder(const NodeIndexHolder& copy, const osg::CopyOp& copyop)
|
||||
: Object(copy, copyop)
|
||||
, mIndex(copy.mIndex)
|
||||
{
|
||||
}
|
||||
|
||||
META_Object(NifOsg, NodeIndexHolder)
|
||||
|
||||
int getIndex() const { return mIndex; }
|
||||
|
||||
private:
|
||||
|
||||
// NIF record index
|
||||
int mIndex{0};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -11,7 +11,7 @@
|
|||
#include <components/nif/controlled.hpp>
|
||||
#include <components/nif/data.hpp>
|
||||
|
||||
#include "userdata.hpp"
|
||||
#include "nodeindexholder.hpp"
|
||||
|
||||
namespace NifOsg
|
||||
{
|
||||
|
@ -383,8 +383,8 @@ void FindGroupByRecIndex::applyNode(osg::Node &searchNode)
|
|||
{
|
||||
if (searchNode.getUserDataContainer() && searchNode.getUserDataContainer()->getNumUserObjects())
|
||||
{
|
||||
NodeUserData* holder = dynamic_cast<NodeUserData*>(searchNode.getUserDataContainer()->getUserObject(0));
|
||||
if (holder && holder->mIndex == mRecIndex)
|
||||
NodeIndexHolder* holder = dynamic_cast<NodeIndexHolder*>(searchNode.getUserDataContainer()->getUserObject(0));
|
||||
if (holder && holder->getIndex() == mRecIndex)
|
||||
{
|
||||
osg::Group* group = searchNode.asGroup();
|
||||
if (!group)
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
#ifndef OPENMW_COMPONENTS_NIFOSG_USERDATA_H
|
||||
#define OPENMW_COMPONENTS_NIFOSG_USERDATA_H
|
||||
|
||||
#include <components/nif/niftypes.hpp>
|
||||
|
||||
#include <osg/Object>
|
||||
|
||||
namespace NifOsg
|
||||
{
|
||||
|
||||
// Note if you are copying a scene graph with this user data you should use the DEEP_COPY_USERDATA copyop.
|
||||
class NodeUserData : public osg::Object
|
||||
{
|
||||
public:
|
||||
NodeUserData(int index, float scale, const Nif::Matrix3& rotationScale)
|
||||
: mIndex(index), mScale(scale), mRotationScale(rotationScale)
|
||||
{
|
||||
}
|
||||
NodeUserData()
|
||||
: mIndex(0), mScale(0)
|
||||
{
|
||||
}
|
||||
NodeUserData(const NodeUserData& copy, const osg::CopyOp& copyop)
|
||||
: Object(copy, copyop)
|
||||
, mIndex(copy.mIndex)
|
||||
, mScale(copy.mScale)
|
||||
, mRotationScale(copy.mRotationScale)
|
||||
{
|
||||
}
|
||||
|
||||
META_Object(NifOsg, NodeUserData)
|
||||
|
||||
// NIF record index
|
||||
int mIndex;
|
||||
|
||||
// Hack: account for Transform differences between OSG and NIFs.
|
||||
// OSG uses a 4x4 matrix, NIF's use a 3x3 rotationScale, float scale, and vec3 position.
|
||||
// Decomposing the original components from the 4x4 matrix isn't possible, which causes
|
||||
// problems when a KeyframeController wants to change only one of these components. So
|
||||
// we store the scale and rotation components separately here.
|
||||
// Note for a cleaner solution it would be possible to write a custom Transform node
|
||||
float mScale;
|
||||
Nif::Matrix3 mRotationScale;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -64,7 +64,7 @@ namespace SceneUtil
|
|||
for (const osg::ref_ptr<osg::Node>& node : mToCopy)
|
||||
{
|
||||
if (node->getNumParents() > 1)
|
||||
Log(Debug::Error) << "Error CopyRigVisitor: node has multiple parents";
|
||||
Log(Debug::Error) << "Error CopyRigVisitor: node has " << node->getNumParents() << " parents";
|
||||
while (node->getNumParents())
|
||||
node->getParent(0)->removeChild(node);
|
||||
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
#include <osgParticle/ParticleSystemUpdater>
|
||||
#include <osgParticle/Emitter>
|
||||
|
||||
#include <components/nifosg/userdata.hpp>
|
||||
|
||||
#include <components/sceneutil/morphgeometry.hpp>
|
||||
#include <components/sceneutil/riggeometry.hpp>
|
||||
|
||||
|
@ -22,15 +20,6 @@ namespace SceneUtil
|
|||
| osg::CopyOp::DEEP_COPY_USERDATA);
|
||||
}
|
||||
|
||||
osg::Object* CopyOp::operator ()(const osg::Object* node) const
|
||||
{
|
||||
// We should copy node transformations when we copy node
|
||||
if (dynamic_cast<const NifOsg::NodeUserData*>(node))
|
||||
return static_cast<NifOsg::NodeUserData*>(node->clone(*this));
|
||||
|
||||
return osg::CopyOp::operator()(node);
|
||||
}
|
||||
|
||||
osg::Node* CopyOp::operator ()(const osg::Node* node) const
|
||||
{
|
||||
if (const osgParticle::ParticleProcessor* processor = dynamic_cast<const osgParticle::ParticleProcessor*>(node))
|
||||
|
|
|
@ -30,8 +30,6 @@ namespace SceneUtil
|
|||
virtual osg::Node* operator() (const osg::Node* node) const;
|
||||
virtual osg::Drawable* operator() (const osg::Drawable* drawable) const;
|
||||
|
||||
virtual osg::Object* operator ()(const osg::Object* node) const;
|
||||
|
||||
private:
|
||||
// maps new pointers to their old pointers
|
||||
// a little messy, but I think this should be the most efficient way
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
#include <osgDB/ObjectWrapper>
|
||||
#include <osgDB/Registry>
|
||||
|
||||
#include <components/nifosg/matrixtransform.hpp>
|
||||
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
#include <components/sceneutil/skeleton.hpp>
|
||||
#include <components/sceneutil/riggeometry.hpp>
|
||||
|
@ -74,6 +76,15 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class MatrixTransformSerializer : public osgDB::ObjectWrapper
|
||||
{
|
||||
public:
|
||||
MatrixTransformSerializer()
|
||||
: osgDB::ObjectWrapper(createInstanceFunc<NifOsg::MatrixTransform>, "NifOsg::MatrixTransform", "osg::Object osg::Node osg::Transform osg::MatrixTransform NifOsg::MatrixTransform")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
osgDB::ObjectWrapper* makeDummySerializer(const std::string& classname)
|
||||
{
|
||||
return new osgDB::ObjectWrapper(createInstanceFunc<osg::DummyObject>, classname, "osg::Object");
|
||||
|
@ -100,6 +111,7 @@ void registerSerializers()
|
|||
mgr->addWrapper(new MorphGeometrySerializer);
|
||||
mgr->addWrapper(new LightManagerSerializer);
|
||||
mgr->addWrapper(new CameraRelativeTransformSerializer);
|
||||
mgr->addWrapper(new MatrixTransformSerializer);
|
||||
|
||||
// Don't serialize Geometry data as we are more interested in the overall structure rather than tons of vertex data that would make the file large and hard to read.
|
||||
mgr->removeWrapper(mgr->findWrapper("osg::Geometry"));
|
||||
|
@ -118,7 +130,6 @@ void registerSerializers()
|
|||
"SceneUtil::StateSetUpdater",
|
||||
"SceneUtil::DisableLight",
|
||||
"SceneUtil::MWShadowTechnique",
|
||||
"NifOsg::NodeUserData",
|
||||
"NifOsg::FlipController",
|
||||
"NifOsg::KeyframeController",
|
||||
"NifOsg::TextKeyMapHolder",
|
||||
|
@ -131,7 +142,8 @@ void registerSerializers()
|
|||
"NifOsg::StaticBoundingBoxCallback",
|
||||
"NifOsg::GeomMorpherController",
|
||||
"NifOsg::UpdateMorphGeometry",
|
||||
"NifOsg::CollisionSwitch",
|
||||
"NifOsg::UVController",
|
||||
"NifOsg::NodeIndexHolder",
|
||||
"osgMyGUI::Drawable",
|
||||
"osg::DrawCallback",
|
||||
"osgOQ::ClearQueriesCallback",
|
||||
|
|
|
@ -34,6 +34,9 @@ namespace Shader
|
|||
foundPos = source.find_first_of("\n\r", foundPos);
|
||||
foundPos = source.find_first_not_of("\n\r", foundPos);
|
||||
|
||||
if (foundPos == std::string::npos)
|
||||
break;
|
||||
|
||||
size_t lineDirectivePosition = source.rfind("#line", foundPos);
|
||||
int lineNumber;
|
||||
if (lineDirectivePosition != std::string::npos)
|
||||
|
|
|
@ -148,7 +148,8 @@ color topic enable
|
|||
:Range: True/False
|
||||
:Default: False
|
||||
|
||||
Control wether additionnal formatting will be applied to dialogs topic. See 'color topic specific' and 'color topic exhausted' for details.
|
||||
This setting controls whether the topics available in the dialogue topic list are coloured according to their state.
|
||||
See 'color topic specific' and 'color topic exhausted' for details.
|
||||
|
||||
color topic specific
|
||||
--------------------
|
||||
|
@ -157,11 +158,11 @@ color topic specific
|
|||
:Range: 0.0 to 1.0
|
||||
:Default: empty
|
||||
|
||||
This setting overrides the color of keywords in the dialogue topic window.
|
||||
This setting overrides the colour of dialogue topics that have a response unique to the actors speaking.
|
||||
The value is composed of four floating point values representing the red, green, blue and alpha channels.
|
||||
The alpha value is currently ignored.
|
||||
|
||||
The color is overriden if the actor is about to give an answer that is unique to him (that is, dialogue with their object ID in the Actor field) that wasn't seen yet.
|
||||
A topic response is considered unique if its Actor filter field contains the speaking actor's object ID and hasn't yet been read.
|
||||
|
||||
color topic exhausted
|
||||
---------------------
|
||||
|
@ -170,8 +171,8 @@ color topic exhausted
|
|||
:Range: 0.0 to 1.0
|
||||
:Default: empty
|
||||
|
||||
This setting overrides the color of keywords in the dialogue topic window.
|
||||
This setting overrides the colour of dialogue topics which have been "exhausted" by the player.
|
||||
The value is composed of four floating point values representing the red, green, blue and alpha channels.
|
||||
The alpha value is currently ignored.
|
||||
|
||||
The color is overridden if the next actor responses to the topic keyword has already been seen by the player.
|
||||
A topic is considered "exhausted" if the response the player is about to see has already been seen.
|
||||
|
|
|
@ -136,7 +136,7 @@ This setting controls third person view mode.
|
|||
False: View is centered on the character's head. Crosshair is hidden.
|
||||
True: In non-combat mode camera is positioned behind the character's shoulder. Crosshair is visible in third person mode as well.
|
||||
|
||||
This setting can only be configured by editing the settings configuration file.
|
||||
This setting can be controlled in Advanced tab of the launcher.
|
||||
|
||||
view over shoulder offset
|
||||
-------------------------
|
||||
|
|
115
docs/source/reference/modding/settings/fog.rst
Normal file
115
docs/source/reference/modding/settings/fog.rst
Normal file
|
@ -0,0 +1,115 @@
|
|||
Fog Settings
|
||||
############
|
||||
|
||||
use distant fog
|
||||
---------------
|
||||
|
||||
:Type: boolean
|
||||
:Range: True/False
|
||||
:Default: False
|
||||
|
||||
This setting overhauls the behavior of fog calculations.
|
||||
|
||||
Normally the fog start and end distance are proportional to the viewing distance
|
||||
and use the fog depth set in the fallback settings.
|
||||
|
||||
Enabling this setting separates the fog distance from the viewing distance and fallback settings and makes fog distance
|
||||
and apparent density dependent on the weather and the current location according to the settings below.
|
||||
|
||||
Unfortunately specific weather-dependent fog factor and offset parameters are currently hard-coded.
|
||||
They are based off the default settings of MGE XE.
|
||||
|
||||
+--------------+------------+--------+
|
||||
| Weather Type | Fog Factor | Offset |
|
||||
+==============+============+========+
|
||||
| Clear | 1.0 | 0.0 |
|
||||
+--------------+------------+--------+
|
||||
| Cloudy | 0.9 | 0.0 |
|
||||
+--------------+------------+--------+
|
||||
| Foggy | 0.2 | 0.3 |
|
||||
+--------------+------------+--------+
|
||||
| Overcast | 0.7 | 0.0 |
|
||||
+--------------+------------+--------+
|
||||
| Rain | 0.5 | 0.1 |
|
||||
+--------------+------------+--------+
|
||||
| Thunderstorm | 0.5 | 0.2 |
|
||||
+--------------+------------+--------+
|
||||
| Ashstorm | 0.2 | 0.5 |
|
||||
+--------------+------------+--------+
|
||||
| Blight | 0.2 | 0.6 |
|
||||
+--------------+------------+--------+
|
||||
| Snow | 0.5 | 0.4 |
|
||||
+--------------+------------+--------+
|
||||
| Blizzard | 0.16 | 0.7 |
|
||||
+--------------+------------+--------+
|
||||
|
||||
Non-underwater fog start and end distance are calculated like this according to these parameters::
|
||||
|
||||
fog start distance = fog factor * (base fog start distance - fog offset * base fog end distance)
|
||||
fog end distance = fog factor * (1.0 - fog offset) * base fog end distance
|
||||
|
||||
Underwater fog distance is used as-is.
|
||||
|
||||
A negative fog start distance means that the fog starts behind the camera
|
||||
so the entirety of the scene will be at least partially fogged.
|
||||
|
||||
A negative fog end distance means that the fog ends behind the camera
|
||||
so the entirety of the scene will be completely submerged in the fog.
|
||||
|
||||
Fog end distance should be larger than the fog start distance.
|
||||
|
||||
This setting and all further settings can only be configured by editing the settings configuration file.
|
||||
|
||||
distant land fog start
|
||||
----------------------
|
||||
|
||||
:Type: floating point
|
||||
:Range: The whole range of 32-bit floating point
|
||||
:Default: 16384 (2 cells)
|
||||
|
||||
This is the base fog start distance used for distant fog calculations in exterior locations.
|
||||
|
||||
distant land fog end
|
||||
--------------------
|
||||
|
||||
:Type: floating point
|
||||
:Range: The whole range of 32-bit floating point
|
||||
:Default: 40960 (5 cells)
|
||||
|
||||
This is the base fog end distance used for distant fog calculations in exterior locations.
|
||||
|
||||
distant underwater fog start
|
||||
----------------------------
|
||||
|
||||
:Type: floating point
|
||||
:Range: The whole range of 32-bit floating point
|
||||
:Default: -4096
|
||||
|
||||
This is the base fog start distance used for distant fog calculations in underwater locations.
|
||||
|
||||
distant underwater fog end
|
||||
--------------------------
|
||||
|
||||
:Type: floating point
|
||||
:Range: The whole range of 32-bit floating point
|
||||
:Default: 2457.6
|
||||
|
||||
This is the base fog end distance used for distant fog calculations in underwater locations.
|
||||
|
||||
distant interior fog start
|
||||
--------------------------
|
||||
|
||||
:Type: floating point
|
||||
:Range: The whole range of 32-bit floating point
|
||||
:Default: 0
|
||||
|
||||
This is the base fog start distance used for distant fog calculations in interior locations.
|
||||
|
||||
distant interior fog end
|
||||
------------------------
|
||||
|
||||
:Type: floating point
|
||||
:Range: The whole range of 32-bit floating point
|
||||
:Default: 16384 (2 cells)
|
||||
|
||||
This is the base fog end distance used for distant fog calculations in interior locations.
|
|
@ -329,7 +329,7 @@ If disabled then the whole character's body is pointed to the direction of view.
|
|||
|
||||
If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it also changes straight right and straight left movement.
|
||||
|
||||
This setting can only be configured by editing the settings configuration file.
|
||||
This setting can be controlled in Advanced tab of the launcher.
|
||||
|
||||
swim upward coef
|
||||
----------------
|
||||
|
@ -356,3 +356,17 @@ If disabled then the 3 best skills of trainers and the training limits take into
|
|||
If enabled then the 3 best skills of trainers and the training limits are based on the trainer base skills.
|
||||
|
||||
This setting can be controlled in Advanced tab of the launcher.
|
||||
|
||||
always allow stealing from knocked out actors
|
||||
---------------------------------------------
|
||||
|
||||
:Type: boolean
|
||||
:Range: True/False
|
||||
:Default: False
|
||||
|
||||
By Bethesda's design, in the latest released version of Morrowind pickpocketing is impossible during combat,
|
||||
even if the fighting NPC is knocked out.
|
||||
|
||||
This setting allows the player to steal items from fighting NPCs that were knocked out if enabled.
|
||||
|
||||
This setting can be controlled in Advanced tab of the launcher.
|
||||
|
|
|
@ -29,7 +29,7 @@ Specify the format for screen shots taken by pressing the screen shot key (bound
|
|||
This setting should be the file extension commonly associated with the desired format.
|
||||
The formats supported will be determined at compilation, but "jpg", "png", and "tga" should be allowed.
|
||||
|
||||
This setting can only be configured by editing the settings configuration file.
|
||||
This setting can be configured in Advanced tab of the launcher.
|
||||
|
||||
texture mag filter
|
||||
------------------
|
||||
|
|
|
@ -41,6 +41,7 @@ The ranges included with each setting are the physically possible ranges, not re
|
|||
|
||||
camera
|
||||
cells
|
||||
fog
|
||||
map
|
||||
GUI
|
||||
HUD
|
||||
|
|
|
@ -5,8 +5,8 @@ Copyright 2020 Bret Curtis <psi29a@gmail.com>
|
|||
-->
|
||||
<component type="desktop">
|
||||
<id>org.openmw.launcher.desktop</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0 and MIT</project_license>
|
||||
<metadata_license>GPL-3+</metadata_license>
|
||||
<project_license>GPL-3+</project_license>
|
||||
<name>OpenMW</name>
|
||||
<summary>Unofficial open source engine re-implementation of the game Morrowind</summary>
|
||||
<description>
|
||||
|
|
|
@ -331,6 +331,9 @@ swim upward coef = 0.0
|
|||
# Make the training skills proposed by a trainer based on its base attribute instead of its modified ones
|
||||
trainers training skills based on base skill = false
|
||||
|
||||
# Make stealing items from NPCs that were knocked down possible during combat.
|
||||
always allow stealing from knocked out actors = false
|
||||
|
||||
[General]
|
||||
|
||||
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).
|
||||
|
|
|
@ -4,18 +4,28 @@
|
|||
<widget class="QWidget" name="AdvancedPage">
|
||||
<layout class="QVBoxLayout" name="pageVerticalLayout">
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
<widget class="QTabWidget" name="AdvancedTabWidget">
|
||||
<property name="tabPosition">
|
||||
<enum>QTabWidget::West</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="GameMechanics">
|
||||
<attribute name="title">
|
||||
<string>Game mechanics</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gameGroup">
|
||||
<property name="title">
|
||||
<string>Game Mechanics</string>
|
||||
<widget class="QCheckBox" name="toggleSneakCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. </p></body></html></string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="gameGroupVerticalLayout">
|
||||
<property name="text">
|
||||
<string>Toggle sneak</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="canLootDuringDeathAnimationCheckBox">
|
||||
<property name="toolTip">
|
||||
|
@ -32,17 +42,7 @@
|
|||
<string><html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Followers attack on sight</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="preventMerchantEquippingCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Prevent merchant equipping</string>
|
||||
<string>Followers defend immediately</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -52,7 +52,7 @@
|
|||
<string><html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Rebalance soul gem values</string>
|
||||
<string>Soulgem values rebalance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -72,7 +72,7 @@
|
|||
<string><html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Barter disposition change is permanent</string>
|
||||
<string>Permanent barter disposition changes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -96,27 +96,32 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uncappedDamageFatigueCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Uncapped Damage Fatigue</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="normaliseRaceSpeedCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Racial variation in speed fix</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignLeft">
|
||||
<widget class="QWidget" name="unarmedFactorsStrengthGroup" native="true">
|
||||
<widget class="QWidget" name="unarmedFactorsStrengthGroup">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Factor strength into hand-to-hand damage calculations, as the MCP formula: damage * (strength / 40).</p><p>The default value is Off.</p></body></html></string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalUnarmedStrengthLayout">
|
||||
<property name="spacing">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="unarmedFactorsStrengthLabel">
|
||||
<property name="text">
|
||||
|
@ -149,6 +154,43 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="stealingFromKnockedOutCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Always allow stealing from knocked out actors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="Visuals">
|
||||
<attribute name="title">
|
||||
<string>Visuals</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="bumpMapLocalLightingCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark.
|
||||
Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option.
|
||||
Affected objects will use shaders.
|
||||
</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Bump/reflect map local lighting</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="magicItemAnimationsCheckBox">
|
||||
<property name="toolTip">
|
||||
|
@ -159,16 +201,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="normaliseRaceSpeedCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Normalise race speed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="animSourcesCheckBox">
|
||||
<property name="toolTip">
|
||||
|
@ -180,23 +212,11 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="sheathingGroup" native="true">
|
||||
<widget class="QWidget" name="sheathingGroup">
|
||||
<layout class="QVBoxLayout" name="sheathingLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="weaponSheathingCheckBox">
|
||||
<property name="enabled">
|
||||
|
@ -227,208 +247,92 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uncappedDamageFatigueCheckBox">
|
||||
<widget class="QCheckBox" name="viewOverShoulderCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html></string>
|
||||
<string><html><head/><body><p>This setting controls third person view mode.</p><p>False: View is centered on the character's head. Crosshair is hidden.
|
||||
True: In non-combat mode camera is positioned behind the character's shoulder. Crosshair is visible in third person mode as well.
|
||||
</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Uncapped Damage Fatigue</string>
|
||||
<string>View over the shoulder</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="trainersTrainingSkillsBasedOnBaseSkillCheckBox">
|
||||
<widget class="QCheckBox" name="turnToMovementDirectionCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html></string>
|
||||
<string><html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it also changes straight right and straight left movement.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Trainers choose their training skills based on their base skill points</string>
|
||||
<string>Turn to movement direction</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="inputGroup">
|
||||
<property name="title">
|
||||
<string>Input</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="inputGroupVerticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="grabCursorCheckBox">
|
||||
<widget class="QCheckBox" name="distantLandCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html></string>
|
||||
<string><html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Grab cursor</string>
|
||||
<string>Distant land</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggleSneakCheckBox">
|
||||
<widget class="QCheckBox" name="activeGridObjectPagingCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. </p></body></html></string>
|
||||
<string><html><head/><body><p>Use object paging for active cells grid.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Toggle sneak</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="savesGroup">
|
||||
<property name="title">
|
||||
<string>Saves</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="savesGroupVerticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="timePlayedCheckbox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add "Time Played" to saves</string>
|
||||
<string>Active grid object paging</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignLeft">
|
||||
<widget class="QWidget" name="maximumQuicksavesGroup" native="true">
|
||||
<widget class="QWidget" name="viewingDistanceGroup">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>This setting determines how many quicksave and autosave slots you can have at a time. If greater than 1, quicksaves will be sequentially created each time you quicksave. Once the maximum number of quicksaves has been reached, the oldest quicksave will be recycled the next time you perform a quicksave.</p></body></html></string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="maximumQuicksavesLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
<string><html><head/><body><p>This value controls the maximum visible distance (in cell units).
|
||||
Larger values significantly improve rendering in exterior spaces,
|
||||
but also increase the amount of rendered geometry and significantly reduce the frame rate.</p></body></html></string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="viewingDistanceLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="maximumQuicksavesLabel">
|
||||
<widget class="QLabel" name="viewingDistanceLabel">
|
||||
<property name="text">
|
||||
<string>Maximum Quicksaves</string>
|
||||
<string>Viewing distance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="maximumQuicksavesComboBox">
|
||||
<widget class="QDoubleSpinBox" name="viewingDistanceComboBox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
<double>0.0</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.5</double>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> Cells</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="testingGroup">
|
||||
<property name="title">
|
||||
<string>Testing</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="testingGroupVerticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="testingLabel">
|
||||
<property name="text">
|
||||
<string>These settings are intended for testing mods and will cause issues if used for normal gameplay.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="testingLine">
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="skipMenuCheckBox">
|
||||
<property name="text">
|
||||
<string>Skip menu and generate default character</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="startDefaultCharacterAtHorizontalLayout">
|
||||
<item>
|
||||
<spacer name="startDefaultCharacterAtHorizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="startDefaultCharacterAtLabel">
|
||||
<property name="text">
|
||||
<string>Start default character at</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="startDefaultCharacterAtField">
|
||||
<property name="placeholderText">
|
||||
<string>default cell</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Run script after startup:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="runScriptAfterStartupHorizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="runScriptAfterStartupField"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="runScriptAfterStartupBrowseButton">
|
||||
<property name="text">
|
||||
<string>Browse…</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="userInterfaceGroup">
|
||||
<property name="title">
|
||||
<string>User Interface</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="userInterfaceVerticalLayout">
|
||||
<widget class="QWidget" name="InterfaceChanges">
|
||||
<attribute name="title">
|
||||
<string>Interface changes</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showEffectDurationCheckBox">
|
||||
<property name="toolTip">
|
||||
|
@ -480,26 +384,11 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignLeft">
|
||||
<widget class="QWidget" name="showOwnedGroup" native="true">
|
||||
<widget class="QWidget" name="showOwnedGroup">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enable visual clues for items owned by NPCs when the crosshair is on the object.</p><p>The default value is Off.</p></body></html></string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="showOwnedLabel">
|
||||
<property name="text">
|
||||
|
@ -537,6 +426,93 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="BugFixes">
|
||||
<attribute name="title">
|
||||
<string>Bug fixes</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="preventMerchantEquippingCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Merchant equipping fix</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="trainersTrainingSkillsBasedOnBaseSkillCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Trainers choose their training skills based on their base skill points</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="Miscellaneous">
|
||||
<attribute name="title">
|
||||
<string>Miscellaneous</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="savesGroup">
|
||||
<property name="title">
|
||||
<string>Saves</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="savesGroupVerticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="timePlayedCheckbox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add "Time Played" to saves</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignLeft">
|
||||
<widget class="QWidget" name="maximumQuicksavesGroup">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>This setting determines how many quicksave and autosave slots you can have at a time. If greater than 1, quicksaves will be sequentially created each time you quicksave. Once the maximum number of quicksaves has been reached, the oldest quicksave will be recycled the next time you perform a quicksave.</p></body></html></string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="maximumQuicksavesLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="maximumQuicksavesLabel">
|
||||
<property name="text">
|
||||
<string>Maximum Quicksaves</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="maximumQuicksavesComboBox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -547,26 +523,11 @@
|
|||
</property>
|
||||
<layout class="QVBoxLayout" name="otherGroupVerticalLayout">
|
||||
<item alignment="Qt::AlignLeft">
|
||||
<widget class="QWidget" name="screenshotFormatGroup" native="true">
|
||||
<widget class="QWidget" name="screenshotFormatGroup">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Specify the format for screen shots taken by pressing the screen shot key (bound to F12 by default). This setting should be the file extension commonly associated with the desired format. The formats supported will be determined at compilation, but “jpg”, “png”, and “tga” should be allowed.</p></body></html></string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="screenshotFormatLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="screenshotFormatLabel">
|
||||
<property name="text">
|
||||
|
@ -599,6 +560,110 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="Testing">
|
||||
<attribute name="title">
|
||||
<string>Testing</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="testingLabel">
|
||||
<property name="text">
|
||||
<string>These settings are intended for testing mods and will cause issues if used for normal gameplay.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="testingLine">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="grabCursorCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Grab cursor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="skipMenuCheckBox">
|
||||
<property name="text">
|
||||
<string>Skip menu and generate default character</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="startDefaultCharacterAtHorizontalLayout">
|
||||
<item>
|
||||
<spacer name="startDefaultCharacterAtHorizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="startDefaultCharacterAtLabel">
|
||||
<property name="text">
|
||||
<string>Start default character at</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="startDefaultCharacterAtField">
|
||||
<property name="placeholderText">
|
||||
<string>default cell</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Run script after startup:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="runScriptAfterStartupHorizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="runScriptAfterStartupField"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="runScriptAfterStartupBrowseButton">
|
||||
<property name="text">
|
||||
<string>Browse…</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<item alignment="Qt::AlignTop">
|
||||
<widget class="QTabWidget" name="DisplayTabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
|
|
Loading…
Reference in a new issue