Add OpenMW commits up to 20 Oct 2019

# Conflicts:
#	apps/openmw/mwgui/recharge.cpp
#	apps/openmw/mwrender/globalmap.cpp
#	apps/openmw/mwrender/globalmap.hpp
#	apps/openmw/mwworld/inventorystore.cpp
pull/556/head
David Cernat 5 years ago
commit bde9f7b817

@ -2,6 +2,7 @@
------
Bug #1515: Opening console masks dialogue, inventory menu
Bug #1933: Actors can have few stocks of the same item
Bug #2395: Duplicated plugins in the launcher when multiple data directories provide the same plugin
Bug #2969: Scripted items can stack
Bug #2976: Data lines in global openmw.cfg take priority over user openmw.cfg
@ -18,8 +19,10 @@
Bug #3778: [Mod] Improved Thrown Weapon Projectiles - weapons have wrong transformation during throw animation
Bug #3812: Wrong multiline tooltips width when word-wrapping is enabled
Bug #3894: Hostile spell effects not detected/present on first frame of OnPCHitMe
Bug #4077: Enchanted items are not recharged if they are not in the player's inventory
Bug #4202: Open .omwaddon files without needing toopen openmw-cs first
Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect
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 #4329: Removed birthsign abilities are restored after reloading the save
@ -27,8 +30,10 @@
Bug #4383: Bow model obscures crosshair when arrow is drawn
Bug #4384: Resist Normal Weapons only checks ammunition for ranged weapons
Bug #4411: Reloading a saved game while falling prevents damage in some cases
Bug #4449: Value returned by GetWindSpeed is incorrect
Bug #4456: AiActivate should not be cancelled after target activation
Bug #4540: Rain delay when exiting water
Bug #4594: Actors without AI packages don't use Hello dialogue
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
@ -77,6 +82,7 @@
Bug #4888: Global variable stray explicit reference calls break script compilation
Bug #4896: Title screen music doesn't loop
Bug #4902: Using scrollbars in settings causes resolution to change
Bug #4904: Editor: Texture painting with duplicate of a base-version texture
Bug #4911: Editor: QOpenGLContext::swapBuffers() warning with Qt5
Bug #4916: Specular power (shininess) material parameter is ignored when shaders are used.
Bug #4918: Abilities don't play looping VFX when they're initially applied
@ -155,6 +161,8 @@
Bug #5169: Nested levelled items/creatures have significantly higher chance not to spawn
Bug #5175: Random script function returns an integer value
Bug #5177: Editor: Unexplored map tiles get corrupted after a file with terrain is saved
Bug #5182: OnPCEquip doesn't trigger on skipped beast race attempts to equip something not equippable by beasts
Bug #5186: Equipped item enchantments don't affect creatures
Feature #1774: Handle AvoidNode
Feature #2229: Improve pathfinding AI
Feature #3025: Analogue gamepad movement controls

@ -101,6 +101,7 @@ Editor Bug Fixes:
- Colour fields in interior-cell records now also use the colour picker widget (#4745)
- Cloned, added, or moved instances no longer disappear at load-time (#4748)
- "Clear" function in the content selector no longer tries to execute a "Remove" action on an empty file list (#4757)
- Terrain texture editing for plugins now correctly handles drags from base file (#4904)
- Engine no longer tries to swap buffers of windows which weren't exposed to Qt's window management system (#4911)
- Minimap doesn't get corrupted, when editing new omwgame (#5177)

@ -41,9 +41,9 @@
CSVRender::TerrainTextureMode::TerrainTextureMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent)
: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-texture"}, Mask_Terrain | Mask_Reference, "Terrain texture editing", parent),
mBrushTexture("L0#0"),
mBrushSize(0),
mBrushSize(1),
mBrushShape(0),
mTextureBrushScenetool(0),
mTextureBrushScenetool(nullptr),
mDragMode(InteractionType_None),
mParentNode(parentNode),
mIsEditing(false)
@ -82,7 +82,7 @@ void CSVRender::TerrainTextureMode::deactivate(CSVWidget::SceneToolbar* toolbar)
{
toolbar->removeTool (mTextureBrushScenetool);
delete mTextureBrushScenetool;
mTextureBrushScenetool = 0;
mTextureBrushScenetool = nullptr;
}
if (mTerrainTextureSelection)

@ -51,36 +51,37 @@ namespace CSVRender
/// \brief Editmode for terrain texture grid
TerrainTextureMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr);
void primaryOpenPressed (const WorldspaceHitResult& hit);
void primaryOpenPressed (const WorldspaceHitResult& hit) final;
/// \brief Create single command for one-click texture editing
void primaryEditPressed (const WorldspaceHitResult& hit);
void primaryEditPressed (const WorldspaceHitResult& hit) final;
/// \brief Open brush settings window
void primarySelectPressed(const WorldspaceHitResult&);
void primarySelectPressed(const WorldspaceHitResult&) final;
void secondarySelectPressed(const WorldspaceHitResult&);
void secondarySelectPressed(const WorldspaceHitResult&) final;
void activate(CSVWidget::SceneToolbar*);
void deactivate(CSVWidget::SceneToolbar*);
void activate(CSVWidget::SceneToolbar*) final;
void deactivate(CSVWidget::SceneToolbar*) final;
/// \brief Start texture editing command macro
virtual bool primaryEditStartDrag (const QPoint& pos);
bool primaryEditStartDrag (const QPoint& pos) final;
virtual bool secondaryEditStartDrag (const QPoint& pos);
virtual bool primarySelectStartDrag (const QPoint& pos);
virtual bool secondarySelectStartDrag (const QPoint& pos);
bool secondaryEditStartDrag (const QPoint& pos) final;
bool primarySelectStartDrag (const QPoint& pos) final;
bool secondarySelectStartDrag (const QPoint& pos) final;
/// \brief Handle texture edit behavior during dragging
virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor);
void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) final;
/// \brief End texture editing command macro
virtual void dragCompleted(const QPoint& pos);
void dragCompleted(const QPoint& pos) final;
virtual void dragAborted();
virtual void dragWheel (int diff, double speedFactor);
virtual void dragMoveEvent (QDragMoveEvent *event);
void dragAborted() final;
void dragWheel (int diff, double speedFactor) final;
void dragMoveEvent (QDragMoveEvent *event) final;
private:
/// \brief Handle brush mechanics, maths regarding worldspace hit etc.
void editTerrainTextureGrid (const WorldspaceHitResult& hit);
@ -100,7 +101,6 @@ namespace CSVRender
/// \brief Create new cell and land if needed
bool allowLandTextureEditing(std::string textureFileName);
private:
std::string mCellId;
std::string mBrushTexture;
int mBrushSize;
@ -113,7 +113,6 @@ namespace CSVRender
std::unique_ptr<TerrainSelection> mTerrainTextureSelection;
const int cellSize {ESM::Land::REAL_SIZE};
const int landSize {ESM::Land::LAND_SIZE};
const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE};
signals:

@ -33,19 +33,19 @@
CSVWidget::BrushSizeControls::BrushSizeControls(const QString &title, QWidget *parent)
: QGroupBox(title, parent)
: QGroupBox(title, parent),
mLayoutSliderSize(new QHBoxLayout),
mBrushSizeSlider(new QSlider(Qt::Horizontal)),
mBrushSizeSpinBox(new QSpinBox)
{
mBrushSizeSlider = new QSlider(Qt::Horizontal);
mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides);
mBrushSizeSlider->setTickInterval(10);
mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt());
mBrushSizeSlider->setSingleStep(1);
mBrushSizeSpinBox = new QSpinBox;
mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["texturebrush-maximumsize"].toInt());
mBrushSizeSpinBox->setSingleStep(1);
mLayoutSliderSize = new QHBoxLayout;
mLayoutSliderSize->addWidget(mBrushSizeSlider);
mLayoutSliderSize->addWidget(mBrushSizeSpinBox);
@ -58,7 +58,7 @@ CSVWidget::BrushSizeControls::BrushSizeControls(const QString &title, QWidget *p
CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QWidget *parent)
: QFrame(parent, Qt::Popup),
mBrushShape(0),
mBrushSize(0),
mBrushSize(1),
mBrushTexture("L0#0"),
mDocument(document)
{
@ -142,60 +142,61 @@ void CSVWidget::TextureBrushWindow::configureButtonInitialSettings(QPushButton *
void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture)
{
mBrushTexture = brushTexture;
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
QUndoStack& undoStack = mDocument.getUndoStack();
CSMWorld::IdCollection<CSMWorld::LandTexture>& landtexturesCollection = mDocument.getData().getLandTextures();
int landTextureFilename = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Texture);
int columnModification = landtexturesCollection.findColumnIndex(CSMWorld::Columns::ColumnId_Modification);
int index = landtexturesCollection.searchId(mBrushTexture);
// Check if texture exists in current plugin
if(landtexturesCollection.getData(index, columnModification).value<int>() == 0)
int index = 0;
int pluginInDragged = 0;
CSMWorld::LandTexture::parseUniqueRecordId(brushTexture, pluginInDragged, index);
std::string newBrushTextureId = CSMWorld::LandTexture::createUniqueRecordId(0, index);
int rowInBase = landtexturesCollection.searchId(brushTexture);
int rowInNew = landtexturesCollection.searchId(newBrushTextureId);
// Check if texture exists in current plugin, and clone if id found in base, otherwise reindex the texture
// TO-DO: Handle case when texture is not found in neither base or plugin properly (finding new index is not enough)
// TO-DO: Handle conflicting plugins properly
if (rowInNew == -1)
{
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
*mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
QUndoStack& undoStack = mDocument.getUndoStack();
QVariant textureFileNameVariant;
textureFileNameVariant.setValue(landtexturesCollection.getData(index, landTextureFilename).value<QString>());
std::size_t hashlocation = mBrushTexture.find("#");
std::string mBrushTexturePlugin = "L0#" + mBrushTexture.substr (hashlocation+1);
int indexPlugin = landtexturesCollection.searchId(mBrushTexturePlugin);
// Reindex texture if needed
if (indexPlugin != -1 && !landtexturesCollection.getRecord(indexPlugin).isDeleted())
if (rowInBase == -1)
{
int counter=0;
bool freeIndexFound = false;
const int maxCounter = std::numeric_limits<uint16_t>::max() - 1;
do {
const size_t maxCounter = std::numeric_limits<uint16_t>::max() - 1;
mBrushTexturePlugin = CSMWorld::LandTexture::createUniqueRecordId(0, counter);
if (landtexturesCollection.searchId(mBrushTexturePlugin) != -1 && landtexturesCollection.getRecord(mBrushTexturePlugin).isDeleted() == 0) counter = (counter + 1) % maxCounter;
newBrushTextureId = CSMWorld::LandTexture::createUniqueRecordId(0, counter);
if (landtexturesCollection.searchId(brushTexture) != -1 &&
landtexturesCollection.getRecord(brushTexture).isDeleted() == 0 &&
landtexturesCollection.searchId(newBrushTextureId) != -1 &&
landtexturesCollection.getRecord(newBrushTextureId).isDeleted() == 0)
counter = (counter + 1) % maxCounter;
else freeIndexFound = true;
} while (freeIndexFound == false);
} while (freeIndexFound == false || counter < maxCounter);
}
undoStack.beginMacro ("Add land texture record");
undoStack.push (new CSMWorld::CloneCommand (ltexTable, mBrushTexture, mBrushTexturePlugin, CSMWorld::UniversalId::Type_LandTexture));
undoStack.push (new CSMWorld::CloneCommand (ltexTable, brushTexture, newBrushTextureId, CSMWorld::UniversalId::Type_LandTexture));
undoStack.endMacro();
mBrushTexture = mBrushTexturePlugin;
emit passTextureId(mBrushTexture);
}
if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted())
{
mBrushTextureLabel = "Selected texture: " + mBrushTexture + " ";
mBrushTextureLabel = "Selected texture: " + newBrushTextureId + " ";
mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel) + landtexturesCollection.getData(index, landTextureFilename).value<QString>());
} else
{
newBrushTextureId = "";
mBrushTextureLabel = "No selected texture or invalid texture";
mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel));
}
emit passBrushShape(mBrushShape); // update icon
mBrushTexture = newBrushTextureId;
emit passTextureId(mBrushTexture);
emit passBrushShape(mBrushShape); // updates the icon tooltip
}
void CSVWidget::TextureBrushWindow::setBrushSize(int brushSize)

