mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-10-05 02:26:30 +00:00
Add OpenMW commits up to 13 Jan 2020
# Conflicts: # apps/openmw/mwmechanics/actors.cpp
This commit is contained in:
commit
60b6f92fa3
47 changed files with 588 additions and 277 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -28,6 +28,7 @@
|
||||||
Bug #4262: Rain settings are hardcoded
|
Bug #4262: Rain settings are hardcoded
|
||||||
Bug #4270: Closing doors while they are obstructed desyncs closing sfx
|
Bug #4270: Closing doors while they are obstructed desyncs closing sfx
|
||||||
Bug #4276: Resizing character window differs from vanilla
|
Bug #4276: Resizing character window differs from vanilla
|
||||||
|
Bug #4284: ForceSneak behaviour is inconsistent if the target has AiWander package
|
||||||
Bug #4329: Removed birthsign abilities are restored after reloading the save
|
Bug #4329: Removed birthsign abilities are restored after reloading the save
|
||||||
Bug #4341: Error message about missing GDB is too vague
|
Bug #4341: Error message about missing GDB is too vague
|
||||||
Bug #4383: Bow model obscures crosshair when arrow is drawn
|
Bug #4383: Bow model obscures crosshair when arrow is drawn
|
||||||
|
@ -41,6 +42,7 @@
|
||||||
Bug #4600: Crash when no sound output is available or --no-sound is used.
|
Bug #4600: Crash when no sound output is available or --no-sound is used.
|
||||||
Bug #4639: Black screen after completing first mages guild mission + training
|
Bug #4639: Black screen after completing first mages guild mission + training
|
||||||
Bug #4650: Focus is lost after pressing ESC in confirmation dialog inside savegame dialog
|
Bug #4650: Focus is lost after pressing ESC in confirmation dialog inside savegame dialog
|
||||||
|
Bug #4680: Heap corruption on faulty esp
|
||||||
Bug #4701: PrisonMarker record is not hardcoded like other markers
|
Bug #4701: PrisonMarker record is not hardcoded like other markers
|
||||||
Bug #4703: Editor: it's possible to preview levelled list records
|
Bug #4703: Editor: it's possible to preview levelled list records
|
||||||
Bug #4705: Editor: unable to open exterior cell views from Instances table
|
Bug #4705: Editor: unable to open exterior cell views from Instances table
|
||||||
|
@ -133,6 +135,7 @@
|
||||||
Bug #5063: Shape named "Tri Shadow" in creature mesh is visible if it isn't hidden
|
Bug #5063: Shape named "Tri Shadow" in creature mesh is visible if it isn't hidden
|
||||||
Bug #5067: Ranged attacks on unaware opponents ("critical hits") differ from the vanilla engine
|
Bug #5067: Ranged attacks on unaware opponents ("critical hits") differ from the vanilla engine
|
||||||
Bug #5069: Blocking creatures' attacks doesn't degrade shields
|
Bug #5069: Blocking creatures' attacks doesn't degrade shields
|
||||||
|
Bug #5073: NPCs open doors in front of them even if they don't have to
|
||||||
Bug #5074: Paralyzed actors greet the player
|
Bug #5074: Paralyzed actors greet the player
|
||||||
Bug #5075: Enchanting cast style can be changed if there's no object
|
Bug #5075: Enchanting cast style can be changed if there's no object
|
||||||
Bug #5078: DisablePlayerLooking is broken
|
Bug #5078: DisablePlayerLooking is broken
|
||||||
|
@ -178,6 +181,7 @@
|
||||||
Bug #5209: Spellcasting ignores race height
|
Bug #5209: Spellcasting ignores race height
|
||||||
Bug #5210: AiActivate allows actors to open dialogue and inventory windows
|
Bug #5210: AiActivate allows actors to open dialogue and inventory windows
|
||||||
Bug #5211: Screen fades in if the first loaded save is in interior cell
|
Bug #5211: Screen fades in if the first loaded save is in interior cell
|
||||||
|
Bug #5212: AiTravel does not work for actors outside of AI processing range
|
||||||
Bug #5213: SameFaction script function is broken
|
Bug #5213: SameFaction script function is broken
|
||||||
Bug #5218: Crash when disabling ToggleBorders
|
Bug #5218: Crash when disabling ToggleBorders
|
||||||
Bug #5220: GetLOS crashes when actor isn't loaded
|
Bug #5220: GetLOS crashes when actor isn't loaded
|
||||||
|
@ -185,6 +189,11 @@
|
||||||
Bug #5223: Bow replacement during attack animation removes attached arrow
|
Bug #5223: Bow replacement during attack animation removes attached arrow
|
||||||
Bug #5226: Reputation should be capped
|
Bug #5226: Reputation should be capped
|
||||||
Bug #5229: Crash if mesh controller node has no data node
|
Bug #5229: Crash if mesh controller node has no data node
|
||||||
|
Bug #5239: OpenMW-CS does not support non-ASCII characters in path names
|
||||||
|
Bug #5241: On-self absorb spells cannot be detected
|
||||||
|
Bug #5242: ExplodeSpell behavior differs from Cast behavior
|
||||||
|
Bug #5249: Wandering NPCs start walking too soon after they hello
|
||||||
|
Bug #5250: Creatures display shield ground mesh instead of shield body part
|
||||||
Feature #1774: Handle AvoidNode
|
Feature #1774: Handle AvoidNode
|
||||||
Feature #2229: Improve pathfinding AI
|
Feature #2229: Improve pathfinding AI
|
||||||
Feature #3025: Analogue gamepad movement controls
|
Feature #3025: Analogue gamepad movement controls
|
||||||
|
@ -196,6 +205,8 @@
|
||||||
Feature #3980: In-game option to disable controller
|
Feature #3980: In-game option to disable controller
|
||||||
Feature #3999: Shift + Double Click should maximize/restore menu size
|
Feature #3999: Shift + Double Click should maximize/restore menu size
|
||||||
Feature #4001: Toggle sneak controller shortcut
|
Feature #4001: Toggle sneak controller shortcut
|
||||||
|
Feature #4068: OpenMW-CS: Add a button to reset key bindings to defaults
|
||||||
|
Feature #4129: Beta Comment to File
|
||||||
Feature #4209: Editor: Faction rank sub-table
|
Feature #4209: Editor: Faction rank sub-table
|
||||||
Feature #4255: Handle broken RepairedOnMe script function
|
Feature #4255: Handle broken RepairedOnMe script function
|
||||||
Feature #4316: Implement RaiseRank/LowerRank functions properly
|
Feature #4316: Implement RaiseRank/LowerRank functions properly
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/filesystem/fstream.hpp>
|
#include <boost/filesystem/fstream.hpp>
|
||||||
|
|
||||||
#include <osgDB/ReadFile>
|
#include <osgDB/ReadFile>
|
||||||
|
@ -349,7 +350,7 @@ namespace ESSImport
|
||||||
|
|
||||||
writer.setFormat (ESM::SavedGame::sCurrentFormat);
|
writer.setFormat (ESM::SavedGame::sCurrentFormat);
|
||||||
|
|
||||||
std::ofstream stream(mOutFile.c_str(), std::ios::binary);
|
boost::filesystem::ofstream stream(boost::filesystem::path(mOutFile), std::ios::out | std::ios::binary);
|
||||||
// all unused
|
// all unused
|
||||||
writer.setVersion(0);
|
writer.setVersion(0);
|
||||||
writer.setType(0);
|
writer.setType(0);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "document.hpp"
|
#include "document.hpp"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/filesystem/fstream.hpp>
|
#include <boost/filesystem/fstream.hpp>
|
||||||
|
@ -289,19 +288,22 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration,
|
||||||
|
|
||||||
if (mNew || !boost::filesystem::exists (mProjectPath))
|
if (mNew || !boost::filesystem::exists (mProjectPath))
|
||||||
{
|
{
|
||||||
boost::filesystem::path customFiltersPath (configuration.getUserDataPath());
|
boost::filesystem::path filtersPath (configuration.getUserDataPath() / "defaultfilters");
|
||||||
customFiltersPath /= "defaultfilters";
|
|
||||||
|
|
||||||
std::ofstream destination (mProjectPath.string().c_str(), std::ios::binary);
|
boost::filesystem::ofstream destination(mProjectPath, std::ios::out | std::ios::binary);
|
||||||
|
if (!destination.is_open())
|
||||||
|
throw std::runtime_error("Can not create project file: " + mProjectPath.string());
|
||||||
|
destination.exceptions(std::ios::failbit | std::ios::badbit);
|
||||||
|
|
||||||
if (boost::filesystem::exists (customFiltersPath))
|
if (!boost::filesystem::exists (filtersPath))
|
||||||
{
|
filtersPath = mResDir / "defaultfilters";
|
||||||
destination << std::ifstream(customFiltersPath.string().c_str(), std::ios::binary).rdbuf();
|
|
||||||
}
|
boost::filesystem::ifstream source(filtersPath, std::ios::in | std::ios::binary);
|
||||||
else
|
if (!source.is_open())
|
||||||
{
|
throw std::runtime_error("Can not read filters file: " + filtersPath.string());
|
||||||
destination << std::ifstream(std::string(mResDir.string() + "/defaultfilters").c_str(), std::ios::binary).rdbuf();
|
source.exceptions(std::ios::failbit | std::ios::badbit);
|
||||||
}
|
|
||||||
|
destination << source.rdbuf();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mNew)
|
if (mNew)
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
|
#include <QPushButton>
|
||||||
#include <QStackedLayout>
|
#include <QStackedLayout>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "../../model/prefs/setting.hpp"
|
#include "../../model/prefs/setting.hpp"
|
||||||
#include "../../model/prefs/category.hpp"
|
#include "../../model/prefs/category.hpp"
|
||||||
|
#include "../../model/prefs/state.hpp"
|
||||||
|
|
||||||
namespace CSVPrefs
|
namespace CSVPrefs
|
||||||
{
|
{
|
||||||
|
@ -29,8 +31,18 @@ namespace CSVPrefs
|
||||||
mPageSelector = new QComboBox();
|
mPageSelector = new QComboBox();
|
||||||
connect(mPageSelector, SIGNAL(currentIndexChanged(int)), mStackedLayout, SLOT(setCurrentIndex(int)));
|
connect(mPageSelector, SIGNAL(currentIndexChanged(int)), mStackedLayout, SLOT(setCurrentIndex(int)));
|
||||||
|
|
||||||
|
QFrame* lineSeparator = new QFrame(topWidget);
|
||||||
|
lineSeparator->setFrameShape(QFrame::HLine);
|
||||||
|
lineSeparator->setFrameShadow(QFrame::Sunken);
|
||||||
|
|
||||||
|
// Reset key bindings button
|
||||||
|
QPushButton* resetButton = new QPushButton ("Reset to Defaults", topWidget);
|
||||||
|
connect(resetButton, SIGNAL(clicked()), this, SLOT(resetKeyBindings()));
|
||||||
|
|
||||||
topLayout->addWidget(mPageSelector);
|
topLayout->addWidget(mPageSelector);
|
||||||
topLayout->addWidget(stackedWidget);
|
topLayout->addWidget(stackedWidget);
|
||||||
|
topLayout->addWidget(lineSeparator);
|
||||||
|
topLayout->addWidget(resetButton);
|
||||||
topLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
topLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
||||||
|
|
||||||
// Add each option
|
// Add each option
|
||||||
|
@ -85,4 +97,9 @@ namespace CSVPrefs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KeyBindingPage::resetKeyBindings()
|
||||||
|
{
|
||||||
|
CSMPrefs::State::get().resetCategory("Key Bindings");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ namespace CSVPrefs
|
||||||
QStackedLayout* mStackedLayout;
|
QStackedLayout* mStackedLayout;
|
||||||
QGridLayout* mPageLayout;
|
QGridLayout* mPageLayout;
|
||||||
QComboBox* mPageSelector;
|
QComboBox* mPageSelector;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
|
||||||
|
void resetKeyBindings();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -400,17 +400,7 @@ namespace MWGui
|
||||||
struct tm* timeinfo;
|
struct tm* timeinfo;
|
||||||
timeinfo = localtime(&time);
|
timeinfo = localtime(&time);
|
||||||
|
|
||||||
// Use system/environment locale settings for datetime formatting
|
text << std::put_time(timeinfo, "%Y.%m.%d %T") << "\n";
|
||||||
char* oldLctime = setlocale(LC_TIME, nullptr);
|
|
||||||
setlocale(LC_TIME, "");
|
|
||||||
|
|
||||||
const int size=1024;
|
|
||||||
char buffer[size];
|
|
||||||
if (std::strftime(buffer, size, "%x %X", timeinfo) > 0)
|
|
||||||
text << buffer << "\n";
|
|
||||||
|
|
||||||
// reset
|
|
||||||
setlocale(LC_TIME, oldLctime);
|
|
||||||
|
|
||||||
text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n";
|
text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n";
|
||||||
text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCell << "}\n";
|
text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCell << "}\n";
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
#include <MyGUI_Gui.h>
|
#include <MyGUI_Gui.h>
|
||||||
#include <MyGUI_TabControl.h>
|
#include <MyGUI_TabControl.h>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
|
|
||||||
#include <SDL_video.h>
|
#include <SDL_video.h>
|
||||||
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
@ -45,10 +43,10 @@ namespace
|
||||||
void parseResolution (int &x, int &y, const std::string& str)
|
void parseResolution (int &x, int &y, const std::string& str)
|
||||||
{
|
{
|
||||||
std::vector<std::string> split;
|
std::vector<std::string> split;
|
||||||
boost::algorithm::split (split, str, boost::is_any_of("@(x"));
|
Misc::StringUtils::split (str, split, "@(x");
|
||||||
assert (split.size() >= 2);
|
assert (split.size() >= 2);
|
||||||
boost::trim(split[0]);
|
Misc::StringUtils::trim(split[0]);
|
||||||
boost::trim(split[1]);
|
Misc::StringUtils::trim(split[1]);
|
||||||
x = MyGUI::utility::parseInt (split[0]);
|
x = MyGUI::utility::parseInt (split[0]);
|
||||||
y = MyGUI::utility::parseInt (split[1]);
|
y = MyGUI::utility::parseInt (split[1]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1697,6 +1697,8 @@ namespace MWInput
|
||||||
return "Zoom In";
|
return "Zoom In";
|
||||||
else if (action == A_ZoomOut)
|
else if (action == A_ZoomOut)
|
||||||
return "Zoom Out";
|
return "Zoom Out";
|
||||||
|
else if (action == A_ToggleHUD)
|
||||||
|
return "Toggle HUD";
|
||||||
|
|
||||||
descriptions[A_Use] = "sUse";
|
descriptions[A_Use] = "sUse";
|
||||||
descriptions[A_Activate] = "sActivate";
|
descriptions[A_Activate] = "sActivate";
|
||||||
|
@ -1875,6 +1877,7 @@ namespace MWInput
|
||||||
ret.push_back(A_Console);
|
ret.push_back(A_Console);
|
||||||
ret.push_back(A_QuickSave);
|
ret.push_back(A_QuickSave);
|
||||||
ret.push_back(A_QuickLoad);
|
ret.push_back(A_QuickLoad);
|
||||||
|
ret.push_back(A_ToggleHUD);
|
||||||
ret.push_back(A_Screenshot);
|
ret.push_back(A_Screenshot);
|
||||||
ret.push_back(A_QuickKeysMenu);
|
ret.push_back(A_QuickKeysMenu);
|
||||||
ret.push_back(A_QuickKey1);
|
ret.push_back(A_QuickKey1);
|
||||||
|
@ -1908,6 +1911,7 @@ namespace MWInput
|
||||||
ret.push_back(A_Rest);
|
ret.push_back(A_Rest);
|
||||||
ret.push_back(A_QuickSave);
|
ret.push_back(A_QuickSave);
|
||||||
ret.push_back(A_QuickLoad);
|
ret.push_back(A_QuickLoad);
|
||||||
|
ret.push_back(A_ToggleHUD);
|
||||||
ret.push_back(A_Screenshot);
|
ret.push_back(A_Screenshot);
|
||||||
ret.push_back(A_QuickKeysMenu);
|
ret.push_back(A_QuickKeysMenu);
|
||||||
ret.push_back(A_QuickKey1);
|
ret.push_back(A_QuickKey1);
|
||||||
|
@ -1948,7 +1952,7 @@ namespace MWInput
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disallow binding reserved keys
|
// Disallow binding reserved keys
|
||||||
if (key == SDL_SCANCODE_F3 || key == SDL_SCANCODE_F4 || key == SDL_SCANCODE_F10 || key == SDL_SCANCODE_F11)
|
if (key == SDL_SCANCODE_F3 || key == SDL_SCANCODE_F4 || key == SDL_SCANCODE_F10)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
|
|
|
@ -161,8 +161,9 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
static const int GREETING_SHOULD_START = 4; //how many updates should pass before NPC can greet player
|
static const int GREETING_SHOULD_START = 4; // how many updates should pass before NPC can greet player
|
||||||
static const int GREETING_SHOULD_END = 10;
|
static const int GREETING_SHOULD_END = 20; // how many updates should pass before NPC stops turning to player
|
||||||
|
static const int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement
|
||||||
static const float DECELERATE_DISTANCE = 512.f;
|
static const float DECELERATE_DISTANCE = 512.f;
|
||||||
|
|
||||||
class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor
|
class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor
|
||||||
|
@ -552,9 +553,10 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
greetingTimer++;
|
greetingTimer++;
|
||||||
|
|
||||||
turnActorToFacePlayer(actor, dir);
|
if (greetingTimer <= GREETING_SHOULD_END || MWBase::Environment::get().getSoundManager()->sayActive(actor))
|
||||||
|
turnActorToFacePlayer(actor, dir);
|
||||||
|
|
||||||
if (greetingTimer >= GREETING_SHOULD_END)
|
if (greetingTimer >= GREETING_COOLDOWN)
|
||||||
{
|
{
|
||||||
greetingState = Greet_Done;
|
greetingState = Greet_Done;
|
||||||
greetingTimer = 0;
|
greetingTimer = 0;
|
||||||
|
@ -1867,6 +1869,11 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if ((isLocalActor || aiActive) && iter->first != player && isConscious(iter->first))
|
||||||
|
{
|
||||||
|
CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||||
|
stats.getAiSequence().execute(iter->first, *ctrl, duration, /*outOfRange*/true);
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
End of tes3mp change (major)
|
End of tes3mp change (major)
|
||||||
*/
|
*/
|
||||||
|
@ -1897,7 +1904,15 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length();
|
const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length();
|
||||||
bool isPlayer = iter->first == player;
|
bool isPlayer = iter->first == player;
|
||||||
bool inRange = isPlayer || dist <= mActorsProcessingRange;
|
CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||||
|
// Actors with active AI should be able to move.
|
||||||
|
bool alwaysActive = false;
|
||||||
|
if (!isPlayer && isConscious(iter->first) && !stats.isParalyzed())
|
||||||
|
{
|
||||||
|
MWMechanics::AiSequence& seq = stats.getAiSequence();
|
||||||
|
alwaysActive = !seq.isEmpty() && seq.getActivePackage()->alwaysActive();
|
||||||
|
}
|
||||||
|
bool inRange = isPlayer || dist <= mActorsProcessingRange || alwaysActive;
|
||||||
int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower)
|
int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower)
|
||||||
if (isPlayer)
|
if (isPlayer)
|
||||||
activeFlag = 2;
|
activeFlag = 2;
|
||||||
|
|
|
@ -104,6 +104,9 @@ namespace MWMechanics
|
||||||
|
|
||||||
virtual osg::Vec3f getDestination() { return osg::Vec3f(0, 0, 0); }
|
virtual osg::Vec3f getDestination() { return osg::Vec3f(0, 0, 0); }
|
||||||
|
|
||||||
|
// Return true if any loaded actor with this AI package must be active.
|
||||||
|
virtual bool alwaysActive() const { return false; }
|
||||||
|
|
||||||
/// Reset pathfinding state
|
/// Reset pathfinding state
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
|
|
@ -198,7 +198,7 @@ bool isActualAiPackage(int packageTypeId)
|
||||||
packageTypeId <= AiPackage::TypeIdActivate);
|
packageTypeId <= AiPackage::TypeIdActivate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration)
|
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange)
|
||||||
{
|
{
|
||||||
if(actor != getPlayer())
|
if(actor != getPlayer())
|
||||||
{
|
{
|
||||||
|
@ -209,6 +209,9 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
|
||||||
}
|
}
|
||||||
|
|
||||||
MWMechanics::AiPackage* package = mPackages.front();
|
MWMechanics::AiPackage* package = mPackages.front();
|
||||||
|
if (!package->alwaysActive() && outOfRange)
|
||||||
|
return;
|
||||||
|
|
||||||
int packageTypeId = package->getTypeId();
|
int packageTypeId = package->getTypeId();
|
||||||
// workaround ai packages not being handled as in the vanilla engine
|
// workaround ai packages not being handled as in the vanilla engine
|
||||||
if (isActualAiPackage(packageTypeId))
|
if (isActualAiPackage(packageTypeId))
|
||||||
|
|
|
@ -110,7 +110,7 @@ namespace MWMechanics
|
||||||
void stopPursuit();
|
void stopPursuit();
|
||||||
|
|
||||||
/// Execute current package, switching if needed.
|
/// Execute current package, switching if needed.
|
||||||
void execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration);
|
void execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange=false);
|
||||||
|
|
||||||
/// Simulate the passing of time using the currently active AI package
|
/// Simulate the passing of time using the currently active AI package
|
||||||
void fastForward(const MWWorld::Ptr &actor);
|
void fastForward(const MWWorld::Ptr &actor);
|
||||||
|
|
|
@ -34,6 +34,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
virtual bool useVariableSpeed() const { return true;}
|
virtual bool useVariableSpeed() const { return true;}
|
||||||
|
|
||||||
|
virtual bool alwaysActive() const { return true; }
|
||||||
|
|
||||||
virtual osg::Vec3f getDestination() { return osg::Vec3f(mX, mY, mZ); }
|
virtual osg::Vec3f getDestination() { return osg::Vec3f(mX, mY, mZ); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -2440,7 +2440,7 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
if(movestate != CharState_None && !isTurning())
|
if(movestate != CharState_None && !isTurning())
|
||||||
clearAnimQueue();
|
clearAnimQueue();
|
||||||
|
|
||||||
if(mAnimQueue.empty() || inwater || sneak)
|
if(mAnimQueue.empty() || inwater || (sneak && mIdleState != CharState_SpecialIdle))
|
||||||
{
|
{
|
||||||
if (inwater)
|
if (inwater)
|
||||||
idlestate = CharState_IdleSwim;
|
idlestate = CharState_IdleSwim;
|
||||||
|
|
|
@ -464,9 +464,9 @@ namespace MWMechanics
|
||||||
if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer))
|
if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// caster needs to be an actor that's not the target for linked effects (e.g. Absorb)
|
// caster needs to be an actor for linked effects (e.g. Absorb)
|
||||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked
|
||||||
&& (caster.isEmpty() || !caster.getClass().isActor() || caster == target))
|
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// If player is healing someone, show the target's HP bar
|
// If player is healing someone, show the target's HP bar
|
||||||
|
@ -569,6 +569,15 @@ namespace MWMechanics
|
||||||
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
|
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
|
||||||
effect.mMagnitude = magnitude;
|
effect.mMagnitude = magnitude;
|
||||||
|
|
||||||
|
// Avoid applying absorb effects if the caster is the target
|
||||||
|
// We still need the spell to be added
|
||||||
|
if (caster == target
|
||||||
|
&& effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute
|
||||||
|
&& effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
|
||||||
|
{
|
||||||
|
effect.mMagnitude = 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||||
if (hasDuration && effectIt->mDuration == 0)
|
if (hasDuration && effectIt->mDuration == 0)
|
||||||
{
|
{
|
||||||
|
@ -641,21 +650,19 @@ namespace MWMechanics
|
||||||
// magnitude, since we're transferring stats from the target to the caster
|
// magnitude, since we're transferring stats from the target to the caster
|
||||||
if (!caster.isEmpty() && caster != target && caster.getClass().isActor())
|
if (!caster.isEmpty() && caster != target && caster.getClass().isActor())
|
||||||
{
|
{
|
||||||
for (int i=0; i<5; ++i)
|
if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute &&
|
||||||
|
effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
|
||||||
{
|
{
|
||||||
if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i)
|
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
|
||||||
{
|
ActiveSpells::ActiveEffect effect_ = effect;
|
||||||
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
|
effect_.mMagnitude *= -1;
|
||||||
ActiveSpells::ActiveEffect effect_ = effect;
|
absorbEffects.push_back(effect_);
|
||||||
effect_.mMagnitude *= -1;
|
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
|
||||||
absorbEffects.push_back(effect_);
|
target.getClass().getCreatureStats(target).getActiveSpells().addSpell("", true,
|
||||||
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
|
absorbEffects, mSourceName, caster.getClass().getCreatureStats(caster).getActorId());
|
||||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell("", true,
|
else
|
||||||
absorbEffects, mSourceName, caster.getClass().getCreatureStats(caster).getActorId());
|
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true,
|
||||||
else
|
absorbEffects, mSourceName, target.getClass().getCreatureStats(target).getActorId());
|
||||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true,
|
|
||||||
absorbEffects, mSourceName, target.getClass().getCreatureStats(target).getActorId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
#include "../mwmechanics/weapontype.hpp"
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
|
@ -87,6 +88,31 @@ PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::st
|
||||||
std::string ActorAnimation::getShieldMesh(MWWorld::ConstPtr shield) const
|
std::string ActorAnimation::getShieldMesh(MWWorld::ConstPtr shield) const
|
||||||
{
|
{
|
||||||
std::string mesh = shield.getClass().getModel(shield);
|
std::string mesh = shield.getClass().getModel(shield);
|
||||||
|
const ESM::Armor *armor = shield.get<ESM::Armor>()->mBase;
|
||||||
|
const std::vector<ESM::PartReference>& bodyparts = armor->mParts.mParts;
|
||||||
|
if (!bodyparts.empty())
|
||||||
|
{
|
||||||
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
|
||||||
|
|
||||||
|
// Try to get shield model from bodyparts first, with ground model as fallback
|
||||||
|
for (const auto& part : bodyparts)
|
||||||
|
{
|
||||||
|
// Assume all creatures use the male mesh.
|
||||||
|
if (part.mPart != ESM::PRT_Shield || part.mMale.empty())
|
||||||
|
continue;
|
||||||
|
const ESM::BodyPart *bodypart = partStore.search(part.mMale);
|
||||||
|
if (bodypart && bodypart->mData.mType == ESM::BodyPart::MT_Armor && !bodypart->mModel.empty())
|
||||||
|
{
|
||||||
|
mesh = "meshes\\" + bodypart->mModel;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mesh.empty())
|
||||||
|
return mesh;
|
||||||
|
|
||||||
std::string holsteredName = mesh;
|
std::string holsteredName = mesh;
|
||||||
holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif");
|
holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif");
|
||||||
if(mResourceSystem->getVFS()->exists(holsteredName))
|
if(mResourceSystem->getVFS()->exists(holsteredName))
|
||||||
|
|
|
@ -13,11 +13,13 @@
|
||||||
|
|
||||||
#include <components/misc/stringops.hpp>
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/weapontype.hpp"
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
|
||||||
namespace MWRender
|
namespace MWRender
|
||||||
{
|
{
|
||||||
|
@ -114,6 +116,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
||||||
MWWorld::ConstPtr item = *it;
|
MWWorld::ConstPtr item = *it;
|
||||||
|
|
||||||
std::string bonename;
|
std::string bonename;
|
||||||
|
std::string itemModel = item.getClass().getModel(item);
|
||||||
if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
|
if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
|
||||||
{
|
{
|
||||||
if(item.getTypeName() == typeid(ESM::Weapon).name())
|
if(item.getTypeName() == typeid(ESM::Weapon).name())
|
||||||
|
@ -132,11 +135,30 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
||||||
bonename = "Weapon Bone";
|
bonename = "Weapon Bone";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
bonename = "Shield Bone";
|
bonename = "Shield Bone";
|
||||||
|
if (item.getTypeName() == typeid(ESM::Armor).name())
|
||||||
|
{
|
||||||
|
// Shield body part model should be used if possible.
|
||||||
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
for (const auto& part : item.get<ESM::Armor>()->mBase->mParts.mParts)
|
||||||
|
{
|
||||||
|
// Assume all creatures use the male mesh.
|
||||||
|
if (part.mPart != ESM::PRT_Shield || part.mMale.empty())
|
||||||
|
continue;
|
||||||
|
const ESM::BodyPart *bodypart = store.get<ESM::BodyPart>().search(part.mMale);
|
||||||
|
if (bodypart && bodypart->mData.mType == ESM::BodyPart::MT_Armor && !bodypart->mModel.empty())
|
||||||
|
{
|
||||||
|
itemModel = "meshes\\" + bodypart->mModel;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item));
|
osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(itemModel);
|
||||||
|
|
||||||
const NodeMap& nodeMap = getNodeMap();
|
const NodeMap& nodeMap = getNodeMap();
|
||||||
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
|
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
|
||||||
|
|
|
@ -518,14 +518,14 @@ std::string NpcAnimation::getShieldMesh(MWWorld::ConstPtr shield) const
|
||||||
{
|
{
|
||||||
std::string mesh = shield.getClass().getModel(shield);
|
std::string mesh = shield.getClass().getModel(shield);
|
||||||
const ESM::Armor *armor = shield.get<ESM::Armor>()->mBase;
|
const ESM::Armor *armor = shield.get<ESM::Armor>()->mBase;
|
||||||
std::vector<ESM::PartReference> bodyparts = armor->mParts.mParts;
|
const std::vector<ESM::PartReference>& bodyparts = armor->mParts.mParts;
|
||||||
if (!bodyparts.empty())
|
if (!bodyparts.empty())
|
||||||
{
|
{
|
||||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
|
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
|
||||||
|
|
||||||
// For NPCs try to get shield model from bodyparts first, with ground model as fallback
|
// Try to get shield model from bodyparts first, with ground model as fallback
|
||||||
for (auto & part : bodyparts)
|
for (const auto& part : bodyparts)
|
||||||
{
|
{
|
||||||
if (part.mPart != ESM::PRT_Shield)
|
if (part.mPart != ESM::PRT_Shield)
|
||||||
continue;
|
continue;
|
||||||
|
@ -538,16 +538,21 @@ std::string NpcAnimation::getShieldMesh(MWWorld::ConstPtr shield) const
|
||||||
|
|
||||||
if (!bodypartName.empty())
|
if (!bodypartName.empty())
|
||||||
{
|
{
|
||||||
const ESM::BodyPart *bodypart = 0;
|
const ESM::BodyPart *bodypart = partStore.search(bodypartName);
|
||||||
bodypart = partStore.search(bodypartName);
|
|
||||||
if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor)
|
if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor)
|
||||||
return "";
|
return std::string();
|
||||||
else if (!bodypart->mModel.empty())
|
else if (!bodypart->mModel.empty())
|
||||||
|
{
|
||||||
mesh = "meshes\\" + bodypart->mModel;
|
mesh = "meshes\\" + bodypart->mModel;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mesh.empty())
|
||||||
|
return std::string();
|
||||||
|
|
||||||
std::string holsteredName = mesh;
|
std::string holsteredName = mesh;
|
||||||
holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif");
|
holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif");
|
||||||
if(mResourceSystem->getVFS()->exists(holsteredName))
|
if(mResourceSystem->getVFS()->exists(holsteredName))
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
#include <components/resource/resourcesystem.hpp>
|
#include <components/resource/resourcesystem.hpp>
|
||||||
#include <components/resource/imagemanager.hpp>
|
#include <components/resource/imagemanager.hpp>
|
||||||
#include <components/resource/scenemanager.hpp>
|
#include <components/resource/scenemanager.hpp>
|
||||||
|
@ -47,8 +49,6 @@
|
||||||
|
|
||||||
#include <components/detournavigator/navigator.hpp>
|
#include <components/detournavigator/navigator.hpp>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
|
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwgui/loadingscreen.hpp"
|
#include "../mwgui/loadingscreen.hpp"
|
||||||
|
@ -767,7 +767,7 @@ namespace MWRender
|
||||||
int screenshotMapping = 0;
|
int screenshotMapping = 0;
|
||||||
|
|
||||||
std::vector<std::string> settingArgs;
|
std::vector<std::string> settingArgs;
|
||||||
boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" "));
|
Misc::StringUtils::split(settingStr, settingArgs);
|
||||||
|
|
||||||
if (settingArgs.size() > 0)
|
if (settingArgs.size() > 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "miscextensions.hpp"
|
#include "miscextensions.hpp"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Start of tes3mp addition
|
Start of tes3mp addition
|
||||||
|
@ -19,6 +20,8 @@
|
||||||
#include <components/compiler/opcodes.hpp>
|
#include <components/compiler/opcodes.hpp>
|
||||||
#include <components/compiler/locals.hpp>
|
#include <components/compiler/locals.hpp>
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
#include <components/interpreter/interpreter.hpp>
|
#include <components/interpreter/interpreter.hpp>
|
||||||
#include <components/interpreter/runtime.hpp>
|
#include <components/interpreter/runtime.hpp>
|
||||||
#include <components/interpreter/opcodes.hpp>
|
#include <components/interpreter/opcodes.hpp>
|
||||||
|
@ -1225,18 +1228,9 @@ namespace MWScript
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power)
|
|
||||||
{
|
|
||||||
runtime.getContext().report("spellcasting failed: you can only cast spells and powers.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ptr == MWMechanics::getPlayer())
|
if (ptr == MWMechanics::getPlayer())
|
||||||
{
|
{
|
||||||
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
|
MWBase::Environment::get().getWorld()->getPlayer().setSelectedSpell(spellId);
|
||||||
store.setSelectedEnchantItem(store.end());
|
|
||||||
MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, ptr)));
|
|
||||||
MWBase::Environment::get().getWindowManager()->updateSpellWindow();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1244,7 +1238,6 @@ namespace MWScript
|
||||||
{
|
{
|
||||||
MWMechanics::AiCast castPackage(targetId, spellId, true);
|
MWMechanics::AiCast castPackage(targetId, spellId, true);
|
||||||
ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr);
|
ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1268,9 +1261,29 @@ namespace MWScript
|
||||||
{
|
{
|
||||||
MWWorld::Ptr ptr = R()(runtime);
|
MWWorld::Ptr ptr = R()(runtime);
|
||||||
|
|
||||||
std::string spell = runtime.getStringLiteral (runtime[0].mInteger);
|
std::string spellId = runtime.getStringLiteral (runtime[0].mInteger);
|
||||||
runtime.pop();
|
runtime.pop();
|
||||||
|
|
||||||
|
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId);
|
||||||
|
if (!spell)
|
||||||
|
{
|
||||||
|
runtime.getContext().report("spellcasting failed: cannot find spell \""+spellId+"\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr == MWMechanics::getPlayer())
|
||||||
|
{
|
||||||
|
MWBase::Environment::get().getWorld()->getPlayer().setSelectedSpell(spellId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr.getClass().isActor())
|
||||||
|
{
|
||||||
|
MWMechanics::AiCast castPackage(ptr.getCellRef().getRefId(), spellId, true);
|
||||||
|
ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MWMechanics::CastSpell cast(ptr, ptr, false, true);
|
MWMechanics::CastSpell cast(ptr, ptr, false, true);
|
||||||
cast.mHitPosition = ptr.getRefData().getPosition().asVec3();
|
cast.mHitPosition = ptr.getRefData().getPosition().asVec3();
|
||||||
cast.mAlwaysSucceed = true;
|
cast.mAlwaysSucceed = true;
|
||||||
|
@ -1341,6 +1354,11 @@ namespace MWScript
|
||||||
|
|
||||||
std::stringstream msg;
|
std::stringstream msg;
|
||||||
|
|
||||||
|
msg << "Report time: ";
|
||||||
|
|
||||||
|
std::time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||||
|
msg << std::put_time(std::gmtime(¤tTime), "%Y.%m.%d %T UTC") << std::endl;
|
||||||
|
|
||||||
msg << "Content file: ";
|
msg << "Content file: ";
|
||||||
|
|
||||||
if (!ptr.getCellRef().hasContentFile())
|
if (!ptr.getCellRef().hasContentFile())
|
||||||
|
@ -1382,6 +1400,8 @@ namespace MWScript
|
||||||
--arg0;
|
--arg0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log(Debug::Warning) << "\n" << msg.str();
|
||||||
|
|
||||||
runtime.getContext().report(msg.str());
|
runtime.getContext().report(msg.str());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -537,14 +537,10 @@ void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor, TSlots&
|
||||||
continue;
|
continue;
|
||||||
if (iter->getClass().canBeEquipped(*iter, actor).first != 1)
|
if (iter->getClass().canBeEquipped(*iter, actor).first != 1)
|
||||||
continue;
|
continue;
|
||||||
if (iter->getClass().getItemHealth(*iter) <= 0)
|
|
||||||
continue;
|
|
||||||
std::pair<std::vector<int>, bool> shieldSlots =
|
std::pair<std::vector<int>, bool> shieldSlots =
|
||||||
iter->getClass().getEquipmentSlots(*iter);
|
iter->getClass().getEquipmentSlots(*iter);
|
||||||
if (shieldSlots.first.empty())
|
|
||||||
continue;
|
|
||||||
int slot = shieldSlots.first[0];
|
int slot = shieldSlots.first[0];
|
||||||
const ContainerStoreIterator& shield = mSlots[slot];
|
const ContainerStoreIterator& shield = slots_[slot];
|
||||||
if (shield != end()
|
if (shield != end()
|
||||||
&& shield.getType() == Type_Armor && shield->get<ESM::Armor>()->mBase->mData.mType == ESM::Armor::Shield)
|
&& shield.getType() == Type_Armor && shield->get<ESM::Armor>()->mBase->mData.mType == ESM::Armor::Shield)
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <components/esm/loadbsgn.hpp>
|
#include <components/esm/loadbsgn.hpp>
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
#include "../mwworld/inventorystore.hpp"
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
|
|
||||||
#include "../mwmechanics/movement.hpp"
|
#include "../mwmechanics/movement.hpp"
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
|
#include "../mwmechanics/spellcasting.hpp"
|
||||||
|
|
||||||
#include "class.hpp"
|
#include "class.hpp"
|
||||||
#include "ptr.hpp"
|
#include "ptr.hpp"
|
||||||
|
@ -527,4 +529,14 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
mPreviousItems.erase(boundItemId);
|
mPreviousItems.erase(boundItemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Player::setSelectedSpell(const std::string& spellId)
|
||||||
|
{
|
||||||
|
Ptr player = getPlayer();
|
||||||
|
InventoryStore& store = player.getClass().getInventoryStore(player);
|
||||||
|
store.setSelectedEnchantItem(store.end());
|
||||||
|
int castChance = int(MWMechanics::getSpellSuccessChance(spellId, player));
|
||||||
|
MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, castChance);
|
||||||
|
MWBase::Environment::get().getWindowManager()->updateSpellWindow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,8 @@ namespace MWWorld
|
||||||
void setPreviousItem(const std::string& boundItemId, const std::string& previousItemId);
|
void setPreviousItem(const std::string& boundItemId, const std::string& previousItemId);
|
||||||
std::string getPreviousItem(const std::string& boundItemId);
|
std::string getPreviousItem(const std::string& boundItemId);
|
||||||
void erasePreviousItem(const std::string& boundItemId);
|
void erasePreviousItem(const std::string& boundItemId);
|
||||||
|
|
||||||
|
void setSelectedSpell(const std::string& spellId);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -266,9 +266,13 @@ namespace
|
||||||
MOCK_CONST_METHOD0(numRecords, std::size_t ());
|
MOCK_CONST_METHOD0(numRecords, std::size_t ());
|
||||||
MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t));
|
MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t));
|
||||||
MOCK_CONST_METHOD0(numRoots, std::size_t ());
|
MOCK_CONST_METHOD0(numRoots, std::size_t ());
|
||||||
|
MOCK_CONST_METHOD1(getString, std::string (std::size_t));
|
||||||
MOCK_METHOD1(setUseSkinning, void (bool));
|
MOCK_METHOD1(setUseSkinning, void (bool));
|
||||||
MOCK_CONST_METHOD0(getUseSkinning, bool ());
|
MOCK_CONST_METHOD0(getUseSkinning, bool ());
|
||||||
MOCK_CONST_METHOD0(getFilename, std::string ());
|
MOCK_CONST_METHOD0(getFilename, std::string ());
|
||||||
|
MOCK_CONST_METHOD0(getVersion, unsigned int ());
|
||||||
|
MOCK_CONST_METHOD0(getUserVersion, unsigned int ());
|
||||||
|
MOCK_CONST_METHOD0(getBethVersion, unsigned int ());
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RecordMock : Nif::Record
|
struct RecordMock : Nif::Record
|
||||||
|
|
|
@ -291,7 +291,6 @@ add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR})
|
||||||
target_link_libraries(components
|
target_link_libraries(components
|
||||||
${Boost_SYSTEM_LIBRARY}
|
${Boost_SYSTEM_LIBRARY}
|
||||||
${Boost_FILESYSTEM_LIBRARY}
|
${Boost_FILESYSTEM_LIBRARY}
|
||||||
${Boost_THREAD_LIBRARY}
|
|
||||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
||||||
${Boost_IOSTREAMS_LIBRARY}
|
${Boost_IOSTREAMS_LIBRARY}
|
||||||
${OSG_LIBRARIES}
|
${OSG_LIBRARIES}
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
|
|
||||||
#include <DetourNavMesh.h>
|
#include <DetourNavMesh.h>
|
||||||
|
|
||||||
#include <fstream>
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/filesystem/fstream.hpp>
|
||||||
|
|
||||||
namespace DetourNavigator
|
namespace DetourNavigator
|
||||||
{
|
{
|
||||||
void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision)
|
void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision)
|
||||||
{
|
{
|
||||||
const auto path = pathPrefix + "recastmesh" + revision + ".obj";
|
const auto path = pathPrefix + "recastmesh" + revision + ".obj";
|
||||||
std::ofstream file(path);
|
boost::filesystem::ofstream file(boost::filesystem::path(path), std::ios::out);
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
throw NavigatorException("Open file failed: " + path);
|
throw NavigatorException("Open file failed: " + path);
|
||||||
file.exceptions(std::ios::failbit | std::ios::badbit);
|
file.exceptions(std::ios::failbit | std::ios::badbit);
|
||||||
|
@ -64,7 +65,7 @@ namespace DetourNavigator
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto path = pathPrefix + "all_tiles_navmesh" + revision + ".bin";
|
const auto path = pathPrefix + "all_tiles_navmesh" + revision + ".bin";
|
||||||
std::ofstream file(path, std::ios::binary);
|
boost::filesystem::ofstream file(boost::filesystem::path(path), std::ios::out | std::ios::binary);
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
throw NavigatorException("Open file failed: " + path);
|
throw NavigatorException("Open file failed: " + path);
|
||||||
file.exceptions(std::ios::failbit | std::ios::badbit);
|
file.exceptions(std::ios::failbit | std::ios::badbit);
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace ESM
|
||||||
int left = esm.getSubSize();
|
int left = esm.getSubSize();
|
||||||
if (left < s)
|
if (left < s)
|
||||||
esm.fail("SCVR string list is smaller than specified");
|
esm.fail("SCVR string list is smaller than specified");
|
||||||
esm.getExact(&tmp[0], s);
|
esm.getExact(tmp.data(), s);
|
||||||
if (left > s)
|
if (left > s)
|
||||||
esm.skip(left-s); // skip the leftover junk
|
esm.skip(left-s); // skip the leftover junk
|
||||||
|
|
||||||
|
@ -29,37 +29,47 @@ namespace ESM
|
||||||
|
|
||||||
// The tmp buffer is a null-byte separated string list, we
|
// The tmp buffer is a null-byte separated string list, we
|
||||||
// just have to pick out one string at a time.
|
// just have to pick out one string at a time.
|
||||||
char* str = &tmp[0];
|
char* str = tmp.data();
|
||||||
if (!str && mVarNames.size() > 0)
|
if (!str && mVarNames.size() > 0)
|
||||||
{
|
{
|
||||||
Log(Debug::Warning) << "SCVR with no variable names";
|
Log(Debug::Warning) << "SCVR with no variable names";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Support '\r' terminated strings like vanilla. See Bug #1324.
|
||||||
|
std::replace(tmp.begin(), tmp.end(), '\r', '\0');
|
||||||
|
// Avoid heap corruption
|
||||||
|
if (!tmp.empty() && tmp[tmp.size()-1] != '\0')
|
||||||
|
{
|
||||||
|
tmp.emplace_back('\0');
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Malformed string table";
|
||||||
|
ss << "\n File: " << esm.getName();
|
||||||
|
ss << "\n Record: " << esm.getContext().recName.toString();
|
||||||
|
ss << "\n Subrecord: " << "SCVR";
|
||||||
|
ss << "\n Offset: 0x" << std::hex << esm.getFileOffset();
|
||||||
|
Log(Debug::Verbose) << ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < mVarNames.size(); i++)
|
for (size_t i = 0; i < mVarNames.size(); i++)
|
||||||
{
|
{
|
||||||
// Support '\r' terminated strings like vanilla. See Bug #1324.
|
|
||||||
char *termsym = strchr(str, '\r');
|
|
||||||
if(termsym) *termsym = '\0';
|
|
||||||
mVarNames[i] = std::string(str);
|
mVarNames[i] = std::string(str);
|
||||||
str += mVarNames[i].size() + 1;
|
str += mVarNames[i].size() + 1;
|
||||||
|
if (static_cast<size_t>(str - tmp.data()) > tmp.size())
|
||||||
if (str - &tmp[0] > s)
|
|
||||||
{
|
{
|
||||||
// Apparently SCVR subrecord is not used and variable names are
|
// SCVR subrecord is unused and variable names are determined
|
||||||
// determined on the fly from the script text. Therefore don't throw
|
// from the script source, so an overflow is not fatal.
|
||||||
// an exeption, just log an error and continue.
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
|
||||||
ss << "String table overflow";
|
ss << "String table overflow";
|
||||||
ss << "\n File: " << esm.getName();
|
ss << "\n File: " << esm.getName();
|
||||||
ss << "\n Record: " << esm.getContext().recName.toString();
|
ss << "\n Record: " << esm.getContext().recName.toString();
|
||||||
ss << "\n Subrecord: " << "SCVR";
|
ss << "\n Subrecord: " << "SCVR";
|
||||||
ss << "\n Offset: 0x" << std::hex << esm.getFileOffset();
|
ss << "\n Offset: 0x" << std::hex << esm.getFileOffset();
|
||||||
Log(Debug::Verbose) << ss.str();
|
Log(Debug::Verbose) << ss.str();
|
||||||
|
// Get rid of empty strings in the list.
|
||||||
|
mVarNames.resize(i+1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,9 @@
|
||||||
#include <osg/Image>
|
#include <osg/Image>
|
||||||
#include <osg/Plane>
|
#include <osg/Plane>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/misc/resourcehelpers.hpp>
|
#include <components/misc/resourcehelpers.hpp>
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
#include <components/vfs/manager.hpp>
|
#include <components/vfs/manager.hpp>
|
||||||
|
|
||||||
namespace ESMTerrain
|
namespace ESMTerrain
|
||||||
|
@ -564,7 +563,7 @@ namespace ESMTerrain
|
||||||
if (mAutoUseNormalMaps)
|
if (mAutoUseNormalMaps)
|
||||||
{
|
{
|
||||||
std::string texture_ = texture;
|
std::string texture_ = texture;
|
||||||
boost::replace_last(texture_, ".", mNormalHeightMapPattern + ".");
|
Misc::StringUtils::replaceLast(texture_, ".", mNormalHeightMapPattern + ".");
|
||||||
if (mVFS->exists(texture_))
|
if (mVFS->exists(texture_))
|
||||||
{
|
{
|
||||||
info.mNormalMap = texture_;
|
info.mNormalMap = texture_;
|
||||||
|
@ -573,7 +572,7 @@ namespace ESMTerrain
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
texture_ = texture;
|
texture_ = texture;
|
||||||
boost::replace_last(texture_, ".", mNormalMapPattern + ".");
|
Misc::StringUtils::replaceLast(texture_, ".", mNormalMapPattern + ".");
|
||||||
if (mVFS->exists(texture_))
|
if (mVFS->exists(texture_))
|
||||||
info.mNormalMap = texture_;
|
info.mNormalMap = texture_;
|
||||||
}
|
}
|
||||||
|
@ -582,7 +581,7 @@ namespace ESMTerrain
|
||||||
if (mAutoUseSpecularMaps)
|
if (mAutoUseSpecularMaps)
|
||||||
{
|
{
|
||||||
std::string texture_ = texture;
|
std::string texture_ = texture;
|
||||||
boost::replace_last(texture_, ".", mSpecularMapPattern + ".");
|
Misc::StringUtils::replaceLast(texture_, ".", mSpecularMapPattern + ".");
|
||||||
if (mVFS->exists(texture_))
|
if (mVFS->exists(texture_))
|
||||||
{
|
{
|
||||||
info.mDiffuseMap = texture_;
|
info.mDiffuseMap = texture_;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef MISC_STRINGOPS_H
|
#ifndef MISC_STRINGOPS_H
|
||||||
#define MISC_STRINGOPS_H
|
#define MISC_STRINGOPS_H
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
@ -245,6 +246,58 @@ public:
|
||||||
{
|
{
|
||||||
return format(fmt.c_str(), args ...);
|
return format(fmt.c_str(), args ...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void trim(std::string &s)
|
||||||
|
{
|
||||||
|
// left trim
|
||||||
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch)
|
||||||
|
{
|
||||||
|
return !std::isspace(ch);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// right trim
|
||||||
|
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch)
|
||||||
|
{
|
||||||
|
return !std::isspace(ch);
|
||||||
|
}).base(), s.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Container>
|
||||||
|
static inline void split(const std::string& str, Container& cont, const std::string& delims = " ")
|
||||||
|
{
|
||||||
|
std::size_t current, previous = 0;
|
||||||
|
current = str.find_first_of(delims);
|
||||||
|
while (current != std::string::npos)
|
||||||
|
{
|
||||||
|
cont.push_back(str.substr(previous, current - previous));
|
||||||
|
previous = current + 1;
|
||||||
|
current = str.find_first_of(delims, previous);
|
||||||
|
}
|
||||||
|
cont.push_back(str.substr(previous, current - previous));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use the std::string_view once we will use the C++17.
|
||||||
|
// It should allow us to avoid data copying while we still will support both string and literal arguments.
|
||||||
|
|
||||||
|
static inline void replaceAll(std::string& data, std::string toSearch, std::string replaceStr)
|
||||||
|
{
|
||||||
|
size_t pos = data.find(toSearch);
|
||||||
|
|
||||||
|
while( pos != std::string::npos)
|
||||||
|
{
|
||||||
|
data.replace(pos, toSearch.size(), replaceStr);
|
||||||
|
pos = data.find(toSearch, pos + replaceStr.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void replaceLast(std::string& str, std::string substr, std::string with)
|
||||||
|
{
|
||||||
|
size_t pos = str.rfind(substr);
|
||||||
|
if (pos == std::string::npos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
str.replace(pos, substr.size(), with);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace Nif
|
||||||
{
|
{
|
||||||
Named::read(nif);
|
Named::read(nif);
|
||||||
|
|
||||||
external = !!nif->getChar();
|
external = nif->getChar() != 0;
|
||||||
if(external)
|
if(external)
|
||||||
filename = nif->getString();
|
filename = nif->getString();
|
||||||
else
|
else
|
||||||
|
|
|
@ -35,16 +35,16 @@ void ShapeData::read(NIFStream *nif)
|
||||||
{
|
{
|
||||||
int verts = nif->getUShort();
|
int verts = nif->getUShort();
|
||||||
|
|
||||||
if(nif->getInt())
|
if (nif->getBoolean())
|
||||||
nif->getVector3s(vertices, verts);
|
nif->getVector3s(vertices, verts);
|
||||||
|
|
||||||
if(nif->getInt())
|
if (nif->getBoolean())
|
||||||
nif->getVector3s(normals, verts);
|
nif->getVector3s(normals, verts);
|
||||||
|
|
||||||
center = nif->getVector3();
|
center = nif->getVector3();
|
||||||
radius = nif->getFloat();
|
radius = nif->getFloat();
|
||||||
|
|
||||||
if(nif->getInt())
|
if (nif->getBoolean())
|
||||||
nif->getVector4s(colors, verts);
|
nif->getVector4s(colors, verts);
|
||||||
|
|
||||||
// Only the first 6 bits are used as a count. I think the rest are
|
// Only the first 6 bits are used as a count. I think the rest are
|
||||||
|
@ -120,7 +120,7 @@ void NiAutoNormalParticlesData::read(NIFStream *nif)
|
||||||
particleRadius = nif->getFloat();
|
particleRadius = nif->getFloat();
|
||||||
activeCount = nif->getUShort();
|
activeCount = nif->getUShort();
|
||||||
|
|
||||||
if(nif->getInt())
|
if (nif->getBoolean())
|
||||||
{
|
{
|
||||||
// Particle sizes
|
// Particle sizes
|
||||||
nif->getFloats(sizes, vertices.size());
|
nif->getFloats(sizes, vertices.size());
|
||||||
|
@ -131,7 +131,7 @@ void NiRotatingParticlesData::read(NIFStream *nif)
|
||||||
{
|
{
|
||||||
NiAutoNormalParticlesData::read(nif);
|
NiAutoNormalParticlesData::read(nif);
|
||||||
|
|
||||||
if(nif->getInt())
|
if (nif->getBoolean())
|
||||||
{
|
{
|
||||||
// Rotation quaternions.
|
// Rotation quaternions.
|
||||||
nif->getQuaternions(rotations, vertices.size());
|
nif->getQuaternions(rotations, vertices.size());
|
||||||
|
@ -176,7 +176,7 @@ void NiPixelData::read(NIFStream *nif)
|
||||||
|
|
||||||
numberOfMipmaps = nif->getUInt();
|
numberOfMipmaps = nif->getUInt();
|
||||||
|
|
||||||
// Bytes per pixel, should be bpp * 8
|
// Bytes per pixel, should be bpp / 8
|
||||||
/* int bytes = */ nif->getUInt();
|
/* int bytes = */ nif->getUInt();
|
||||||
|
|
||||||
for(unsigned int i=0; i<numberOfMipmaps; i++)
|
for(unsigned int i=0; i<numberOfMipmaps; i++)
|
||||||
|
@ -228,10 +228,8 @@ void NiSkinData::read(NIFStream *nif)
|
||||||
nif->getInt(); // -1
|
nif->getInt(); // -1
|
||||||
|
|
||||||
bones.resize(boneNum);
|
bones.resize(boneNum);
|
||||||
for(int i=0;i<boneNum;i++)
|
for (BoneInfo &bi : bones)
|
||||||
{
|
{
|
||||||
BoneInfo &bi = bones[i];
|
|
||||||
|
|
||||||
bi.trafo.rotation = nif->getMatrix3();
|
bi.trafo.rotation = nif->getMatrix3();
|
||||||
bi.trafo.pos = nif->getVector3();
|
bi.trafo.pos = nif->getVector3();
|
||||||
bi.trafo.scale = nif->getFloat();
|
bi.trafo.scale = nif->getFloat();
|
||||||
|
@ -267,7 +265,7 @@ void NiKeyframeData::read(NIFStream *nif)
|
||||||
{
|
{
|
||||||
mRotations = std::make_shared<QuaternionKeyMap>();
|
mRotations = std::make_shared<QuaternionKeyMap>();
|
||||||
mRotations->read(nif);
|
mRotations->read(nif);
|
||||||
if(mRotations->mInterpolationType == Vector3KeyMap::sXYZInterpolation)
|
if(mRotations->mInterpolationType == InterpolationType_XYZ)
|
||||||
{
|
{
|
||||||
//Chomp unused float
|
//Chomp unused float
|
||||||
nif->getFloat();
|
nif->getFloat();
|
||||||
|
|
|
@ -9,9 +9,7 @@ namespace Nif
|
||||||
|
|
||||||
/// Open a NIF stream. The name is used for error messages.
|
/// Open a NIF stream. The name is used for error messages.
|
||||||
NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name)
|
NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name)
|
||||||
: ver(0)
|
: filename(name)
|
||||||
, filename(name)
|
|
||||||
, mUseSkinning(false)
|
|
||||||
{
|
{
|
||||||
parse(stream);
|
parse(stream);
|
||||||
}
|
}
|
||||||
|
@ -139,27 +137,18 @@ void NIFFile::parse(Files::IStreamPtr stream)
|
||||||
// Check the header string
|
// Check the header string
|
||||||
std::string head = nif.getVersionString();
|
std::string head = nif.getVersionString();
|
||||||
if(head.compare(0, 22, "NetImmerse File Format") != 0)
|
if(head.compare(0, 22, "NetImmerse File Format") != 0)
|
||||||
fail("Invalid NIF header: " + head);
|
fail("Invalid NIF header: " + head);
|
||||||
|
|
||||||
// Get BCD version
|
// Get BCD version
|
||||||
ver = nif.getUInt();
|
ver = nif.getUInt();
|
||||||
// 4.0.0.0 is an older, practically identical version of the format.
|
// 4.0.0.0 is an older, practically identical version of the format.
|
||||||
// It's not used by Morrowind assets but Morrowind supports it.
|
// It's not used by Morrowind assets but Morrowind supports it.
|
||||||
if(ver != 0x04000000 && ver != VER_MW)
|
if(ver != VER_4_0_0_0 && ver != VER_MW)
|
||||||
fail("Unsupported NIF version: " + printVersion(ver));
|
fail("Unsupported NIF version: " + printVersion(ver));
|
||||||
// Number of records
|
// Number of records
|
||||||
size_t recNum = nif.getInt();
|
size_t recNum = nif.getInt();
|
||||||
records.resize(recNum);
|
records.resize(recNum);
|
||||||
|
|
||||||
/* The format for 10.0.1.0 seems to be a bit different. After the
|
|
||||||
header, it contains the number of records, r (int), just like
|
|
||||||
4.0.0.2, but following that it contains a short x, followed by x
|
|
||||||
strings. Then again by r shorts, one for each record, giving
|
|
||||||
which of the above strings to use to identify the record. After
|
|
||||||
this follows two ints (zero?) and then the record data. However
|
|
||||||
we do not support or plan to support other versions yet.
|
|
||||||
*/
|
|
||||||
|
|
||||||
for(size_t i = 0;i < recNum;i++)
|
for(size_t i = 0;i < recNum;i++)
|
||||||
{
|
{
|
||||||
Record *r = nullptr;
|
Record *r = nullptr;
|
||||||
|
|
|
@ -26,21 +26,27 @@ struct File
|
||||||
|
|
||||||
virtual size_t numRoots() const = 0;
|
virtual size_t numRoots() const = 0;
|
||||||
|
|
||||||
|
virtual std::string getString(size_t index) const = 0;
|
||||||
|
|
||||||
virtual void setUseSkinning(bool skinning) = 0;
|
virtual void setUseSkinning(bool skinning) = 0;
|
||||||
|
|
||||||
virtual bool getUseSkinning() const = 0;
|
virtual bool getUseSkinning() const = 0;
|
||||||
|
|
||||||
virtual std::string getFilename() const = 0;
|
virtual std::string getFilename() const = 0;
|
||||||
|
|
||||||
|
virtual unsigned int getVersion() const = 0;
|
||||||
|
|
||||||
|
virtual unsigned int getUserVersion() const = 0;
|
||||||
|
|
||||||
|
virtual unsigned int getBethVersion() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NIFFile final : public File
|
class NIFFile final : public File
|
||||||
{
|
{
|
||||||
enum NIFVersion {
|
/// File version, user version, Bethesda version
|
||||||
VER_MW = 0x04000002 // Morrowind NIFs
|
unsigned int ver = 0;
|
||||||
};
|
unsigned int userVer = 0;
|
||||||
|
unsigned int bethVer = 0;
|
||||||
/// Nif file version
|
|
||||||
unsigned int ver;
|
|
||||||
|
|
||||||
/// File name, used for error messages and opening the file
|
/// File name, used for error messages and opening the file
|
||||||
std::string filename;
|
std::string filename;
|
||||||
|
@ -51,7 +57,10 @@ class NIFFile final : public File
|
||||||
/// Root list. This is a select portion of the pointers from records
|
/// Root list. This is a select portion of the pointers from records
|
||||||
std::vector<Record*> roots;
|
std::vector<Record*> roots;
|
||||||
|
|
||||||
bool mUseSkinning;
|
/// String table
|
||||||
|
std::vector<std::string> strings;
|
||||||
|
|
||||||
|
bool mUseSkinning = false;
|
||||||
|
|
||||||
/// Parse the file
|
/// Parse the file
|
||||||
void parse(Files::IStreamPtr stream);
|
void parse(Files::IStreamPtr stream);
|
||||||
|
@ -66,6 +75,34 @@ class NIFFile final : public File
|
||||||
void operator = (NIFFile const &);
|
void operator = (NIFFile const &);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
enum NIFVersion
|
||||||
|
{
|
||||||
|
// Feature-relevant
|
||||||
|
VER_4_1_0_0 = 0x04010000, // 1-byte booleans (previously 4-byte)
|
||||||
|
VER_5_0_0_1 = 0x05000001, // Optimized record type listings
|
||||||
|
VER_5_0_0_6 = 0x05000006, // Record groups
|
||||||
|
VER_10_0_1_8 = 0x0A000108, // The last version without user version
|
||||||
|
VER_20_1_0_1 = 0x14010001, // String tables
|
||||||
|
VER_20_2_0_5 = 0x14020005, // Record sizes
|
||||||
|
// Game-relevant
|
||||||
|
VER_4_0_0_0 = 0x04000000, // Freedom Force NIFs, supported by Morrowind
|
||||||
|
VER_MW = 0x04000002, // 4.0.0.2. Morrowind and Freedom Force NIFs
|
||||||
|
VER_4_2_1_0 = 0x04020100, // Used in Civ4 and Dark Age of Camelot
|
||||||
|
VER_CI = 0x04020200, // 4.2.2.0. Main Culpa Innata NIF version, also used in Civ4
|
||||||
|
VER_ZT2 = 0x0A000100, // 10.0.1.0. Main Zoo Tycoon 2 NIF version, also used in Oblivion and Civ4
|
||||||
|
VER_OB_OLD = 0x0A000102, // 10.0.1.2. Main older Oblivion NIF version
|
||||||
|
VER_GAMEBRYO = 0x0A010000, // 10.1.0.0. Lots of games use it. The first version that has Gamebryo File Format header.
|
||||||
|
VER_10_2_0_0 = 0x0A020000, // Lots of games use this version as well.
|
||||||
|
VER_CIV4 = 0x14000004, // 20.0.0.4. Main Civilization IV NIF version.
|
||||||
|
VER_OB = 0x14000005, // 20.0.0.5. Main Oblivion NIF version
|
||||||
|
VER_BGS = 0x14020007 // 20.2.0.7. Main Fallout 3/4/76/New Vegas and Skyrim/SkyrimSE NIF version.
|
||||||
|
};
|
||||||
|
enum BethVersion
|
||||||
|
{
|
||||||
|
BETHVER_FO3 = 34, // Fallout 3
|
||||||
|
BETHVER_FO4 = 130 // Fallout 4
|
||||||
|
};
|
||||||
|
|
||||||
/// Used if file parsing fails
|
/// Used if file parsing fails
|
||||||
void fail(const std::string &msg) const
|
void fail(const std::string &msg) const
|
||||||
{
|
{
|
||||||
|
@ -101,6 +138,12 @@ public:
|
||||||
/// Number of roots
|
/// Number of roots
|
||||||
size_t numRoots() const override { return roots.size(); }
|
size_t numRoots() const override { return roots.size(); }
|
||||||
|
|
||||||
|
/// Get a given string from the file's string table
|
||||||
|
std::string getString(size_t index) const override
|
||||||
|
{
|
||||||
|
return strings.at(index);
|
||||||
|
}
|
||||||
|
|
||||||
/// Set whether there is skinning contained in this NIF file.
|
/// Set whether there is skinning contained in this NIF file.
|
||||||
/// @note This is just a hint for users of the NIF file and has no effect on the loading procedure.
|
/// @note This is just a hint for users of the NIF file and has no effect on the loading procedure.
|
||||||
void setUseSkinning(bool skinning) override;
|
void setUseSkinning(bool skinning) override;
|
||||||
|
@ -109,8 +152,17 @@ public:
|
||||||
|
|
||||||
/// Get the name of the file
|
/// Get the name of the file
|
||||||
std::string getFilename() const override { return filename; }
|
std::string getFilename() const override { return filename; }
|
||||||
|
|
||||||
|
/// Get the version of the NIF format used
|
||||||
|
unsigned int getVersion() const override { return ver; }
|
||||||
|
|
||||||
|
/// Get the user version of the NIF format used
|
||||||
|
unsigned int getUserVersion() const override { return userVer; }
|
||||||
|
|
||||||
|
/// Get the Bethesda version of the NIF format used
|
||||||
|
unsigned int getBethVersion() const override { return bethVer; }
|
||||||
};
|
};
|
||||||
typedef std::shared_ptr<const Nif::NIFFile> NIFFilePtr;
|
using NIFFilePtr = std::shared_ptr<const Nif::NIFFile>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,16 @@
|
||||||
namespace Nif
|
namespace Nif
|
||||||
{
|
{
|
||||||
|
|
||||||
|
enum InterpolationType
|
||||||
|
{
|
||||||
|
InterpolationType_Unknown = 0,
|
||||||
|
InterpolationType_Linear = 1,
|
||||||
|
InterpolationType_Quadratic = 2,
|
||||||
|
InterpolationType_TBC = 3,
|
||||||
|
InterpolationType_XYZ = 4,
|
||||||
|
InterpolationType_Constant = 5
|
||||||
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct KeyT {
|
struct KeyT {
|
||||||
T mValue;
|
T mValue;
|
||||||
|
@ -26,34 +36,27 @@ struct KeyT {
|
||||||
float mContinuity; // Only for TBC interpolation
|
float mContinuity; // Only for TBC interpolation
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
typedef KeyT<float> FloatKey;
|
using FloatKey = KeyT<float>;
|
||||||
typedef KeyT<osg::Vec3f> Vector3Key;
|
using Vector3Key = KeyT<osg::Vec3f>;
|
||||||
typedef KeyT<osg::Vec4f> Vector4Key;
|
using Vector4Key = KeyT<osg::Vec4f>;
|
||||||
typedef KeyT<osg::Quat> QuaternionKey;
|
using QuaternionKey = KeyT<osg::Quat>;
|
||||||
|
|
||||||
template<typename T, T (NIFStream::*getValue)()>
|
template<typename T, T (NIFStream::*getValue)()>
|
||||||
struct KeyMapT {
|
struct KeyMapT {
|
||||||
typedef std::map< float, KeyT<T> > MapType;
|
using MapType = std::map<float, KeyT<T>>;
|
||||||
|
|
||||||
typedef T ValueType;
|
using ValueType = T;
|
||||||
typedef KeyT<T> KeyType;
|
using KeyType = KeyT<T>;
|
||||||
|
|
||||||
static const unsigned int sLinearInterpolation = 1;
|
unsigned int mInterpolationType = InterpolationType_Linear;
|
||||||
static const unsigned int sQuadraticInterpolation = 2;
|
|
||||||
static const unsigned int sTBCInterpolation = 3;
|
|
||||||
static const unsigned int sXYZInterpolation = 4;
|
|
||||||
|
|
||||||
unsigned int mInterpolationType;
|
|
||||||
MapType mKeys;
|
MapType mKeys;
|
||||||
|
|
||||||
KeyMapT() : mInterpolationType(sLinearInterpolation) {}
|
|
||||||
|
|
||||||
//Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html)
|
//Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html)
|
||||||
void read(NIFStream *nif, bool force=false)
|
void read(NIFStream *nif, bool force=false)
|
||||||
{
|
{
|
||||||
assert(nif);
|
assert(nif);
|
||||||
|
|
||||||
mInterpolationType = 0;
|
mInterpolationType = InterpolationType_Unknown;
|
||||||
|
|
||||||
size_t count = nif->getUInt();
|
size_t count = nif->getUInt();
|
||||||
if(count == 0 && !force)
|
if(count == 0 && !force)
|
||||||
|
@ -66,7 +69,8 @@ struct KeyMapT {
|
||||||
KeyT<T> key;
|
KeyT<T> key;
|
||||||
NIFStream &nifReference = *nif;
|
NIFStream &nifReference = *nif;
|
||||||
|
|
||||||
if(mInterpolationType == sLinearInterpolation)
|
if (mInterpolationType == InterpolationType_Linear
|
||||||
|
|| mInterpolationType == InterpolationType_Constant)
|
||||||
{
|
{
|
||||||
for(size_t i = 0;i < count;i++)
|
for(size_t i = 0;i < count;i++)
|
||||||
{
|
{
|
||||||
|
@ -75,7 +79,7 @@ struct KeyMapT {
|
||||||
mKeys[time] = key;
|
mKeys[time] = key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(mInterpolationType == sQuadraticInterpolation)
|
else if (mInterpolationType == InterpolationType_Quadratic)
|
||||||
{
|
{
|
||||||
for(size_t i = 0;i < count;i++)
|
for(size_t i = 0;i < count;i++)
|
||||||
{
|
{
|
||||||
|
@ -84,7 +88,7 @@ struct KeyMapT {
|
||||||
mKeys[time] = key;
|
mKeys[time] = key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(mInterpolationType == sTBCInterpolation)
|
else if (mInterpolationType == InterpolationType_TBC)
|
||||||
{
|
{
|
||||||
for(size_t i = 0;i < count;i++)
|
for(size_t i = 0;i < count;i++)
|
||||||
{
|
{
|
||||||
|
@ -94,11 +98,11 @@ struct KeyMapT {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//XYZ keys aren't actually read here.
|
//XYZ keys aren't actually read here.
|
||||||
//data.hpp sees that the last type read was sXYZInterpolation and:
|
//data.hpp sees that the last type read was InterpolationType_XYZ and:
|
||||||
// Eats a floating point number, then
|
// Eats a floating point number, then
|
||||||
// Re-runs the read function 3 more times.
|
// Re-runs the read function 3 more times.
|
||||||
// When it does that it's reading in a bunch of sLinearInterpolation keys, not sXYZInterpolation.
|
// When it does that it's reading in a bunch of InterpolationType_Linear keys, not InterpolationType_XYZ.
|
||||||
else if(mInterpolationType == sXYZInterpolation)
|
else if(mInterpolationType == InterpolationType_XYZ)
|
||||||
{
|
{
|
||||||
//Don't try to read XYZ keys into the wrong part
|
//Don't try to read XYZ keys into the wrong part
|
||||||
if ( count != 1 )
|
if ( count != 1 )
|
||||||
|
@ -109,7 +113,7 @@ struct KeyMapT {
|
||||||
nif->file->fail(error.str());
|
nif->file->fail(error.str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (0 == mInterpolationType)
|
else if (mInterpolationType == InterpolationType_Unknown)
|
||||||
{
|
{
|
||||||
if (count != 0)
|
if (count != 0)
|
||||||
nif->file->fail("Interpolation type 0 doesn't work with keys");
|
nif->file->fail("Interpolation type 0 doesn't work with keys");
|
||||||
|
@ -149,15 +153,17 @@ private:
|
||||||
/*key.mContinuity = */nif.getFloat();
|
/*key.mContinuity = */nif.getFloat();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
typedef KeyMapT<float,&NIFStream::getFloat> FloatKeyMap;
|
using FloatKeyMap = KeyMapT<float,&NIFStream::getFloat>;
|
||||||
typedef KeyMapT<osg::Vec3f,&NIFStream::getVector3> Vector3KeyMap;
|
using Vector3KeyMap = KeyMapT<osg::Vec3f,&NIFStream::getVector3>;
|
||||||
typedef KeyMapT<osg::Vec4f,&NIFStream::getVector4> Vector4KeyMap;
|
using Vector4KeyMap = KeyMapT<osg::Vec4f,&NIFStream::getVector4>;
|
||||||
typedef KeyMapT<osg::Quat,&NIFStream::getQuaternion> QuaternionKeyMap;
|
using QuaternionKeyMap = KeyMapT<osg::Quat,&NIFStream::getQuaternion>;
|
||||||
|
using ByteKeyMap = KeyMapT<char,&NIFStream::getChar>;
|
||||||
|
|
||||||
typedef std::shared_ptr<FloatKeyMap> FloatKeyMapPtr;
|
using FloatKeyMapPtr = std::shared_ptr<FloatKeyMap>;
|
||||||
typedef std::shared_ptr<Vector3KeyMap> Vector3KeyMapPtr;
|
using Vector3KeyMapPtr = std::shared_ptr<Vector3KeyMap>;
|
||||||
typedef std::shared_ptr<Vector4KeyMap> Vector4KeyMapPtr;
|
using Vector4KeyMapPtr = std::shared_ptr<Vector4KeyMap>;
|
||||||
typedef std::shared_ptr<QuaternionKeyMap> QuaternionKeyMapPtr;
|
using QuaternionKeyMapPtr = std::shared_ptr<QuaternionKeyMap>;
|
||||||
|
using ByteKeyMapPtr = std::shared_ptr<ByteKeyMap>;
|
||||||
|
|
||||||
} // Namespace
|
} // Namespace
|
||||||
#endif //#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP
|
#endif //#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP
|
||||||
|
|
|
@ -24,4 +24,21 @@ namespace Nif
|
||||||
t.scale = getFloat();
|
t.scale = getFloat();
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///Currently specific for 4.0.0.2 and earlier
|
||||||
|
bool NIFStream::getBoolean()
|
||||||
|
{
|
||||||
|
return getInt() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
///Read in a string, either from the string table using the index (currently absent) or from the stream using the specified length
|
||||||
|
std::string NIFStream::getString()
|
||||||
|
{
|
||||||
|
return getSizedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience utility functions: get the versions of the currently read file
|
||||||
|
unsigned int NIFStream::getVersion() { return file->getVersion(); }
|
||||||
|
unsigned int NIFStream::getUserVersion() { return file->getBethVersion(); }
|
||||||
|
unsigned int NIFStream::getBethVersion() { return file->getBethVersion(); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,8 +155,16 @@ public:
|
||||||
|
|
||||||
Transformation getTrafo();
|
Transformation getTrafo();
|
||||||
|
|
||||||
|
bool getBoolean();
|
||||||
|
|
||||||
|
std::string getString();
|
||||||
|
|
||||||
|
unsigned int getVersion();
|
||||||
|
unsigned int getUserVersion();
|
||||||
|
unsigned int getBethVersion();
|
||||||
|
|
||||||
///Read in a string of the given length
|
///Read in a string of the given length
|
||||||
std::string getString(size_t length)
|
std::string getSizedString(size_t length)
|
||||||
{
|
{
|
||||||
std::vector<char> str(length + 1, 0);
|
std::vector<char> str(length + 1, 0);
|
||||||
|
|
||||||
|
@ -165,11 +173,19 @@ public:
|
||||||
return str.data();
|
return str.data();
|
||||||
}
|
}
|
||||||
///Read in a string of the length specified in the file
|
///Read in a string of the length specified in the file
|
||||||
std::string getString()
|
std::string getSizedString()
|
||||||
{
|
{
|
||||||
size_t size = readLittleEndianType<uint32_t,uint32_t>(inp);
|
size_t size = readLittleEndianType<uint32_t,uint32_t>(inp);
|
||||||
return getString(size);
|
return getSizedString(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///Specific to Bethesda headers, uses a byte for length
|
||||||
|
std::string getExportString()
|
||||||
|
{
|
||||||
|
size_t size = static_cast<size_t>(readLittleEndianType<uint8_t,uint8_t>(inp));
|
||||||
|
return getSizedString(size);
|
||||||
|
}
|
||||||
|
|
||||||
///This is special since the version string doesn't start with a number, and ends with "\n"
|
///This is special since the version string doesn't start with a number, and ends with "\n"
|
||||||
std::string getVersionString()
|
std::string getVersionString()
|
||||||
{
|
{
|
||||||
|
@ -190,6 +206,18 @@ public:
|
||||||
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, vec.data(), size);
|
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, vec.data(), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getInts(std::vector<int> &vec, size_t size)
|
||||||
|
{
|
||||||
|
vec.resize(size);
|
||||||
|
readLittleEndianDynamicBufferOfType<int,int>(inp, vec.data(), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getUInts(std::vector<unsigned int> &vec, size_t size)
|
||||||
|
{
|
||||||
|
vec.resize(size);
|
||||||
|
readLittleEndianDynamicBufferOfType<unsigned int,unsigned int>(inp, vec.data(), size);
|
||||||
|
}
|
||||||
|
|
||||||
void getVector2s(std::vector<osg::Vec2f> &vec, size_t size)
|
void getVector2s(std::vector<osg::Vec2f> &vec, size_t size)
|
||||||
{
|
{
|
||||||
vec.resize(size);
|
vec.resize(size);
|
||||||
|
@ -217,6 +245,20 @@ public:
|
||||||
for (size_t i = 0;i < quat.size();i++)
|
for (size_t i = 0;i < quat.size();i++)
|
||||||
quat[i] = getQuaternion();
|
quat[i] = getQuaternion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getStrings(std::vector<std::string> &vec, size_t size)
|
||||||
|
{
|
||||||
|
vec.resize(size);
|
||||||
|
for (size_t i = 0; i < vec.size(); i++)
|
||||||
|
vec[i] = getString();
|
||||||
|
}
|
||||||
|
/// We need to use this when the string table isn't actually initialized.
|
||||||
|
void getSizedStrings(std::vector<std::string> &vec, size_t size)
|
||||||
|
{
|
||||||
|
vec.resize(size);
|
||||||
|
for (size_t i = 0; i < vec.size(); i++)
|
||||||
|
vec[i] = getSizedString();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ class Node : public Named
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// Node flags. Interpretation depends somewhat on the type of node.
|
// Node flags. Interpretation depends somewhat on the type of node.
|
||||||
int flags;
|
unsigned int flags;
|
||||||
Transformation trafo;
|
Transformation trafo;
|
||||||
osg::Vec3f velocity; // Unused? Might be a run-time game state
|
osg::Vec3f velocity; // Unused? Might be a run-time game state
|
||||||
PropertyList props;
|
PropertyList props;
|
||||||
|
@ -44,7 +44,7 @@ public:
|
||||||
velocity = nif->getVector3();
|
velocity = nif->getVector3();
|
||||||
props.read(nif);
|
props.read(nif);
|
||||||
|
|
||||||
hasBounds = !!nif->getInt();
|
hasBounds = nif->getBoolean();
|
||||||
if(hasBounds)
|
if(hasBounds)
|
||||||
{
|
{
|
||||||
nif->getInt(); // always 1
|
nif->getInt(); // always 1
|
||||||
|
|
|
@ -14,7 +14,7 @@ void Property::read(NIFStream *nif)
|
||||||
|
|
||||||
void NiTexturingProperty::Texture::read(NIFStream *nif)
|
void NiTexturingProperty::Texture::read(NIFStream *nif)
|
||||||
{
|
{
|
||||||
inUse = !!nif->getInt();
|
inUse = nif->getBoolean();
|
||||||
if(!inUse) return;
|
if(!inUse) return;
|
||||||
|
|
||||||
texture.read(nif);
|
texture.read(nif);
|
||||||
|
|
|
@ -167,6 +167,7 @@ using NiParticleModifierPtr = RecordPtrT<NiParticleModifier>;
|
||||||
|
|
||||||
using NodeList = RecordListT<Node>;
|
using NodeList = RecordListT<Node>;
|
||||||
using PropertyList = RecordListT<Property>;
|
using PropertyList = RecordListT<Property>;
|
||||||
|
using ExtraList = RecordListT<Extra>;
|
||||||
using NiSourceTextureList = RecordListT<NiSourceTexture>;
|
using NiSourceTextureList = RecordListT<NiSourceTexture>;
|
||||||
|
|
||||||
} // Namespace
|
} // Namespace
|
||||||
|
|
|
@ -35,17 +35,33 @@ namespace NifOsg
|
||||||
{
|
{
|
||||||
|
|
||||||
// interpolation of keyframes
|
// interpolation of keyframes
|
||||||
template <typename MapT, typename InterpolationFunc>
|
template <typename MapT>
|
||||||
class ValueInterpolator
|
class ValueInterpolator
|
||||||
{
|
{
|
||||||
public:
|
typename MapT::MapType::const_iterator retrieveKey(float time) const
|
||||||
typedef typename MapT::ValueType ValueT;
|
|
||||||
|
|
||||||
ValueInterpolator()
|
|
||||||
: mDefaultVal(ValueT())
|
|
||||||
{
|
{
|
||||||
|
// retrieve the current position in the map, optimized for the most common case
|
||||||
|
// where time moves linearly along the keyframe track
|
||||||
|
if (mLastHighKey != mKeys->mKeys.end())
|
||||||
|
{
|
||||||
|
if (time > mLastHighKey->first)
|
||||||
|
{
|
||||||
|
// try if we're there by incrementing one
|
||||||
|
++mLastLowKey;
|
||||||
|
++mLastHighKey;
|
||||||
|
}
|
||||||
|
if (mLastHighKey != mKeys->mKeys.end() && time >= mLastLowKey->first && time <= mLastHighKey->first)
|
||||||
|
return mLastHighKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mKeys->mKeys.lower_bound(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
using ValueT = typename MapT::ValueType;
|
||||||
|
|
||||||
|
ValueInterpolator() = default;
|
||||||
|
|
||||||
ValueInterpolator(std::shared_ptr<const MapT> keys, ValueT defaultVal = ValueT())
|
ValueInterpolator(std::shared_ptr<const MapT> keys, ValueT defaultVal = ValueT())
|
||||||
: mKeys(keys)
|
: mKeys(keys)
|
||||||
, mDefaultVal(defaultVal)
|
, mDefaultVal(defaultVal)
|
||||||
|
@ -67,44 +83,21 @@ namespace NifOsg
|
||||||
if(time <= keys.begin()->first)
|
if(time <= keys.begin()->first)
|
||||||
return keys.begin()->second.mValue;
|
return keys.begin()->second.mValue;
|
||||||
|
|
||||||
// retrieve the current position in the map, optimized for the most common case
|
typename MapT::MapType::const_iterator it = retrieveKey(time);
|
||||||
// where time moves linearly along the keyframe track
|
|
||||||
typename MapT::MapType::const_iterator it = mLastHighKey;
|
|
||||||
if (mLastHighKey != keys.end())
|
|
||||||
{
|
|
||||||
if (time > mLastHighKey->first)
|
|
||||||
{
|
|
||||||
// try if we're there by incrementing one
|
|
||||||
++mLastLowKey;
|
|
||||||
++mLastHighKey;
|
|
||||||
it = mLastHighKey;
|
|
||||||
}
|
|
||||||
if (mLastHighKey == keys.end() || (time < mLastLowKey->first || time > mLastHighKey->first))
|
|
||||||
it = keys.lower_bound(time); // still not there, reorient by performing lower_bound check on the whole map
|
|
||||||
}
|
|
||||||
else
|
|
||||||
it = keys.lower_bound(time);
|
|
||||||
|
|
||||||
// now do the actual interpolation
|
// now do the actual interpolation
|
||||||
if (it != keys.end())
|
if (it != keys.end())
|
||||||
{
|
{
|
||||||
float aTime = it->first;
|
|
||||||
const typename MapT::KeyType* aKey = &it->second;
|
|
||||||
|
|
||||||
// cache for next time
|
// cache for next time
|
||||||
mLastHighKey = it;
|
mLastHighKey = it;
|
||||||
|
mLastLowKey = --it;
|
||||||
|
|
||||||
typename MapT::MapType::const_iterator last = --it;
|
float a = (time - mLastLowKey->first) / (mLastHighKey->first - mLastLowKey->first);
|
||||||
mLastLowKey = last;
|
|
||||||
float aLastTime = last->first;
|
|
||||||
const typename MapT::KeyType* aLastKey = &last->second;
|
|
||||||
|
|
||||||
float a = (time - aLastTime) / (aTime - aLastTime);
|
return interpolate(mLastLowKey->second, mLastHighKey->second, a, mKeys->mInterpolationType);
|
||||||
|
|
||||||
return InterpolationFunc()(aLastKey->mValue, aKey->mValue, a);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
return keys.rbegin()->second.mValue;
|
return keys.rbegin()->second.mValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool empty() const
|
bool empty() const
|
||||||
|
@ -113,36 +106,44 @@ namespace NifOsg
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
template <typename ValueType>
|
||||||
|
ValueType interpolate(const Nif::KeyT<ValueType>& a, const Nif::KeyT<ValueType>& b, float fraction, unsigned int type) const
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case Nif::InterpolationType_Constant:
|
||||||
|
return fraction > 0.5f ? b.mValue : a.mValue;
|
||||||
|
default:
|
||||||
|
return a.mValue + ((b.mValue - a.mValue) * fraction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
osg::Quat interpolate(const Nif::KeyT<osg::Quat>& a, const Nif::KeyT<osg::Quat>& b, float fraction, unsigned int type) const
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case Nif::InterpolationType_Constant:
|
||||||
|
return fraction > 0.5f ? b.mValue : a.mValue;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
osg::Quat result;
|
||||||
|
result.slerp(fraction, a.mValue, b.mValue);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mutable typename MapT::MapType::const_iterator mLastLowKey;
|
mutable typename MapT::MapType::const_iterator mLastLowKey;
|
||||||
mutable typename MapT::MapType::const_iterator mLastHighKey;
|
mutable typename MapT::MapType::const_iterator mLastHighKey;
|
||||||
|
|
||||||
std::shared_ptr<const MapT> mKeys;
|
std::shared_ptr<const MapT> mKeys;
|
||||||
|
|
||||||
ValueT mDefaultVal;
|
ValueT mDefaultVal = ValueT();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LerpFunc
|
using QuaternionInterpolator = ValueInterpolator<Nif::QuaternionKeyMap>;
|
||||||
{
|
using FloatInterpolator = ValueInterpolator<Nif::FloatKeyMap>;
|
||||||
template <typename ValueType>
|
using Vec3Interpolator = ValueInterpolator<Nif::Vector3KeyMap>;
|
||||||
inline ValueType operator()(const ValueType& a, const ValueType& b, float fraction)
|
using Vec4Interpolator = ValueInterpolator<Nif::Vector4KeyMap>;
|
||||||
{
|
|
||||||
return a + ((b - a) * fraction);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct QuaternionSlerpFunc
|
|
||||||
{
|
|
||||||
inline osg::Quat operator()(const osg::Quat& a, const osg::Quat& b, float fraction)
|
|
||||||
{
|
|
||||||
osg::Quat result;
|
|
||||||
result.slerp(fraction, a, b);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef ValueInterpolator<Nif::QuaternionKeyMap, QuaternionSlerpFunc> QuaternionInterpolator;
|
|
||||||
typedef ValueInterpolator<Nif::FloatKeyMap, LerpFunc> FloatInterpolator;
|
|
||||||
typedef ValueInterpolator<Nif::Vector3KeyMap, LerpFunc> Vec3Interpolator;
|
|
||||||
|
|
||||||
class ControllerFunction : public SceneUtil::ControllerFunction
|
class ControllerFunction : public SceneUtil::ControllerFunction
|
||||||
{
|
{
|
||||||
|
|
|
@ -184,14 +184,16 @@ namespace NifOsg
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// @param filename used for warning messages.
|
/// @param filename used for warning messages.
|
||||||
LoaderImpl(const std::string& filename)
|
LoaderImpl(const std::string& filename, unsigned int ver, unsigned int userver, unsigned int bethver)
|
||||||
: mFilename(filename), mFirstRootTextureIndex(-1), mFoundFirstRootTexturingProperty(false)
|
: mFilename(filename), mVersion(ver), mUserVersion(userver), mBethVersion(bethver)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
std::string mFilename;
|
std::string mFilename;
|
||||||
size_t mFirstRootTextureIndex;
|
unsigned int mVersion, mUserVersion, mBethVersion;
|
||||||
bool mFoundFirstRootTexturingProperty;
|
|
||||||
|
size_t mFirstRootTextureIndex = -1;
|
||||||
|
bool mFoundFirstRootTexturingProperty = false;
|
||||||
|
|
||||||
static void loadKf(Nif::NIFFilePtr nif, KeyframeHolder& target)
|
static void loadKf(Nif::NIFFilePtr nif, KeyframeHolder& target)
|
||||||
{
|
{
|
||||||
|
@ -1846,13 +1848,13 @@ namespace NifOsg
|
||||||
|
|
||||||
osg::ref_ptr<osg::Node> Loader::load(Nif::NIFFilePtr file, Resource::ImageManager* imageManager)
|
osg::ref_ptr<osg::Node> Loader::load(Nif::NIFFilePtr file, Resource::ImageManager* imageManager)
|
||||||
{
|
{
|
||||||
LoaderImpl impl(file->getFilename());
|
LoaderImpl impl(file->getFilename(), file->getVersion(), file->getUserVersion(), file->getBethVersion());
|
||||||
return impl.load(file, imageManager);
|
return impl.load(file, imageManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Loader::loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target)
|
void Loader::loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target)
|
||||||
{
|
{
|
||||||
LoaderImpl impl(kf->getFilename());
|
LoaderImpl impl(kf->getFilename(), kf->getVersion(), kf->getUserVersion(), kf->getBethVersion());
|
||||||
impl.loadKf(kf, target);
|
impl.loadKf(kf, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,7 +151,6 @@ namespace NifOsg
|
||||||
float mCachedDefaultSize;
|
float mCachedDefaultSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef ValueInterpolator<Nif::Vector4KeyMap, LerpFunc> Vec4Interpolator;
|
|
||||||
class ParticleColorAffector : public osgParticle::Operator
|
class ParticleColorAffector : public osgParticle::Operator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -3,10 +3,9 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
#include <boost/filesystem/fstream.hpp>
|
#include <boost/filesystem/fstream.hpp>
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
#include <boost/lexical_cast.hpp>
|
|
||||||
|
|
||||||
void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, CategorySettingValueMap& settings)
|
void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, CategorySettingValueMap& settings)
|
||||||
{
|
{
|
||||||
|
@ -36,7 +35,7 @@ void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, Cat
|
||||||
fail("unterminated category");
|
fail("unterminated category");
|
||||||
|
|
||||||
currentCategory = line.substr(i+1, end - (i+1));
|
currentCategory = line.substr(i+1, end - (i+1));
|
||||||
boost::algorithm::trim(currentCategory);
|
Misc::StringUtils::trim(currentCategory);
|
||||||
i = end+1;
|
i = end+1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,11 +50,11 @@ void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, Cat
|
||||||
fail("unterminated setting name");
|
fail("unterminated setting name");
|
||||||
|
|
||||||
std::string setting = line.substr(i, (settingEnd-i));
|
std::string setting = line.substr(i, (settingEnd-i));
|
||||||
boost::algorithm::trim(setting);
|
Misc::StringUtils::trim(setting);
|
||||||
|
|
||||||
size_t valueBegin = settingEnd+1;
|
size_t valueBegin = settingEnd+1;
|
||||||
std::string value = line.substr(valueBegin);
|
std::string value = line.substr(valueBegin);
|
||||||
boost::algorithm::trim(value);
|
Misc::StringUtils::trim(value);
|
||||||
|
|
||||||
if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false)
|
if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false)
|
||||||
fail(std::string("duplicate setting: [" + currentCategory + "] " + setting));
|
fail(std::string("duplicate setting: [" + currentCategory + "] " + setting));
|
||||||
|
@ -142,7 +141,7 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con
|
||||||
|
|
||||||
// Update the current category.
|
// Update the current category.
|
||||||
currentCategory = line.substr(i+1, end - (i+1));
|
currentCategory = line.substr(i+1, end - (i+1));
|
||||||
boost::algorithm::trim(currentCategory);
|
Misc::StringUtils::trim(currentCategory);
|
||||||
|
|
||||||
// Write the (new) current category to the file.
|
// Write the (new) current category to the file.
|
||||||
ostream << "[" << currentCategory << "]" << std::endl;
|
ostream << "[" << currentCategory << "]" << std::endl;
|
||||||
|
@ -176,12 +175,12 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
std::string setting = line.substr(i, (settingEnd-i));
|
std::string setting = line.substr(i, (settingEnd-i));
|
||||||
boost::algorithm::trim(setting);
|
Misc::StringUtils::trim(setting);
|
||||||
|
|
||||||
// Get the existing value so we can see if we've changed it.
|
// Get the existing value so we can see if we've changed it.
|
||||||
size_t valueBegin = settingEnd+1;
|
size_t valueBegin = settingEnd+1;
|
||||||
std::string value = line.substr(valueBegin);
|
std::string value = line.substr(valueBegin);
|
||||||
boost::algorithm::trim(value);
|
Misc::StringUtils::trim(value);
|
||||||
|
|
||||||
// Construct the setting map key to determine whether the setting has already been
|
// Construct the setting map key to determine whether the setting has already been
|
||||||
// written to the file.
|
// written to the file.
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
|
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
#include <boost/filesystem/fstream.hpp>
|
#include <boost/filesystem/fstream.hpp>
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
namespace Shader
|
namespace Shader
|
||||||
{
|
{
|
||||||
|
@ -60,7 +60,7 @@ namespace Shader
|
||||||
|
|
||||||
bool parseIncludes(boost::filesystem::path shaderPath, std::string& source)
|
bool parseIncludes(boost::filesystem::path shaderPath, std::string& source)
|
||||||
{
|
{
|
||||||
boost::replace_all(source, "\r\n", "\n");
|
Misc::StringUtils::replaceAll(source, "\r\n", "\n");
|
||||||
|
|
||||||
std::set<boost::filesystem::path> includedFiles;
|
std::set<boost::filesystem::path> includedFiles;
|
||||||
size_t foundPos = 0;
|
size_t foundPos = 0;
|
||||||
|
@ -165,7 +165,7 @@ namespace Shader
|
||||||
std::string list = source.substr(listStart, listEnd - listStart);
|
std::string list = source.substr(listStart, listEnd - listStart);
|
||||||
std::vector<std::string> listElements;
|
std::vector<std::string> listElements;
|
||||||
if (list != "")
|
if (list != "")
|
||||||
boost::split(listElements, list, boost::is_any_of(","));
|
Misc::StringUtils::split (list, listElements, ",");
|
||||||
|
|
||||||
size_t contentStart = source.find_first_not_of("\n\r", listEnd);
|
size_t contentStart = source.find_first_not_of("\n\r", listEnd);
|
||||||
size_t contentEnd = source.find("$endforeach", contentStart);
|
size_t contentEnd = source.find("$endforeach", contentStart);
|
||||||
|
|
|
@ -6,9 +6,8 @@
|
||||||
|
|
||||||
#include <osgUtil/TangentSpaceGenerator>
|
#include <osgUtil/TangentSpaceGenerator>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
#include <components/resource/imagemanager.hpp>
|
#include <components/resource/imagemanager.hpp>
|
||||||
#include <components/vfs/manager.hpp>
|
#include <components/vfs/manager.hpp>
|
||||||
#include <components/sceneutil/riggeometry.hpp>
|
#include <components/sceneutil/riggeometry.hpp>
|
||||||
|
@ -145,7 +144,7 @@ namespace Shader
|
||||||
osg::ref_ptr<osg::Image> image;
|
osg::ref_ptr<osg::Image> image;
|
||||||
bool normalHeight = false;
|
bool normalHeight = false;
|
||||||
std::string normalHeightMap = normalMapFileName;
|
std::string normalHeightMap = normalMapFileName;
|
||||||
boost::replace_last(normalHeightMap, ".", mNormalHeightMapPattern + ".");
|
Misc::StringUtils::replaceLast(normalHeightMap, ".", mNormalHeightMapPattern + ".");
|
||||||
if (mImageManager.getVFS()->exists(normalHeightMap))
|
if (mImageManager.getVFS()->exists(normalHeightMap))
|
||||||
{
|
{
|
||||||
image = mImageManager.getImage(normalHeightMap);
|
image = mImageManager.getImage(normalHeightMap);
|
||||||
|
@ -153,7 +152,7 @@ namespace Shader
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
boost::replace_last(normalMapFileName, ".", mNormalMapPattern + ".");
|
Misc::StringUtils::replaceLast(normalMapFileName, ".", mNormalMapPattern + ".");
|
||||||
if (mImageManager.getVFS()->exists(normalMapFileName))
|
if (mImageManager.getVFS()->exists(normalMapFileName))
|
||||||
{
|
{
|
||||||
image = mImageManager.getImage(normalMapFileName);
|
image = mImageManager.getImage(normalMapFileName);
|
||||||
|
@ -184,7 +183,7 @@ namespace Shader
|
||||||
if (mAutoUseSpecularMaps && diffuseMap != nullptr && specularMap == nullptr && diffuseMap->getImage(0))
|
if (mAutoUseSpecularMaps && diffuseMap != nullptr && specularMap == nullptr && diffuseMap->getImage(0))
|
||||||
{
|
{
|
||||||
std::string specularMapFileName = diffuseMap->getImage(0)->getFileName();
|
std::string specularMapFileName = diffuseMap->getImage(0)->getFileName();
|
||||||
boost::replace_last(specularMapFileName, ".", mSpecularMapPattern + ".");
|
Misc::StringUtils::replaceLast(specularMapFileName, ".", mSpecularMapPattern + ".");
|
||||||
if (mImageManager.getVFS()->exists(specularMapFileName))
|
if (mImageManager.getVFS()->exists(specularMapFileName))
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Image> image (mImageManager.getImage(specularMapFileName));
|
osg::ref_ptr<osg::Image> image (mImageManager.getImage(specularMapFileName));
|
||||||
|
|
|
@ -103,13 +103,13 @@ actors processing range
|
||||||
:Range: 3584 to 7168
|
:Range: 3584 to 7168
|
||||||
:Default: 7168
|
:Default: 7168
|
||||||
|
|
||||||
This setting allows to specify a distance from player in game units, in which OpenMW updates actor's state.
|
This setting specifies the actor state update distance from the player in game units.
|
||||||
Actor state update includes AI, animations, and physics processing.
|
Actor state update includes AI, animations, and physics processing.
|
||||||
Actors near that border start softly fade out instead of just appearing/disapperaing.
|
Actors close to this distance softly fade in and out instead of appearing or disappearing abruptly.
|
||||||
It is not recommended to change this value from default if you use mods with
|
Keep in mind that actors running Travel AI packages are always active to avoid
|
||||||
long-range AiTravel packages (e.g. patrols, caravans and travellers).
|
issues in mods with long-range AiTravel packages (for example, patrols, caravans and travellers).
|
||||||
|
|
||||||
This setting can be controlled in game with the "Actors processing range slider" in the Prefs panel of the Options menu.
|
This setting can be controlled in game with the "Actors Processing Range" slider in the Prefs panel of the Options menu.
|
||||||
|
|
||||||
classic reflected absorb spells behavior
|
classic reflected absorb spells behavior
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
|
@ -213,7 +213,7 @@
|
||||||
<Widget type="AutoSizedButton" skin="MW_Button" position="4 194 137 24" align="Left Bottom" name="ResetControlsButton">
|
<Widget type="AutoSizedButton" skin="MW_Button" position="4 194 137 24" align="Left Bottom" name="ResetControlsButton">
|
||||||
<Property key="Caption" value="#{sControlsMenu1}"/>
|
<Property key="Caption" value="#{sControlsMenu1}"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="HBox" skin="" position="4 224 360 24" align="Left Bottom">
|
<Widget type="HBox" skin="" position="4 224 380 24" align="Left Bottom">
|
||||||
<Widget type="AutoSizedButton" skin="MW_Button" position="0 0 24 24" align="Left Bottom">
|
<Widget type="AutoSizedButton" skin="MW_Button" position="0 0 24 24" align="Left Bottom">
|
||||||
<UserString key="SettingCategory" value="Input"/>
|
<UserString key="SettingCategory" value="Input"/>
|
||||||
<UserString key="SettingName" value="invert x axis"/>
|
<UserString key="SettingName" value="invert x axis"/>
|
||||||
|
@ -231,10 +231,10 @@
|
||||||
<Property key="Caption" value="#{sMouseFlip}"/>
|
<Property key="Caption" value="#{sMouseFlip}"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="TextBox" skin="NormalText" position="4 254 336 18" align="Left Bottom">
|
<Widget type="TextBox" skin="NormalText" position="4 254 352 18" align="Left Bottom">
|
||||||
<Property key="Caption" value="Camera Sensitivity"/>
|
<Property key="Caption" value="Camera Sensitivity"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="ScrollBar" skin="MW_HScroll" position="4 278 336 18" align="HStretch Bottom">
|
<Widget type="ScrollBar" skin="MW_HScroll" position="4 278 352 18" align="HStretch Bottom">
|
||||||
<Property key="Range" value="10000"/>
|
<Property key="Range" value="10000"/>
|
||||||
<Property key="Page" value="300"/>
|
<Property key="Page" value="300"/>
|
||||||
<UserString key="SettingType" value="Slider"/>
|
<UserString key="SettingType" value="Slider"/>
|
||||||
|
@ -244,15 +244,15 @@
|
||||||
<UserString key="SettingMin" value="0.2"/>
|
<UserString key="SettingMin" value="0.2"/>
|
||||||
<UserString key="SettingMax" value="5.0"/>
|
<UserString key="SettingMax" value="5.0"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="TextBox" skin="SandText" position="4 302 336 18" align="Left Bottom">
|
<Widget type="TextBox" skin="SandText" position="4 302 352 18" align="Left Bottom">
|
||||||
<Property key="Caption" value="#{sLow}"/>
|
<Property key="Caption" value="#{sLow}"/>
|
||||||
<Property key="TextAlign" value="Left"/>
|
<Property key="TextAlign" value="Left"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="TextBox" skin="SandText" position="4 302 336 18" align="Right Bottom">
|
<Widget type="TextBox" skin="SandText" position="4 302 352 18" align="Right Bottom">
|
||||||
<Property key="Caption" value="#{sHigh}"/>
|
<Property key="Caption" value="#{sHigh}"/>
|
||||||
<Property key="TextAlign" value="Right"/>
|
<Property key="TextAlign" value="Right"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="HBox" skin="" position="4 324 336 24" align="Left Bottom">
|
<Widget type="HBox" skin="" position="4 324 352 24" align="Left Bottom">
|
||||||
<Widget type="AutoSizedButton" skin="MW_Button" position="0 0 24 24" align="Left Bottom">
|
<Widget type="AutoSizedButton" skin="MW_Button" position="0 0 24 24" align="Left Bottom">
|
||||||
<UserString key="SettingCategory" value="Input"/>
|
<UserString key="SettingCategory" value="Input"/>
|
||||||
<UserString key="SettingName" value="enable controller"/>
|
<UserString key="SettingName" value="enable controller"/>
|
||||||
|
@ -306,9 +306,9 @@
|
||||||
<Property key="Caption" value="Hint: press F3 to show \nthe current frame rate."/>
|
<Property key="Caption" value="Hint: press F3 to show \nthe current frame rate."/>
|
||||||
</Widget>
|
</Widget>
|
||||||
|
|
||||||
<Widget type="TextBox" skin="NormalText" position="0 198 329 18" align="Left Top" name="FovText">
|
<Widget type="TextBox" skin="NormalText" position="0 198 352 18" align="Left Top" name="FovText">
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="ScrollBar" skin="MW_HScroll" position="0 222 329 18" align="HStretch Top">
|
<Widget type="ScrollBar" skin="MW_HScroll" position="0 222 352 18" align="HStretch Top">
|
||||||
<Property key="Range" value="81"/>
|
<Property key="Range" value="81"/>
|
||||||
<Property key="Page" value="1"/>
|
<Property key="Page" value="1"/>
|
||||||
<UserString key="SettingType" value="Slider"/>
|
<UserString key="SettingType" value="Slider"/>
|
||||||
|
@ -320,18 +320,18 @@
|
||||||
<UserString key="SettingLabelWidget" value="FovText"/>
|
<UserString key="SettingLabelWidget" value="FovText"/>
|
||||||
<UserString key="SettingLabelCaption" value="Field of View (%s)"/>
|
<UserString key="SettingLabelCaption" value="Field of View (%s)"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="TextBox" skin="SandText" position="0 246 329 18" align="Left Top">
|
<Widget type="TextBox" skin="SandText" position="0 246 352 18" align="Left Top">
|
||||||
<Property key="Caption" value="#{sLow}"/>
|
<Property key="Caption" value="#{sLow}"/>
|
||||||
<Property key="TextAlign" value="Left"/>
|
<Property key="TextAlign" value="Left"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="TextBox" skin="SandText" position="0 246 329 18" align="Right Top">
|
<Widget type="TextBox" skin="SandText" position="0 246 352 18" align="Right Top">
|
||||||
<Property key="Caption" value="#{sHigh}"/>
|
<Property key="Caption" value="#{sHigh}"/>
|
||||||
<Property key="TextAlign" value="Right"/>
|
<Property key="TextAlign" value="Right"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="TextBox" skin="NormalText" position="0 268 329 18" align="Left Top" name="GammaText">
|
<Widget type="TextBox" skin="NormalText" position="0 268 352 18" align="Left Top" name="GammaText">
|
||||||
<Property key="Caption" value="#{sGamma_Correction}"/>
|
<Property key="Caption" value="#{sGamma_Correction}"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="ScrollBar" skin="MW_HScroll" position="0 292 329 18" align="HStretch Top" name="GammaSlider">
|
<Widget type="ScrollBar" skin="MW_HScroll" position="0 292 352 18" align="HStretch Top" name="GammaSlider">
|
||||||
<Property key="Range" value="10000"/>
|
<Property key="Range" value="10000"/>
|
||||||
<Property key="Page" value="300"/>
|
<Property key="Page" value="300"/>
|
||||||
<UserString key="SettingType" value="Slider"/>
|
<UserString key="SettingType" value="Slider"/>
|
||||||
|
@ -341,11 +341,11 @@
|
||||||
<UserString key="SettingMin" value="0.1"/>
|
<UserString key="SettingMin" value="0.1"/>
|
||||||
<UserString key="SettingMax" value="3.0"/>
|
<UserString key="SettingMax" value="3.0"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="TextBox" skin="SandText" position="0 316 329 18" align="Left Top" name="GammaTextDark">
|
<Widget type="TextBox" skin="SandText" position="0 316 352 18" align="Left Top" name="GammaTextDark">
|
||||||
<Property key="Caption" value="#{sDark_Gamma}"/>
|
<Property key="Caption" value="#{sDark_Gamma}"/>
|
||||||
<Property key="TextAlign" value="Left"/>
|
<Property key="TextAlign" value="Left"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="TextBox" skin="SandText" position="0 316 329 18" align="Right Top" name="GammaTextLight">
|
<Widget type="TextBox" skin="SandText" position="0 316 352 18" align="Right Top" name="GammaTextLight">
|
||||||
<Property key="Caption" value="#{sLight_Gamma}"/>
|
<Property key="Caption" value="#{sLight_Gamma}"/>
|
||||||
<Property key="TextAlign" value="Right"/>
|
<Property key="TextAlign" value="Right"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
|
@ -373,10 +373,10 @@
|
||||||
<UserString key="SettingLabelCaption" value="Anisotropy (%s)"/>
|
<UserString key="SettingLabelCaption" value="Anisotropy (%s)"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="TextBox" skin="NormalText" position="4 130 322 18" align="Left Top" name="RenderDistanceLabel">
|
<Widget type="TextBox" skin="NormalText" position="0 130 352 18" align="Left Top" name="RenderDistanceLabel">
|
||||||
<Property key="Caption" value="#{sRender_Distance}"/>
|
<Property key="Caption" value="#{sRender_Distance}"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="ScrollBar" skin="MW_HScroll" position="4 154 322 18" align="Left Top" name="RenderingDistanceSlider">
|
<Widget type="ScrollBar" skin="MW_HScroll" position="0 154 352 18" align="HStretch Top" name="RenderingDistanceSlider">
|
||||||
<Property key="Range" value="4609"/>
|
<Property key="Range" value="4609"/>
|
||||||
<Property key="Page" value="128"/>
|
<Property key="Page" value="128"/>
|
||||||
<UserString key="SettingType" value="Slider"/>
|
<UserString key="SettingType" value="Slider"/>
|
||||||
|
@ -388,7 +388,7 @@
|
||||||
<UserString key="SettingLabelWidget" value="RenderDistanceLabel"/>
|
<UserString key="SettingLabelWidget" value="RenderDistanceLabel"/>
|
||||||
<UserString key="SettingLabelCaption" value="#{sRender_Distance} (%s)"/>
|
<UserString key="SettingLabelCaption" value="#{sRender_Distance} (%s)"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="ScrollBar" skin="MW_HScroll" position="4 154 322 18" align="Left Top" name="LargeRenderingDistanceSlider">
|
<Widget type="ScrollBar" skin="MW_HScroll" position="0 154 352 18" align="HStretch Top" name="LargeRenderingDistanceSlider">
|
||||||
<Property key="Range" value="79873"/>
|
<Property key="Range" value="79873"/>
|
||||||
<Property key="Page" value="2048"/>
|
<Property key="Page" value="2048"/>
|
||||||
<UserString key="SettingType" value="Slider"/>
|
<UserString key="SettingType" value="Slider"/>
|
||||||
|
@ -400,11 +400,11 @@
|
||||||
<UserString key="SettingLabelWidget" value="RenderDistanceLabel"/>
|
<UserString key="SettingLabelWidget" value="RenderDistanceLabel"/>
|
||||||
<UserString key="SettingLabelCaption" value="#{sRender_Distance} (x%s)"/>
|
<UserString key="SettingLabelCaption" value="#{sRender_Distance} (x%s)"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="TextBox" skin="SandText" position="4 178 332 18" align="Left Top">
|
<Widget type="TextBox" skin="SandText" position="0 178 352 18" align="Left Top">
|
||||||
<Property key="Caption" value="#{sNear}"/>
|
<Property key="Caption" value="#{sNear}"/>
|
||||||
<Property key="TextAlign" value="Left"/>
|
<Property key="TextAlign" value="Left"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
<Widget type="TextBox" skin="SandText" position="4 178 332 18" align="Left Top">
|
<Widget type="TextBox" skin="SandText" position="0 178 352 18" align="Right Top">
|
||||||
<Property key="Caption" value="#{sFar}"/>
|
<Property key="Caption" value="#{sFar}"/>
|
||||||
<Property key="TextAlign" value="Right"/>
|
<Property key="TextAlign" value="Right"/>
|
||||||
</Widget>
|
</Widget>
|
||||||
|
|
Loading…
Reference in a new issue