Merge pull request #295 from TES3MP/master

Add master commits up to 20 Sep 2017
new-script-api
David Cernat 7 years ago committed by GitHub
commit 76f1a61538

@ -24,7 +24,7 @@ void CSVPrefs::Dialogue::buildCategorySelector (QSplitter *main)
main->addWidget (list);
QFontMetrics metrics (QApplication::font());
QFontMetrics metrics (QApplication::font(list));
int maxWidth = 1;

@ -235,6 +235,10 @@ namespace MWBase
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0;
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const = 0;
/// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0;
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0;
/// List the owners that the player has stolen this item from (the owner can be an NPC or a faction).

@ -82,9 +82,6 @@ namespace MWBase
///< Play a soundifle
/// \param filename name of a sound file in "Music/" in the data directory.
virtual void startRandomTitle() = 0;
///< Starts a random track from the current playlist
virtual bool isMusicPlaying() = 0;
///< Returns true if music is playing

@ -3,6 +3,7 @@
#include <MyGUI_ListBox.h>
#include <MyGUI_ImageBox.h>
#include <MyGUI_Gui.h>
#include <MyGUI_ScrollView.h>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -244,6 +245,11 @@ namespace MWGui
}
}
}
}
// Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden
mSpellArea->setVisibleVScroll(false);
mSpellArea->setCanvasSize(MyGUI::IntSize(mSpellArea->getWidth(), std::max(mSpellArea->getHeight(), coord.top)));
mSpellArea->setVisibleVScroll(true);
mSpellArea->setViewOffset(MyGUI::IntPoint(0, 0));
}
}

@ -47,7 +47,7 @@ namespace MWGui
void updateSpells();
MyGUI::ListBox* mBirthList;
MyGUI::Widget* mSpellArea;
MyGUI::ScrollView* mSpellArea;
MyGUI::ImageBox* mBirthImage;
std::vector<MyGUI::Widget*> mSpellItems;

@ -750,7 +750,9 @@ namespace MWGui
lastId = item.getCellRef().getRefId();
if (item.getClass().getTypeName() == typeid(ESM::Weapon).name() && isRightHandWeapon(item))
if (item.getClass().getTypeName() == typeid(ESM::Weapon).name() &&
isRightHandWeapon(item) &&
item.getClass().canBeEquipped(item, player).first)
{
found = true;
break;

@ -206,7 +206,8 @@ namespace MWGui
int textButtonPadding = 10; // padding between the text-widget und the button-widget
int buttonLeftPadding = 10; // padding between the buttons if horizontal
int buttonTopPadding = 10; // ^-- if vertical
int buttonPadding = 5; // padding between button label and button itself
int buttonLabelLeftPadding = 12; // padding between button label and button itself, from left
int buttonLabelTopPadding = 4; // padding between button label and button itself, from top
int buttonMainPadding = 10; // padding between buttons and bottom of the main widget
mMarkedToDelete = false;
@ -245,10 +246,10 @@ namespace MWGui
if (buttonsWidth != 0)
buttonsWidth += buttonLeftPadding;
int buttonWidth = button->getTextSize().width + 2*buttonPadding;
int buttonWidth = button->getTextSize().width + 2*buttonLabelLeftPadding;
buttonsWidth += buttonWidth;
buttonHeight = button->getTextSize().height + 2*buttonPadding;
buttonHeight = button->getTextSize().height + 2*buttonLabelTopPadding;
if (buttonsHeight != 0)
buttonsHeight += buttonTopPadding;
@ -295,8 +296,8 @@ namespace MWGui
buttonCord.left = left;
buttonCord.top = messageWidgetCoord.top + textSize.height + textButtonPadding;
buttonSize.width = (*button)->getTextSize().width + 2*buttonPadding;
buttonSize.height = (*button)->getTextSize().height + 2*buttonPadding;
buttonSize.width = (*button)->getTextSize().width + 2*buttonLabelLeftPadding;
buttonSize.height = (*button)->getTextSize().height + 2*buttonLabelTopPadding;
(*button)->setCoord(buttonCord);
(*button)->setSize(buttonSize);
@ -322,8 +323,8 @@ namespace MWGui
std::vector<MyGUI::Button*>::const_iterator button;
for(button = mButtons.begin(); button != mButtons.end(); ++button)
{
buttonSize.width = (*button)->getTextSize().width + buttonPadding*2;
buttonSize.height = (*button)->getTextSize().height + buttonPadding*2;
buttonSize.width = (*button)->getTextSize().width + buttonLabelLeftPadding*2;
buttonSize.height = (*button)->getTextSize().height + buttonLabelTopPadding*2;
buttonCord.top = top;
buttonCord.left = (mainWidgetSize.width - buttonSize.width)/2;

@ -1175,6 +1175,39 @@ namespace MWMechanics
}
}
bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer)
{
if (!actor.getClass().isActor())
return false;
// If an observer is NPC, check if he detected an actor
if (!observer.isEmpty() && observer.getClass().isNpc())
{
return
MWBase::Environment::get().getWorld()->getLOS(observer, actor) &&
MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer);
}
// Otherwise check if any actor in AI processing range sees the target actor
std::vector<MWWorld::Ptr> actors;
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
getObjectsInRange(position, aiProcessingDistance, actors);
for(std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); ++it)
{
if (*it == actor)
continue;
bool result =
MWBase::Environment::get().getWorld()->getLOS(*it, actor) &&
MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, *it);
if (result)
return true;
}
return false;
}
void Actors::updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr)
{
PtrActorMap::iterator iter = mActors.find(old);
@ -1988,8 +2021,9 @@ namespace MWMechanics
{
std::string id = reader.getHString();
int count;
reader.getHNT (count, "COUN");
mDeathCount[id] = count;
reader.getHNT(count, "COUN");
if (MWBase::Environment::get().getWorld()->getStore().find(id))
mDeathCount[id] = count;
}
}
}