@ -81,7 +81,7 @@ add_openmw_dir (mwclass
add_openmw_dir (mwmechanics
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
aicast aiescort aiface aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellsuccess spellcasting
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
character actors objects aistate coordinateconverter trading weaponpriority spellpriority weapontype
)

@ -215,6 +215,7 @@ bool OMW::Engine::frame(float frametime)
{
double hours = (frametime * mEnvironment.getWorld()->getTimeScaleFactor()) / 3600.0;
mEnvironment.getWorld()->advanceTime(hours, true);
mEnvironment.getWorld()->rechargeItems(frametime, true);
}
}
osg::Timer_t afterScriptTick = osg::Timer::instance()->tick();

@ -73,8 +73,6 @@ namespace MWBase
/// \param paused In game type does not currently advance (this usually means some GUI
/// component is up).
virtual void advanceTime (float duration) = 0;
virtual void setPlayerName (const std::string& name) = 0;
///< Set player name.

@ -776,6 +776,7 @@ namespace MWBase
virtual bool isPlayerInJail() const = 0;
virtual void rest(double hours) = 0;
virtual void rechargeItems(double duration, bool activeOnly) = 0;
virtual void setPlayerTraveling(bool traveling) = 0;
virtual bool isPlayerTraveling() const = 0;

@ -25,12 +25,12 @@ namespace MWGui
{
}
MWWorld::Ptr CompanionItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner=false)
MWWorld::Ptr CompanionItemModel::copyItem (const ItemStack& item, size_t count)
{
if (hasProfit(mActor))
modifyProfit(mActor, item.mBase.getClass().getValue(item.mBase) * count);
return InventoryItemModel::copyItem(item, count, setNewOwner);
return InventoryItemModel::copyItem(item, count);
}
void CompanionItemModel::removeItem (const ItemStack& item, size_t count)

@ -13,7 +13,7 @@ namespace MWGui
public:
CompanionItemModel (const MWWorld::Ptr& actor);
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner);
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count);
virtual void removeItem (const ItemStack& item, size_t count);
bool hasProfit(const MWWorld::Ptr& actor);

@ -91,7 +91,7 @@ ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item)
return -1;
}
MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner)
MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count)
{
const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1];
if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source))

@ -26,7 +26,7 @@ namespace MWGui
virtual ModelIndex getIndex (ItemStack item);
virtual size_t getItemCount();
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false);
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count);
virtual void removeItem (const ItemStack& item, size_t count);
virtual void update();

@ -52,7 +52,7 @@ namespace MWGui
public:
WorldItemModel(float left, float top) : mLeft(left), mTop(top) {}
virtual ~WorldItemModel() {}
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false)
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
@ -61,8 +61,7 @@ namespace MWGui
dropped = world->placeObject(item.mBase, mLeft, mTop, count);
else
dropped = world->dropObjectOnGround(world->getPlayerPtr(), item.mBase, count);
if (setNewOwner)
dropped.getCellRef().setOwner("");
dropped.getCellRef().setOwner("");
/*
Start of tes3mp addition

@ -46,11 +46,11 @@ ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item)
return -1;
}
MWWorld::Ptr InventoryItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner)
MWWorld::Ptr InventoryItemModel::copyItem (const ItemStack& item, size_t count)
{
if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor))
throw std::runtime_error("Item to copy needs to be from a different container!");
return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor, setNewOwner);
return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor);
}
void InventoryItemModel::removeItem (const ItemStack& item, size_t count)
@ -88,7 +88,7 @@ MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack &item, size_t count, I
if (item.mFlags & ItemStack::Flag_Bound)
return MWWorld::Ptr();
MWWorld::Ptr ret = otherModel->copyItem(item, count, false);
MWWorld::Ptr ret = otherModel->copyItem(item, count);
removeItem(item, count);
return ret;
}

@ -17,7 +17,7 @@ namespace MWGui
virtual bool onTakeItem(const MWWorld::Ptr &item, int count);
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false);
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count);
virtual void removeItem (const ItemStack& item, size_t count);
/// Move items from this model to \a otherModel.

@ -540,7 +540,11 @@ namespace MWGui
if (canEquip.first == 0)
{
MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second);
/// If PCSkipEquip is set, set OnPCEquip to 1 and don't message anything
if (!script.empty() && ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 1)
ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1);
else
MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second);
updateItemView();
return;
}

@ -116,9 +116,9 @@ namespace MWGui
return mSourceModel->allowedToUseItems();
}
MWWorld::Ptr ProxyItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner)
MWWorld::Ptr ProxyItemModel::copyItem (const ItemStack& item, size_t count)
{
return mSourceModel->copyItem (item, count, setNewOwner);
return mSourceModel->copyItem (item, count);
}
void ProxyItemModel::removeItem (const ItemStack& item, size_t count)

@ -67,7 +67,7 @@ namespace MWGui
/// @param setNewOwner If true, set the copied item's owner to the actor we are copying to,
/// otherwise reset owner to ""
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) = 0;
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count) = 0;
virtual void removeItem (const ItemStack& item, size_t count) = 0;
/// Is the player allowed to use items from this item model? (default true)
@ -97,7 +97,7 @@ namespace MWGui
virtual bool onDropItem(const MWWorld::Ptr &item, int count);
virtual bool onTakeItem(const MWWorld::Ptr &item, int count);
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false);
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count);
virtual void removeItem (const ItemStack& item, size_t count);
virtual ModelIndex getIndex (ItemStack item);

@ -7,19 +7,6 @@
#include <components/misc/rng.hpp>
/*
Start of tes3mp addition
Include additional headers for multiplayer purposes
*/
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/LocalPlayer.hpp"
#include "../mwmp/MechanicsHelper.hpp"
/*
End of tes3mp addition
*/
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
@ -30,6 +17,7 @@
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/recharge.hpp"
#include "itemwidget.hpp"
#include "itemchargeview.hpp"
@ -143,79 +131,9 @@ void Recharge::onItemCancel()
void Recharge::onItemClicked(MyGUI::Widget *sender, const MWWorld::Ptr& item)
{
MWWorld::Ptr gem = *mGemIcon->getUserData<MWWorld::Ptr>();
if (!gem.getRefData().getCount())
if (!MWMechanics::rechargeItem(item, gem))
return;
MWWorld::Ptr player = MWMechanics::getPlayer();
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
float luckTerm = 0.1f * stats.getAttribute(ESM::Attribute::Luck).getModified();
if (luckTerm < 1|| luckTerm > 10)
luckTerm = 1;
float intelligenceTerm = 0.2f * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
if (intelligenceTerm > 20)
intelligenceTerm = 20;
if (intelligenceTerm < 1)
intelligenceTerm = 1;
float x = (player.getClass().getSkill(player, ESM::Skill::Enchant) + intelligenceTerm + luckTerm) * stats.getFatigueTerm();
int roll = Misc::Rng::roll0to99();
if (roll < x)
{
std::string soul = gem.getCellRef().getSoul();
const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().find(soul);
float restored = creature->mData.mSoul * (roll / x);
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
item.getClass().getEnchantment(item));
/*
Start of tes3mp change (minor)
Send PlayerInventory packets that replace the original item with the new one
*/
mwmp::LocalPlayer *localPlayer = mwmp::Main::get().getLocalPlayer();
mwmp::Item removedItem = MechanicsHelper::getItem(item, 1);
item.getCellRef().setEnchantmentCharge(
std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast<float>(enchantment->mData.mCharge)));
mwmp::Item addedItem = MechanicsHelper::getItem(item, 1);
localPlayer->sendItemChange(addedItem, mwmp::InventoryChanges::ADD);
localPlayer->sendItemChange(removedItem, mwmp::InventoryChanges::REMOVE);
/*
End of tes3mp change (minor)
*/
MWBase::Environment::get().getWindowManager()->playSound("Enchant Success");
player.getClass().getContainerStore(player).restack(item);
}
else
{
MWBase::Environment::get().getWindowManager()->playSound("Enchant Fail");
}
player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0);
gem.getContainerStore()->remove(gem, 1, player);
if (gem.getRefData().getCount() == 0)
{
std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage51")->mValue.getString();
message = Misc::StringUtils::format(message, gem.getClass().getName(gem));
MWBase::Environment::get().getWindowManager()->messageBox(message);
// special case: readd Azura's Star
if (Misc::StringUtils::ciEqual(gem.get<ESM::Miscellaneous>()->mBase->mId, "Misc_SoulGem_Azura"))
player.getClass().getContainerStore(player).add("Misc_SoulGem_Azura", 1, player);
}
updateView();
}

