mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-07-12 17:51:42 +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 #4270: Closing doors while they are obstructed desyncs closing sfx
|
||||
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 #4341: Error message about missing GDB is too vague
|
||||
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 #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 #4680: Heap corruption on faulty esp
|
||||
Bug #4701: PrisonMarker record is not hardcoded like other markers
|
||||
Bug #4703: Editor: it's possible to preview levelled list records
|
||||
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 #5067: Ranged attacks on unaware opponents ("critical hits") differ from the vanilla engine
|
||||
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 #5075: Enchanting cast style can be changed if there's no object
|
||||
Bug #5078: DisablePlayerLooking is broken
|
||||
|
@ -178,6 +181,7 @@
|
|||
Bug #5209: Spellcasting ignores race height
|
||||
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 #5212: AiTravel does not work for actors outside of AI processing range
|
||||
Bug #5213: SameFaction script function is broken
|
||||
Bug #5218: Crash when disabling ToggleBorders
|
||||
Bug #5220: GetLOS crashes when actor isn't loaded
|
||||
|
@ -185,6 +189,11 @@
|
|||
Bug #5223: Bow replacement during attack animation removes attached arrow
|
||||
Bug #5226: Reputation should be capped
|
||||
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 #2229: Improve pathfinding AI
|
||||
Feature #3025: Analogue gamepad movement controls
|
||||
|
@ -196,6 +205,8 @@
|
|||
Feature #3980: In-game option to disable controller
|
||||
Feature #3999: Shift + Double Click should maximize/restore menu size
|
||||
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 #4255: Handle broken RepairedOnMe script function
|
||||
Feature #4316: Implement RaiseRank/LowerRank functions properly
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <iomanip>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
|
||||
#include <osgDB/ReadFile>
|
||||
|
@ -349,7 +350,7 @@ namespace ESSImport
|
|||
|
||||
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
|
||||
writer.setVersion(0);
|
||||
writer.setType(0);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "document.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
|
@ -289,19 +288,22 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration,
|
|||
|
||||
if (mNew || !boost::filesystem::exists (mProjectPath))
|
||||
{
|
||||
boost::filesystem::path customFiltersPath (configuration.getUserDataPath());
|
||||
customFiltersPath /= "defaultfilters";
|
||||
boost::filesystem::path filtersPath (configuration.getUserDataPath() / "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))
|
||||
{
|
||||
destination << std::ifstream(customFiltersPath.string().c_str(), std::ios::binary).rdbuf();
|
||||
}
|
||||
else
|
||||
{
|
||||
destination << std::ifstream(std::string(mResDir.string() + "/defaultfilters").c_str(), std::ios::binary).rdbuf();
|
||||
}
|
||||
if (!boost::filesystem::exists (filtersPath))
|
||||
filtersPath = mResDir / "defaultfilters";
|
||||
|
||||
boost::filesystem::ifstream source(filtersPath, std::ios::in | std::ios::binary);
|
||||
if (!source.is_open())
|
||||
throw std::runtime_error("Can not read filters file: " + filtersPath.string());
|
||||
source.exceptions(std::ios::failbit | std::ios::badbit);
|
||||
|
||||
destination << source.rdbuf();
|
||||
}
|
||||
|
||||
if (mNew)
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
|
||||
#include <QComboBox>
|
||||
#include <QGridLayout>
|
||||
#include <QPushButton>
|
||||
#include <QStackedLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "../../model/prefs/setting.hpp"
|
||||
#include "../../model/prefs/category.hpp"
|
||||
#include "../../model/prefs/state.hpp"
|
||||
|
||||
namespace CSVPrefs
|
||||
{
|
||||
|
@ -29,8 +31,18 @@ namespace CSVPrefs
|
|||
mPageSelector = new QComboBox();
|
||||
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(stackedWidget);
|
||||
topLayout->addWidget(lineSeparator);
|
||||
topLayout->addWidget(resetButton);
|
||||
topLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
||||
|
||||
// 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;
|
||||
QGridLayout* mPageLayout;
|
||||
QComboBox* mPageSelector;
|
||||
|
||||
private slots:
|
||||
|
||||
void resetKeyBindings();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -400,17 +400,7 @@ namespace MWGui
|
|||
struct tm* timeinfo;
|
||||
timeinfo = localtime(&time);
|
||||
|
||||
// Use system/environment locale settings for datetime formatting
|
||||
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 << std::put_time(timeinfo, "%Y.%m.%d %T") << "\n";
|
||||
|
||||
text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n";
|
||||
text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCell << "}\n";
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
#include <MyGUI_Gui.h>
|
||||
#include <MyGUI_TabControl.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <SDL_video.h>
|
||||
|
||||
#include <iomanip>
|
||||
|
@ -45,10 +43,10 @@ namespace
|
|||
void parseResolution (int &x, int &y, const std::string& str)
|
||||
{
|
||||
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);
|
||||
boost::trim(split[0]);
|
||||
boost::trim(split[1]);
|
||||
Misc::StringUtils::trim(split[0]);
|
||||
Misc::StringUtils::trim(split[1]);
|
||||
x = MyGUI::utility::parseInt (split[0]);
|
||||
y = MyGUI::utility::parseInt (split[1]);
|
||||
}
|
||||
|
|
|
@ -1697,6 +1697,8 @@ namespace MWInput
|
|||
return "Zoom In";
|
||||
else if (action == A_ZoomOut)
|
||||
return "Zoom Out";
|
||||
else if (action == A_ToggleHUD)
|
||||
return "Toggle HUD";
|
||||
|
||||
descriptions[A_Use] = "sUse";
|
||||
descriptions[A_Activate] = "sActivate";
|
||||
|
@ -1875,6 +1877,7 @@ namespace MWInput
|
|||
ret.push_back(A_Console);
|
||||
ret.push_back(A_QuickSave);
|
||||
ret.push_back(A_QuickLoad);
|
||||
ret.push_back(A_ToggleHUD);
|
||||
ret.push_back(A_Screenshot);
|
||||
ret.push_back(A_QuickKeysMenu);
|
||||
ret.push_back(A_QuickKey1);
|
||||
|
@ -1908,6 +1911,7 @@ namespace MWInput
|
|||
ret.push_back(A_Rest);
|
||||
ret.push_back(A_QuickSave);
|
||||
ret.push_back(A_QuickLoad);
|
||||
ret.push_back(A_ToggleHUD);
|
||||
ret.push_back(A_Screenshot);
|
||||
ret.push_back(A_QuickKeysMenu);
|
||||
ret.push_back(A_QuickKey1);
|
||||
|
@ -1948,7 +1952,7 @@ namespace MWInput
|
|||
}
|
||||
|
||||
// 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;
|
||||
|
||||
#ifndef __APPLE__
|
||||
|
|
|
@ -161,8 +161,9 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
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_START = 4; // how many updates should pass before NPC can greet player
|
||||
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;
|
||||
|
||||
class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor
|
||||
|
@ -552,9 +553,10 @@ namespace MWMechanics
|
|||
{
|
||||
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;
|
||||
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)
|
||||
*/
|
||||
|
@ -1897,7 +1904,15 @@ namespace MWMechanics
|
|||
{
|
||||
const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length();
|
||||
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)
|
||||
if (isPlayer)
|
||||
activeFlag = 2;
|
||||
|
|
|
@ -104,6 +104,9 @@ namespace MWMechanics
|
|||
|
||||
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
|
||||
void reset();
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@ bool isActualAiPackage(int packageTypeId)
|
|||
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())
|
||||
{
|
||||
|
@ -209,6 +209,9 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
|
|||
}
|
||||
|
||||
MWMechanics::AiPackage* package = mPackages.front();
|
||||
if (!package->alwaysActive() && outOfRange)
|
||||
return;
|
||||
|
||||
int packageTypeId = package->getTypeId();
|
||||
// workaround ai packages not being handled as in the vanilla engine
|
||||
if (isActualAiPackage(packageTypeId))
|
||||
|
|
|
@ -110,7 +110,7 @@ namespace MWMechanics
|
|||
void stopPursuit();
|
||||
|
||||
/// 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
|
||||
void fastForward(const MWWorld::Ptr &actor);
|
||||
|
|
|
@ -34,6 +34,8 @@ namespace MWMechanics
|
|||
|
||||
virtual bool useVariableSpeed() const { return true;}
|
||||
|
||||
virtual bool alwaysActive() const { return true; }
|
||||
|
||||
virtual osg::Vec3f getDestination() { return osg::Vec3f(mX, mY, mZ); }
|
||||
|
||||
private:
|
||||
|
|
|
@ -2440,7 +2440,7 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
if(movestate != CharState_None && !isTurning())
|
||||
clearAnimQueue();
|
||||
|
||||
if(mAnimQueue.empty() || inwater || sneak)
|
||||
if(mAnimQueue.empty() || inwater || (sneak && mIdleState != CharState_SpecialIdle))
|
||||
{
|
||||
if (inwater)
|
||||
idlestate = CharState_IdleSwim;
|
||||
|
|
|
@ -464,9 +464,9 @@ namespace MWMechanics
|
|||
if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer))
|
||||
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
|
||||
&& (caster.isEmpty() || !caster.getClass().isActor() || caster == target))
|
||||
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
||||
continue;
|
||||
|
||||
// If player is healing someone, show the target's HP bar
|
||||
|
@ -569,6 +569,15 @@ namespace MWMechanics
|
|||
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
|
||||
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);
|
||||
if (hasDuration && effectIt->mDuration == 0)
|
||||
{
|
||||
|
@ -641,21 +650,19 @@ namespace MWMechanics
|
|||
// magnitude, since we're transferring stats from the target to the caster
|
||||
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;
|
||||
effect_.mMagnitude *= -1;
|
||||
absorbEffects.push_back(effect_);
|
||||
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell("", true,
|
||||
absorbEffects, mSourceName, caster.getClass().getCreatureStats(caster).getActorId());
|
||||
else
|
||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true,
|
||||
absorbEffects, mSourceName, target.getClass().getCreatureStats(target).getActorId());
|
||||
}
|
||||
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
|
||||
ActiveSpells::ActiveEffect effect_ = effect;
|
||||
effect_.mMagnitude *= -1;
|
||||
absorbEffects.push_back(effect_);
|
||||
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell("", true,
|
||||
absorbEffects, mSourceName, caster.getClass().getCreatureStats(caster).getActorId());
|
||||
else
|
||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true,
|
||||
absorbEffects, mSourceName, target.getClass().getCreatureStats(target).getActorId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwmechanics/actorutil.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 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;
|
||||
holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif");
|
||||
if(mResourceSystem->getVFS()->exists(holsteredName))
|
||||
|
|
|
@ -13,11 +13,13 @@
|
|||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
@ -114,6 +116,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
|||
MWWorld::ConstPtr item = *it;
|
||||
|
||||
std::string bonename;
|
||||
std::string itemModel = item.getClass().getModel(item);
|
||||
if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
|
||||
{
|
||||
if(item.getTypeName() == typeid(ESM::Weapon).name())
|
||||
|
@ -132,11 +135,30 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
|||
bonename = "Weapon Bone";
|
||||
}
|
||||
else
|
||||
{
|
||||
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
|
||||
{
|
||||
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();
|
||||
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);
|
||||
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())
|
||||
{
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
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
|
||||
for (auto & part : bodyparts)
|
||||
// Try to get shield model from bodyparts first, with ground model as fallback
|
||||
for (const auto& part : bodyparts)
|
||||
{
|
||||
if (part.mPart != ESM::PRT_Shield)
|
||||
continue;
|
||||
|
@ -538,16 +538,21 @@ std::string NpcAnimation::getShieldMesh(MWWorld::ConstPtr shield) const
|
|||
|
||||
if (!bodypartName.empty())
|
||||
{
|
||||
const ESM::BodyPart *bodypart = 0;
|
||||
bodypart = partStore.search(bodypartName);
|
||||
const ESM::BodyPart *bodypart = partStore.search(bodypartName);
|
||||
if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor)
|
||||
return "";
|
||||
return std::string();
|
||||
else if (!bodypart->mModel.empty())
|
||||
{
|
||||
mesh = "meshes\\" + bodypart->mModel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mesh.empty())
|
||||
return std::string();
|
||||
|
||||
std::string holsteredName = mesh;
|
||||
holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif");
|
||||
if(mResourceSystem->getVFS()->exists(holsteredName))
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
|
@ -47,8 +49,6 @@
|
|||
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwgui/loadingscreen.hpp"
|
||||
|
@ -767,7 +767,7 @@ namespace MWRender
|
|||
int screenshotMapping = 0;
|
||||
|
||||
std::vector<std::string> settingArgs;
|
||||
boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" "));
|
||||
Misc::StringUtils::split(settingStr, settingArgs);
|
||||
|
||||
if (settingArgs.size() > 0)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "miscextensions.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
@ -19,6 +20,8 @@
|
|||
#include <components/compiler/opcodes.hpp>
|
||||
#include <components/compiler/locals.hpp>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/interpreter/interpreter.hpp>
|
||||
#include <components/interpreter/runtime.hpp>
|
||||
#include <components/interpreter/opcodes.hpp>
|
||||
|
@ -1225,18 +1228,9 @@ namespace MWScript
|
|||
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())
|
||||
{
|
||||
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
|
||||
store.setSelectedEnchantItem(store.end());
|
||||
MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, ptr)));
|
||||
MWBase::Environment::get().getWindowManager()->updateSpellWindow();
|
||||
MWBase::Environment::get().getWorld()->getPlayer().setSelectedSpell(spellId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1244,7 +1238,6 @@ namespace MWScript
|
|||
{
|
||||
MWMechanics::AiCast castPackage(targetId, spellId, true);
|
||||
ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(castPackage, ptr);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1268,9 +1261,29 @@ namespace MWScript
|
|||
{
|
||||
MWWorld::Ptr ptr = R()(runtime);
|
||||
|
||||
std::string spell = runtime.getStringLiteral (runtime[0].mInteger);
|
||||
std::string spellId = runtime.getStringLiteral (runtime[0].mInteger);
|
||||
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);
|
||||
cast.mHitPosition = ptr.getRefData().getPosition().asVec3();
|
||||
cast.mAlwaysSucceed = true;
|
||||
|
@ -1341,6 +1354,11 @@ namespace MWScript
|
|||
|
||||
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: ";
|
||||
|
||||
if (!ptr.getCellRef().hasContentFile())
|
||||
|
@ -1382,6 +1400,8 @@ namespace MWScript
|
|||
--arg0;
|
||||
}
|
||||
|
||||
Log(Debug::Warning) << "\n" << msg.str();
|
||||
|
||||
runtime.getContext().report(msg.str());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -537,14 +537,10 @@ void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor, TSlots&
|
|||
continue;
|
||||
if (iter->getClass().canBeEquipped(*iter, actor).first != 1)
|
||||
continue;
|
||||
if (iter->getClass().getItemHealth(*iter) <= 0)
|
||||
continue;
|
||||
std::pair<std::vector<int>, bool> shieldSlots =
|
||||
iter->getClass().getEquipmentSlots(*iter);
|
||||
if (shieldSlots.first.empty())
|
||||
continue;
|
||||
int slot = shieldSlots.first[0];
|
||||
const ContainerStoreIterator& shield = mSlots[slot];
|
||||
const ContainerStoreIterator& shield = slots_[slot];
|
||||
if (shield != end()
|
||||
&& shield.getType() == Type_Armor && shield->get<ESM::Armor>()->mBase->mData.mType == ESM::Armor::Shield)
|
||||
{
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <components/esm/loadbsgn.hpp>
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
@ -31,6 +32,7 @@
|
|||
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
|
||||
#include "class.hpp"
|
||||
#include "ptr.hpp"
|
||||
|
@ -527,4 +529,14 @@ namespace MWWorld
|
|||
{
|
||||
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);
|
||||
std::string getPreviousItem(const std::string& boundItemId);
|
||||
void erasePreviousItem(const std::string& boundItemId);
|
||||
|
||||
void setSelectedSpell(const std::string& spellId);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -266,9 +266,13 @@ namespace
|
|||
MOCK_CONST_METHOD0(numRecords, std::size_t ());
|
||||
MOCK_CONST_METHOD1(getRoot, Nif::Record* (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_CONST_METHOD0(getUseSkinning, bool ());
|
||||
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
|
||||
|
|
|
@ -291,7 +291,6 @@ add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR})
|
|||
target_link_libraries(components
|
||||
${Boost_SYSTEM_LIBRARY}
|
||||
${Boost_FILESYSTEM_LIBRARY}
|
||||
${Boost_THREAD_LIBRARY}
|
||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
||||
${Boost_IOSTREAMS_LIBRARY}
|
||||
${OSG_LIBRARIES}
|
||||
|
|
|
@ -4,14 +4,15 @@
|
|||
|
||||
#include <DetourNavMesh.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision)
|
||||
{
|
||||
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())
|
||||
throw NavigatorException("Open file failed: " + path);
|
||||
file.exceptions(std::ios::failbit | std::ios::badbit);
|
||||
|
@ -64,7 +65,7 @@ namespace DetourNavigator
|
|||
};
|
||||
|
||||
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())
|
||||
throw NavigatorException("Open file failed: " + path);
|
||||
file.exceptions(std::ios::failbit | std::ios::badbit);
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace ESM
|
|||
int left = esm.getSubSize();
|
||||
if (left < s)
|
||||
esm.fail("SCVR string list is smaller than specified");
|
||||
esm.getExact(&tmp[0], s);
|
||||
esm.getExact(tmp.data(), s);
|
||||
if (left > s)
|
||||
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
|
||||
// just have to pick out one string at a time.
|
||||
char* str = &tmp[0];
|
||||
char* str = tmp.data();
|
||||
if (!str && mVarNames.size() > 0)
|
||||
{
|
||||
Log(Debug::Warning) << "SCVR with no variable names";
|
||||
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++)
|
||||
{
|
||||
// Support '\r' terminated strings like vanilla. See Bug #1324.
|
||||
char *termsym = strchr(str, '\r');
|
||||
if(termsym) *termsym = '\0';
|
||||
mVarNames[i] = std::string(str);
|
||||
str += mVarNames[i].size() + 1;
|
||||
|
||||
if (str - &tmp[0] > s)
|
||||
if (static_cast<size_t>(str - tmp.data()) > tmp.size())
|
||||
{
|
||||
// Apparently SCVR subrecord is not used and variable names are
|
||||
// determined on the fly from the script text. Therefore don't throw
|
||||
// an exeption, just log an error and continue.
|
||||
// SCVR subrecord is unused and variable names are determined
|
||||
// from the script source, so an overflow is not fatal.
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "String table overflow";
|
||||
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();
|
||||
// Get rid of empty strings in the list.
|
||||
mVarNames.resize(i+1);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
#include <osg/Image>
|
||||
#include <osg/Plane>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
namespace ESMTerrain
|
||||
|
@ -564,7 +563,7 @@ namespace ESMTerrain
|
|||
if (mAutoUseNormalMaps)
|
||||
{
|
||||
std::string texture_ = texture;
|
||||
boost::replace_last(texture_, ".", mNormalHeightMapPattern + ".");
|
||||
Misc::StringUtils::replaceLast(texture_, ".", mNormalHeightMapPattern + ".");
|
||||
if (mVFS->exists(texture_))
|
||||
{
|
||||
info.mNormalMap = texture_;
|
||||
|
@ -573,7 +572,7 @@ namespace ESMTerrain
|
|||
else
|
||||
{
|
||||
texture_ = texture;
|
||||
boost::replace_last(texture_, ".", mNormalMapPattern + ".");
|
||||
Misc::StringUtils::replaceLast(texture_, ".", mNormalMapPattern + ".");
|
||||
if (mVFS->exists(texture_))
|
||||
info.mNormalMap = texture_;
|
||||
}
|
||||
|
@ -582,7 +581,7 @@ namespace ESMTerrain
|
|||
if (mAutoUseSpecularMaps)
|
||||
{
|
||||
std::string texture_ = texture;
|
||||
boost::replace_last(texture_, ".", mSpecularMapPattern + ".");
|
||||
Misc::StringUtils::replaceLast(texture_, ".", mSpecularMapPattern + ".");
|
||||
if (mVFS->exists(texture_))
|
||||
{
|
||||
info.mDiffuseMap = texture_;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef MISC_STRINGOPS_H
|
||||
#define MISC_STRINGOPS_H
|
||||
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -245,6 +246,58 @@ public:
|
|||
{
|
||||
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);
|
||||
|
||||
external = !!nif->getChar();
|
||||
external = nif->getChar() != 0;
|
||||
if(external)
|
||||
filename = nif->getString();
|
||||
else
|
||||
|
|
|
@ -35,16 +35,16 @@ void ShapeData::read(NIFStream *nif)
|
|||
{
|
||||
int verts = nif->getUShort();
|
||||
|
||||
if(nif->getInt())
|
||||
if (nif->getBoolean())
|
||||
nif->getVector3s(vertices, verts);
|
||||
|
||||
if(nif->getInt())
|
||||
if (nif->getBoolean())
|
||||
nif->getVector3s(normals, verts);
|
||||
|
||||
center = nif->getVector3();
|
||||
radius = nif->getFloat();
|
||||
|
||||
if(nif->getInt())
|
||||
if (nif->getBoolean())
|
||||
nif->getVector4s(colors, verts);
|
||||
|
||||
// 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();
|
||||
activeCount = nif->getUShort();
|
||||
|
||||
if(nif->getInt())
|
||||
if (nif->getBoolean())
|
||||
{
|
||||
// Particle sizes
|
||||
nif->getFloats(sizes, vertices.size());
|
||||
|
@ -131,7 +131,7 @@ void NiRotatingParticlesData::read(NIFStream *nif)
|
|||
{
|
||||
NiAutoNormalParticlesData::read(nif);
|
||||
|
||||
if(nif->getInt())
|
||||
if (nif->getBoolean())
|
||||
{
|
||||
// Rotation quaternions.
|
||||
nif->getQuaternions(rotations, vertices.size());
|
||||
|
@ -176,7 +176,7 @@ void NiPixelData::read(NIFStream *nif)
|
|||
|
||||
numberOfMipmaps = nif->getUInt();
|
||||
|
||||
// Bytes per pixel, should be bpp * 8
|
||||
// Bytes per pixel, should be bpp / 8
|
||||
/* int bytes = */ nif->getUInt();
|
||||
|
||||
for(unsigned int i=0; i<numberOfMipmaps; i++)
|
||||
|
@ -228,10 +228,8 @@ void NiSkinData::read(NIFStream *nif)
|
|||
nif->getInt(); // -1
|
||||
|
||||
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.pos = nif->getVector3();
|
||||
bi.trafo.scale = nif->getFloat();
|
||||
|
@ -267,7 +265,7 @@ void NiKeyframeData::read(NIFStream *nif)
|
|||
{
|
||||
mRotations = std::make_shared<QuaternionKeyMap>();
|
||||
mRotations->read(nif);
|
||||
if(mRotations->mInterpolationType == Vector3KeyMap::sXYZInterpolation)
|
||||
if(mRotations->mInterpolationType == InterpolationType_XYZ)
|
||||
{
|
||||
//Chomp unused float
|
||||
nif->getFloat();
|
||||
|
|
|
@ -9,9 +9,7 @@ namespace Nif
|
|||
|
||||
/// Open a NIF stream. The name is used for error messages.
|
||||
NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name)
|
||||
: ver(0)
|
||||
, filename(name)
|
||||
, mUseSkinning(false)
|
||||
: filename(name)
|
||||
{
|
||||
parse(stream);
|
||||
}
|
||||
|
@ -139,27 +137,18 @@ void NIFFile::parse(Files::IStreamPtr stream)
|
|||
// Check the header string
|
||||
std::string head = nif.getVersionString();
|
||||
if(head.compare(0, 22, "NetImmerse File Format") != 0)
|
||||
fail("Invalid NIF header: " + head);
|
||||
fail("Invalid NIF header: " + head);
|
||||
|
||||
// Get BCD version
|
||||
ver = nif.getUInt();
|
||||
// 4.0.0.0 is an older, practically identical version of the format.
|
||||
// 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));
|
||||
// Number of records
|
||||
size_t recNum = nif.getInt();
|
||||
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++)
|
||||
{
|
||||
Record *r = nullptr;
|
||||
|
|
|
@ -26,21 +26,27 @@ struct File
|
|||
|
||||
virtual size_t numRoots() const = 0;
|
||||
|
||||
virtual std::string getString(size_t index) const = 0;
|
||||
|
||||
virtual void setUseSkinning(bool skinning) = 0;
|
||||
|
||||
virtual bool getUseSkinning() 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
|
||||
{
|
||||
enum NIFVersion {
|
||||
VER_MW = 0x04000002 // Morrowind NIFs
|
||||
};
|
||||
|
||||
/// Nif file version
|
||||
unsigned int ver;
|
||||
/// File version, user version, Bethesda version
|
||||
unsigned int ver = 0;
|
||||
unsigned int userVer = 0;
|
||||
unsigned int bethVer = 0;
|
||||
|
||||
/// File name, used for error messages and opening the file
|
||||
std::string filename;
|
||||
|
@ -51,7 +57,10 @@ class NIFFile final : public File
|
|||
/// Root list. This is a select portion of the pointers from records
|
||||
std::vector<Record*> roots;
|
||||
|
||||
bool mUseSkinning;
|
||||
/// String table
|
||||
std::vector<std::string> strings;
|
||||
|
||||
bool mUseSkinning = false;
|
||||
|
||||
/// Parse the file
|
||||
void parse(Files::IStreamPtr stream);
|
||||
|
@ -66,6 +75,34 @@ class NIFFile final : public File
|
|||
void operator = (NIFFile const &);
|
||||
|
||||
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
|
||||
void fail(const std::string &msg) const
|
||||
{
|
||||
|
@ -101,6 +138,12 @@ public:
|
|||
/// Number of roots
|
||||
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.
|
||||
/// @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;
|
||||
|
@ -109,8 +152,17 @@ public:
|
|||
|
||||
/// Get the name of the file
|
||||
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
|
||||
{
|
||||
|
||||
enum InterpolationType
|
||||
{
|
||||
InterpolationType_Unknown = 0,
|
||||
InterpolationType_Linear = 1,
|
||||
InterpolationType_Quadratic = 2,
|
||||
InterpolationType_TBC = 3,
|
||||
InterpolationType_XYZ = 4,
|
||||
InterpolationType_Constant = 5
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct KeyT {
|
||||
T mValue;
|
||||
|
@ -26,34 +36,27 @@ struct KeyT {
|
|||
float mContinuity; // Only for TBC interpolation
|
||||
*/
|
||||
};
|
||||
typedef KeyT<float> FloatKey;
|
||||
typedef KeyT<osg::Vec3f> Vector3Key;
|
||||
typedef KeyT<osg::Vec4f> Vector4Key;
|
||||
typedef KeyT<osg::Quat> QuaternionKey;
|
||||
using FloatKey = KeyT<float>;
|
||||
using Vector3Key = KeyT<osg::Vec3f>;
|
||||
using Vector4Key = KeyT<osg::Vec4f>;
|
||||
using QuaternionKey = KeyT<osg::Quat>;
|
||||
|
||||
template<typename T, T (NIFStream::*getValue)()>
|
||||
struct KeyMapT {
|
||||
typedef std::map< float, KeyT<T> > MapType;
|
||||
using MapType = std::map<float, KeyT<T>>;
|
||||
|
||||
typedef T ValueType;
|
||||
typedef KeyT<T> KeyType;
|
||||
using ValueType = T;
|
||||
using KeyType = KeyT<T>;
|
||||
|
||||
static const unsigned int sLinearInterpolation = 1;
|
||||
static const unsigned int sQuadraticInterpolation = 2;
|
||||
static const unsigned int sTBCInterpolation = 3;
|
||||
static const unsigned int sXYZInterpolation = 4;
|
||||
|
||||
unsigned int mInterpolationType;
|
||||
unsigned int mInterpolationType = InterpolationType_Linear;
|
||||
MapType mKeys;
|
||||
|
||||
KeyMapT() : mInterpolationType(sLinearInterpolation) {}
|
||||
|
||||
//Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html)
|
||||
void read(NIFStream *nif, bool force=false)
|
||||
{
|
||||
assert(nif);
|
||||
|
||||
mInterpolationType = 0;
|
||||
mInterpolationType = InterpolationType_Unknown;
|
||||
|
||||
size_t count = nif->getUInt();
|
||||
if(count == 0 && !force)
|
||||
|
@ -66,7 +69,8 @@ struct KeyMapT {
|
|||
KeyT<T> key;
|
||||
NIFStream &nifReference = *nif;
|
||||
|
||||
if(mInterpolationType == sLinearInterpolation)
|
||||
if (mInterpolationType == InterpolationType_Linear
|
||||
|| mInterpolationType == InterpolationType_Constant)
|
||||
{
|
||||
for(size_t i = 0;i < count;i++)
|
||||
{
|
||||
|
@ -75,7 +79,7 @@ struct KeyMapT {
|
|||
mKeys[time] = key;
|
||||
}
|
||||
}
|
||||
else if(mInterpolationType == sQuadraticInterpolation)
|
||||
else if (mInterpolationType == InterpolationType_Quadratic)
|
||||
{
|
||||
for(size_t i = 0;i < count;i++)
|
||||
{
|
||||
|
@ -84,7 +88,7 @@ struct KeyMapT {
|
|||
mKeys[time] = key;
|
||||
}
|
||||
}
|
||||
else if(mInterpolationType == sTBCInterpolation)
|
||||
else if (mInterpolationType == InterpolationType_TBC)
|
||||
{
|
||||
for(size_t i = 0;i < count;i++)
|
||||
{
|
||||
|
@ -94,11 +98,11 @@ struct KeyMapT {
|
|||
}
|
||||
}
|
||||
//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
|
||||
// Re-runs the read function 3 more times.
|
||||
// When it does that it's reading in a bunch of sLinearInterpolation keys, not sXYZInterpolation.
|
||||
else if(mInterpolationType == sXYZInterpolation)
|
||||
// When it does that it's reading in a bunch of InterpolationType_Linear keys, not InterpolationType_XYZ.
|
||||
else if(mInterpolationType == InterpolationType_XYZ)
|
||||
{
|
||||
//Don't try to read XYZ keys into the wrong part
|
||||
if ( count != 1 )
|
||||
|
@ -109,7 +113,7 @@ struct KeyMapT {
|
|||
nif->file->fail(error.str());
|
||||
}
|
||||
}
|
||||
else if (0 == mInterpolationType)
|
||||
else if (mInterpolationType == InterpolationType_Unknown)
|
||||
{
|
||||
if (count != 0)
|
||||
nif->file->fail("Interpolation type 0 doesn't work with keys");
|
||||
|
@ -149,15 +153,17 @@ private:
|
|||
/*key.mContinuity = */nif.getFloat();
|
||||
}
|
||||
};
|
||||
typedef KeyMapT<float,&NIFStream::getFloat> FloatKeyMap;
|
||||
typedef KeyMapT<osg::Vec3f,&NIFStream::getVector3> Vector3KeyMap;
|
||||
typedef KeyMapT<osg::Vec4f,&NIFStream::getVector4> Vector4KeyMap;
|
||||
typedef KeyMapT<osg::Quat,&NIFStream::getQuaternion> QuaternionKeyMap;
|
||||
using FloatKeyMap = KeyMapT<float,&NIFStream::getFloat>;
|
||||
using Vector3KeyMap = KeyMapT<osg::Vec3f,&NIFStream::getVector3>;
|
||||
using Vector4KeyMap = KeyMapT<osg::Vec4f,&NIFStream::getVector4>;
|
||||
using QuaternionKeyMap = KeyMapT<osg::Quat,&NIFStream::getQuaternion>;
|
||||
using ByteKeyMap = KeyMapT<char,&NIFStream::getChar>;
|
||||
|
||||
typedef std::shared_ptr<FloatKeyMap> FloatKeyMapPtr;
|
||||
typedef std::shared_ptr<Vector3KeyMap> Vector3KeyMapPtr;
|
||||
typedef std::shared_ptr<Vector4KeyMap> Vector4KeyMapPtr;
|
||||
typedef std::shared_ptr<QuaternionKeyMap> QuaternionKeyMapPtr;
|
||||
using FloatKeyMapPtr = std::shared_ptr<FloatKeyMap>;
|
||||
using Vector3KeyMapPtr = std::shared_ptr<Vector3KeyMap>;
|
||||
using Vector4KeyMapPtr = std::shared_ptr<Vector4KeyMap>;
|
||||
using QuaternionKeyMapPtr = std::shared_ptr<QuaternionKeyMap>;
|
||||
using ByteKeyMapPtr = std::shared_ptr<ByteKeyMap>;
|
||||
|
||||
} // Namespace
|
||||
#endif //#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP
|
||||
|
|
|
@ -24,4 +24,21 @@ namespace Nif
|
|||
t.scale = getFloat();
|
||||
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();
|
||||
|
||||
bool getBoolean();
|
||||
|
||||
std::string getString();
|
||||
|
||||
unsigned int getVersion();
|
||||
unsigned int getUserVersion();
|
||||
unsigned int getBethVersion();
|
||||
|
||||
///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);
|
||||
|
||||
|
@ -165,11 +173,19 @@ public:
|
|||
return str.data();
|
||||
}
|
||||
///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);
|
||||
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"
|
||||
std::string getVersionString()
|
||||
{
|
||||
|
@ -190,6 +206,18 @@ public:
|
|||
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)
|
||||
{
|
||||
vec.resize(size);
|
||||
|
@ -217,6 +245,20 @@ public:
|
|||
for (size_t i = 0;i < quat.size();i++)
|
||||
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:
|
||||
// Node flags. Interpretation depends somewhat on the type of node.
|
||||
int flags;
|
||||
unsigned int flags;
|
||||
Transformation trafo;
|
||||
osg::Vec3f velocity; // Unused? Might be a run-time game state
|
||||
PropertyList props;
|
||||
|
@ -44,7 +44,7 @@ public:
|
|||
velocity = nif->getVector3();
|
||||
props.read(nif);
|
||||
|
||||
hasBounds = !!nif->getInt();
|
||||
hasBounds = nif->getBoolean();
|
||||
if(hasBounds)
|
||||
{
|
||||
nif->getInt(); // always 1
|
||||
|
|
|
@ -14,7 +14,7 @@ void Property::read(NIFStream *nif)
|
|||
|
||||
void NiTexturingProperty::Texture::read(NIFStream *nif)
|
||||
{
|
||||
inUse = !!nif->getInt();
|
||||
inUse = nif->getBoolean();
|
||||
if(!inUse) return;
|
||||
|
||||
texture.read(nif);
|
||||
|
|
|
@ -167,6 +167,7 @@ using NiParticleModifierPtr = RecordPtrT<NiParticleModifier>;
|
|||
|
||||
using NodeList = RecordListT<Node>;
|
||||
using PropertyList = RecordListT<Property>;
|
||||
using ExtraList = RecordListT<Extra>;
|
||||
using NiSourceTextureList = RecordListT<NiSourceTexture>;
|
||||
|
||||
} // Namespace
|
||||
|
|
|
@ -35,17 +35,33 @@ namespace NifOsg
|
|||
{
|
||||
|
||||
// interpolation of keyframes
|
||||
template <typename MapT, typename InterpolationFunc>
|
||||
template <typename MapT>
|
||||
class ValueInterpolator
|
||||
{
|
||||
public:
|
||||
typedef typename MapT::ValueType ValueT;
|
||||
|
||||
ValueInterpolator()
|
||||
: mDefaultVal(ValueT())
|
||||
typename MapT::MapType::const_iterator retrieveKey(float time) const
|
||||
{
|
||||
// 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())
|
||||
: mKeys(keys)
|
||||
, mDefaultVal(defaultVal)
|
||||
|
@ -67,44 +83,21 @@ namespace NifOsg
|
|||
if(time <= keys.begin()->first)
|
||||
return keys.begin()->second.mValue;
|
||||
|
||||
// retrieve the current position in the map, optimized for the most common case
|
||||
// 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);
|
||||
typename MapT::MapType::const_iterator it = retrieveKey(time);
|
||||
|
||||
// now do the actual interpolation
|
||||
if (it != keys.end())
|
||||
{
|
||||
float aTime = it->first;
|
||||
const typename MapT::KeyType* aKey = &it->second;
|
||||
|
||||
// cache for next time
|
||||
mLastHighKey = it;
|
||||
mLastLowKey = --it;
|
||||
|
||||
typename MapT::MapType::const_iterator last = --it;
|
||||
mLastLowKey = last;
|
||||
float aLastTime = last->first;
|
||||
const typename MapT::KeyType* aLastKey = &last->second;
|
||||
float a = (time - mLastLowKey->first) / (mLastHighKey->first - mLastLowKey->first);
|
||||
|
||||
float a = (time - aLastTime) / (aTime - aLastTime);
|
||||
|
||||
return InterpolationFunc()(aLastKey->mValue, aKey->mValue, a);
|
||||
return interpolate(mLastLowKey->second, mLastHighKey->second, a, mKeys->mInterpolationType);
|
||||
}
|
||||
else
|
||||
return keys.rbegin()->second.mValue;
|
||||
|
||||
return keys.rbegin()->second.mValue;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
|
@ -113,36 +106,44 @@ namespace NifOsg
|
|||
}
|
||||
|
||||
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 mLastHighKey;
|
||||
|
||||
std::shared_ptr<const MapT> mKeys;
|
||||
|
||||
ValueT mDefaultVal;
|
||||
ValueT mDefaultVal = ValueT();
|
||||
};
|
||||
|
||||
struct LerpFunc
|
||||
{
|
||||
template <typename ValueType>
|
||||
inline ValueType operator()(const ValueType& a, const ValueType& b, float fraction)
|
||||
{
|
||||
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;
|
||||
using QuaternionInterpolator = ValueInterpolator<Nif::QuaternionKeyMap>;
|
||||
using FloatInterpolator = ValueInterpolator<Nif::FloatKeyMap>;
|
||||
using Vec3Interpolator = ValueInterpolator<Nif::Vector3KeyMap>;
|
||||
using Vec4Interpolator = ValueInterpolator<Nif::Vector4KeyMap>;
|
||||
|
||||
class ControllerFunction : public SceneUtil::ControllerFunction
|
||||
{
|
||||
|
|
|
@ -184,14 +184,16 @@ namespace NifOsg
|
|||
{
|
||||
public:
|
||||
/// @param filename used for warning messages.
|
||||
LoaderImpl(const std::string& filename)
|
||||
: mFilename(filename), mFirstRootTextureIndex(-1), mFoundFirstRootTexturingProperty(false)
|
||||
LoaderImpl(const std::string& filename, unsigned int ver, unsigned int userver, unsigned int bethver)
|
||||
: mFilename(filename), mVersion(ver), mUserVersion(userver), mBethVersion(bethver)
|
||||
{
|
||||
|
||||
}
|
||||
std::string mFilename;
|
||||
size_t mFirstRootTextureIndex;
|
||||
bool mFoundFirstRootTexturingProperty;
|
||||
unsigned int mVersion, mUserVersion, mBethVersion;
|
||||
|
||||
size_t mFirstRootTextureIndex = -1;
|
||||
bool mFoundFirstRootTexturingProperty = false;
|
||||
|
||||
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)
|
||||
{
|
||||
LoaderImpl impl(file->getFilename());
|
||||
LoaderImpl impl(file->getFilename(), file->getVersion(), file->getUserVersion(), file->getBethVersion());
|
||||
return impl.load(file, imageManager);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -151,7 +151,6 @@ namespace NifOsg
|
|||
float mCachedDefaultSize;
|
||||
};
|
||||
|
||||
typedef ValueInterpolator<Nif::Vector4KeyMap, LerpFunc> Vec4Interpolator;
|
||||
class ParticleColorAffector : public osgParticle::Operator
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
#include <sstream>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/stringops.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)
|
||||
{
|
||||
|
@ -36,7 +35,7 @@ void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, Cat
|
|||
fail("unterminated category");
|
||||
|
||||
currentCategory = line.substr(i+1, end - (i+1));
|
||||
boost::algorithm::trim(currentCategory);
|
||||
Misc::StringUtils::trim(currentCategory);
|
||||
i = end+1;
|
||||
}
|
||||
|
||||
|
@ -51,11 +50,11 @@ void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, Cat
|
|||
fail("unterminated setting name");
|
||||
|
||||
std::string setting = line.substr(i, (settingEnd-i));
|
||||
boost::algorithm::trim(setting);
|
||||
Misc::StringUtils::trim(setting);
|
||||
|
||||
size_t valueBegin = settingEnd+1;
|
||||
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)
|
||||
fail(std::string("duplicate setting: [" + currentCategory + "] " + setting));
|
||||
|
@ -142,7 +141,7 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con
|
|||
|
||||
// Update the current category.
|
||||
currentCategory = line.substr(i+1, end - (i+1));
|
||||
boost::algorithm::trim(currentCategory);
|
||||
Misc::StringUtils::trim(currentCategory);
|
||||
|
||||
// Write the (new) current category to the file.
|
||||
ostream << "[" << currentCategory << "]" << std::endl;
|
||||
|
@ -176,12 +175,12 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con
|
|||
continue;
|
||||
}
|
||||
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.
|
||||
size_t valueBegin = settingEnd+1;
|
||||
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
|
||||
// written to the file.
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
namespace Shader
|
||||
{
|
||||
|
@ -60,7 +60,7 @@ namespace Shader
|
|||
|
||||
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;
|
||||
size_t foundPos = 0;
|
||||
|
@ -165,7 +165,7 @@ namespace Shader
|
|||
std::string list = source.substr(listStart, listEnd - listStart);
|
||||
std::vector<std::string> listElements;
|
||||
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 contentEnd = source.find("$endforeach", contentStart);
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
|
||||
#include <osgUtil/TangentSpaceGenerator>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
#include <components/sceneutil/riggeometry.hpp>
|
||||
|
@ -145,7 +144,7 @@ namespace Shader
|
|||
osg::ref_ptr<osg::Image> image;
|
||||
bool normalHeight = false;
|
||||
std::string normalHeightMap = normalMapFileName;
|
||||
boost::replace_last(normalHeightMap, ".", mNormalHeightMapPattern + ".");
|
||||
Misc::StringUtils::replaceLast(normalHeightMap, ".", mNormalHeightMapPattern + ".");
|
||||
if (mImageManager.getVFS()->exists(normalHeightMap))
|
||||
{
|
||||
image = mImageManager.getImage(normalHeightMap);
|
||||
|
@ -153,7 +152,7 @@ namespace Shader
|
|||
}
|
||||
else
|
||||
{
|
||||
boost::replace_last(normalMapFileName, ".", mNormalMapPattern + ".");
|
||||
Misc::StringUtils::replaceLast(normalMapFileName, ".", mNormalMapPattern + ".");
|
||||
if (mImageManager.getVFS()->exists(normalMapFileName))
|
||||
{
|
||||
image = mImageManager.getImage(normalMapFileName);
|
||||
|
@ -184,7 +183,7 @@ namespace Shader
|
|||
if (mAutoUseSpecularMaps && diffuseMap != nullptr && specularMap == nullptr && diffuseMap->getImage(0))
|
||||
{
|
||||
std::string specularMapFileName = diffuseMap->getImage(0)->getFileName();
|
||||
boost::replace_last(specularMapFileName, ".", mSpecularMapPattern + ".");
|
||||
Misc::StringUtils::replaceLast(specularMapFileName, ".", mSpecularMapPattern + ".");
|
||||
if (mImageManager.getVFS()->exists(specularMapFileName))
|
||||
{
|
||||
osg::ref_ptr<osg::Image> image (mImageManager.getImage(specularMapFileName));
|
||||
|
|
|
@ -103,13 +103,13 @@ actors processing range
|
|||
:Range: 3584 to 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.
|
||||
Actors near that border start softly fade out instead of just appearing/disapperaing.
|
||||
It is not recommended to change this value from default if you use mods with
|
||||
long-range AiTravel packages (e.g. patrols, caravans and travellers).
|
||||
Actors close to this distance softly fade in and out instead of appearing or disappearing abruptly.
|
||||
Keep in mind that actors running Travel AI packages are always active to avoid
|
||||
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
|
||||
----------------------------------------
|
||||
|
|
|
@ -213,7 +213,7 @@
|
|||
<Widget type="AutoSizedButton" skin="MW_Button" position="4 194 137 24" align="Left Bottom" name="ResetControlsButton">
|
||||
<Property key="Caption" value="#{sControlsMenu1}"/>
|
||||
</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">
|
||||
<UserString key="SettingCategory" value="Input"/>
|
||||
<UserString key="SettingName" value="invert x axis"/>
|
||||
|
@ -231,10 +231,10 @@
|
|||
<Property key="Caption" value="#{sMouseFlip}"/>
|
||||
</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"/>
|
||||
</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="Page" value="300"/>
|
||||
<UserString key="SettingType" value="Slider"/>
|
||||
|
@ -244,15 +244,15 @@
|
|||
<UserString key="SettingMin" value="0.2"/>
|
||||
<UserString key="SettingMax" value="5.0"/>
|
||||
</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="TextAlign" value="Left"/>
|
||||
</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="TextAlign" value="Right"/>
|
||||
</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">
|
||||
<UserString key="SettingCategory" value="Input"/>
|
||||
<UserString key="SettingName" value="enable controller"/>
|
||||
|
@ -306,9 +306,9 @@
|
|||
<Property key="Caption" value="Hint: press F3 to show \nthe current frame rate."/>
|
||||
</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 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="Page" value="1"/>
|
||||
<UserString key="SettingType" value="Slider"/>
|
||||
|
@ -320,18 +320,18 @@
|
|||
<UserString key="SettingLabelWidget" value="FovText"/>
|
||||
<UserString key="SettingLabelCaption" value="Field of View (%s)"/>
|
||||
</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="TextAlign" value="Left"/>
|
||||
</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="TextAlign" value="Right"/>
|
||||
</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}"/>
|
||||
</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="Page" value="300"/>
|
||||
<UserString key="SettingType" value="Slider"/>
|
||||
|
@ -341,11 +341,11 @@
|
|||
<UserString key="SettingMin" value="0.1"/>
|
||||
<UserString key="SettingMax" value="3.0"/>
|
||||
</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="TextAlign" value="Left"/>
|
||||
</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="TextAlign" value="Right"/>
|
||||
</Widget>
|
||||
|
@ -373,10 +373,10 @@
|
|||
<UserString key="SettingLabelCaption" value="Anisotropy (%s)"/>
|
||||
</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}"/>
|
||||
</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="Page" value="128"/>
|
||||
<UserString key="SettingType" value="Slider"/>
|
||||
|
@ -388,7 +388,7 @@
|
|||
<UserString key="SettingLabelWidget" value="RenderDistanceLabel"/>
|
||||
<UserString key="SettingLabelCaption" value="#{sRender_Distance} (%s)"/>
|
||||
</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="Page" value="2048"/>
|
||||
<UserString key="SettingType" value="Slider"/>
|
||||
|
@ -400,11 +400,11 @@
|
|||
<UserString key="SettingLabelWidget" value="RenderDistanceLabel"/>
|
||||
<UserString key="SettingLabelCaption" value="#{sRender_Distance} (x%s)"/>
|
||||
</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="TextAlign" value="Left"/>
|
||||
</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="TextAlign" value="Right"/>
|
||||
</Widget>
|
||||
|
|
Loading…
Reference in a new issue