@ -57,6 +57,10 @@ namespace MWMechanics
PtrActorMap::const_iterator begin() { return mActors.begin(); }
PtrActorMap::const_iterator end() { return mActors.end(); }
/// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer);
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
/// paused we may want to do it manually (after equipping permanent enchantment)
void updateMagicEffects (const MWWorld::Ptr& ptr);

@ -1822,20 +1822,11 @@ void CharacterController::update(float duration)
if(sneak || inwater || flying)
vec.z() = 0.0f;
if (inwater || flying)
cls.getCreatureStats(mPtr).land();
bool inJump = true;
if(!onground && !flying && !inwater)
{
// In the air (either getting up —ascending part of jump— or falling).
if (world->isSlowFalling(mPtr))
{
// SlowFalling spell effect is active, do not keep previous fall height
cls.getCreatureStats(mPtr).land();
}
forcestateupdate = (mJumpState != JumpState_InAir);
jumpstate = JumpState_InAir;
@ -1881,7 +1872,7 @@ void CharacterController::update(float duration)
}
}
}
else if(mJumpState == JumpState_InAir)
else if(mJumpState == JumpState_InAir && !inwater && !flying)
{
forcestateupdate = true;
jumpstate = JumpState_Landing;
@ -1964,9 +1955,6 @@ void CharacterController::update(float duration)
movestate = mMovementState;
}
if (onground)
cls.getCreatureStats(mPtr).land();
if(movestate != CharState_None && movestate != CharState_TurnLeft && movestate != CharState_TurnRight)
clearAnimQueue();
@ -2158,76 +2146,71 @@ void CharacterController::unpersistAnimationState()
bool CharacterController::playGroup(const std::string &groupname, int mode, int count, bool persist)
{
if(!mAnimation || !mAnimation->hasAnimation(groupname))
{
std::cerr<< "Animation "<<groupname<<" not found for " << mPtr.getCellRef().getRefId() << std::endl;
return false;
}
else
// If this animation is a looped animation (has a "loop start" key) that is already playing
// and has not yet reached the end of the loop, allow it to continue animating with its existing loop count
// and remove any other animations that were queued.
// This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners correctly.
if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname &&
mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 &&
mAnimation->isPlaying(groupname))
{
// If this animation is a looped animation (has a "loop start" key) that is already playing
// and has not yet reached the end of the loop, allow it to continue animating with its existing loop count
// and remove any other animations that were queued.
// This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners correctly.
if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname &&
mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 &&
mAnimation->isPlaying(groupname))
{
float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop");
float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop");
if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key
endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop");
if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key
endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop");
if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop))
{
mAnimQueue.resize(1);
return true;
}
if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop))
{
mAnimQueue.resize(1);
return true;
}
}
count = std::max(count, 1);
count = std::max(count, 1);
AnimationQueueEntry entry;
entry.mGroup = groupname;
entry.mLoopCount = count-1;
entry.mPersist = persist;
AnimationQueueEntry entry;
entry.mGroup = groupname;
entry.mLoopCount = count-1;
entry.mPersist = persist;
if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup))
{
clearAnimQueue();
mAnimQueue.push_back(entry);
if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup))
{
clearAnimQueue();
mAnimQueue.push_back(entry);
mAnimation->disable(mCurrentIdle);
mCurrentIdle.clear();
mAnimation->disable(mCurrentIdle);
mCurrentIdle.clear();
mIdleState = CharState_SpecialIdle;
bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0);
mAnimation->play(groupname, Priority_Default,
MWRender::Animation::BlendMask_All, false, 1.0f,
((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback);
mIdleState = CharState_SpecialIdle;
bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0);
mAnimation->play(groupname, Priority_Default,
MWRender::Animation::BlendMask_All, false, 1.0f,
((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback);
/*
Start of tes3mp addition
/*
Start of tes3mp addition
If we are the cell authority over this actor, we need to record this new
animation for it
*/
if (mwmp::Main::get().getCellController()->isLocalActor(mPtr))
{
mwmp::LocalActor *actor = mwmp::Main::get().getCellController()->getLocalActor(mPtr);
actor->animation.groupname = groupname;
actor->animation.mode = mode;
actor->animation.count = count;
actor->animation.persist = persist;
}
/*
End of tes3mp addition
*/
}
else if(mode == 0)
If we are the cell authority over this actor, we need to record this new
animation for it
*/
if (mwmp::Main::get().getCellController()->isLocalActor(mPtr))
{
mAnimQueue.resize(1);
mAnimQueue.push_back(entry);
mwmp::LocalActor *actor = mwmp::Main::get().getCellController()->getLocalActor(mPtr);
actor->animation.groupname = groupname;
actor->animation.mode = mode;
actor->animation.count = count;
actor->animation.persist = persist;
}
/*
End of tes3mp addition
*/
}
else if(mode == 0)
{
mAnimQueue.resize(1);
mAnimQueue.push_back(entry);
}
return true;
}

@ -436,6 +436,11 @@ namespace MWMechanics
mObjects.update(duration, paused);
}
bool MechanicsManager::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer)
{
return mActors.isActorDetected(actor, observer);
}
bool MechanicsManager::isAttackPrepairing(const MWWorld::Ptr& ptr)
{
return mActors.isAttackPrepairing(ptr);

@ -201,6 +201,10 @@ namespace MWMechanics
/// Is \a ptr casting spell or using weapon now?
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const;
/// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer);
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer);
/// List the owners that the player has stolen this item from (the owner can be an NPC or a faction).

@ -1404,14 +1404,16 @@ namespace MWPhysics
// Slow fall reduces fall speed by a factor of (effect magnitude / 200)
float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f));
bool flying = world->isFlying(iter->first);
bool wasOnGround = physicActor->getOnGround();
osg::Vec3f position = physicActor->getPosition();
float oldHeight = position.z();
bool positionChanged = false;
for (int i=0; i<numSteps; ++i)
{
position = MovementSolver::move(position, physicActor->getPtr(), physicActor, iter->second, physicsDt,
world->isFlying(iter->first),
waterlevel, slowFall, mCollisionWorld, mStandingCollisions);
flying, waterlevel, slowFall, mCollisionWorld, mStandingCollisions);
if (position != physicActor->getPosition())
positionChanged = true;
physicActor->setPosition(position); // always set even if unchanged to make sure interpolation is correct
@ -1424,8 +1426,11 @@ namespace MWPhysics
float heightDiff = position.z() - oldHeight;
if (heightDiff < 0)
iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff);
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
if ((wasOnGround && physicActor->getOnGround()) || flying || world->isSwimming(iter->first) || slowFall < 1)
stats.land();
else if (heightDiff < 0)
stats.addToFallHeight(-heightDiff);
mMovementResults.push_back(std::make_pair(iter->first, interpolated));
}