@ -652,19 +652,22 @@ namespace MWGui
std::string ret;
ret += getMiscString(cellref.getOwner(), "Owner");
const std::string factionId = cellref.getFaction();
ret += getMiscString(factionId, "Faction");
if (!factionId.empty() && cellref.getFactionRank() >= 0)
if (!factionId.empty())
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Faction *fact = store.get<ESM::Faction>().search(factionId);
if (fact != nullptr)
{
int rank = cellref.getFactionRank();
const std::string rankName = fact->mRanks[rank];
if (rankName.empty())
ret += getValueString(cellref.getFactionRank(), "Rank");
else
ret += getMiscString(rankName, "Rank");
ret += getMiscString(fact->mName.empty() ? factionId : fact->mName, "Owner Faction");
if (cellref.getFactionRank() >= 0)
{
int rank = cellref.getFactionRank();
const std::string rankName = fact->mRanks[rank];
if (rankName.empty())
ret += getValueString(cellref.getFactionRank(), "Rank");
else
ret += getMiscString(rankName, "Rank");
}
}
}

@ -44,6 +44,7 @@
#include "../mwrender/vismask.hpp"
#include "spellcasting.hpp"
#include "steering.hpp"
#include "npcstats.hpp"
#include "creaturestats.hpp"
#include "movement.hpp"
@ -158,6 +159,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;
class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor
{
public:
@ -325,10 +329,31 @@ namespace MWMechanics
bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId);
store.remove(itemId, 1, actor, true);
store.remove(itemId, 1, actor);
if (actor != MWMechanics::getPlayer())
{
// Equip a replacement
if (!wasEquipped)
return;
std::string type = currentItem->getTypeName();
if (type != typeid(ESM::Weapon).name() && type != typeid(ESM::Armor).name() && type != typeid(ESM::Clothing).name())
return;
if (actor.getClass().getCreatureStats(actor).isDead())
return;
if (!actor.getClass().hasInventoryStore(actor) || !actor.getClass().getInventoryStore(actor).canActorAutoEquip(actor))
return;
if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
return;
actor.getClass().getInventoryStore(actor).autoEquip(actor);
return;
}
MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
std::string prevItemId = player.getPreviousItem(itemId);
@ -428,6 +453,113 @@ namespace MWMechanics
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
}
void Actors::updateGreetingState(const MWWorld::Ptr& actor, bool turnOnly)
{
if (!actor.getClass().isActor() || actor == getPlayer())
return;
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
int hello = stats.getAiSetting(CreatureStats::AI_Hello).getModified();
if (hello == 0)
return;
if (MWBase::Environment::get().getWorld()->isSwimming(actor))
return;
MWWorld::Ptr player = getPlayer();
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
osg::Vec3f dir = playerPos - actorPos;
const MWMechanics::AiSequence& seq = stats.getAiSequence();
int packageId = seq.getTypeId();
if (seq.isInCombat() || (packageId != AiPackage::TypeIdWander && packageId != AiPackage::TypeIdTravel && packageId != -1))
{
stats.setTurningToPlayer(false);
stats.setGreetingTimer(0);
stats.setGreetingState(Greet_None);
return;
}
if (stats.isTurningToPlayer())
{
// Reduce the turning animation glitch by using a *HUGE* value of
// epsilon... TODO: a proper fix might be in either the physics or the
// animation subsystem
if (zTurn(actor, stats.getAngleToPlayer(), osg::DegreesToRadians(5.f)))
{
stats.setTurningToPlayer(false);
// An original engine launches an endless idle2 when an actor greets player.
playAnimationGroup (actor, "idle2", 0, std::numeric_limits<int>::max(), false);
}
}
if (turnOnly)
return;
// Play a random voice greeting if the player gets too close
float helloDistance = static_cast<float>(hello);
static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->mValue.getInteger();
helloDistance *= iGreetDistanceMultiplier;
int greetingTimer = stats.getGreetingTimer();
GreetingState greetingState = stats.getGreetingState();
if (greetingState == Greet_None)
{
if ((playerPos - actorPos).length2() <= helloDistance*helloDistance &&
!player.getClass().getCreatureStats(player).isDead() && !actor.getClass().getCreatureStats(actor).isParalyzed()
&& MWBase::Environment::get().getWorld()->getLOS(player, actor)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
greetingTimer++;
if (greetingTimer >= GREETING_SHOULD_START)
{
greetingState = Greet_InProgress;
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
greetingTimer = 0;
}
}
if (greetingState == Greet_InProgress)
{
greetingTimer++;
turnActorToFacePlayer(actor, dir);
if (greetingTimer >= GREETING_SHOULD_END)
{
greetingState = Greet_Done;
greetingTimer = 0;
}
}
if (greetingState == Greet_Done)
{
float resetDist = 2 * helloDistance;
if ((playerPos - actorPos).length2() >= resetDist*resetDist)
greetingState = Greet_None;
}
stats.setGreetingTimer(greetingTimer);
stats.setGreetingState(greetingState);
}
void Actors::turnActorToFacePlayer(const MWWorld::Ptr& actor, const osg::Vec3f& dir)
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
if (!stats.isTurningToPlayer())
{
stats.setAngleToPlayer(std::atan2(dir.x(), dir.y()));
stats.setTurningToPlayer(true);
}
}
void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr> >& cachedAllies, bool againstPlayer)
{
// No combat for totally static creatures
@ -599,7 +731,7 @@ namespace MWMechanics
MagicEffects now = creatureStats.getSpells().getMagicEffects();
if (creature.getClass().isNpc())
if (creature.getClass().hasInventoryStore(creature))
{
MWWorld::InventoryStore& store = creature.getClass().getInventoryStore (creature);
now += store.getMagicEffects();
@ -1161,7 +1293,7 @@ namespace MWMechanics
heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
// If we have a torch and can equip it, then equip it now.
if (heldIter == inventoryStore.end())
if (heldIter == inventoryStore.end() && inventoryStore.canActorAutoEquip(ptr))
{
inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr);
}
@ -1528,11 +1660,13 @@ namespace MWMechanics
static float timerUpdateAITargets = 0;
static float timerUpdateHeadTrack = 0;
static float timerUpdateEquippedLight = 0;
static float timerUpdateHello = 0;
const float updateEquippedLightInterval = 1.0f;
// target lists get updated once every 1.0 sec
if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0;
if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0;
if (timerUpdateHello >= 0.25f) timerUpdateHello = 0;
if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0;
if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0;
@ -1729,6 +1863,7 @@ namespace MWMechanics
if (isConscious(iter->first))
{
stats.getAiSequence().execute(iter->first, *ctrl, duration);
updateGreetingState(iter->first, timerUpdateHello > 0);
playIdleDialogue(iter->first);
}
}
@ -1754,6 +1889,7 @@ namespace MWMechanics
timerUpdateAITargets += duration;
timerUpdateHeadTrack += duration;
timerUpdateEquippedLight += duration;
timerUpdateHello += duration;
mTimerDisposeSummonsCorpses += duration;
// Animation/movement update
@ -1923,6 +2059,8 @@ namespace MWMechanics
// Make sure spell effects are removed
purgeSpellEffects(stats.getActorId());
calculateCreatureStatModifiers(iter->first, 0);
if( iter->first == getPlayer())
{
//player's death animation is over

@ -121,6 +121,8 @@ namespace MWMechanics
void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr> >& cachedAllies, bool againstPlayer);
void playIdleDialogue(const MWWorld::Ptr& actor);
void updateGreetingState(const MWWorld::Ptr& actor, bool turnOnly);
void turnActorToFacePlayer(const MWWorld::Ptr& actor, const osg::Vec3f& dir);
void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor,
MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance);

@ -43,11 +43,16 @@ namespace MWMechanics
bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
{
auto& stats = actor.getClass().getCreatureStats(actor);
if (stats.isTurningToPlayer() || stats.getGreetingState() == Greet_InProgress)
return false;
const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
const osg::Vec3f targetPos(mX, mY, mZ);
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
stats.setMovementFlag(CreatureStats::Flag_Run, false);
stats.setDrawState(DrawState_Nothing);
if (!isWithinMaxRange(targetPos, actorPos))
return false;

@ -17,7 +17,6 @@
#include "pathgrid.hpp"
#include "creaturestats.hpp"
#include "steering.hpp"
#include "movement.hpp"
#include "coordinateconverter.hpp"
#include "actorutil.hpp"
@ -26,8 +25,6 @@ namespace MWMechanics
{
static const int COUNT_BEFORE_RESET = 10;
static const float DOOR_CHECK_INTERVAL = 1.5f;
static const int GREETING_SHOULD_START = 4; //how many reaction intervals should pass before NPC can greet player
static const int GREETING_SHOULD_END = 10;
// to prevent overcrowding
static const int DESTINATION_TOLERANCE = 64;
@ -176,6 +173,17 @@ namespace MWMechanics
storage.setState(AiWanderStorage::Wander_Walking);
}
GreetingState greetingState = cStats.getGreetingState();
if (greetingState == Greet_InProgress)
{
if (storage.mState == AiWanderStorage::Wander_Walking)
{
stopWalking(actor, storage, false);
mObstacleCheck.clear();
storage.setState(AiWanderStorage::Wander_IdleNow);
}
}
doPerFrameActionsForState(actor, duration, storage);
float& lastReaction = storage.mReaction;
@ -245,13 +253,7 @@ namespace MWMechanics
if(mDistance && cellChange)
mDistance = 0;
// Allow interrupting a walking actor to trigger a greeting
AiWanderStorage::WanderState& wanderState = storage.mState;
if ((wanderState == AiWanderStorage::Wander_IdleNow) || (wanderState == AiWanderStorage::Wander_Walking))
{
playGreetingIfPlayerGetsTooClose(actor, storage);
}
if ((wanderState == AiWanderStorage::Wander_MoveNow) && storage.mCanWanderAlongPathGrid)
{
// Construct a new path if there isn't one
@ -416,19 +418,9 @@ namespace MWMechanics
}
}
bool& rotate = storage.mTurnActorGivingGreetingToFacePlayer;
if (rotate)
{
// Reduce the turning animation glitch by using a *HUGE* value of
// epsilon... TODO: a proper fix might be in either the physics or the
// animation subsystem
if (zTurn(actor, storage.mTargetAngleRadians, osg::DegreesToRadians(5.f)))
rotate = false;
}
// Check if idle animation finished
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == AiWanderStorage::Greet_Done || greetingState == AiWanderStorage::Greet_None))
GreetingState greetingState = actor.getClass().getCreatureStats(actor).getGreetingState();
if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None))
{
if (mPathFinder.isPathConstructed())
storage.setState(AiWanderStorage::Wander_Walking);
@ -517,74 +509,7 @@ namespace MWMechanics
}
}
void AiWander::playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage)
{
// Play a random voice greeting if the player gets too close
int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
float helloDistance = static_cast<float>(hello);
static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->mValue.getInteger();
helloDistance *= iGreetDistanceMultiplier;
MWWorld::Ptr player = getPlayer();
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
int& greetingTimer = storage.mGreetingTimer;
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
if (greetingState == AiWanderStorage::Greet_None)
{
if ((playerPos - actorPos).length2() <= helloDistance*helloDistance &&
!player.getClass().getCreatureStats(player).isDead() && !actor.getClass().getCreatureStats(actor).isParalyzed()
&& MWBase::Environment::get().getWorld()->getLOS(player, actor)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
greetingTimer++;
if (greetingTimer >= GREETING_SHOULD_START)
{
greetingState = AiWanderStorage::Greet_InProgress;
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
greetingTimer = 0;
}
}
if (greetingState == AiWanderStorage::Greet_InProgress)
{
greetingTimer++;
if (storage.mState == AiWanderStorage::Wander_Walking)
{
stopWalking(actor, storage, false);
mObstacleCheck.clear();
storage.setState(AiWanderStorage::Wander_IdleNow);
}
turnActorToFacePlayer(actorPos, playerPos, storage);
if (greetingTimer >= GREETING_SHOULD_END)
{
greetingState = AiWanderStorage::Greet_Done;
greetingTimer = 0;
}
}
if (greetingState == AiWanderStorage::Greet_Done)
{
float resetDist = 2 * helloDistance;
if ((playerPos - actorPos).length2() >= resetDist*resetDist)
greetingState = AiWanderStorage::Greet_None;
}
}
void AiWander::turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage)
{
osg::Vec3f dir = playerPosition - actorPosition;
float faceAngleRadians = std::atan2(dir.x(), dir.y());
storage.mTargetAngleRadians = faceAngleRadians;
storage.mTurnActorGivingGreetingToFacePlayer = true;
}
void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
{

@ -25,21 +25,8 @@ namespace MWMechanics
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
struct AiWanderStorage : AiTemporaryBase
{
// the z rotation angle to reach
// when mTurnActorGivingGreetingToFacePlayer is true
float mTargetAngleRadians;
bool mTurnActorGivingGreetingToFacePlayer;
float mReaction; // update some actions infrequently
enum GreetingState
{
Greet_None,
Greet_InProgress,
Greet_Done
};
GreetingState mSaidGreeting;
int mGreetingTimer;
const MWWorld::CellStore* mCell; // for detecting cell change
// AiWander states
@ -72,11 +59,7 @@ namespace MWMechanics
int mStuckCount;
AiWanderStorage():
mTargetAngleRadians(0),
mTurnActorGivingGreetingToFacePlayer(false),
mReaction(0),
mSaidGreeting(Greet_None),
mGreetingTimer(0),
mCell(nullptr),
mState(Wander_ChooseAction),
mIsWanderingManually(false),
@ -136,7 +119,6 @@ namespace MWMechanics
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
short unsigned getRandomIdle();
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void evadeObstacles(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);

@ -34,12 +34,53 @@ namespace MWMechanics
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
mHitRecovery(false), mBlock(false), mMovementFlags(0),
mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1),
mDeathAnimation(-1), mTimeOfDeath(), mLevel (0)
mDeathAnimation(-1), mTimeOfDeath(), mGreetingState(Greet_None),
mGreetingTimer(0), mTargetAngleRadians(0), mIsTurningToPlayer(false), mLevel (0)
{
for (int i=0; i<4; ++i)
mAiSettings[i] = 0;
}
int MWMechanics::CreatureStats::getGreetingTimer() const
{
return mGreetingTimer;
}
void MWMechanics::CreatureStats::setGreetingTimer(int timer)
{
mGreetingTimer = timer;
}
float MWMechanics::CreatureStats::getAngleToPlayer() const
{
return mTargetAngleRadians;
}
void MWMechanics::CreatureStats::setAngleToPlayer(float angle)
{
mTargetAngleRadians = angle;
}
GreetingState MWMechanics::CreatureStats::getGreetingState() const
{
return mGreetingState;
}
void MWMechanics::CreatureStats::setGreetingState(GreetingState state)
{
mGreetingState = state;
}
bool MWMechanics::CreatureStats::isTurningToPlayer() const
{
return mIsTurningToPlayer;
}
void MWMechanics::CreatureStats::setTurningToPlayer(bool turning)
{
mIsTurningToPlayer = turning;
}
const AiSequence& CreatureStats::getAiSequence() const
{
return mAiSequence;

@ -19,6 +19,13 @@ namespace ESM
namespace MWMechanics
{
enum GreetingState
{
Greet_None,
Greet_InProgress,
Greet_Done
};
/// \brief Common creature stats
///
///
@ -70,6 +77,11 @@ namespace MWMechanics
MWWorld::TimeStamp mTimeOfDeath;
GreetingState mGreetingState;
int mGreetingTimer;
float mTargetAngleRadians;
bool mIsTurningToPlayer;
public:
typedef std::pair<int, std::string> SummonKey; // <ESM::MagicEffect index, spell ID>
private:
@ -85,6 +97,18 @@ namespace MWMechanics
public:
CreatureStats();
int getGreetingTimer() const;
void setGreetingTimer(int timer);
float getAngleToPlayer() const;
void setAngleToPlayer(float angle);
GreetingState getGreetingState() const;
void setGreetingState(GreetingState state);
bool isTurningToPlayer() const;
void setTurningToPlayer(bool turning);
DrawState_ getDrawState() const;
void setDrawState(DrawState_ state);

@ -300,16 +300,6 @@ namespace MWMechanics
mWatched = ptr;
}
void MechanicsManager::advanceTime (float duration)
{
// Uses ingame time, but scaled to real time
const float timeScaleFactor = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
if (timeScaleFactor != 0.0f)
duration /= timeScaleFactor;
MWWorld::Ptr player = getPlayer();
player.getClass().getInventoryStore(player).rechargeItems(duration);
}
void MechanicsManager::update(float duration, bool paused)
{
if(!mWatched.isEmpty())

@ -78,8 +78,6 @@ namespace MWMechanics
/// \param paused In game type does not currently advance (this usually means some GUI
/// component is up).
virtual void advanceTime (float duration) override;
virtual void setPlayerName (const std::string& name) override;
///< Set player name.

@ -0,0 +1,124 @@
#include "recharge.hpp"
#include <components/misc/rng.hpp>
/*
Start of tes3mp addition
Include additional headers for multiplayer purposes
*/
#include "../mwmp/Main.hpp"
#include "../mwmp/Networking.hpp"
#include "../mwmp/LocalPlayer.hpp"
#include "../mwmp/MechanicsHelper.hpp"
/*
End of tes3mp addition
*/
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "creaturestats.hpp"
#include "actorutil.hpp"
namespace MWMechanics
{
bool rechargeItem(const MWWorld::Ptr &item, const float maxCharge, const float duration)
{
float charge = item.getCellRef().getEnchantmentCharge();
if (charge == -1 || charge == maxCharge)
return false;
static const float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"fMagicItemRechargePerSecond")->mValue.getFloat();
item.getCellRef().setEnchantmentCharge(std::min(charge + fMagicItemRechargePerSecond * duration, maxCharge));
return true;
}
bool rechargeItem(const MWWorld::Ptr &item, const MWWorld::Ptr &gem)
{
if (!gem.getRefData().getCount())
return false;
MWWorld::Ptr player = MWMechanics::getPlayer();
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
float luckTerm = 0.1f * stats.getAttribute(ESM::Attribute::Luck).getModified();
if (luckTerm < 1 || luckTerm > 10)
luckTerm = 1;
float intelligenceTerm = 0.2f * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
if (intelligenceTerm > 20)
intelligenceTerm = 20;
if (intelligenceTerm < 1)
intelligenceTerm = 1;
float x = (player.getClass().getSkill(player, ESM::Skill::Enchant) + intelligenceTerm + luckTerm) * stats.getFatigueTerm();
int roll = Misc::Rng::roll0to99();
if (roll < x)
{
std::string soul = gem.getCellRef().getSoul();
const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().find(soul);
float restored = creature->mData.mSoul * (roll / x);
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
item.getClass().getEnchantment(item));
item.getCellRef().setEnchantmentCharge(
std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast<float>(enchantment->mData.mCharge)));
/*
Start of tes3mp change (minor)
Send PlayerInventory packets that replace the original item with the new one
*/
mwmp::LocalPlayer *localPlayer = mwmp::Main::get().getLocalPlayer();
mwmp::Item removedItem = MechanicsHelper::getItem(item, 1);
item.getCellRef().setEnchantmentCharge(
std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast<float>(enchantment->mData.mCharge)));
mwmp::Item addedItem = MechanicsHelper::getItem(item, 1);
localPlayer->sendItemChange(addedItem, mwmp::InventoryChanges::ADD);
localPlayer->sendItemChange(removedItem, mwmp::InventoryChanges::REMOVE);
/*
End of tes3mp change (minor)
*/
MWBase::Environment::get().getWindowManager()->playSound("Enchant Success");
player.getClass().getContainerStore(player).restack(item);
}
else
{
MWBase::Environment::get().getWindowManager()->playSound("Enchant Fail");
}
player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0);
gem.getContainerStore()->remove(gem, 1, player);
if (gem.getRefData().getCount() == 0)
{
std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage51")->mValue.getString();
message = Misc::StringUtils::format(message, gem.getClass().getName(gem));
MWBase::Environment::get().getWindowManager()->messageBox(message);
// special case: readd Azura's Star
if (Misc::StringUtils::ciEqual(gem.get<ESM::Miscellaneous>()->mBase->mId, "Misc_SoulGem_Azura"))
player.getClass().getContainerStore(player).add("Misc_SoulGem_Azura", 1, player);
}
return true;
}
}