@ -757,8 +757,6 @@ namespace MWRender
break;
}
}
if(iter == mAnimSources.rend())
std::cerr<< "Failed to find animation "<<groupname<<" for "<<mPtr.getCellRef().getRefId() <<std::endl;
resetActiveGroups();
}
@ -795,7 +793,7 @@ namespace MWRender
// We have to ignore extra garbage at the end.
// The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop".
// Why, just why? :(
&& (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag))
&& (stopkey->second.size() < stoptag.size() || stopkey->second.compare(0,stoptag.size(), stoptag) != 0))
++stopkey;
if(stopkey == keys.rend())
return false;

@ -530,8 +530,10 @@ namespace MWRender
void RenderingManager::updatePlayerPtr(const MWWorld::Ptr &ptr)
{
if(mPlayerAnimation.get())
{
setupPlayer(ptr);
mPlayerAnimation->updatePtr(ptr);
}
mCamera->attachTo(ptr);
}
@ -834,6 +836,7 @@ namespace MWRender
player.getRefData().setBaseNode(mPlayerNode);
mWater->removeEmitter(player);
mWater->addEmitter(player);
}

@ -372,21 +372,14 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime)
{
MWWorld::Ptr observer = R()(runtime);
MWWorld::Ptr observer = R()(runtime, false); // required=false
std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPtr(actorID, true);
if(!actor.getClass().isActor() || !observer.getClass().isActor())
{
runtime.push(0);
return;
}
Interpreter::Type_Integer value =
MWBase::Environment::get().getWorld()->getLOS(observer, actor) &&
MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer);
Interpreter::Type_Integer value = MWBase::Environment::get().getMechanicsManager()->isActorDetected(actor, observer);
runtime.push (value);
}