@ -0,0 +1,15 @@
#ifndef MWMECHANICS_RECHARGE_H
#define MWMECHANICS_RECHARGE_H
#include "../mwworld/ptr.hpp"
namespace MWMechanics
{
bool rechargeItem(const MWWorld::Ptr &item, const float maxCharge, const float duration);
bool rechargeItem(const MWWorld::Ptr &item, const MWWorld::Ptr &gem);
}
#endif

@ -943,7 +943,7 @@ namespace MWMechanics
{
int type = item.get<ESM::Weapon>()->mBase->mData.mType;
ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass;
isProjectile = (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ranged);
isProjectile = (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo);
}
int type = enchantment->mData.mType;

@ -133,9 +133,10 @@ namespace MWMechanics
}
// Update summon effects
bool casterDead = creatureStats.isDead();
for (std::map<CreatureStats::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
{
bool found = mActiveEffects.find(it->first) != mActiveEffects.end();
bool found = !casterDead && mActiveEffects.find(it->first) != mActiveEffects.end();
if (!found)
{
// Effect has ended

@ -184,7 +184,7 @@ void ObjectList::editContainers(MWWorld::CellStore* cellStore)
if (!containerItem.soul.empty())
newPtr.getCellRef().setSoul(containerItem.soul);
containerStore.add(newPtr, containerItem.count, ownerPtr, true);
containerStore.add(newPtr, containerItem.count, ownerPtr);
}
else if (action == BaseObjectList::REMOVE && containerItem.actionCount > 0)

@ -87,17 +87,17 @@ namespace
{
if (mRendered)
{
node->setNodeMask(0);
if (mParent->copyResult(static_cast<osg::Camera*>(node), nv->getTraversalNumber()))
{
node->setNodeMask(0);
mParent->markForRemoval(static_cast<osg::Camera*>(node));
}
return;
}
traverse(node, nv);
if (!mRendered)
{
mRendered = true;
mParent->markForRemoval(static_cast<osg::Camera*>(node));
}
mRendered = true;
}
private:
@ -361,7 +361,7 @@ namespace MWRender
imageDest.mImage = image;
imageDest.mX = x;
imageDest.mY = y;
mPendingImageDest.push_back(imageDest);
mPendingImageDest[camera] = imageDest;
}
// Create a quad rendering the updated texture
@ -614,35 +614,21 @@ namespace MWRender
}
}
void GlobalMap::markForRemoval(osg::Camera *camera)
{
CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), camera);
if (found == mActiveCameras.end())
{
Log(Debug::Error) << "Error: GlobalMap trying to remove an inactive camera";
return;
}
mActiveCameras.erase(found);
mCamerasPendingRemoval.push_back(camera);
}
void GlobalMap::cleanupCameras()
bool GlobalMap::copyResult(osg::Camera *camera, unsigned int frame)
{
for (auto& camera : mCamerasPendingRemoval)
removeCamera(camera);
mCamerasPendingRemoval.clear();
for (ImageDestVector::iterator it = mPendingImageDest.begin(); it != mPendingImageDest.end();)
ImageDestMap::iterator it = mPendingImageDest.find(camera);
if (it == mPendingImageDest.end())
return true;
else
{
ImageDest& imageDest = *it;
if (--imageDest.mFramesUntilDone > 0)
ImageDest& imageDest = it->second;
if (imageDest.mFrameDone == 0) imageDest.mFrameDone = frame+2; // wait an extra frame to ensure the draw thread has completed its frame.
if (imageDest.mFrameDone > frame)
{
++it;
continue;
return false;
}
ensureLoaded();
mOverlayImage->copySubImage(imageDest.mX, imageDest.mY, 0, imageDest.mImage);
/*
@ -673,7 +659,7 @@ namespace MWRender
if (!readerwriter)
{
std::cerr << "Error: Can't write temporary map image, no '" << "png" << "' readerwriter found" << std::endl;
return;
return false;
}
std::ostringstream ostream;
@ -697,7 +683,28 @@ namespace MWRender
*/
it = mPendingImageDest.erase(it);
return true;
}
}
void GlobalMap::markForRemoval(osg::Camera *camera)
{
CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), camera);
if (found == mActiveCameras.end())
{
Log(Debug::Error) << "Error: GlobalMap trying to remove an inactive camera";
return;
}
mActiveCameras.erase(found);
mCamerasPendingRemoval.push_back(camera);
}
void GlobalMap::cleanupCameras()
{
for (auto& camera : mCamerasPendingRemoval)
removeCamera(camera);
mCamerasPendingRemoval.clear();
}
void GlobalMap::removeCamera(osg::Camera *cam)

@ -3,6 +3,7 @@
#include <string>
#include <vector>
#include <map>
#include <osg/ref_ptr>
@ -60,6 +61,8 @@ namespace MWRender
void removeCamera(osg::Camera* cam);
bool copyResult(osg::Camera* cam, unsigned int frame);
/*
Start of tes3mp addition
@ -106,18 +109,18 @@ namespace MWRender
{
ImageDest()
: mX(0), mY(0)
, mFramesUntilDone(3) // wait an extra frame to ensure the draw thread has completed its frame.
, mFrameDone(0)
{
}
osg::ref_ptr<osg::Image> mImage;
int mX, mY;
int mFramesUntilDone;
unsigned int mFrameDone;
};
typedef std::vector<ImageDest> ImageDestVector;
typedef std::map<osg::ref_ptr<osg::Camera>, ImageDest> ImageDestMap;
ImageDestVector mPendingImageDest;
ImageDestMap mPendingImageDest;
std::vector< std::pair<int,int> > mExploredCells;

@ -18,10 +18,10 @@
#include <osg/PolygonOffset>
#include <osg/observer_ptr>
#include <osgParticle/BoxPlacer>
#include <osgParticle/ModularEmitter>
#include <osgParticle/ParticleSystem>
#include <osgParticle/ParticleSystemUpdater>
#include <osgParticle/ModularEmitter>
#include <osgParticle/BoxPlacer>
#include <osgParticle/ConstantRateCounter>
#include <osgParticle/RadialShooter>
@ -1117,7 +1117,11 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana
, mRemainingTransitionTime(0.0f)
, mRainEnabled(false)
, mRainSpeed(0)
, mRainFrequency(1)
, mRainDiameter(0)
, mRainMinHeight(0)
, mRainMaxHeight(0)
, mRainEntranceSpeed(1)
, mRainMaxRaindrops(0)
, mWindSpeed(0.f)
, mEnabled(true)
, mSunEnabled(true)
@ -1458,7 +1462,7 @@ void SkyManager::createRain()
mRainNode = new osg::Group;
mRainParticleSystem = new osgParticle::ParticleSystem;
osg::Vec3 rainRange = osg::Vec3(600,600,600);
osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f);
mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED);
mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1,0,0));
@ -1485,14 +1489,19 @@ void SkyManager::createRain()
emitter->setParticleSystem(mRainParticleSystem);
osg::ref_ptr<osgParticle::BoxPlacer> placer (new osgParticle::BoxPlacer);
placer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); // Rain_Diameter
placer->setXRange(-rainRange.x() / 2, rainRange.x() / 2);
placer->setYRange(-rainRange.y() / 2, rainRange.y() / 2);
placer->setZRange(300, 300);
placer->setZRange(-rainRange.z() / 2, rainRange.z() / 2);
emitter->setPlacer(placer);
mPlacer = placer;
// FIXME: vanilla engine does not use a particle system to handle rain, it uses a NIF-file with 20 raindrops in it.
// It spawns the (maxRaindrops-getParticleSystem()->numParticles())*dt/rainEntranceSpeed batches every frame (near 1-2).
// Since the rain is a regular geometry, it produces water ripples, also in theory it can be removed if collides with something.
osg::ref_ptr<RainCounter> counter (new RainCounter);
counter->setNumberOfParticlesPerSecondToCreate(600.0);
counter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20);
emitter->setCounter(counter);
mCounter = counter;
osg::ref_ptr<RainShooter> shooter (new RainShooter);
mRainShooter = shooter;
@ -1525,6 +1534,8 @@ void SkyManager::destroyRain()
mRootNode->removeChild(mRainNode);
mRainNode = nullptr;
mPlacer = nullptr;
mCounter = nullptr;
mRainParticleSystem = nullptr;
mRainShooter = nullptr;
mRainFader = nullptr;
@ -1625,10 +1636,17 @@ void SkyManager::updateRainParameters()
{
if (mRainShooter)
{
float windFactor = mWindSpeed/3.f;
float angle = windFactor * osg::PI/4;
mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed * windFactor, -mRainSpeed));
float angle = -std::atan2(1, 50.f/mWindSpeed);
mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed*std::sin(angle), -mRainSpeed/std::cos(angle)));
mRainShooter->setAngle(angle);
osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f);
mPlacer->setXRange(-rainRange.x() / 2, rainRange.x() / 2);
mPlacer->setYRange(-rainRange.y() / 2, rainRange.y() / 2);
mPlacer->setZRange(-rainRange.z() / 2, rainRange.z() / 2);
mCounter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20);
}
}
@ -1645,6 +1663,14 @@ void SkyManager::setWeather(const WeatherResult& weather)
{
if (!mCreated) return;
mRainEntranceSpeed = weather.mRainEntranceSpeed;
mRainMaxRaindrops = weather.mRainMaxRaindrops;
mRainDiameter = weather.mRainDiameter;
mRainMinHeight = weather.mRainMinHeight;
mRainMaxHeight = weather.mRainMaxHeight;
mRainSpeed = weather.mRainSpeed;
mWindSpeed = weather.mWindSpeed;
if (mRainEffect != weather.mRainEffect)
{
mRainEffect = weather.mRainEffect;
@ -1658,9 +1684,6 @@ void SkyManager::setWeather(const WeatherResult& weather)
}
}
mRainFrequency = weather.mRainFrequency;
mRainSpeed = weather.mRainSpeed;
mWindSpeed = weather.mWindSpeed;
updateRainParameters();
mIsStorm = weather.mIsStorm;

@ -25,6 +25,7 @@ namespace osg
namespace osgParticle
{
class ParticleSystem;
class BoxPlacer;
}
namespace Resource
@ -39,6 +40,7 @@ namespace MWRender
class CloudUpdater;
class Sun;
class Moon;
class RainCounter;
class RainShooter;
class RainFader;
class AlphaFader;
@ -68,6 +70,8 @@ namespace MWRender
float mDLFogOffset;
float mWindSpeed;
float mCurrentWindSpeed;
float mNextWindSpeed;
float mCloudSpeed;
@ -85,8 +89,12 @@ namespace MWRender
std::string mRainEffect;
float mEffectFade;
float mRainDiameter;
float mRainMinHeight;
float mRainMaxHeight;
float mRainSpeed;
float mRainFrequency;
float mRainEntranceSpeed;
int mRainMaxRaindrops;
};
struct MoonState
@ -216,6 +224,8 @@ namespace MWRender
osg::ref_ptr<osg::Group> mRainNode;
osg::ref_ptr<osgParticle::ParticleSystem> mRainParticleSystem;
osg::ref_ptr<osgParticle::BoxPlacer> mPlacer;
osg::ref_ptr<RainCounter> mCounter;
osg::ref_ptr<RainShooter> mRainShooter;
osg::ref_ptr<RainFader> mRainFader;
@ -249,7 +259,11 @@ namespace MWRender
bool mRainEnabled;
std::string mRainEffect;
float mRainSpeed;
float mRainFrequency;
float mRainDiameter;
float mRainMinHeight;
float mRainMaxHeight;
float mRainEntranceSpeed;
int mRainMaxRaindrops;
float mWindSpeed;
bool mEnabled;

@ -527,7 +527,7 @@ namespace MWScript
MWMechanics::MagicEffects effects = stats.getSpells().getMagicEffects();
effects += stats.getActiveSpells().getMagicEffects();
if (ptr.getClass().isNpc())
if (ptr.getClass().hasInventoryStore(ptr))
{
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
effects += store.getMagicEffects();

@ -184,6 +184,19 @@ void MWWorld::Cells::rest (double hours)
}
}
void MWWorld::Cells::recharge (float duration)
{
for (auto &interior : mInteriors)
{
interior.second.recharge(duration);
}
for (auto &exterior : mExteriors)
{
exterior.second.recharge(duration);
}
}
MWWorld::CellStore *MWWorld::Cells::getCell (const ESM::CellId& id)
{
if (id.mPaged)

@ -72,7 +72,9 @@ namespace MWWorld
/// @note name must be lower case
Ptr getPtr (const std::string& name);
void rest (double hours);
void recharge (float duration);
/// Get all Ptrs referencing \a name in exterior cells
/// @note Due to the current implementation of getPtr this only supports one Ptr per cell.

@ -34,6 +34,7 @@
#include "../mwbase/world.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/recharge.hpp"
#include "ptr.hpp"
#include "esmstore.hpp"
@ -385,6 +386,7 @@ namespace MWWorld
void CellStore::updateMergedRefs()
{
mMergedRefs.clear();
mRechargingItemsUpToDate = false;
MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell);
forEachInternal(visitor);
visitor.merge();
@ -419,7 +421,7 @@ namespace MWWorld
}
CellStore::CellStore (const ESM::Cell *cell, const MWWorld::ESMStore& esmStore, std::vector<ESM::ESMReader>& readerList)
: mStore(esmStore), mReader(readerList), mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0)
: mStore(esmStore), mReader(readerList), mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0), mRechargingItemsUpToDate(false)
{
mWaterLevel = cell->mWater;
}
@ -1178,6 +1180,42 @@ namespace MWWorld
}
}
void CellStore::recharge(float duration)
{
if (duration <= 0)
return;
if (mState == State_Loaded)
{
for (CellRefList<ESM::Creature>::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it)
{
Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
{
ptr.getClass().getContainerStore(ptr).rechargeItems(duration);
}
}
for (CellRefList<ESM::NPC>::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it)
{
Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
{
ptr.getClass().getContainerStore(ptr).rechargeItems(duration);
}
}
for (CellRefList<ESM::Container>::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it)
{
Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getRefData().getCount() > 0)
{
ptr.getClass().getContainerStore(ptr).rechargeItems(duration);
}
}
rechargeItems(duration);
}
}
void CellStore::respawn()
{
if (mState == State_Loaded)
@ -1213,4 +1251,73 @@ namespace MWWorld
}
}
}
void MWWorld::CellStore::rechargeItems(float duration)
{
if (!mRechargingItemsUpToDate)
{
updateRechargingItems();
mRechargingItemsUpToDate = true;
}
for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it)
{
MWMechanics::rechargeItem(it->first, it->second, duration);
}
}
void MWWorld::CellStore::updateRechargingItems()
{
mRechargingItems.clear();
for (CellRefList<ESM::Weapon>::List::iterator it (mWeapons.mList.begin()); it!=mWeapons.mList.end(); ++it)
{
Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
{
checkItem(ptr);
}
}
for (CellRefList<ESM::Armor>::List::iterator it (mArmors.mList.begin()); it!=mArmors.mList.end(); ++it)
{
Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
{
checkItem(ptr);
}
}
for (CellRefList<ESM::Clothing>::List::iterator it (mClothes.mList.begin()); it!=mClothes.mList.end(); ++it)
{
Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
{
checkItem(ptr);
}
}
for (CellRefList<ESM::Book>::List::iterator it (mBooks.mList.begin()); it!=mBooks.mList.end(); ++it)
{
Ptr ptr = getCurrentPtr(&*it);
if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
{
checkItem(ptr);
}
}
}
void MWWorld::CellStore::checkItem(Ptr ptr)
{
if (ptr.getClass().getEnchantment(ptr).empty())
return;
std::string enchantmentId = ptr.getClass().getEnchantment(ptr);
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().search(enchantmentId);
if (!enchantment)
{
Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << ptr.getCellRef().getRefId();
return;
}
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed
|| enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
mRechargingItems.emplace_back(ptr.getBase(), static_cast<float>(enchantment->mData.mCharge));
}
}

@ -118,6 +118,16 @@ namespace MWWorld
/// Repopulate mMergedRefs.
void updateMergedRefs();
// (item, max charge)
typedef std::vector<std::pair<LiveCellRefBase*, float> > TRechargingItems;
TRechargingItems mRechargingItems;
bool mRechargingItemsUpToDate;
void updateRechargingItems();
void rechargeItems(float duration);
void checkItem(Ptr ptr);
// helper function for forEachInternal
template<class Visitor, class List>
bool forEachImp (Visitor& visitor, List& list)
@ -184,6 +194,7 @@ namespace MWWorld
MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo);
void rest(double hours);
void recharge(float duration);
/// Make a copy of the given object and insert it into this cell.
/// @note If you get a linker error here, this means the given type can not be inserted into a cell.

@ -25,6 +25,7 @@
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/levelledlist.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/recharge.hpp"
#include "manualref.hpp"
#include "refdata.hpp"
@ -126,7 +127,11 @@ void MWWorld::ContainerStore::storeStates (const CellRefList<T>& collection,
const std::string MWWorld::ContainerStore::sGoldId = "gold_001";
MWWorld::ContainerStore::ContainerStore() : mListener(nullptr), mCachedWeight (0), mWeightUpToDate (false) {}
MWWorld::ContainerStore::ContainerStore()
: mListener(nullptr)
, mRechargingItemsUpToDate(false)
, mCachedWeight (0)
, mWeightUpToDate (false) {}
MWWorld::ContainerStore::~ContainerStore() {}
@ -272,7 +277,6 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2)
}
return ptr1 != ptr2 // an item never stacks onto itself
&& ptr1.getCellRef().getOwner() == ptr2.getCellRef().getOwner()
&& ptr1.getCellRef().getSoul() == ptr2.getCellRef().getSoul()
&& ptr1.getClass().getRemainingUsageTime(ptr1) == ptr2.getClass().getRemainingUsageTime(ptr2)
@ -290,30 +294,14 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2)
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string &id, int count, const Ptr &actorPtr)
{
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count);
return add(ref.getPtr(), count, actorPtr, true);
return add(ref.getPtr(), count, actorPtr);
}
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner)
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr)
{
Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
MWWorld::ContainerStoreIterator it = end();
// HACK: Set owner on the original item, then reset it after we have copied it
// If we set the owner on the copied item, it would not stack correctly...
std::string oldOwner = itemPtr.getCellRef().getOwner();
if (!setOwner || actorPtr == MWMechanics::getPlayer()) // No point in setting owner to the player - NPCs will not respect this anyway
{
itemPtr.getCellRef().setOwner("");
}
else
{
itemPtr.getCellRef().setOwner(actorPtr.getCellRef().getRefId());
}
it = addImp(itemPtr, count);
itemPtr.getCellRef().setOwner(oldOwner);
MWWorld::ContainerStoreIterator it = addImp(itemPtr, count);
// The copy of the original item we just made
MWWorld::Ptr item = *it;
@ -354,7 +342,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
pos.pos[2] = 0;
item.getCellRef().setPosition(pos);
// reset ownership stuff, owner was already handled above
// We do not need to store owners for items in container stores - we do not use it anyway.
item.getCellRef().setOwner("");
item.getCellRef().resetGlobalVariable();
item.getCellRef().setFaction("");
item.getCellRef().setFactionRank(-1);
@ -472,6 +461,46 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Cons
return it;
}
void MWWorld::ContainerStore::rechargeItems(float duration)
{
if (!mRechargingItemsUpToDate)
{
updateRechargingItems();
mRechargingItemsUpToDate = true;
}
for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it)
{
if (!MWMechanics::rechargeItem(*it->first, it->second, duration))
continue;
// attempt to restack when fully recharged
if (it->first->getCellRef().getEnchantmentCharge() == it->second)
it->first = restack(*it->first);
}
}
void MWWorld::ContainerStore::updateRechargingItems()
{
mRechargingItems.clear();
for (ContainerStoreIterator it = begin(); it != end(); ++it)
{
const std::string& enchantmentId = it->getClass().getEnchantment(*it);
if (!enchantmentId.empty())
{
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().search(enchantmentId);
if (!enchantment)
{
Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << it->getCellRef().getRefId();
continue;
}
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed
|| enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
mRechargingItems.emplace_back(it, static_cast<float>(enchantment->mData.mCharge));
}
}
}
int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor)
{
int toRemove = count;
@ -725,6 +754,7 @@ void MWWorld::ContainerStore::clear()
void MWWorld::ContainerStore::flagAsModified()
{
mWeightUpToDate = false;
mRechargingItemsUpToDate = false;
}
float MWWorld::ContainerStore::getWeight() const

@ -71,6 +71,12 @@ namespace MWWorld
protected:
ContainerStoreListener* mListener;
// (item, max charge)
typedef std::vector<std::pair<ContainerStoreIterator, float> > TRechargingItems;
TRechargingItems mRechargingItems;
bool mRechargingItemsUpToDate;
private:
MWWorld::CellRefList<ESM::Potion> potions;
@ -108,6 +114,7 @@ namespace MWWorld
ESM::InventoryState& inventory, int& index,
bool equipable = false) const;
void updateRechargingItems();
virtual void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const;
@ -131,7 +138,7 @@ namespace MWWorld
bool hasVisibleItems() const;
virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false);
virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr);
///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)
///
/// \note The item pointed to is not required to exist beyond this function call.
@ -156,6 +163,9 @@ namespace MWWorld
///
/// @return the number of items actually removed
void rechargeItems (float duration);
///< Restore charge on enchanted items. Note this should only be done for the player.
ContainerStoreIterator unstack (const Ptr& ptr, const Ptr& container, int count = 1);
///< Unstack an item in this container. The item's count will be set to count, then a new stack will be added with (origCount-count).
///

@ -211,7 +211,7 @@ void ESMStore::validate()
const ESM::MagicEffect* mgef = mMagicEffects.search(iter->mEffectID);
if (!mgef)
{
Log(Debug::Verbose) << "Spell '" << spell.mId << "' has an an invalid effect (index " << iter->mEffectID << ") present, dropping it.";
Log(Debug::Verbose) << "Spell '" << spell.mId << "' has an invalid effect (index " << iter->mEffectID << ") present. Dropping the effect.";
iter = spell.mEffects.mList.erase(iter);
changed = true;
continue;
@ -223,7 +223,7 @@ void ESMStore::validate()
{
iter->mAttribute = -1;
Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) <<
" effect of spell '" << spell.mId << "' has an attribute argument present, dropping it.";
" effect of spell '" << spell.mId << "' has an attribute argument present. Dropping the argument.";
changed = true;
}
}
@ -233,7 +233,7 @@ void ESMStore::validate()
{
iter->mSkill = -1;
Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) <<
" effect of spell '" << spell.mId << "' has a skill argument present, dropping it.";
" effect of spell '" << spell.mId << "' has a skill argument present. Dropping the argument.";
changed = true;
}
}
@ -242,7 +242,7 @@ void ESMStore::validate()
iter->mSkill = -1;
iter->mAttribute = -1;
Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) <<
" effect of spell '" << spell.mId << "' has argument(s) present, dropping them.";
" effect of spell '" << spell.mId << "' has argument(s) present. Dropping the argument(s).";
changed = true;
}

@ -114,7 +114,6 @@ MWWorld::InventoryStore::InventoryStore()
, mUpdatesEnabled (true)
, mFirstAutoEquip(true)
, mSelectedEnchantItem(end())
, mRechargingItemsUpToDate(false)
{
initSlots (mSlots);
}
@ -127,7 +126,6 @@ MWWorld::InventoryStore::InventoryStore (const InventoryStore& store)
, mFirstAutoEquip(store.mFirstAutoEquip)
, mPermanentMagicEffectMagnitudes(store.mPermanentMagicEffectMagnitudes)
, mSelectedEnchantItem(end())
, mRechargingItemsUpToDate(false)
{
copySlots (store);
}
@ -146,11 +144,11 @@ MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStor
return *this;
}
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner)
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, int count, const Ptr& actorPtr)
{
const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, setOwner);
const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr);
// Auto-equip items if an armor/clothing or weapon item is added, but not for the player nor werewolves
// Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves
if (actorPtr != MWMechanics::getPlayer()
&& actorPtr.getClass().isNpc() && !actorPtr.getClass().getNpcStats(actorPtr).isWerewolf())
{
@ -223,22 +221,29 @@ MWWorld::ConstContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot)
return findSlot (slot);
}
bool MWWorld::InventoryStore::canActorAutoEquip(const MWWorld::Ptr& actor, const MWWorld::Ptr& item)
bool MWWorld::InventoryStore::canActorAutoEquip(const MWWorld::Ptr& actor)
{
if (!Settings::Manager::getBool("prevent merchant equipping", "Game"))
// Treat player as non-trader indifferently from service flags.
if (actor == MWMechanics::getPlayer())
return true;
// Only autoEquip if we are the original owner of the item.
// This stops merchants from auto equipping anything you sell to them.
// ...unless this is a companion, he should always equip items given to him.
if (!Misc::StringUtils::ciEqual(item.getCellRef().getOwner(), actor.getCellRef().getRefId()) &&
(actor.getClass().getScript(actor).empty() ||
!actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion"))
&& !actor.getClass().getCreatureStats(actor).isDead() // Corpses can be dressed up by the player as desired
)
{
return false;
}
static const bool prevent = Settings::Manager::getBool("prevent merchant equipping", "Game");
if (!prevent)
return true;
// Corpses can be dressed up by the player as desired.
if (actor.getClass().getCreatureStats(actor).isDead())
return true;
// Companions can autoequip items.
if (!actor.getClass().getScript(actor).empty() &&
actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion"))
return true;
// If the actor is trader, he can auto-equip items only during initial auto-equipping
int services = actor.getClass().getServices(actor);
if (services & ESM::NPC::AllItems)
return mFirstAutoEquip;
return true;
}
@ -352,9 +357,6 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots
for (ContainerStoreIterator iter(begin(ContainerStore::Type_Weapon)); iter!=end(); ++iter)
{
if (!canActorAutoEquip(actor, *iter))
continue;
const ESM::Weapon* esmWeapon = iter->get<ESM::Weapon>()->mBase;
if (MWMechanics::getWeaponType(esmWeapon->mData.mType)->mWeaponClass == ESM::WeaponType::Ammo)
@ -456,9 +458,6 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots&
{
Ptr test = *iter;
if (!canActorAutoEquip(actor, test))
continue;
switch(test.getClass().canBeEquipped (test, actor).first)
{
case 0:
@ -590,6 +589,9 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
End of tes3mp addition
*/
if (!canActorAutoEquip(actor))
return;
TSlots slots_;
initSlots (slots_);
@ -746,12 +748,6 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
mFirstAutoEquip = false;
}
void MWWorld::InventoryStore::flagAsModified()
{
ContainerStore::flagAsModified();
mRechargingItemsUpToDate = false;
}
bool MWWorld::InventoryStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const
{
bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2);
@ -994,57 +990,6 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito
}
}
void MWWorld::InventoryStore::updateRechargingItems()
{
mRechargingItems.clear();
for (ContainerStoreIterator it = begin(); it != end(); ++it)
{
if (it->getClass().getEnchantment(*it) != "")
{
std::string enchantmentId = it->getClass().getEnchantment(*it);
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().search(
enchantmentId);
if (!enchantment)
{
Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << it->getCellRef().getRefId();
continue;
}
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed
|| enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
mRechargingItems.push_back(std::make_pair(it, static_cast<float>(enchantment->mData.mCharge)));
}
}
}
void MWWorld::InventoryStore::rechargeItems(float duration)
{
if (!mRechargingItemsUpToDate)
{
updateRechargingItems();
mRechargingItemsUpToDate = true;
}
for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it)
{
if (it->first->getCellRef().getEnchantmentCharge() == -1
|| it->first->getCellRef().getEnchantmentCharge() == it->second)
continue;
static float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"fMagicItemRechargePerSecond")->mValue.getFloat();
if (it->first->getCellRef().getEnchantmentCharge() <= it->second)
{
it->first->getCellRef().setEnchantmentCharge(std::min (it->first->getCellRef().getEnchantmentCharge() + fMagicItemRechargePerSecond * duration,
it->second));
// attempt to restack when fully recharged
if (it->first->getCellRef().getEnchantmentCharge() == it->second)
it->first = restack(*it->first);
}
}
}
void MWWorld::InventoryStore::purgeEffect(short effectId)
{
for (TSlots::const_iterator it = mSlots.begin(); it != mSlots.end(); ++it)

@ -101,25 +101,17 @@ namespace MWWorld
// selected magic item (for using enchantments of type "Cast once" or "Cast when used")
ContainerStoreIterator mSelectedEnchantItem;
// (item, max charge)
typedef std::vector<std::pair<ContainerStoreIterator, float> > TRechargingItems;
TRechargingItems mRechargingItems;
bool mRechargingItemsUpToDate;
void copySlots (const InventoryStore& store);
void initSlots (TSlots& slots_);
void updateMagicEffects(const Ptr& actor);
void updateRechargingItems();
void fireEquipmentChangedEvent(const Ptr& actor);
virtual void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const;
virtual void readEquipmentState (const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory);
bool canActorAutoEquip(const MWWorld::Ptr& actor, const MWWorld::Ptr& item);
ContainerStoreIterator findSlot (int slot) const;
public:
@ -132,7 +124,7 @@ namespace MWWorld
virtual InventoryStore* clone() { return new InventoryStore(*this); }
virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false);
virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr);
///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)
/// Auto-equip items if specific conditions are fulfilled (see the implementation).
///
@ -168,13 +160,11 @@ namespace MWWorld
void autoEquip (const MWWorld::Ptr& actor);
///< Auto equip items according to stats and item value.
bool canActorAutoEquip(const MWWorld::Ptr& actor);
const MWMechanics::MagicEffects& getMagicEffects() const;
///< Return magic effects from worn items.
virtual void flagAsModified();
///< \attention This function is internal to the world model and should not be called from
/// outside.
virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const;
///< @return true if the two specified objects can stack with each other
@ -216,9 +206,6 @@ namespace MWWorld
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor);
void rechargeItems (float duration);
///< Restore charge on enchanted items. Note this should only be done for the player.
void purgeEffect (short effectId);
///< Remove a magic effect