@ -192,12 +192,11 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type)
{ AL_FORMAT_STEREO8, ChannelConfig_Stereo, SampleType_UInt8 },
}};
auto fmt = std::find_if(fmtlist.cbegin(), fmtlist.cend(),
[chans,type](const FormatEntry &fmt) -> bool
{ return fmt.chans == chans && fmt.type == type; }
);
if(fmt != fmtlist.cend())
return fmt->format;
for(auto &fmt : fmtlist)
{
if(fmt.chans == chans && fmt.type == type)
return fmt.format;
}
if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
{
@ -209,18 +208,16 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type)
{ "AL_FORMAT_71CHN16", ChannelConfig_7point1, SampleType_Int16 },
{ "AL_FORMAT_71CHN8", ChannelConfig_7point1, SampleType_UInt8 },
}};
ALenum format = AL_NONE;
std::find_if(mcfmtlist.cbegin(), mcfmtlist.cend(),
[&format,chans,type](const FormatEntryExt &fmt) -> bool
for(auto &fmt : mcfmtlist)
{
if(fmt.chans == chans && fmt.type == type)
{
if(fmt.chans == chans && fmt.type == type)
format = alGetEnumValue(fmt.name);
return format != 0 && format != -1;
ALenum format = alGetEnumValue(fmt.name);
if(format != 0 && format != -1)
return format;
}
);
if(format != 0 && format != -1)
return format;
}
}
if(alIsExtensionPresent("AL_EXT_FLOAT32"))
{
@ -228,18 +225,16 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type)
{ "AL_FORMAT_MONO_FLOAT32", ChannelConfig_Mono, SampleType_Float32 },
{ "AL_FORMAT_STEREO_FLOAT32", ChannelConfig_Stereo, SampleType_Float32 },
}};
ALenum format = AL_NONE;
std::find_if(fltfmtlist.cbegin(), fltfmtlist.cend(),
[&format,chans,type](const FormatEntryExt &fmt) -> bool
for(auto &fmt : fltfmtlist)
{
if(fmt.chans == chans && fmt.type == type)
{
if(fmt.chans == chans && fmt.type == type)
format = alGetEnumValue(fmt.name);
return format != 0 && format != -1;
ALenum format = alGetEnumValue(fmt.name);
if(format != 0 && format != -1)
return format;
}
);
if(format != 0 && format != -1)
return format;
}
if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
{
@ -249,16 +244,15 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type)
{ "AL_FORMAT_71CHN32", ChannelConfig_7point1, SampleType_Float32 },
}};
std::find_if(fltmcfmtlist.cbegin(), fltmcfmtlist.cend(),
[&format,chans,type](const FormatEntryExt &fmt) -> bool
for(auto &fmt : fltmcfmtlist)
{
if(fmt.chans == chans && fmt.type == type)
{
if(fmt.chans == chans && fmt.type == type)
format = alGetEnumValue(fmt.name);
return format != 0 && format != -1;
ALenum format = alGetEnumValue(fmt.name);
if(format != 0 && format != -1)
return format;
}
);
if(format != 0 && format != -1)
return format;
}
}
}
@ -547,11 +541,11 @@ ALint OpenAL_SoundStream::refillQueue()
std::vector<char> data(mBufferSize);
for(;!mIsFinished && (ALuint)queued < mBuffers.size();++queued)
{
size_t got = mDecoder->read(&data[0], data.size());
size_t got = mDecoder->read(data.data(), data.size());
if(got < data.size())
{
mIsFinished = true;
std::memset(&data[got], mSilence, data.size()-got);
std::fill(data.begin()+got, data.end(), mSilence);
}
if(got > 0)
{
@ -559,7 +553,7 @@ ALint OpenAL_SoundStream::refillQueue()
mLoudnessAnalyzer->analyzeLoudness(data);
ALuint bufid = mBuffers[mCurrentBufIdx];
alBufferData(bufid, mFormat, &data[0], data.size(), mSampleRate);
alBufferData(bufid, mFormat, data.data(), data.size(), mSampleRate);
alSourceQueueBuffers(mSource, 1, &bufid);
mCurrentBufIdx = (mCurrentBufIdx+1) % mBuffers.size();
}
@ -594,25 +588,33 @@ bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname
{
deinit();
std::cout<< "Initializing OpenAL..." <<std::endl;
mDevice = alcOpenDevice(devname.c_str());
if(!mDevice)
if(!mDevice && !devname.empty())
{
if(devname.empty())
std::cerr<< "Failed to open default audio device" <<std::endl;
else
std::cerr<< "Failed to open \""<<devname<<"\"" <<std::endl;
return false;
std::cerr<< "Failed to open \""<<devname<<"\", trying default" <<std::endl;
mDevice = alcOpenDevice(nullptr);
}
else
if(!mDevice)
{
const ALCchar *name = NULL;
if(alcIsExtensionPresent(mDevice, "ALC_ENUMERATE_ALL_EXT"))
name = alcGetString(mDevice, ALC_ALL_DEVICES_SPECIFIER);
if(alcGetError(mDevice) != AL_NO_ERROR || !name)
name = alcGetString(mDevice, ALC_DEVICE_SPECIFIER);
std::cout << "Opened \""<<name<<"\"" << std::endl;
std::cerr<< "Failed to open default audio device" <<std::endl;
return false;
}
const ALCchar *name = nullptr;
if(alcIsExtensionPresent(mDevice, "ALC_ENUMERATE_ALL_EXT"))
name = alcGetString(mDevice, ALC_ALL_DEVICES_SPECIFIER);
if(alcGetError(mDevice) != AL_NO_ERROR || !name)
name = alcGetString(mDevice, ALC_DEVICE_SPECIFIER);
std::cout<< "Opened \""<<name<<"\"" <<std::endl;
ALCint major=0, minor=0;
alcGetIntegerv(mDevice, ALC_MAJOR_VERSION, 1, &major);
alcGetIntegerv(mDevice, ALC_MINOR_VERSION, 1, &minor);
std::cout<< " ALC Version: "<<major<<"."<<minor<<"\n"<<
" ALC Extensions: "<<alcGetString(mDevice, ALC_EXTENSIONS) <<std::endl;
ALC.EXT_EFX = alcIsExtensionPresent(mDevice, "ALC_EXT_EFX");
ALC.SOFT_HRTF = alcIsExtensionPresent(mDevice, "ALC_SOFT_HRTF");
@ -643,7 +645,7 @@ bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname
}
if(index < 0)
std::cerr<< "Failed to find HRTF name \""<<hrtfname<<"\", using default" <<std::endl;
std::cerr<< "Failed to find HRTF \""<<hrtfname<<"\", using default" <<std::endl;
else
{
attrs.push_back(ALC_HRTF_ID_SOFT);
@ -665,6 +667,11 @@ bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname
return false;
}
std::cout<< " Vendor: "<<alGetString(AL_VENDOR)<<"\n"<<
" Renderer: "<<alGetString(AL_RENDERER)<<"\n"<<
" Version: "<<alGetString(AL_VERSION)<<"\n"<<
" Extensions: "<<alGetString(AL_EXTENSIONS)<<std::endl;
if(!ALC.SOFT_HRTF)
std::cout<< "HRTF status unavailable" <<std::endl;
else
@ -698,7 +705,7 @@ bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname
{
ALuint src = 0;
alGenSources(1, &src);
if(getALError() != AL_NO_ERROR)
if(alGetError() != AL_NO_ERROR)
break;
mFreeSources.push_back(src);
}
@ -824,8 +831,8 @@ void OpenAL_Output::deinit()
{
mStreamThread->removeAll();
for(size_t i = 0;i < mFreeSources.size();i++)
alDeleteSources(1, &mFreeSources[i]);
for(ALuint source : mFreeSources)
alDeleteSources(1, &source);
mFreeSources.clear();
if(mEffectSlot)
@ -934,7 +941,7 @@ void OpenAL_Output::setHrtf(const std::string &hrtfname, HrtfMode hrtfmode)
}
Sound_Handle OpenAL_Output::loadSound(const std::string &fname)
std::pair<Sound_Handle,size_t> OpenAL_Output::loadSound(const std::string &fname)
{
getALError();
@ -959,27 +966,31 @@ Sound_Handle OpenAL_Output::loadSound(const std::string &fname)
decoder->getInfo(&srate, &chans, &type);
format = getALFormat(chans, type);
if(!format) return nullptr;
if(!format) return std::make_pair(nullptr, 0);
decoder->readAll(data);
decoder->close();
ALint size;
ALuint buf = 0;
alGenBuffers(1, &buf);
alBufferData(buf, format, &data[0], data.size(), srate);
alBufferData(buf, format, data.data(), data.size(), srate);
alGetBufferi(buf, AL_SIZE, &size);
if(getALError() != AL_NO_ERROR)
{
if(buf && alIsBuffer(buf))
alDeleteBuffers(1, &buf);
getALError();
return nullptr;
return std::make_pair(nullptr, 0);
}
return MAKE_PTRID(buf);
return std::make_pair(MAKE_PTRID(buf), size);
}
void OpenAL_Output::unloadSound(Sound_Handle data)
size_t OpenAL_Output::unloadSound(Sound_Handle data)
{
ALuint buffer = GET_PTRID(data);
if(!buffer) return 0;
// Make sure no sources are playing this buffer before unloading it.
SoundVec::const_iterator iter = mActiveSounds.begin();
for(;iter != mActiveSounds.end();++iter)
@ -996,19 +1007,11 @@ void OpenAL_Output::unloadSound(Sound_Handle data)
alSourcei(source, AL_BUFFER, 0);
}
}
alDeleteBuffers(1, &buffer);
getALError();
}
size_t OpenAL_Output::getSoundDataSize(Sound_Handle data) const
{
ALuint buffer = GET_PTRID(data);
ALint size = 0;
alGetBufferi(buffer, AL_SIZE, &size);
alDeleteBuffers(1, &buffer);
getALError();
return (ALuint)size;
return size;
}
@ -1368,23 +1371,19 @@ void OpenAL_Output::updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdi
if(mWaterFilter)
{
ALuint filter = (env == Env_Underwater) ? mWaterFilter : AL_FILTER_NULL;
std::for_each(mActiveSounds.cbegin(), mActiveSounds.cend(),
[filter](const SoundVec::value_type &item) -> void
{
if(item->getUseEnv())
alSourcei(GET_PTRID(item->mHandle), AL_DIRECT_FILTER, filter);
}
);
std::for_each(mActiveStreams.cbegin(), mActiveStreams.cend(),
[filter](const StreamVec::value_type &item) -> void
{
if(item->getUseEnv())
alSourcei(
reinterpret_cast<OpenAL_SoundStream*>(item->mHandle)->mSource,
AL_DIRECT_FILTER, filter
);
}
);
for(Sound *sound : mActiveSounds)
{
if(sound->getUseEnv())
alSourcei(GET_PTRID(sound->mHandle), AL_DIRECT_FILTER, filter);
}
for(Stream *sound : mActiveStreams)
{
if(sound->getUseEnv())
alSourcei(
reinterpret_cast<OpenAL_SoundStream*>(sound->mHandle)->mSource,
AL_DIRECT_FILTER, filter
);
}
}
// Update the environment effect
if(mEffectSlot)
@ -1403,23 +1402,19 @@ void OpenAL_Output::updateListener(const osg::Vec3f &pos, const osg::Vec3f &atdi
void OpenAL_Output::pauseSounds(int types)
{
std::vector<ALuint> sources;
std::for_each(mActiveSounds.cbegin(), mActiveSounds.cend(),
[types,&sources](const SoundVec::value_type &sound) -> void
{
if(sound && sound->mHandle && (types&sound->getPlayType()))
sources.push_back(GET_PTRID(sound->mHandle));
}
);
std::for_each(mActiveStreams.cbegin(), mActiveStreams.cend(),
[types,&sources](const StreamVec::value_type &stream) -> void
for(Sound *sound : mActiveSounds)
{
if((types&sound->getPlayType()))
sources.push_back(GET_PTRID(sound->mHandle));
}
for(Stream *sound : mActiveStreams)
{
if((types&sound->getPlayType()))
{
if(stream && stream->mHandle && (types&stream->getPlayType()))
{
OpenAL_SoundStream *strm = reinterpret_cast<OpenAL_SoundStream*>(stream->mHandle);
sources.push_back(strm->mSource);
}
OpenAL_SoundStream *stream = reinterpret_cast<OpenAL_SoundStream*>(sound->mHandle);
sources.push_back(stream->mSource);
}
);
}
if(!sources.empty())
{
alSourcePausev(sources.size(), sources.data());
@ -1430,23 +1425,19 @@ void OpenAL_Output::pauseSounds(int types)
void OpenAL_Output::resumeSounds(int types)
{
std::vector<ALuint> sources;
std::for_each(mActiveSounds.cbegin(), mActiveSounds.cend(),
[types,&sources](const SoundVec::value_type &sound) -> void
{
if(sound && sound->mHandle && (types&sound->getPlayType()))
sources.push_back(GET_PTRID(sound->mHandle));
}
);
std::for_each(mActiveStreams.cbegin(), mActiveStreams.cend(),
[types,&sources](const StreamVec::value_type &stream) -> void
for(Sound *sound : mActiveSounds)
{
if((types&sound->getPlayType()))
sources.push_back(GET_PTRID(sound->mHandle));
}
for(Stream *sound : mActiveStreams)
{
if((types&sound->getPlayType()))
{
if(stream && stream->mHandle && (types&stream->getPlayType()))
{
OpenAL_SoundStream *strm = reinterpret_cast<OpenAL_SoundStream*>(stream->mHandle);
sources.push_back(strm->mSource);
}
OpenAL_SoundStream *stream = reinterpret_cast<OpenAL_SoundStream*>(sound->mHandle);
sources.push_back(stream->mSource);
}
);
}
if(!sources.empty())
{
alSourcePlayv(sources.size(), sources.data());

@ -66,9 +66,8 @@ namespace MWSound
virtual std::vector<std::string> enumerateHrtf();
virtual void setHrtf(const std::string &hrtfname, HrtfMode hrtfmode);
virtual Sound_Handle loadSound(const std::string &fname);
virtual void unloadSound(Sound_Handle data);
virtual size_t getSoundDataSize(Sound_Handle data) const;
virtual std::pair<Sound_Handle,size_t> loadSound(const std::string &fname);
virtual size_t unloadSound(Sound_Handle data);
virtual bool playSound(Sound *sound, Sound_Handle data, float offset);
virtual bool playSound3D(Sound *sound, Sound_Handle data, float offset);

@ -36,9 +36,8 @@ namespace MWSound
virtual std::vector<std::string> enumerateHrtf() = 0;
virtual void setHrtf(const std::string &hrtfname, HrtfMode hrtfmode) = 0;
virtual Sound_Handle loadSound(const std::string &fname) = 0;
virtual void unloadSound(Sound_Handle data) = 0;
virtual size_t getSoundDataSize(Sound_Handle data) const = 0;
virtual std::pair<Sound_Handle,size_t> loadSound(const std::string &fname) = 0;
virtual size_t unloadSound(Sound_Handle data) = 0;
virtual bool playSound(Sound *sound, Sound_Handle data, float offset) = 0;
virtual bool playSound3D(Sound *sound, Sound_Handle data, float offset) = 0;

@ -26,11 +26,7 @@
#include "sound.hpp"
#include "openal_output.hpp"
#define SOUND_OUT "OpenAL"
#include "ffmpeg_decoder.hpp"
#ifndef SOUND_IN
#define SOUND_IN "FFmpeg"
#endif
namespace MWSound
@ -84,45 +80,35 @@ namespace MWSound
mBufferCacheMin = std::min(mBufferCacheMin*1024*1024, mBufferCacheMax);
if(!useSound)
{
std::cout<< "Sound disabled." <<std::endl;
return;
}
std::string hrtfname = Settings::Manager::getString("hrtf", "Sound");
int hrtfstate = Settings::Manager::getInt("hrtf enable", "Sound");
HrtfMode hrtfmode = hrtfstate < 0 ? HrtfMode::Auto :
hrtfstate > 0 ? HrtfMode::Enable : HrtfMode::Disable;
std::cout << "Sound output: " << SOUND_OUT << std::endl;
std::cout << "Sound decoder: " << SOUND_IN << std::endl;
std::vector<std::string> names = mOutput->enumerate();
std::cout <<"Enumerated output devices:\n";
std::for_each(names.cbegin(), names.cend(),
[](const std::string &name) -> void
{ std::cout <<" "<<name<<"\n"; }
);
std::cout.flush();
std::string devname = Settings::Manager::getString("device", "Sound");
bool inited = mOutput->init(devname, hrtfname, hrtfmode);
if(!inited && !devname.empty())
if(!mOutput->init(devname, hrtfname, hrtfmode))
{
std::cerr<< "Failed to initialize device \""<<devname<<"\", trying default" <<std::endl;
inited = mOutput->init(std::string(), hrtfname, hrtfmode);
}
if(!inited)
{
std::cerr<< "Failed to initialize default audio device, sound disabled" <<std::endl;
std::cerr<< "Failed to initialize audio output, sound disabled" <<std::endl;
return;
}
std::vector<std::string> names = mOutput->enumerate();
std::cout <<"Enumerated output devices:\n";
for(const std::string &name : names)
std::cout <<" "<<name<<"\n";
std::cout.flush();
names = mOutput->enumerateHrtf();
if(!names.empty())
{
std::cout<< "Enumerated HRTF names:\n";
std::for_each(names.cbegin(), names.cend(),
[](const std::string &name) -> void
{ std::cout<< " "<<name<<"\n"; }
);
for(const std::string &name : names)
std::cout <<" "<<name<<"\n";
std::cout.flush();
}
}
@ -130,12 +116,11 @@ namespace MWSound
SoundManager::~SoundManager()
{
clear();
SoundBufferList::element_type::iterator sfxiter = mSoundBuffers->begin();
for(;sfxiter != mSoundBuffers->end();++sfxiter)
for(Sound_Buffer &sfx : *mSoundBuffers)
{
if(sfxiter->mHandle)
mOutput->unloadSound(sfxiter->mHandle);
sfxiter->mHandle = 0;
if(sfx.mHandle)
mOutput->unloadSound(sfx.mHandle);
sfx.mHandle = 0;
}
mUnusedBuffers.clear();
mOutput.reset();
@ -200,9 +185,23 @@ namespace MWSound
// minRange, and maxRange), and ensure it's ready for use.
Sound_Buffer *SoundManager::loadSound(const std::string &soundId)
{
#ifdef __GNUC__
#define LIKELY(x) __builtin_expect((bool)(x), true)
#define UNLIKELY(x) __builtin_expect((bool)(x), false)
#else
#define LIKELY(x) (bool)(x)
#define UNLIKELY(x) (bool)(x)
#endif
if(UNLIKELY(mBufferNameMap.empty()))
{
MWBase::World *world = MWBase::Environment::get().getWorld();
for(const ESM::Sound &sound : world->getStore().get<ESM::Sound>())
insertSound(Misc::StringUtils::lowerCase(sound.mId), &sound);
}
Sound_Buffer *sfx;
NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId);
if(snd != mBufferNameMap.end())
if(LIKELY(snd != mBufferNameMap.end()))
sfx = snd->second;
else
{
@ -211,13 +210,16 @@ namespace MWSound
if(!sound) return nullptr;
sfx = insertSound(soundId, sound);
}
#undef LIKELY
#undef UNLIKELY
if(!sfx->mHandle)
{
sfx->mHandle = mOutput->loadSound(sfx->mResourceName);
size_t size;
std::tie(sfx->mHandle, size) = mOutput->loadSound(sfx->mResourceName);
if(!sfx->mHandle) return nullptr;
mBufferCacheSize += mOutput->getSoundDataSize(sfx->mHandle);
mBufferCacheSize += size;
if(mBufferCacheSize > mBufferCacheMax)
{
do {
@ -228,8 +230,8 @@ namespace MWSound
}
Sound_Buffer *unused = mUnusedBuffers.back();
mBufferCacheSize -= mOutput->getSoundDataSize(unused->mHandle);
mOutput->unloadSound(unused->mHandle);
size = mOutput->unloadSound(unused->mHandle);
mBufferCacheSize -= size;
unused->mHandle = 0;
mUnusedBuffers.pop_back();
@ -389,11 +391,6 @@ namespace MWSound
mMusic->setFadeout(0.5f);
}
void SoundManager::streamMusic(const std::string& filename)
{
advanceMusic("Music/"+filename);
}
void SoundManager::startRandomTitle()
{
std::vector<std::string> filelist;
@ -444,6 +441,12 @@ namespace MWSound
tracklist.pop_back();
}
void SoundManager::streamMusic(const std::string& filename)
{
advanceMusic("Music/"+filename);
}
bool SoundManager::isMusicPlaying()
{
return mMusic && mOutput->isStreamPlaying(mMusic);
@ -678,11 +681,10 @@ namespace MWSound
if(snditer != mActiveSounds.end())
{
Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId));
SoundBufferRefPairList::iterator sndidx = snditer->second.begin();
for(;sndidx != snditer->second.end();++sndidx)
for(SoundBufferRefPair &snd : snditer->second)
{
if(sndidx->second == sfx)
mOutput->finishSound(sndidx->first);
if(snd.second == sfx)
mOutput->finishSound(snd.first);
}
}
}
@ -692,33 +694,28 @@ namespace MWSound
SoundMap::iterator snditer = mActiveSounds.find(ptr);
if(snditer != mActiveSounds.end())
{
SoundBufferRefPairList::iterator sndidx = snditer->second.begin();
for(;sndidx != snditer->second.end();++sndidx)
mOutput->finishSound(sndidx->first);
for(SoundBufferRefPair &snd : snditer->second)
mOutput->finishSound(snd.first);
}
SaySoundMap::iterator sayiter = mActiveSaySounds.find(ptr);
if(sayiter != mActiveSaySounds.end())
mOutput->finishStream(sayiter->second);
}
void SoundManager::stopSound(const MWWorld::CellStore *cell)
{
SoundMap::iterator snditer = mActiveSounds.begin();
for(;snditer != mActiveSounds.end();++snditer)
for(SoundMap::value_type &snd : mActiveSounds)
{
if(snditer->first != MWWorld::ConstPtr() &&
snditer->first != MWMechanics::getPlayer() &&
snditer->first.getCell() == cell)
if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell)
{
SoundBufferRefPairList::iterator sndidx = snditer->second.begin();
for(;sndidx != snditer->second.end();++sndidx)
mOutput->finishSound(sndidx->first);
for(SoundBufferRefPair &sndbuf : snd.second)
mOutput->finishSound(sndbuf.first);
}
}
SaySoundMap::iterator sayiter = mActiveSaySounds.begin();
for(;sayiter != mActiveSaySounds.end();++sayiter)
for(SaySoundMap::value_type &snd : mActiveSaySounds)
{
if(sayiter->first != MWWorld::ConstPtr() &&
sayiter->first != MWMechanics::getPlayer() &&
sayiter->first.getCell() == cell)
mOutput->finishStream(sayiter->second);
if(!snd.first.isEmpty() && snd.first != MWMechanics::getPlayer() && snd.first.getCell() == cell)
mOutput->finishStream(snd.second);
}
}
@ -728,11 +725,10 @@ namespace MWSound
if(snditer != mActiveSounds.end())
{
Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId));
SoundBufferRefPairList::iterator sndidx = snditer->second.begin();
for(;sndidx != snditer->second.end();++sndidx)
for(SoundBufferRefPair &sndbuf : snditer->second)
{
if(sndidx->second == sfx)
mOutput->finishSound(sndidx->first);
if(sndbuf.second == sfx)
mOutput->finishSound(sndbuf.first);
}
}
}
@ -744,11 +740,10 @@ namespace MWSound
if(snditer != mActiveSounds.end())
{
Sound_Buffer *sfx = loadSound(Misc::StringUtils::lowerCase(soundId));
SoundBufferRefPairList::iterator sndidx = snditer->second.begin();
for(;sndidx != snditer->second.end();++sndidx)
for(SoundBufferRefPair &sndbuf : snditer->second)
{
if(sndidx->second == sfx)
sndidx->first->setFadeout(duration);
if(sndbuf.second == sfx)
sndbuf.first->setFadeout(duration);
}
}
}
@ -759,12 +754,10 @@ namespace MWSound
if(snditer != mActiveSounds.end())
{
Sound_Buffer *sfx = lookupSound(Misc::StringUtils::lowerCase(soundId));
SoundBufferRefPairList::const_iterator sndidx = snditer->second.begin();
for(;sndidx != snditer->second.end();++sndidx)
{
if(sndidx->second == sfx && mOutput->isSoundPlaying(sndidx->first))
return true;
}
return std::find_if(snditer->second.cbegin(), snditer->second.cend(),
[this,sfx](const SoundBufferRefPair &snd) -> bool
{ return snd.second == sfx && mOutput->isSoundPlaying(snd.first); }
) != snditer->second.cend();
}
return false;
}
@ -823,10 +816,8 @@ namespace MWSound
if(total == 0)
{
std::for_each(regn->mSoundList.cbegin(), regn->mSoundList.cend(),
[](const ESM::Region::SoundRef &sndref) -> void
{ total += (int)sndref.mChance; }
);
for(const ESM::Region::SoundRef &sndref : regn->mSoundList)
total += (int)sndref.mChance;
if(total == 0)
return;
}
@ -834,20 +825,15 @@ namespace MWSound
int r = Misc::Rng::rollDice(total);
int pos = 0;
std::find_if_not(regn->mSoundList.cbegin(), regn->mSoundList.cend(),
[&pos, r, this](const ESM::Region::SoundRef &sndref) -> bool
for(const ESM::Region::SoundRef &sndref : regn->mSoundList)
{
if(r - pos < sndref.mChance)
{
if(r - pos < sndref.mChance)
{
playSound(sndref.mSound.toString(), 1.0f, 1.0f);
// Played this sound, stop iterating
return false;
}
pos += sndref.mChance;
// Not this sound, keep iterating
return true;
playSound(sndref.mSound.toString(), 1.0f, 1.0f);
break;
}
);
pos += sndref.mChance;
}
}
void SoundManager::updateWaterSound(float /*duration*/)
@ -1135,28 +1121,23 @@ namespace MWSound
if(!mOutput->isInitialized())
return;
mOutput->startUpdate();
SoundMap::iterator snditer = mActiveSounds.begin();
for(;snditer != mActiveSounds.end();++snditer)
for(SoundMap::value_type &snd : mActiveSounds)
{
SoundBufferRefPairList::iterator sndidx = snditer->second.begin();
for(;sndidx != snditer->second.end();++sndidx)
for(SoundBufferRefPair &sndbuf : snd.second)
{
Sound *sound = sndidx->first;
Sound *sound = sndbuf.first;
sound->setBaseVolume(volumeFromType(sound->getPlayType()));
mOutput->updateSound(sound);
}
}
SaySoundMap::iterator sayiter = mActiveSaySounds.begin();
for(;sayiter != mActiveSaySounds.end();++sayiter)
for(SaySoundMap::value_type &snd : mActiveSaySounds)
{
Stream *sound = sayiter->second;
Stream *sound = snd.second;
sound->setBaseVolume(volumeFromType(sound->getPlayType()));
mOutput->updateStream(sound);
}
TrackList::iterator trkiter = mActiveTracks.begin();
for(;trkiter != mActiveTracks.end();++trkiter)
for(Stream *sound : mActiveTracks)
{
Stream *sound = *trkiter;
sound->setBaseVolume(volumeFromType(sound->getPlayType()));
mOutput->updateStream(sound);
}
@ -1262,36 +1243,35 @@ namespace MWSound
void SoundManager::clear()
{
SoundMap::iterator snditer = mActiveSounds.begin();
for(;snditer != mActiveSounds.end();++snditer)
stopMusic();
for(SoundMap::value_type &snd : mActiveSounds)
{
SoundBufferRefPairList::iterator sndidx = snditer->second.begin();
for(;sndidx != snditer->second.end();++sndidx)
for(SoundBufferRefPair &sndbuf : snd.second)
{
mOutput->finishSound(sndidx->first);
mUnusedSounds.push_back(sndidx->first);
Sound_Buffer *sfx = sndidx->second;
mOutput->finishSound(sndbuf.first);
mUnusedSounds.push_back(sndbuf.first);
Sound_Buffer *sfx = sndbuf.second;
if(sfx->mUses-- == 1)
mUnusedBuffers.push_front(sfx);
}
}
mActiveSounds.clear();
SaySoundMap::iterator sayiter = mActiveSaySounds.begin();
for(;sayiter != mActiveSaySounds.end();++sayiter)
mUnderwaterSound = nullptr;
mNearWaterSound = nullptr;
for(SaySoundMap::value_type &snd : mActiveSaySounds)
{
mOutput->finishStream(sayiter->second);
mUnusedStreams.push_back(sayiter->second);
mOutput->finishStream(snd.second);
mUnusedStreams.push_back(snd.second);
}
mActiveSaySounds.clear();
TrackList::iterator trkiter = mActiveTracks.begin();
for(;trkiter != mActiveTracks.end();++trkiter)
for(Stream *sound : mActiveTracks)
{
mOutput->finishStream(*trkiter);
mUnusedStreams.push_back(*trkiter);
mOutput->finishStream(sound);
mUnusedStreams.push_back(sound);
}
mActiveTracks.clear();
mUnderwaterSound = nullptr;
mNearWaterSound = nullptr;
stopMusic();
}
}