@ -160,7 +160,12 @@ Weather::Weather(const std::string& name,
, mGlareView(Fallback::Map::getFloat("Weather_" + name + "_Glare_View"))
, mIsStorm(mWindSpeed > stormWindSpeed)
, mRainSpeed(rainSpeed)
, mRainFrequency(Fallback::Map::getFloat("Weather_" + name + "_Rain_Entrance_Speed"))
, mRainEntranceSpeed(Fallback::Map::getFloat("Weather_" + name + "_Rain_Entrance_Speed"))
, mRainMaxRaindrops(Fallback::Map::getFloat("Weather_" + name + "_Max_Raindrops"))
, mRainDiameter(Fallback::Map::getFloat("Weather_" + name + "_Rain_Diameter"))
, mRainThreshold(Fallback::Map::getFloat("Weather_" + name + "_Rain_Threshold"))
, mRainMinHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Min"))
, mRainMaxHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Max"))
, mParticleEffect(particleEffect)
, mRainEffect(Fallback::Map::getBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "")
, mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta"))
@ -191,15 +196,6 @@ Weather::Weather(const std::string& name,
if (Misc::StringUtils::ciEqual(mAmbientLoopSoundID, "None"))
mAmbientLoopSoundID.clear();
/*
Unhandled:
Rain Diameter=600 ?
Rain Height Min=200 ?
Rain Height Max=700 ?
Rain Threshold=0.6 ?
Max Raindrops=650 ?
*/
}
float Weather::transitionDelta() const
@ -551,6 +547,8 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, MWWorld::E
, mMasser("Masser")
, mSecunda("Secunda")
, mWindSpeed(0.f)
, mCurrentWindSpeed(0.f)
, mNextWindSpeed(0.f)
, mIsStorm(false)
, mPrecipitation(false)
, mStormDirection(0,1,0)
@ -747,6 +745,40 @@ void WeatherManager::setRegionWeather(const std::string& region, const int curre
End of tes3mp addition
*/
osg::Vec3f WeatherManager::calculateStormDirection()
{
osg::Vec3f playerPos (MWMechanics::getPlayer().getRefData().getPosition().asVec3());
playerPos.z() = 0;
osg::Vec3f redMountainPos (25000, 70000, 0);
osg::Vec3f stormDirection = (playerPos - redMountainPos);
stormDirection.normalize();
return stormDirection;
}
float WeatherManager::calculateWindSpeed(int weatherId, float currentSpeed)
{
float targetSpeed = std::min(8.0f * mWeatherSettings[weatherId].mWindSpeed, 70.f);
if (currentSpeed == 0.f)
currentSpeed = targetSpeed;
float multiplier = mWeatherSettings[weatherId].mRainEffect.empty() ? 1.f : 0.5f;
float updatedSpeed = (Misc::Rng::rollClosedProbability() - 0.5f) * multiplier * targetSpeed + currentSpeed;
if (updatedSpeed > 0.5f * targetSpeed && updatedSpeed < 2.f * targetSpeed)
{
currentSpeed = updatedSpeed;
}
// Take in account direction to the Red Mountain, when needed
if (weatherId == 6 || weatherId == 7)
{
currentSpeed = (calculateStormDirection() * currentSpeed).length();
}
return currentSpeed;
}
void WeatherManager::update(float duration, bool paused, const TimeStamp& time, bool isExterior)
{
MWWorld::ConstPtr player = MWMechanics::getPlayer();
@ -779,12 +811,21 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time,
{
mRendering.setSkyEnabled(false);
stopSounds();
mWindSpeed = 0.f;
mCurrentWindSpeed = 0.f;
mNextWindSpeed = 0.f;
return;
}
calculateWeatherResult(time.getHour(), duration, paused);
mWindSpeed = mResult.mWindSpeed;
if (!paused)
{
mWindSpeed = mResult.mWindSpeed;
mCurrentWindSpeed = mResult.mCurrentWindSpeed;
mNextWindSpeed = mResult.mNextWindSpeed;
}
mIsStorm = mResult.mIsStorm;
// For some reason Ash Storm is not considered as a precipitation weather in game
@ -793,11 +834,7 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time,
if (mIsStorm)
{
osg::Vec3f playerPos (player.getRefData().getPosition().asVec3());
playerPos.z() = 0;
osg::Vec3f redMountainPos (25000, 70000, 0);
mStormDirection = (playerPos - redMountainPos);
mStormDirection.normalize();
mStormDirection = calculateStormDirection();
mRendering.getSkyManager()->setStormDirection(mStormDirection);
}
@ -1320,7 +1357,9 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam
mResult.mCloudTexture = current.mCloudTexture;
mResult.mCloudBlendFactor = 0;
mResult.mWindSpeed = current.mWindSpeed;
mResult.mNextWindSpeed = 0;
mResult.mWindSpeed = mResult.mCurrentWindSpeed = calculateWindSpeed(weatherID, mWindSpeed);
mResult.mCloudSpeed = current.mCloudSpeed;
mResult.mGlareView = current.mGlareView;
mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID;
@ -1330,7 +1369,11 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam
mResult.mIsStorm = current.mIsStorm;
mResult.mRainSpeed = current.mRainSpeed;
mResult.mRainFrequency = current.mRainFrequency;
mResult.mRainEntranceSpeed = current.mRainEntranceSpeed;
mResult.mRainDiameter = current.mRainDiameter;
mResult.mRainMinHeight = current.mRainMinHeight;
mResult.mRainMaxHeight = current.mRainMaxHeight;
mResult.mRainMaxRaindrops = current.mRainMaxRaindrops;
mResult.mParticleEffect = current.mParticleEffect;
mResult.mRainEffect = current.mRainEffect;
@ -1404,23 +1447,35 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const
mResult.mFogDepth = lerp(current.mFogDepth, other.mFogDepth, factor);
mResult.mDLFogFactor = lerp(current.mDLFogFactor, other.mDLFogFactor, factor);
mResult.mDLFogOffset = lerp(current.mDLFogOffset, other.mDLFogOffset, factor);
mResult.mWindSpeed = lerp(current.mWindSpeed, other.mWindSpeed, factor);
mResult.mCurrentWindSpeed = calculateWindSpeed(mCurrentWeather, mCurrentWindSpeed);
mResult.mNextWindSpeed = calculateWindSpeed(mNextWeather, mNextWindSpeed);
mResult.mWindSpeed = lerp(mResult.mCurrentWindSpeed, mResult.mNextWindSpeed, factor);
mResult.mCloudSpeed = lerp(current.mCloudSpeed, other.mCloudSpeed, factor);
mResult.mGlareView = lerp(current.mGlareView, other.mGlareView, factor);
mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor);
mResult.mNight = current.mNight;
if(factor < 0.5)
float threshold = mWeatherSettings[mNextWeather].mRainThreshold;
if (threshold <= 0)
threshold = 0.5f;
if(factor < threshold)
{
mResult.mIsStorm = current.mIsStorm;
mResult.mParticleEffect = current.mParticleEffect;
mResult.mRainEffect = current.mRainEffect;
mResult.mRainSpeed = current.mRainSpeed;
mResult.mRainFrequency = current.mRainFrequency;
mResult.mAmbientSoundVolume = 1-(factor*2);
mResult.mRainEntranceSpeed = current.mRainEntranceSpeed;
mResult.mAmbientSoundVolume = 1 - factor / threshold;
mResult.mEffectFade = mResult.mAmbientSoundVolume;
mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID;
mResult.mRainDiameter = current.mRainDiameter;
mResult.mRainMinHeight = current.mRainMinHeight;
mResult.mRainMaxHeight = current.mRainMaxHeight;
mResult.mRainMaxRaindrops = current.mRainMaxRaindrops;
}
else
{
@ -1428,10 +1483,15 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const
mResult.mParticleEffect = other.mParticleEffect;
mResult.mRainEffect = other.mRainEffect;
mResult.mRainSpeed = other.mRainSpeed;
mResult.mRainFrequency = other.mRainFrequency;
mResult.mAmbientSoundVolume = 2*(factor-0.5f);
mResult.mRainEntranceSpeed = other.mRainEntranceSpeed;
mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold);
mResult.mEffectFade = mResult.mAmbientSoundVolume;
mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID;
mResult.mRainDiameter = other.mRainDiameter;
mResult.mRainMinHeight = other.mRainMinHeight;
mResult.mRainMaxHeight = other.mRainMaxHeight;
mResult.mRainMaxRaindrops = other.mRainMaxRaindrops;
}
}

@ -170,7 +170,20 @@ namespace MWWorld
float mRainSpeed;
// How often does a new rain mesh spawn?
float mRainFrequency;
float mRainEntranceSpeed;
// Maximum count of rain particles
int mRainMaxRaindrops;
// Radius of rain effect
float mRainDiameter;
// Transition threshold to spawn rain
float mRainThreshold;
// Height of rain particles spawn
float mRainMinHeight;
float mRainMaxHeight;
std::string mParticleEffect;
@ -370,6 +383,8 @@ namespace MWWorld
MoonModel mSecunda;
float mWindSpeed;
float mCurrentWindSpeed;
float mNextWindSpeed;
bool mIsStorm;
bool mPrecipitation;
osg::Vec3f mStormDirection;
@ -411,6 +426,7 @@ namespace MWWorld
bool updateWeatherRegion(const std::string& playerRegion);
void updateWeatherTransitions(const float elapsedRealSeconds);
void forceWeather(const int weatherID);
osg::Vec3f calculateStormDirection();
bool inTransition();
void addWeatherTransition(const int weatherID);
@ -418,6 +434,7 @@ namespace MWWorld
void calculateWeatherResult(const float gameHour, const float elapsedSeconds, const bool isPaused);
void calculateResult(const int weatherID, const float gameHour);
void calculateTransitionResult(const float factor, const float gameHour);
float calculateWindSpeed(int weatherId, float currentSpeed);
};
}

@ -971,7 +971,17 @@ namespace MWWorld
void World::advanceTime (double hours, bool incremental)
{
MWBase::Environment::get().getMechanicsManager()->advanceTime(static_cast<float>(hours * 3600));
if (!incremental)
{
// When we fast-forward time, we should recharge magic items
// in all loaded cells, using game world time
float duration = hours * 3600;
const float timeScaleFactor = getTimeScaleFactor();
if (timeScaleFactor != 0.0f)
duration /= timeScaleFactor;
rechargeItems(duration, false);
}
mWeatherManager->advanceTime (hours, incremental);
@ -3762,7 +3772,6 @@ namespace MWWorld
closestDistance = distance;
closestMarker = marker;
}
}
return closestMarker;
@ -3773,6 +3782,22 @@ namespace MWWorld
mCells.rest(hours);
}
void World::rechargeItems(double duration, bool activeOnly)
{
MWWorld::Ptr player = getPlayerPtr();
player.getClass().getInventoryStore(player).rechargeItems(duration);
if (activeOnly)
{
for (auto &cell : mWorldScene->getActiveCells())
{
cell->recharge(duration);
}
}
else
mCells.recharge(duration);
}
void World::teleportToClosestMarker (const MWWorld::Ptr& ptr,
const std::string& id)
{

@ -752,6 +752,7 @@ namespace MWWorld
///< check if the player is allowed to rest
void rest(double hours) override;
void rechargeItems(double duration, bool activeOnly) override;
/// \todo Probably shouldn't be here
MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) override;

Loading…
Cancel
Save