@ -127,6 +127,7 @@ namespace MWSound
void streamMusicFull(const std::string& filename);
void advanceMusic(const std::string& filename);
void startRandomTitle();
void updateSounds(float duration);
void updateRegionSound(float duration);
@ -157,9 +158,6 @@ namespace MWSound
///< Play a soundifle
/// \param filename name of a sound file in "Music/" in the data directory.
virtual void startRandomTitle();
///< Starts a random track from the current playlist
virtual bool isMusicPlaying();
///< Returns true if music is playing

@ -34,21 +34,19 @@ namespace Misc
if (i < m.size())
{
int precision = 0;
bool precisionSet = false;
int precision = -1;
if (m[i] == '.')
{
precision = 0;
while (++i < m.size() && m[i] >= '0' && m[i] <= '9')
{
precision = precision * 10 + (m[i] - '0');
precisionSet = true;
}
}
if (i < m.size())
{
width = (widthSet) ? width : -1;
precision = (precisionSet) ? precision : -1;
if (m[i] == 'S' || m[i] == 's')
visitedPlaceholder(StringPlaceholder, pad, width, precision);

@ -141,11 +141,11 @@ namespace Resource
void setUnRefImageDataAfterApply(bool unref);
/// @see ResourceManager::updateCache
virtual void updateCache(double referenceTime);
void updateCache(double referenceTime) override;
virtual void clearCache();
void clearCache() override;
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
private:

@ -32,9 +32,9 @@ namespace Terrain
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, int lod, unsigned int lodFlags);
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
virtual void clearCache();
void clearCache() override;
void releaseGLObjects(osg::State* state) override;

@ -304,3 +304,42 @@ already checked. Load a game and make your way to Seyda Neen - or start a new ga
Check whether Arrille has one (or more) for sale, and whether Fargoth give you one
when you return his healing ring.
Placing in a chest
******************
For this example we will use the small chest intended for lockpick practice,
located in the Census and Excise Office in Seyda Neen.
First we need the ID of the chest - this can be obtained either by clicking on it in the console
in the game, or by applying a similar process in the CS -
World/Cells
Select "Seyda Neen, Census and Excise Office"
Right-click and select "View"
Use mouse wheel to zoom in/out, and mouse plus WASD keys to navigate
Click on the small chest
Either way, you should find the ID, which is "chest_small_02_lockprac".
Open the Objects table (World/Objects) and scroll down to find this item.
Alternatively use the Edit/Search facility, selecting ID rather than text,
enter "lockprac" (without the quotes) into the search box, press "Search",
which should return two rows, then select the "Container" one rather than the "Instance"
Right-click and "Edit Record".
Right-click the "Content" section and select "Add a row"
Set the Item ID of the new row to be your new ring - simplest way is probably to open the Objects
table if it's not already open, sort on the "Modified" column which should bring the ring,
with its status of "Added" to the top, then drag and drop to the chest row.
Increase the Count to 1.
Save the addon, then test to ensure it works - e.g. start a new game and lockpick the chest.

@ -11,7 +11,9 @@
</Widget>
<!-- Spell list -->
<Widget type="Widget" skin="" position="8 160 519 178" align="Left Top" name="SpellArea"/>
<Widget type="ScrollView" skin="MW_ScrollView" position="8 160 507 170" align="Left Top" name="SpellArea">
<Property key="CanvasAlign" value="Left"/>
</Widget>>
<!-- Dialog buttons -->
<Widget type="HBox" position="0 338 511 24">

Loading…
Cancel
Save