Merge pull request #113 from OpenMW/master

Add OpenMW commits up to 17 Dec
coverity_scan^2
David Cernat 8 years ago committed by GitHub
commit 973db7c78a

@ -1,6 +1,6 @@
os: os:
- linux - linux
- osx # - osx
osx_image: xcode7.2 osx_image: xcode7.2
language: cpp language: cpp
sudo: required sudo: required

@ -23,6 +23,7 @@ Programmers
Alexander Olofsson (Ace) Alexander Olofsson (Ace)
Allofich Allofich
AnyOldName3 AnyOldName3
Aussiemon
Austin Salgat (Salgat) Austin Salgat (Salgat)
Artem Kotsynyak (greye) Artem Kotsynyak (greye)
artemutin artemutin
@ -74,8 +75,10 @@ Programmers
Karl-Felix Glatzer (k1ll) Karl-Felix Glatzer (k1ll)
Kevin Poitra (PuppyKevin) Kevin Poitra (PuppyKevin)
Koncord Koncord
Kurnevsky Evgeny (kurnevsky)
Lars Söderberg (Lazaroth) Lars Söderberg (Lazaroth)
lazydev lazydev
Leon Krieg (lkrieg)
Leon Saunders (emoose) Leon Saunders (emoose)
lohikaarme lohikaarme
Lukasz Gromanowski (lgro) Lukasz Gromanowski (lgro)

@ -13,7 +13,7 @@ namespace ESM
namespace ESSImport namespace ESSImport
{ {
/// Local variable assigments for a running script /// Local variable assignments for a running script
struct SCRI struct SCRI
{ {
std::string mScript; std::string mScript;

@ -29,7 +29,7 @@ namespace CSMPrefs
enum SecondaryMode enum SecondaryMode
{ {
SM_Replace, ///< The secondary signal replaces the regular signal when the modifier is active SM_Replace, ///< The secondary signal replaces the regular signal when the modifier is active
SM_Detach, ///< The secondary signal is emitted independant of the regular signal, even if not active SM_Detach, ///< The secondary signal is emitted independent of the regular signal, even if not active
SM_Ignore ///< The secondary signal will not ever be emitted SM_Ignore ///< The secondary signal will not ever be emitted
}; };

@ -425,7 +425,7 @@ void CSMTools::ReferenceableCheckStage::creatureCheck (
//stats checks //stats checks
if (creature.mData.mLevel < 1) if (creature.mData.mLevel < 1)
messages.push_back (std::make_pair (id, creature.mId + " has non-postive level")); messages.push_back (std::make_pair (id, creature.mId + " has non-positive level"));
if (creature.mData.mStrength < 0) if (creature.mData.mStrength < 0)
messages.push_back (std::make_pair (id, creature.mId + " has negative strength")); messages.push_back (std::make_pair (id, creature.mId + " has negative strength"));
@ -659,7 +659,7 @@ void CSMTools::ReferenceableCheckStage::npcCheck (
{ {
if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0010 = autocalculated flag if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0010 = autocalculated flag
{ {
messages.push_back (std::make_pair (id, npc.mId + " mNpdtType or flags mismatch!")); //should not happend? messages.push_back (std::make_pair (id, npc.mId + " mNpdtType or flags mismatch!")); //should not happen?
return; return;
} }
@ -915,7 +915,7 @@ void CSMTools::ReferenceableCheckStage::inventoryListCheck(
id + " contains non-existing item (" + itemName + ")")); id + " contains non-existing item (" + itemName + ")"));
else else
{ {
// Needs to accomodate Containers, Creatures, and NPCs // Needs to accommodate containers, creatures, and NPCs
switch (localIndex.second) switch (localIndex.second)
{ {
case CSMWorld::UniversalId::Type_Potion: case CSMWorld::UniversalId::Type_Potion:

@ -330,6 +330,8 @@ namespace CSMWorld
{ ColumnId_WeatherName, "Type" }, { ColumnId_WeatherName, "Type" },
{ ColumnId_WeatherChance, "Percent Chance" }, { ColumnId_WeatherChance, "Percent Chance" },
{ ColumnId_Text, "Text" },
{ ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue1, "Use value 1" },
{ ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue2, "Use value 2" },
{ ColumnId_UseValue3, "Use value 3" }, { ColumnId_UseValue3, "Use value 3" },

@ -329,6 +329,8 @@ namespace CSMWorld
ColumnId_WeatherName = 295, ColumnId_WeatherName = 295,
ColumnId_WeatherChance = 296, ColumnId_WeatherChance = 296,
ColumnId_Text = 297,
// Allocated to a separate value range, so we don't get a collision should we ever need // Allocated to a separate value range, so we don't get a collision should we ever need
// to extend the number of use values. // to extend the number of use values.
ColumnId_UseValue1 = 0x10000, ColumnId_UseValue1 = 0x10000,

@ -65,7 +65,7 @@ namespace CSMWorld
void setRecord (const std::string& id, const RecordBase& record, void setRecord (const std::string& id, const RecordBase& record,
UniversalId::Type type = UniversalId::Type_None); UniversalId::Type type = UniversalId::Type_None);
///< Add record or overwrite existing recrod. ///< Add record or overwrite existing record.
const RecordBase& getRecord (const std::string& id) const; const RecordBase& getRecord (const std::string& id) const;

@ -198,12 +198,12 @@ QModelIndex CSMWorld::IdTree::parent (const QModelIndex& index) const
return QModelIndex(); return QModelIndex();
unsigned int id = index.internalId(); unsigned int id = index.internalId();
const std::pair<int, int>& adress(unfoldIndexAddress(id)); const std::pair<int, int>& address(unfoldIndexAddress(id));
if (adress.first >= this->rowCount() || adress.second >= this->columnCount()) if (address.first >= this->rowCount() || address.second >= this->columnCount())
throw "Parent index is not present in the model"; throw "Parent index is not present in the model";
return createIndex(adress.first, adress.second); return createIndex(address.first, address.second);
} }
unsigned int CSMWorld::IdTree::foldIndexAddress (const QModelIndex& index) const unsigned int CSMWorld::IdTree::foldIndexAddress (const QModelIndex& index) const

@ -8,9 +8,9 @@
/*! \brief /*! \brief
* Class for holding the model. Uses typical qt table abstraction/interface for granting access * Class for holding the model. Uses typical qt table abstraction/interface for granting access
* to the individiual fields of the records, Some records are holding nested data (for instance * to the individiual fields of the records, Some records are holding nested data (for instance
* inventory list of the npc). In casses like this, table model offers interface to access * inventory list of the npc). In cases like this, table model offers interface to access
* nested data in the qt way - that is specify parent. Since some of those nested data require * nested data in the qt way - that is specify parent. Since some of those nested data require
* multiple columns to represent informations, single int (default way to index model in the * multiple columns to represent information, single int (default way to index model in the
* qmodelindex) is not sufficiant. Therefore tablemodelindex class can hold two ints for the * qmodelindex) is not sufficiant. Therefore tablemodelindex class can hold two ints for the
* sake of indexing two dimensions of the table. This model does not support multiple levels of * sake of indexing two dimensions of the table. This model does not support multiple levels of
* the nested data. Vast majority of methods makes sense only for the top level data. * the nested data. Vast majority of methods makes sense only for the top level data.

@ -8,7 +8,7 @@
* Adapters acts as indirection layer, abstracting details of the record types (in the wrappers) from the higher levels of model. * Adapters acts as indirection layer, abstracting details of the record types (in the wrappers) from the higher levels of model.
* Please notice that nested adaptor uses helper classes for actually performing any actions. Different record types require different helpers (needs to be created in the subclass and then fetched via member function). * Please notice that nested adaptor uses helper classes for actually performing any actions. Different record types require different helpers (needs to be created in the subclass and then fetched via member function).
* *
* Important point: don't forget to make sure that getData on the nestedColumn returns true (otherwise code will not treat the index pointing to the column as having childs! * Important point: don't forget to make sure that getData on the nestedColumn returns true (otherwise code will not treat the index pointing to the column as having children!
*/ */
class QVariant; class QVariant;

@ -301,9 +301,9 @@ void CSMWorld::ArmorRefIdAdapter::setData (const RefIdColumn *column, RefIdData&
} }
CSMWorld::BookRefIdAdapter::BookRefIdAdapter (const EnchantableColumns& columns, CSMWorld::BookRefIdAdapter::BookRefIdAdapter (const EnchantableColumns& columns,
const RefIdColumn *scroll, const RefIdColumn *skill) const RefIdColumn *scroll, const RefIdColumn *skill, const RefIdColumn *text)
: EnchantableRefIdAdapter<ESM::Book> (UniversalId::Type_Book, columns), : EnchantableRefIdAdapter<ESM::Book> (UniversalId::Type_Book, columns),
mScroll (scroll), mSkill (skill) mScroll (scroll), mSkill (skill), mText (text)
{} {}
QVariant CSMWorld::BookRefIdAdapter::getData (const RefIdColumn *column, QVariant CSMWorld::BookRefIdAdapter::getData (const RefIdColumn *column,
@ -318,6 +318,9 @@ QVariant CSMWorld::BookRefIdAdapter::getData (const RefIdColumn *column,
if (column==mSkill) if (column==mSkill)
return record.get().mData.mSkillID; return record.get().mData.mSkillID;
if (column==mText)
return QString::fromUtf8 (record.get().mText.c_str());
return EnchantableRefIdAdapter<ESM::Book>::getData (column, data, index); return EnchantableRefIdAdapter<ESM::Book>::getData (column, data, index);
} }
@ -333,6 +336,8 @@ void CSMWorld::BookRefIdAdapter::setData (const RefIdColumn *column, RefIdData&
book.mData.mIsScroll = value.toInt(); book.mData.mIsScroll = value.toInt();
else if (column==mSkill) else if (column==mSkill)
book.mData.mSkillID = value.toInt(); book.mData.mSkillID = value.toInt();
else if (column==mText)
book.mText = value.toString().toUtf8().data();
else else
{ {
EnchantableRefIdAdapter<ESM::Book>::setData (column, data, index, value); EnchantableRefIdAdapter<ESM::Book>::setData (column, data, index, value);

@ -696,11 +696,12 @@ namespace CSMWorld
{ {
const RefIdColumn *mScroll; const RefIdColumn *mScroll;
const RefIdColumn *mSkill; const RefIdColumn *mSkill;
const RefIdColumn *mText;
public: public:
BookRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *scroll, BookRefIdAdapter (const EnchantableColumns& columns, const RefIdColumn *scroll,
const RefIdColumn *skill); const RefIdColumn *skill, const RefIdColumn *text);
virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
const; const;

@ -297,6 +297,9 @@ CSMWorld::RefIdCollection::RefIdCollection()
mColumns.push_back (RefIdColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); mColumns.push_back (RefIdColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute));
const RefIdColumn *attribute = &mColumns.back(); const RefIdColumn *attribute = &mColumns.back();
mColumns.push_back (RefIdColumn (Columns::ColumnId_Text, ColumnBase::Display_LongString));
const RefIdColumn *text = &mColumns.back();
mColumns.push_back (RefIdColumn (Columns::ColumnId_ClothingType, ColumnBase::Display_ClothingType)); mColumns.push_back (RefIdColumn (Columns::ColumnId_ClothingType, ColumnBase::Display_ClothingType));
const RefIdColumn *clothingType = &mColumns.back(); const RefIdColumn *clothingType = &mColumns.back();
@ -656,7 +659,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
mAdapters.insert (std::make_pair (UniversalId::Type_Armor, mAdapters.insert (std::make_pair (UniversalId::Type_Armor,
new ArmorRefIdAdapter (enchantableColumns, armorType, health, armor, partRef))); new ArmorRefIdAdapter (enchantableColumns, armorType, health, armor, partRef)));
mAdapters.insert (std::make_pair (UniversalId::Type_Book, mAdapters.insert (std::make_pair (UniversalId::Type_Book,
new BookRefIdAdapter (enchantableColumns, scroll, attribute))); new BookRefIdAdapter (enchantableColumns, scroll, attribute, text)));
mAdapters.insert (std::make_pair (UniversalId::Type_Clothing, mAdapters.insert (std::make_pair (UniversalId::Type_Clothing,
new ClothingRefIdAdapter (enchantableColumns, clothingType, partRef))); new ClothingRefIdAdapter (enchantableColumns, clothingType, partRef)));
mAdapters.insert (std::make_pair (UniversalId::Type_Container, mAdapters.insert (std::make_pair (UniversalId::Type_Container,

@ -50,7 +50,7 @@ namespace CSVRender
updateCellData(mData.getCells().getRecord(cellIndex)); updateCellData(mData.getCells().getRecord(cellIndex));
} }
// Keep water existance/height up to date // Keep water existence/height up to date
QAbstractItemModel* cells = mData.getTableModel(CSMWorld::UniversalId::Type_Cells); QAbstractItemModel* cells = mData.getTableModel(CSMWorld::UniversalId::Type_Cells);
connect(cells, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), connect(cells, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(cellDataChanged(const QModelIndex&, const QModelIndex&))); this, SLOT(cellDataChanged(const QModelIndex&, const QModelIndex&)));

@ -516,7 +516,7 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint)
// Current coordinate // Current coordinate
int x, y; int x, y;
// Loop throught all the coordinates to add them to selection // Loop through all the coordinates to add them to selection
while (stream >> ignore1 >> ignore2 >> x >> y) while (stream >> ignore1 >> ignore2 >> x >> y)
selection.add (CSMWorld::CellCoordinates (x, y)); selection.add (CSMWorld::CellCoordinates (x, y));

@ -214,7 +214,7 @@ SceneWidget::SceneWidget(boost::shared_ptr<Resource::ResourceSystem> resourceSys
SceneWidget::~SceneWidget() SceneWidget::~SceneWidget()
{ {
// Since we're holding on to the scene templates past the existance of this graphics context, we'll need to manually release the created objects // Since we're holding on to the scene templates past the existence of this graphics context, we'll need to manually release the created objects
mResourceSystem->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); mResourceSystem->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
} }

@ -45,8 +45,8 @@ namespace CSVWorld
///< \note Non-existent cells are not listed. ///< \note Non-existent cells are not listed.
QModelIndexList getSelectedCells (bool existent = true, bool nonExistent = false) const; QModelIndexList getSelectedCells (bool existent = true, bool nonExistent = false) const;
///< \param existant Include existant cells. ///< \param existent Include existent cells.
/// \param nonExistant Include non-existant cells. /// \param nonExistent Include non-existent cells.
QModelIndexList getMissingRegionCells() const; QModelIndexList getMissingRegionCells() const;
///< Unselected cells within all regions that have at least one selected cell. ///< Unselected cells within all regions that have at least one selected cell.

@ -41,4 +41,4 @@
/// \namespace MWScript /// \namespace MWScript
/// \ingroup openmw /// \ingroup openmw
/// \brief MW-specific script extentions and integration of the script system into OpenMW /// \brief MW-specific script extensions and integration of the script system into OpenMW

@ -514,7 +514,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mEnvironment.setWindowManager (window); mEnvironment.setWindowManager (window);
// Create sound system // Create sound system
mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound)); mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mFallbackMap, mUseSound));
if (!mSkipMenu) if (!mSkipMenu)
{ {

@ -233,7 +233,7 @@ namespace MWBase
virtual void removeDialog(MWGui::Layout* dialog) = 0; virtual void removeDialog(MWGui::Layout* dialog) = 0;
///Gracefully attempts to exit the topmost GUI mode ///Gracefully attempts to exit the topmost GUI mode
/** No guarentee of actually closing the window **/ /** No guarantee of actually closing the window **/
virtual void exitCurrentGuiMode() = 0; virtual void exitCurrentGuiMode() = 0;
virtual void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0; virtual void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0;

@ -231,7 +231,7 @@ namespace MWClass
if(lockLevel!=0) if(lockLevel!=0)
ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, in positive ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, in positive
else else
ptr.getCellRef().setLockLevel(abs(ptr.getCellRef().getLockLevel())); //No locklevel given, just flip the origional one ptr.getCellRef().setLockLevel(abs(ptr.getCellRef().getLockLevel())); //No locklevel given, just flip the original one
} }
void Door::unlock (const MWWorld::Ptr& ptr) const void Door::unlock (const MWWorld::Ptr& ptr) const

@ -122,7 +122,7 @@ namespace MWClass
info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects); info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects);
// hide effects the player doesnt know about // hide effects the player doesn't know about
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
for (unsigned int i=0; i<info.effects.size(); ++i) for (unsigned int i=0; i<info.effects.size(); ++i)
info.effects[i].mKnown = MWMechanics::Alchemy::knownEffect(i, player); info.effects[i].mKnown = MWMechanics::Alchemy::knownEffect(i, player);

@ -174,7 +174,7 @@ namespace MWDialogue
executeScript (info->mResultScript); executeScript (info->mResultScript);
mLastTopic = Misc::StringUtils::lowerCase(it->mId); mLastTopic = Misc::StringUtils::lowerCase(it->mId);
// update topics again to accomodate changes resulting from executeScript // update topics again to accommodate changes resulting from executeScript
updateTopics(); updateTopics();
return; return;

@ -36,7 +36,6 @@ void DragAndDrop::startDrag (int index, SortFilterItemModel* sortModel, ItemMode
mSourceModel = sourceModel; mSourceModel = sourceModel;
mSourceView = sourceView; mSourceView = sourceView;
mSourceSortModel = sortModel; mSourceSortModel = sortModel;
mIsOnDragAndDrop = true;
// If picking up an item that isn't from the player's inventory, the item gets added to player inventory backend // If picking up an item that isn't from the player's inventory, the item gets added to player inventory backend
// immediately, even though it's still floating beneath the mouse cursor. A bit counterintuitive, // immediately, even though it's still floating beneath the mouse cursor. A bit counterintuitive,
@ -88,6 +87,8 @@ void DragAndDrop::startDrag (int index, SortFilterItemModel* sortModel, ItemMode
sourceView->update(); sourceView->update();
MWBase::Environment::get().getWindowManager()->setDragDrop(true); MWBase::Environment::get().getWindowManager()->setDragDrop(true);
mIsOnDragAndDrop = true;
} }
void DragAndDrop::drop(ItemModel *targetModel, ItemView *targetView) void DragAndDrop::drop(ItemModel *targetModel, ItemView *targetView)

@ -101,6 +101,8 @@ namespace MWGui
{ {
if (mFrame) if (mFrame)
mFrame->setImageTexture(""); mFrame->setImageTexture("");
if (mItemShadow)
mItemShadow->setImageTexture("");
mItem->setImageTexture(""); mItem->setImageTexture("");
mText->setCaption(""); mText->setCaption("");
return; return;

@ -517,8 +517,8 @@ namespace MWMechanics
if (magnitude > 0 && remainingTime > 0 && remainingTime < mDuration) if (magnitude > 0 && remainingTime > 0 && remainingTime < mDuration)
{ {
CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor);
effectTick(creatureStats, mActor, key, magnitude * remainingTime); if (effectTick(creatureStats, mActor, key, magnitude * remainingTime))
creatureStats.getMagicEffects().add(key, -magnitude); creatureStats.getMagicEffects().add(key, -magnitude);
} }
} }
}; };
@ -532,8 +532,10 @@ namespace MWMechanics
if (duration > 0) if (duration > 0)
{ {
// apply correct magnitude for tickable effects that have just expired, // Apply correct magnitude for tickable effects that have just expired,
// in case duration > remaining time of effect // in case duration > remaining time of effect.
// One case where this will happen is when the player uses the rest/wait command
// while there is a tickable effect active that should expire before the end of the rest/wait.
ExpiryVisitor visitor(ptr, duration); ExpiryVisitor visitor(ptr, duration);
creatureStats.getActiveSpells().visitEffectSources(visitor); creatureStats.getActiveSpells().visitEffectSources(visitor);
@ -1289,8 +1291,6 @@ namespace MWMechanics
stats.getActiveSpells().clear(); stats.getActiveSpells().clear();
calculateCreatureStatModifiers(iter->first, 0); calculateCreatureStatModifiers(iter->first, 0);
MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false);
if (cls.isEssential(iter->first)) if (cls.isEssential(iter->first))
MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}");
} }
@ -1308,6 +1308,11 @@ namespace MWMechanics
//player's death animation is over //player's death animation is over
MWBase::Environment::get().getStateManager()->askLoadRecent(); MWBase::Environment::get().getStateManager()->askLoadRecent();
} }
else
{
// NPC death animation is over, disable actor collision
MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false);
}
// Play Death Music if it was the player dying // Play Death Music if it was the player dying
if(iter->first == getPlayer()) if(iter->first == getPlayer())

@ -60,30 +60,30 @@ namespace MWMechanics
FleeState_RunToDestination FleeState_RunToDestination
}; };
FleeState mFleeState; FleeState mFleeState;
bool mFleeLOS; bool mLOS;
float mFleeUpdateLOSTimer; float mUpdateLOSTimer;
float mFleeBlindRunTimer; float mFleeBlindRunTimer;
ESM::Pathgrid::Point mFleeDest; ESM::Pathgrid::Point mFleeDest;
AiCombatStorage(): AiCombatStorage():
mAttackCooldown(0), mAttackCooldown(0.0f),
mTimerReact(AI_REACTION_TIME), mTimerReact(AI_REACTION_TIME),
mTimerCombatMove(0), mTimerCombatMove(0.0f),
mReadyToAttack(false), mReadyToAttack(false),
mAttack(false), mAttack(false),
mAttackRange(0), mAttackRange(0.0f),
mCombatMove(false), mCombatMove(false),
mLastTargetPos(0,0,0), mLastTargetPos(0,0,0),
mCell(NULL), mCell(NULL),
mCurrentAction(), mCurrentAction(),
mActionCooldown(0), mActionCooldown(0.0f),
mStrength(), mStrength(),
mForceNoShortcut(false), mForceNoShortcut(false),
mShortcutFailPos(), mShortcutFailPos(),
mMovement(), mMovement(),
mFleeState(FleeState_None), mFleeState(FleeState_None),
mFleeLOS(false), mLOS(false),
mFleeUpdateLOSTimer(0.0f), mUpdateLOSTimer(0.0f),
mFleeBlindRunTimer(0.0f) mFleeBlindRunTimer(0.0f)
{} {}
@ -157,7 +157,7 @@ namespace MWMechanics
* *
* TODO: * TODO:
* *
* Use the Observer Pattern to co-ordinate attacks, provide intelligence on * Use the observer pattern to coordinate attacks, provide intelligence on
* whether the target was hit, etc. * whether the target was hit, etc.
*/ */
@ -181,10 +181,14 @@ namespace MWMechanics
if (!storage.isFleeing()) if (!storage.isFleeing())
{ {
if (storage.mCurrentAction.get()) // need to wait to init action with it's attack range if (storage.mCurrentAction.get()) // need to wait to init action with its attack range
{ {
//Update every frame //Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame.
bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange); updateLOS(actor, target, duration, storage);
float targetReachedTolerance = 0.0f;
if (storage.mLOS)
targetReachedTolerance = storage.mAttackRange;
bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, targetReachedTolerance);
if (is_target_reached) storage.mReadyToAttack = true; if (is_target_reached) storage.mReadyToAttack = true;
} }
@ -283,7 +287,7 @@ namespace MWMechanics
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack); storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
if (storage.mReadyToAttack) if (storage.mReadyToAttack)
{ {
@ -309,18 +313,23 @@ namespace MWMechanics
} }
} }
void MWMechanics::AiCombat::updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage) void MWMechanics::AiCombat::updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage)
{ {
static const float LOS_UPDATE_DURATION = 0.5f; static const float LOS_UPDATE_DURATION = 0.5f;
static const float BLIND_RUN_DURATION = 1.0f; if (storage.mUpdateLOSTimer <= 0.f)
if (storage.mFleeUpdateLOSTimer <= 0.f)
{ {
storage.mFleeLOS = MWBase::Environment::get().getWorld()->getLOS(actor, target); storage.mLOS = MWBase::Environment::get().getWorld()->getLOS(actor, target);
storage.mFleeUpdateLOSTimer = LOS_UPDATE_DURATION; storage.mUpdateLOSTimer = LOS_UPDATE_DURATION;
} }
else else
storage.mFleeUpdateLOSTimer -= duration; storage.mUpdateLOSTimer -= duration;
}
void MWMechanics::AiCombat::updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage)
{
static const float BLIND_RUN_DURATION = 1.0f;
updateLOS(actor, target, duration, storage);
AiCombatStorage::FleeState& state = storage.mFleeState; AiCombatStorage::FleeState& state = storage.mFleeState;
switch (state) switch (state)
@ -332,7 +341,7 @@ namespace MWMechanics
{ {
float triggerDist = getMaxAttackDistance(target); float triggerDist = getMaxAttackDistance(target);
if (storage.mFleeLOS && if (storage.mLOS &&
(triggerDist >= 1000 || getDistanceMinusHalfExtents(actor, target) <= triggerDist)) (triggerDist >= 1000 || getDistanceMinusHalfExtents(actor, target) <= triggerDist))
{ {
const ESM::Pathgrid* pathgrid = const ESM::Pathgrid* pathgrid =
@ -399,7 +408,7 @@ namespace MWMechanics
static const float fFleeDistance = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fFleeDistance")->getFloat(); static const float fFleeDistance = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fFleeDistance")->getFloat();
float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length(); float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
if ((dist > fFleeDistance && !storage.mFleeLOS) if ((dist > fFleeDistance && !storage.mLOS)
|| pathTo(actor, storage.mFleeDest, duration)) || pathTo(actor, storage.mFleeDest, duration))
{ {
state = AiCombatStorage::FleeState_Idle; state = AiCombatStorage::FleeState_Idle;
@ -602,9 +611,6 @@ namespace MWMechanics
mMovement.mPosition[2] = 0; mMovement.mPosition[2] = 0;
mFleeState = FleeState_None; mFleeState = FleeState_None;
mFleeDest = ESM::Pathgrid::Point(0, 0, 0); mFleeDest = ESM::Pathgrid::Point(0, 0, 0);
mFleeLOS = false;
mFleeUpdateLOSTimer = 0.0f;
mFleeUpdateLOSTimer = 0.0f;
} }
bool AiCombatStorage::isFleeing() bool AiCombatStorage::isFleeing()

@ -61,6 +61,8 @@ namespace MWMechanics
void attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); void attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController);
void updateLOS(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage);
void updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage); void updateFleeing(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, AiCombatStorage& storage);
/// Transfer desired movement (from AiCombatStorage) to Actor /// Transfer desired movement (from AiCombatStorage) to Actor

@ -152,7 +152,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
if (dist > 450) if (dist > 450)
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run
else if (dist < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold else if (dist < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshold
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk
} }

@ -32,7 +32,7 @@ namespace MWMechanics
class AiPackage class AiPackage
{ {
public: public:
///Enumerates the various AITypes availible. ///Enumerates the various AITypes available
enum TypeId { enum TypeId {
TypeIdNone = -1, TypeIdNone = -1,
TypeIdWander = 0, TypeIdWander = 0,

@ -336,7 +336,7 @@ namespace MWMechanics
{ {
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition)); ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition));
// actor position is already in world co-ordinates // actor position is already in world coordinates
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
// don't take shortcuts for wandering // don't take shortcuts for wandering
@ -473,8 +473,8 @@ namespace MWMechanics
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor,
float duration, AiWanderStorage& storage, ESM::Position& pos) float duration, AiWanderStorage& storage, ESM::Position& pos)
{ {
// Are we there yet? // Is there no destination or are we there yet?
if (pathTo(actor, mPathFinder.getPath().back(), duration, DESTINATION_TOLERANCE)) if ((!mPathFinder.isPathConstructed()) || pathTo(actor, mPathFinder.getPath().back(), duration, DESTINATION_TOLERANCE))
{ {
stopWalking(actor, storage); stopWalking(actor, storage);
storage.setState(Wander_ChooseAction); storage.setState(Wander_ChooseAction);
@ -649,7 +649,7 @@ namespace MWMechanics
ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]); ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]);
ToWorldCoordinates(dest, storage.mCell->getCell()); ToWorldCoordinates(dest, storage.mCell->getCell());
// actor position is already in world co-ordinates // actor position is already in world coordinates
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos)); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos));
// don't take shortcuts for wandering // don't take shortcuts for wandering
@ -693,8 +693,8 @@ namespace MWMechanics
ESM::Pathgrid::Point pt = paths.back(); ESM::Pathgrid::Point pt = paths.back();
for(unsigned int j = 0; j < nodes.size(); j++) for(unsigned int j = 0; j < nodes.size(); j++)
{ {
// FIXME: doesn't hadle a door with the same X/Y // FIXME: doesn't handle a door with the same X/Y
// co-ordinates but with a different Z // coordinates but with a different Z
if(nodes[j].mX == pt.mX && nodes[j].mY == pt.mY) if(nodes[j].mX == pt.mX && nodes[j].mY == pt.mY)
{ {
nodes.erase(nodes.begin() + j); nodes.erase(nodes.begin() + j);
@ -828,7 +828,7 @@ namespace MWMechanics
// ... pathgrids don't usually include water, so swimmers ignore them // ... pathgrids don't usually include water, so swimmers ignore them
if (mDistance && storage.mCanWanderAlongPathGrid && !actor.getClass().isPureWaterCreature(actor)) if (mDistance && storage.mCanWanderAlongPathGrid && !actor.getClass().isPureWaterCreature(actor))
{ {
// get NPC's position in local (i.e. cell) co-ordinates // get NPC's position in local (i.e. cell) coordinates
osg::Vec3f npcPos(mInitialActorPosition); osg::Vec3f npcPos(mInitialActorPosition);
CoordinateConverter(cell).toLocal(npcPos); CoordinateConverter(cell).toLocal(npcPos);
@ -837,7 +837,7 @@ namespace MWMechanics
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance // mAllowedNodes for this actor with pathgrid point indexes based on mDistance
// and if the point is connected to the closest current point // and if the point is connected to the closest current point
// NOTE: mPoints and mAllowedNodes are in local co-ordinates // NOTE: mPoints and mAllowedNodes are in local coordinates
int pointIndex = 0; int pointIndex = 0;
for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
{ {

@ -119,7 +119,7 @@ namespace MWMechanics
GroupIndex_MaxIdle = 9 GroupIndex_MaxIdle = 9
}; };
/// convert point from local (i.e. cell) to world co-ordinates /// convert point from local (i.e. cell) to world coordinates
void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell); void ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell);
void SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos, AiWanderStorage& storage); void SetCurrentNodeToClosestAllowedNode(osg::Vec3f npcPos, AiWanderStorage& storage);

@ -1052,6 +1052,9 @@ bool CharacterController::updateCreatureState()
mUpperBodyState = UpperCharState_StartToMinAttack; mUpperBodyState = UpperCharState_StartToMinAttack;
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
if (weapType == WeapType_HandToHand)
playSwishSound(0.0f);
} }
} }
@ -1380,13 +1383,7 @@ bool CharacterController::updateWeaponState()
} }
else else
{ {
std::string sound = "SwishM"; playSwishSound(attackStrength);
if(attackStrength < 0.5f)
sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack
else if(attackStrength < 1.0f)
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack
else
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack
} }
} }
mAttackStrength = attackStrength; mAttackStrength = attackStrength;
@ -2281,6 +2278,19 @@ void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target)
mHeadTrackTarget = target; mHeadTrackTarget = target;
} }
void CharacterController::playSwishSound(float attackStrength)
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
std::string sound = "Weapon Swish";
if(attackStrength < 0.5f)
sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack
else if(attackStrength < 1.0f)
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack
else
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack
}
void CharacterController::updateHeadTracking(float duration) void CharacterController::updateHeadTracking(float duration)
{ {
const osg::Node* head = mAnimation->getNode("Bip01 Head"); const osg::Node* head = mAnimation->getNode("Bip01 Head");

@ -281,6 +281,8 @@ public:
/// Make this character turn its head towards \a target. To turn off head tracking, pass an empty Ptr. /// Make this character turn its head towards \a target. To turn off head tracking, pass an empty Ptr.
void setHeadTrackTarget(const MWWorld::ConstPtr& target); void setHeadTrackTarget(const MWWorld::ConstPtr& target);
void playSwishSound(float attackStrength);
}; };
MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype); MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype);

@ -236,7 +236,7 @@ namespace MWMechanics
: mWatchedTimeToStartDrowning(0), mWatchedStatsEmpty (true), mUpdatePlayer (true), mClassSelected (false), : mWatchedTimeToStartDrowning(0), mWatchedStatsEmpty (true), mUpdatePlayer (true), mClassSelected (false),
mRaceSelected (false), mAI(true) mRaceSelected (false), mAI(true)
{ {
//buildPlayer no longer here, needs to be done explicitely after all subsystems are up and running //buildPlayer no longer here, needs to be done explicitly after all subsystems are up and running
} }
void MechanicsManager::add(const MWWorld::Ptr& ptr) void MechanicsManager::add(const MWWorld::Ptr& ptr)

@ -19,7 +19,7 @@ namespace MWMechanics
bool proximityToDoor(const MWWorld::Ptr& actor, bool proximityToDoor(const MWWorld::Ptr& actor,
float minSqr = MIN_DIST_TO_DOOR_SQUARED); float minSqr = MIN_DIST_TO_DOOR_SQUARED);
/// Returns door pointer within range. No guarentee is given as too which one /// Returns door pointer within range. No guarantee is given as to which one
/** \return Pointer to the door, or NULL if none exists **/ /** \return Pointer to the door, or NULL if none exists **/
MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor,
float minSqr = MIN_DIST_TO_DOOR_SQUARED); float minSqr = MIN_DIST_TO_DOOR_SQUARED);

@ -139,7 +139,7 @@ namespace MWMechanics
* NOTE: It may be desirable to simply go directly to the endPoint if for * NOTE: It may be desirable to simply go directly to the endPoint if for
* example there are no pathgrids in this cell. * example there are no pathgrids in this cell.
* *
* NOTE: startPoint & endPoint are in world co-ordinates * NOTE: startPoint & endPoint are in world coordinates
* *
* Updates mPath using aStarSearch() or ray test (if shortcut allowed). * Updates mPath using aStarSearch() or ray test (if shortcut allowed).
* mPath consists of pathgrid points, except the last element which is * mPath consists of pathgrid points, except the last element which is
@ -148,7 +148,7 @@ namespace MWMechanics
* pathgrid point (e.g. wander) then it may be worth while to call * pathgrid point (e.g. wander) then it may be worth while to call
* pop_back() to remove the redundant entry. * pop_back() to remove the redundant entry.
* *
* NOTE: co-ordinates must be converted prior to calling GetClosestPoint() * NOTE: coordinates must be converted prior to calling GetClosestPoint()
* *
* | * |
* | cell * | cell
@ -164,8 +164,8 @@ namespace MWMechanics
* +----------------------------- * +-----------------------------
* *
* i = x value of cell itself (multiply by ESM::Land::REAL_SIZE to convert) * i = x value of cell itself (multiply by ESM::Land::REAL_SIZE to convert)
* j = @.x in local co-ordinates (i.e. within the cell) * j = @.x in local coordinates (i.e. within the cell)
* k = @.x in world co-ordinates * k = @.x in world coordinates
*/ */
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint,
const ESM::Pathgrid::Point &endPoint, const ESM::Pathgrid::Point &endPoint,
@ -188,7 +188,7 @@ namespace MWMechanics
return; return;
} }
// NOTE: GetClosestPoint expects local co-ordinates // NOTE: GetClosestPoint expects local coordinates
CoordinateConverter converter(mCell->getCell()); CoordinateConverter converter(mCell->getCell());
// NOTE: It is possible that GetClosestPoint returns a pathgrind point index // NOTE: It is possible that GetClosestPoint returns a pathgrind point index
@ -230,7 +230,7 @@ namespace MWMechanics
{ {
mPath = mCell->aStarSearch(startNode, endNode.first); mPath = mCell->aStarSearch(startNode, endNode.first);
// convert supplied path to world co-ordinates // convert supplied path to world coordinates
for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter) for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter)
{ {
converter.toWorld(*iter); converter.toWorld(*iter);

@ -84,7 +84,7 @@ namespace MWMechanics
/** Synchronize new path with old one to avoid visiting 1 waypoint 2 times /** Synchronize new path with old one to avoid visiting 1 waypoint 2 times
@note @note
BuildPath() takes closest PathGrid point to NPC as first point of path. BuildPath() takes closest PathGrid point to NPC as first point of path.
This is undesireable if NPC has just passed a Pathgrid point, as this This is undesirable if NPC has just passed a Pathgrid point, as this
makes the 2nd point of the new path == the 1st point of old path. makes the 2nd point of the new path == the 1st point of old path.
Which results in NPC "running in a circle" back to the just passed waypoint. Which results in NPC "running in a circle" back to the just passed waypoint.
*/ */
@ -122,11 +122,11 @@ namespace MWMechanics
return (MWMechanics::PathFinder::MakeOsgVec3(point) - pos).length2(); return (MWMechanics::PathFinder::MakeOsgVec3(point) - pos).length2();
} }
// Return the closest pathgrid point index from the specified position co // Return the closest pathgrid point index from the specified position
// -ordinates. NOTE: Does not check if there is a sensible way to get there // coordinates. NOTE: Does not check if there is a sensible way to get there
// (e.g. a cliff in front). // (e.g. a cliff in front).
// //
// NOTE: pos is expected to be in local co-ordinates, as is grid->mPoints // NOTE: pos is expected to be in local coordinates, as is grid->mPoints
// //
static int GetClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos) static int GetClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
{ {

@ -225,7 +225,7 @@ namespace MWMechanics
* Should be possible to make this MT safe. * Should be possible to make this MT safe.
* *
* Returns path which may be empty. path contains pathgrid points in local * Returns path which may be empty. path contains pathgrid points in local
* cell co-ordinates (indoors) or world co-ordinates (external). * cell coordinates (indoors) or world coordinates (external).
* *
* Input params: * Input params:
* start, goal - pathgrid point indexes (for this cell) * start, goal - pathgrid point indexes (for this cell)
@ -239,7 +239,7 @@ namespace MWMechanics
* TODO: An intersting exercise might be to cache the paths created for a * TODO: An intersting exercise might be to cache the paths created for a
* start/goal pair. To cache the results the paths need to be in * start/goal pair. To cache the results the paths need to be in
* pathgrid points form (currently they are converted to world * pathgrid points form (currently they are converted to world
* co-ordinates). Essentially trading speed w/ memory. * coordinates). Essentially trading speed w/ memory.
*/ */
std::list<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start, std::list<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start,
const int goal) const const int goal) const
@ -312,7 +312,7 @@ namespace MWMechanics
if(current != goal) if(current != goal)
return path; // for some reason couldn't build a path return path; // for some reason couldn't build a path
// reconstruct path to return, using local co-ordinates // reconstruct path to return, using local coordinates
while(graphParent[current] != -1) while(graphParent[current] != -1)
{ {
path.push_front(mPathgrid->mPoints[current]); path.push_front(mPathgrid->mPoints[current]);

@ -30,7 +30,7 @@ namespace MWMechanics
// the input parameters are pathgrid point indexes // the input parameters are pathgrid point indexes
// the output list is in local (internal cells) or world (external // the output list is in local (internal cells) or world (external
// cells) co-ordinates // cells) coordinates
// //
// NOTE: if start equals end an empty path is returned // NOTE: if start equals end an empty path is returned
std::list<ESM::Pathgrid::Point> aStarSearch(const int start, std::list<ESM::Pathgrid::Point> aStarSearch(const int start,

@ -495,7 +495,7 @@ namespace MWMechanics
appliedLastingEffects.push_back(effect); appliedLastingEffects.push_back(effect);
// For absorb effects, also apply the effect to the caster - but with a negative // For absorb effects, also apply the effect to the caster - but with a negative
// magnitude, since we're transfering stats from the target to the caster // magnitude, since we're transferring stats from the target to the caster
if (!caster.isEmpty() && caster.getClass().isActor()) if (!caster.isEmpty() && caster.getClass().isActor())
{ {
for (int i=0; i<5; ++i) for (int i=0; i<5; ++i)
@ -927,7 +927,7 @@ namespace MWMechanics
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
if (mCaster.getClass().isActor()) // TODO: Non-actors should also create a spell cast vfx if (animation && mCaster.getClass().isActor()) // TODO: Non-actors should also create a spell cast vfx even if they are disabled (animation == NULL)
{ {
const ESM::Static* castStatic; const ESM::Static* castStatic;
@ -941,7 +941,7 @@ namespace MWMechanics
animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture); animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture);
} }
if (!mCaster.getClass().isActor()) if (animation && !mCaster.getClass().isActor())
animation->addSpellCastGlow(effect); animation->addSpellCastGlow(effect);
static const std::string schools[] = { static const std::string schools[] = {
@ -994,8 +994,10 @@ namespace MWMechanics
if (charge == 0) if (charge == 0)
return false; return false;
// FIXME: charge should be a float, not int so that damage < 1 per frame can be applied. // Store remainder of disintegrate amount (automatically subtracted if > 1)
// This was also a bug in the original engine. item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate));
charge = item->getClass().getItemHealth(*item);
charge -= charge -=
std::min(static_cast<int>(disintegrate), std::min(static_cast<int>(disintegrate),
charge); charge);
@ -1023,10 +1025,10 @@ namespace MWMechanics
creatureStats.setDynamic(index, stat); creatureStats.setDynamic(index, stat);
} }
void effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude) bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude)
{ {
if (magnitude == 0.f) if (magnitude == 0.f)
return; return false;
bool receivedMagicDamage = false; bool receivedMagicDamage = false;
@ -1158,10 +1160,13 @@ namespace MWMechanics
case ESM::MagicEffect::RemoveCurse: case ESM::MagicEffect::RemoveCurse:
actor.getClass().getCreatureStats(actor).getSpells().purgeCurses(); actor.getClass().getCreatureStats(actor).getSpells().purgeCurses();
break; break;
default:
return false;
} }
if (receivedMagicDamage && actor == getPlayer()) if (receivedMagicDamage && actor == getPlayer())
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
return true;
} }
} }

@ -63,7 +63,9 @@ namespace MWMechanics
int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor);
void effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const MWMechanics::EffectKey& effectKey, float magnitude); /// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
/// @return Was the effect a tickable effect with a magnitude?
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const MWMechanics::EffectKey& effectKey, float magnitude);
class CastSpell class CastSpell
{ {

@ -47,7 +47,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape>
updateScale(); updateScale();
updatePosition(); updatePosition();
updateCollisionMask(); addCollisionMask(getCollisionMask());
} }
Actor::~Actor() Actor::~Actor()
@ -70,15 +70,26 @@ void Actor::enableCollisionBody(bool collision)
} }
} }
void Actor::addCollisionMask(int collisionMask)
{
mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask);
}
void Actor::updateCollisionMask() void Actor::updateCollisionMask()
{ {
mCollisionWorld->removeCollisionObject(mCollisionObject.get()); mCollisionWorld->removeCollisionObject(mCollisionObject.get());
addCollisionMask(getCollisionMask());
}
int Actor::getCollisionMask()
{
int collisionMask = CollisionType_World | CollisionType_HeightMap; int collisionMask = CollisionType_World | CollisionType_HeightMap;
if (mExternalCollisionMode) if (mExternalCollisionMode)
collisionMask |= CollisionType_Actor | CollisionType_Projectile | CollisionType_Door; collisionMask |= CollisionType_Actor | CollisionType_Projectile | CollisionType_Door;
if (mCanWaterWalk) if (mCanWaterWalk)
collisionMask |= CollisionType_Water; collisionMask |= CollisionType_Water;
mCollisionWorld->addCollisionObject(mCollisionObject.get(), CollisionType_Actor, collisionMask); return collisionMask;
} }
void Actor::updatePosition() void Actor::updatePosition()

@ -139,6 +139,8 @@ namespace MWPhysics
private: private:
/// Removes then re-adds the collision object to the dynamics world /// Removes then re-adds the collision object to the dynamics world
void updateCollisionMask(); void updateCollisionMask();
void addCollisionMask(int collisionMask);
int getCollisionMask();
bool mCanWaterWalk; bool mCanWaterWalk;
bool mWalkingOnWater; bool mWalkingOnWater;

@ -69,7 +69,14 @@ namespace MWPhysics
return osg::RadiansToDegrees(std::acos(normal * osg::Vec3f(0.f, 0.f, 1.f))); return osg::RadiansToDegrees(std::acos(normal * osg::Vec3f(0.f, 0.f, 1.f)));
} }
static bool stepMove(const btCollisionObject *colobj, osg::Vec3f &position, enum StepMoveResult
{
Result_Blocked, // unable to move over obstacle
Result_MaxSlope, // unable to end movement on this slope
Result_Success
};
static StepMoveResult stepMove(const btCollisionObject *colobj, osg::Vec3f &position,
const osg::Vec3f &toMove, float &remainingTime, const btCollisionWorld* collisionWorld) const osg::Vec3f &toMove, float &remainingTime, const btCollisionWorld* collisionWorld)
{ {
/* /*
@ -120,7 +127,7 @@ namespace MWPhysics
stepper.doTrace(colobj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), collisionWorld); stepper.doTrace(colobj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), collisionWorld);
if(stepper.mFraction < std::numeric_limits<float>::epsilon()) if(stepper.mFraction < std::numeric_limits<float>::epsilon())
return false; // didn't even move the smallest representable amount return Result_Blocked; // didn't even move the smallest representable amount
// (TODO: shouldn't this be larger? Why bother with such a small amount?) // (TODO: shouldn't this be larger? Why bother with such a small amount?)
/* /*
@ -138,7 +145,7 @@ namespace MWPhysics
*/ */
tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + toMove, collisionWorld); tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + toMove, collisionWorld);
if(tracer.mFraction < std::numeric_limits<float>::epsilon()) if(tracer.mFraction < std::numeric_limits<float>::epsilon())
return false; // didn't even move the smallest representable amount return Result_Blocked; // didn't even move the smallest representable amount
/* /*
* Try moving back down sStepSizeDown using stepper. * Try moving back down sStepSizeDown using stepper.
@ -156,22 +163,22 @@ namespace MWPhysics
* ============================================== * ==============================================
*/ */
stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), collisionWorld); stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), collisionWorld);
if(stepper.mFraction < 1.0f && getSlope(stepper.mPlaneNormal) <= sMaxSlope) if (getSlope(stepper.mPlaneNormal) > sMaxSlope)
return Result_MaxSlope;
if(stepper.mFraction < 1.0f)
{ {
// don't allow stepping up other actors // don't allow stepping up other actors
if (stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor) if (stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor)
return false; return Result_Blocked;
// only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall.
// TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing // TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing
// NOTE: caller's variables 'position' & 'remainingTime' are modified here // NOTE: caller's variables 'position' & 'remainingTime' are modified here
position = stepper.mEndPos; position = stepper.mEndPos;
remainingTime *= (1.0f-tracer.mFraction); // remaining time is proportional to remaining distance remainingTime *= (1.0f-tracer.mFraction); // remaining time is proportional to remaining distance
return true; return Result_Success;
} }
// moved between 0 and just under sStepSize distance but slope was too great, return Result_Blocked;
// or moved full sStepSize distance (FIXME: is this a bug?)
return false;
} }
@ -361,14 +368,15 @@ namespace MWPhysics
osg::Vec3f oldPosition = newPosition; osg::Vec3f oldPosition = newPosition;
// We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over) // We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over)
// NOTE: stepMove modifies newPosition if successful // NOTE: stepMove modifies newPosition if successful
bool result = stepMove(colobj, newPosition, velocity*remainingTime, remainingTime, collisionWorld); const float minStep = 10.f;
if (!result) // to make sure the maximum stepping distance isn't framerate-dependent or movement-speed dependent StepMoveResult result = stepMove(colobj, newPosition, velocity*remainingTime, remainingTime, collisionWorld);
if (result == Result_MaxSlope && (velocity*remainingTime).length() < minStep) // to make sure the maximum stepping distance isn't framerate-dependent or movement-speed dependent
{ {
osg::Vec3f normalizedVelocity = velocity; osg::Vec3f normalizedVelocity = velocity;
normalizedVelocity.normalize(); normalizedVelocity.normalize();
result = stepMove(colobj, newPosition, normalizedVelocity*10.f, remainingTime, collisionWorld); result = stepMove(colobj, newPosition, normalizedVelocity*minStep, remainingTime, collisionWorld);
} }
if(result) if(result == Result_Success)
{ {
// don't let pure water creatures move out of water after stepMove // don't let pure water creatures move out of water after stepMove
if (ptr.getClass().isPureWaterCreature(ptr) if (ptr.getClass().isPureWaterCreature(ptr)
@ -1459,7 +1467,10 @@ namespace MWPhysics
} }
if (!mWaterEnabled) if (!mWaterEnabled)
{
mWaterCollisionObject.reset();
return; return;
}
mWaterCollisionObject.reset(new btCollisionObject()); mWaterCollisionObject.reset(new btCollisionObject());
mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight)); mWaterCollisionShape.reset(new btStaticPlaneShape(btVector3(0,0,1), mWaterHeight));

@ -200,7 +200,7 @@ namespace MWPhysics
typedef std::map<MWWorld::Ptr, MWWorld::Ptr> CollisionMap; typedef std::map<MWWorld::Ptr, MWWorld::Ptr> CollisionMap;
CollisionMap mStandingCollisions; CollisionMap mStandingCollisions;
// replaces all occurences of 'old' in the map by 'updated', no matter if its a key or value // replaces all occurrences of 'old' in the map by 'updated', no matter if it's a key or value
void updateCollisionMapPtr(CollisionMap& map, const MWWorld::Ptr &old, const MWWorld::Ptr &updated); void updateCollisionMapPtr(CollisionMap& map, const MWWorld::Ptr &old, const MWWorld::Ptr &updated);
PtrVelocityList mMovementQueue; PtrVelocityList mMovementQueue;

@ -302,7 +302,7 @@ protected:
/** Sets the root model of the object. /** Sets the root model of the object.
* *
* Note that you must make sure all animation sources are cleared before reseting the object * Note that you must make sure all animation sources are cleared before resetting the object
* root. All nodes previously retrieved with getNode will also become invalidated. * root. All nodes previously retrieved with getNode will also become invalidated.
* @param forceskeleton Wrap the object root in a Skeleton, even if it contains no skinned parts. Use this if you intend to add skinned parts manually. * @param forceskeleton Wrap the object root in a Skeleton, even if it contains no skinned parts. Use this if you intend to add skinned parts manually.
* @param baseonly If true, then any meshes or particle systems in the model are ignored * @param baseonly If true, then any meshes or particle systems in the model are ignored

@ -110,8 +110,6 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item)); osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item));
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(node, mObjectRoot, bonename, bonename); osg::ref_ptr<osg::Node> attached = SceneUtil::attach(node, mObjectRoot, bonename, bonename);
mResourceSystem->getSceneManager()->notifyAttached(attached); mResourceSystem->getSceneManager()->notifyAttached(attached);
if (mSkeleton)
mSkeleton->markDirty();
scene.reset(new PartHolder(attached)); scene.reset(new PartHolder(attached));

@ -671,8 +671,6 @@ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const st
{ {
osg::ref_ptr<osg::Node> instance = mResourceSystem->getSceneManager()->getInstance(model); osg::ref_ptr<osg::Node> instance = mResourceSystem->getSceneManager()->getInstance(model);
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, bonename); osg::ref_ptr<osg::Node> attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, bonename);
if (mSkeleton)
mSkeleton->markDirty();
mResourceSystem->getSceneManager()->notifyAttached(attached); mResourceSystem->getSceneManager()->notifyAttached(attached);
if (enchantedGlow) if (enchantedGlow)
addGlow(attached, *glowColor); addGlow(attached, *glowColor);

@ -211,7 +211,7 @@ namespace MWRender
osg::Vec3f mStormDirection; osg::Vec3f mStormDirection;
// remember some settings so we don't have to apply them again if they didnt change // remember some settings so we don't have to apply them again if they didn't change
std::string mClouds; std::string mClouds;
std::string mNextClouds; std::string mNextClouds;
float mCloudBlendFactor; float mCloudBlendFactor;

@ -11,7 +11,7 @@ namespace MWScript
enum Type enum Type
{ {
Type_Full, // global, local, targetted Type_Full, // global, local, targeted
Type_Dialogue, Type_Dialogue,
Type_Console Type_Console
}; };

@ -34,8 +34,9 @@
namespace MWSound namespace MWSound
{ {
SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound) SoundManager::SoundManager(const VFS::Manager* vfs, const std::map<std::string,std::string>& fallbackMap, bool useSound)
: mVFS(vfs) : mVFS(vfs)
, mFallback(fallbackMap)
, mOutput(new DEFAULT_OUTPUT(*this)) , mOutput(new DEFAULT_OUTPUT(*this))
, mMasterVolume(1.0f) , mMasterVolume(1.0f)
, mSFXVolume(1.0f) , mSFXVolume(1.0f)
@ -61,6 +62,13 @@ namespace MWSound
mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound"); mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound");
mFootstepsVolume = std::min(std::max(mFootstepsVolume, 0.0f), 1.0f); mFootstepsVolume = std::min(std::max(mFootstepsVolume, 0.0f), 1.0f);
mNearWaterRadius = mFallback.getFallbackInt("Water_NearWaterRadius");
mNearWaterPoints = mFallback.getFallbackInt("Water_NearWaterPoints");
mNearWaterIndoorTolerance = mFallback.getFallbackFloat("Water_NearWaterIndoorTolerance");
mNearWaterOutdoorTolerance = mFallback.getFallbackFloat("Water_NearWaterOutdoorTolerance");
mNearWaterIndoorID = mFallback.getFallbackString("Water_NearWaterIndoorID");
mNearWaterOutdoorID = mFallback.getFallbackString("Water_NearWaterOutdoorID");
mBufferCacheMin = std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1); mBufferCacheMin = std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1);
mBufferCacheMax = std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1); mBufferCacheMax = std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1);
mBufferCacheMax *= 1024*1024; mBufferCacheMax *= 1024*1024;
@ -796,6 +804,96 @@ namespace MWSound
} }
} }
void SoundManager::updateWaterSound(float /*duration*/)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::ConstPtr player = world->getPlayerPtr();
osg::Vec3f pos = player.getRefData().getPosition().asVec3();
float volume = 0.0f;
const std::string& soundId = player.getCell()->isExterior() ? mNearWaterOutdoorID : mNearWaterIndoorID;
if (!mListenerUnderwater)
{
if (player.getCell()->getCell()->hasWater())
{
float dist = std::abs(player.getCell()->getWaterLevel() - pos.z());
if (player.getCell()->isExterior() && dist < mNearWaterOutdoorTolerance)
{
volume = (mNearWaterOutdoorTolerance - dist) / mNearWaterOutdoorTolerance;
if (mNearWaterPoints > 1)
{
int underwaterPoints = 0;
float step = mNearWaterRadius * 2.0f / (mNearWaterPoints - 1);
for (int x = 0; x < mNearWaterPoints; x++)
{
for (int y = 0; y < mNearWaterPoints; y++)
{
float height = world->getTerrainHeightAt(
osg::Vec3f(pos.x() - mNearWaterRadius + x*step, pos.y() - mNearWaterRadius + y*step, 0.0f));
if (height < 0)
underwaterPoints++;
}
}
volume *= underwaterPoints * 2.0f / (mNearWaterPoints*mNearWaterPoints);
}
}
else if (!player.getCell()->isExterior() && dist < mNearWaterIndoorTolerance)
{
volume = (mNearWaterIndoorTolerance - dist) / mNearWaterIndoorTolerance;
}
}
}
else
volume = 1.0f;
volume = std::min(volume, 1.0f);
if (mNearWaterSound)
{
if (volume == 0.0f)
{
mOutput->finishSound(mNearWaterSound);
mNearWaterSound.reset();
}
else
{
bool soundIdChanged = false;
Sound_Buffer* sfx = lookupSound(Misc::StringUtils::lowerCase(soundId));
for (SoundMap::const_iterator snditer = mActiveSounds.begin(); snditer != mActiveSounds.end(); ++snditer)
{
for (SoundBufferRefPairList::const_iterator pairiter = snditer->second.begin(); pairiter != snditer->second.end(); ++pairiter)
{
if (pairiter->first == mNearWaterSound)
{
if (pairiter->second != sfx)
soundIdChanged = true;
break;
}
}
}
if (soundIdChanged)
{
mOutput->finishSound(mNearWaterSound);
mNearWaterSound = playSound(soundId, volume, 1.0f, Play_TypeSfx, Play_Loop);
}
else if (sfx)
mNearWaterSound->setVolume(volume * sfx->mVolume);
}
}
else if (volume > 0.0f)
mNearWaterSound = playSound(soundId, volume, 1.0f, Play_TypeSfx, Play_Loop);
}
void SoundManager::updateSounds(float duration) void SoundManager::updateSounds(float duration)
{ {
static float timePassed = 0.0; static float timePassed = 0.0;
@ -941,6 +1039,7 @@ namespace MWSound
{ {
updateSounds(duration); updateSounds(duration);
updateRegionSound(duration); updateRegionSound(duration);
updateWaterSound(duration);
} }
} }
@ -1105,6 +1204,7 @@ namespace MWSound
mOutput->finishStream(*trkiter); mOutput->finishStream(*trkiter);
mActiveTracks.clear(); mActiveTracks.clear();
mUnderwaterSound.reset(); mUnderwaterSound.reset();
mNearWaterSound.reset();
stopMusic(); stopMusic();
} }
} }

@ -11,6 +11,8 @@
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/fallback/fallback.hpp>
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
namespace VFS namespace VFS
@ -44,6 +46,8 @@ namespace MWSound
{ {
const VFS::Manager* mVFS; const VFS::Manager* mVFS;
Fallback::Map mFallback;
std::auto_ptr<Sound_Output> mOutput; std::auto_ptr<Sound_Output> mOutput;
// Caches available music tracks by <playlist name, (sound files) > // Caches available music tracks by <playlist name, (sound files) >
@ -56,6 +60,13 @@ namespace MWSound
float mVoiceVolume; float mVoiceVolume;
float mFootstepsVolume; float mFootstepsVolume;
int mNearWaterRadius;
int mNearWaterPoints;
float mNearWaterIndoorTolerance;
float mNearWaterOutdoorTolerance;
std::string mNearWaterIndoorID;
std::string mNearWaterOutdoorID;
typedef std::auto_ptr<std::deque<Sound_Buffer> > SoundBufferList; typedef std::auto_ptr<std::deque<Sound_Buffer> > SoundBufferList;
// List of sound buffers, grown as needed. New enties are added to the // List of sound buffers, grown as needed. New enties are added to the
// back, allowing existing Sound_Buffer references/pointers to remain // back, allowing existing Sound_Buffer references/pointers to remain
@ -94,6 +105,7 @@ namespace MWSound
int mPausedSoundTypes; int mPausedSoundTypes;
MWBase::SoundPtr mUnderwaterSound; MWBase::SoundPtr mUnderwaterSound;
MWBase::SoundPtr mNearWaterSound;
Sound_Buffer *insertSound(const std::string &soundId, const ESM::Sound *sound); Sound_Buffer *insertSound(const std::string &soundId, const ESM::Sound *sound);
@ -108,6 +120,7 @@ namespace MWSound
void streamMusicFull(const std::string& filename); void streamMusicFull(const std::string& filename);
void updateSounds(float duration); void updateSounds(float duration);
void updateRegionSound(float duration); void updateRegionSound(float duration);
void updateWaterSound(float duration);
float volumeFromType(PlayType type) const; float volumeFromType(PlayType type) const;
@ -119,7 +132,7 @@ namespace MWSound
friend class OpenAL_Output; friend class OpenAL_Output;
public: public:
SoundManager(const VFS::Manager* vfs, bool useSound); SoundManager(const VFS::Manager* vfs, const std::map<std::string, std::string>& fallbackMap, bool useSound);
virtual ~SoundManager(); virtual ~SoundManager();
virtual void processChangedSettings(const Settings::CategorySettingVector& settings); virtual void processChangedSettings(const Settings::CategorySettingVector& settings);

@ -100,6 +100,24 @@ namespace MWWorld
} }
} }
void CellRef::applyChargeRemainderToBeSubtracted(float chargeRemainder)
{
mCellRef.mChargeIntRemainder += std::abs(chargeRemainder);
if (mCellRef.mChargeIntRemainder > 1.0f)
{
float newChargeRemainder = (mCellRef.mChargeIntRemainder - std::floor(mCellRef.mChargeIntRemainder));
if (mCellRef.mChargeInt <= static_cast<int>(mCellRef.mChargeIntRemainder))
{
mCellRef.mChargeInt = 0;
}
else
{
mCellRef.mChargeInt -= static_cast<int>(mCellRef.mChargeIntRemainder);
}
mCellRef.mChargeIntRemainder = newChargeRemainder;
}
}
float CellRef::getChargeFloat() const float CellRef::getChargeFloat() const
{ {
return mCellRef.mChargeFloat; return mCellRef.mChargeFloat;

@ -69,6 +69,7 @@ namespace MWWorld
float getChargeFloat() const; // Implemented as union with int charge float getChargeFloat() const; // Implemented as union with int charge
void setCharge(int charge); void setCharge(int charge);
void setChargeFloat(float charge); void setChargeFloat(float charge);
void applyChargeRemainderToBeSubtracted(float chargeRemainder); // Stores remainders and applies if > 1
// The NPC that owns this object (and will get angry if you steal it) // The NPC that owns this object (and will get angry if you steal it)
std::string getOwner() const; std::string getOwner() const;

@ -16,6 +16,7 @@ namespace
cellRef.mScale = 1; cellRef.mScale = 1;
cellRef.mFactionRank = 0; cellRef.mFactionRank = 0;
cellRef.mChargeInt = -1; cellRef.mChargeInt = -1;
cellRef.mChargeIntRemainder = 0.0f;
cellRef.mGoldValue = 1; cellRef.mGoldValue = 1;
cellRef.mEnchantmentCharge = -1; cellRef.mEnchantmentCharge = -1;
cellRef.mTeleport = false; cellRef.mTeleport = false;

@ -93,6 +93,31 @@ namespace
} }
return projectileEffects; return projectileEffects;
} }
osg::Vec4 getMagicBoltLightDiffuseColor(const ESM::EffectList& effects)
{
// Calculate combined light diffuse color from magical effects
osg::Vec4 lightDiffuseColor;
float lightDiffuseRed = 0.0f;
float lightDiffuseGreen = 0.0f;
float lightDiffuseBlue = 0.0f;
for (std::vector<ESM::ENAMstruct>::const_iterator iter(effects.mList.begin());
iter != effects.mList.end(); ++iter)
{
const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(
iter->mEffectID);
lightDiffuseRed += (static_cast<float>(magicEffect->mData.mRed) / 255.f);
lightDiffuseGreen += (static_cast<float>(magicEffect->mData.mGreen) / 255.f);
lightDiffuseBlue += (static_cast<float>(magicEffect->mData.mBlue) / 255.f);
}
int numberOfEffects = effects.mList.size();
lightDiffuseColor = osg::Vec4(lightDiffuseRed / numberOfEffects
, lightDiffuseGreen / numberOfEffects
, lightDiffuseBlue / numberOfEffects
, 1.0f);
return lightDiffuseColor;
}
} }
namespace MWWorld namespace MWWorld
@ -136,7 +161,8 @@ namespace MWWorld
}; };
void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, std::string texture) void ProjectileManager::createModel(State &state, const std::string &model, const osg::Vec3f& pos, const osg::Quat& orient,
bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture)
{ {
state.mNode = new osg::PositionAttitudeTransform; state.mNode = new osg::PositionAttitudeTransform;
state.mNode->setNodeMask(MWRender::Mask_Effect); state.mNode->setNodeMask(MWRender::Mask_Effect);
@ -167,6 +193,25 @@ namespace MWWorld
mResourceSystem->getSceneManager()->getInstance("meshes\\" + weapon->mModel, findVisitor.mFoundNode); mResourceSystem->getSceneManager()->getInstance("meshes\\" + weapon->mModel, findVisitor.mFoundNode);
} }
if (createLight)
{
osg::ref_ptr<osg::Light> projectileLight(new osg::Light);
projectileLight->setAmbient(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
projectileLight->setDiffuse(lightDiffuseColor);
projectileLight->setSpecular(osg::Vec4(0.0f, 0.0f, 0.0f, 0.0f));
projectileLight->setConstantAttenuation(0.f);
projectileLight->setLinearAttenuation(0.1f);
projectileLight->setQuadraticAttenuation(0.f);
projectileLight->setPosition(osg::Vec4(pos, 1.0));
SceneUtil::LightSource* projectileLightSource = new SceneUtil::LightSource;
projectileLightSource->setNodeMask(MWRender::Mask_Lighting);
projectileLightSource->setRadius(66.f);
state.mNode->addChild(projectileLightSource);
projectileLightSource->setLight(projectileLight);
}
SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor;
state.mNode->accept(disableFreezeOnCullVisitor); state.mNode->accept(disableFreezeOnCullVisitor);
@ -229,7 +274,8 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
MWWorld::Ptr ptr = ref.getPtr(); MWWorld::Ptr ptr = ref.getPtr();
createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, texture); osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(effects);
createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
for (size_t it = 0; it != state.mSoundIds.size(); it++) for (size_t it = 0; it != state.mSoundIds.size(); it++)
@ -253,7 +299,7 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId());
MWWorld::Ptr ptr = ref.getPtr(); MWWorld::Ptr ptr = ref.getPtr();
createModel(state, ptr.getClass().getModel(ptr), pos, orient, false); createModel(state, ptr.getClass().getModel(ptr), pos, orient, false, false, osg::Vec4(0,0,0,0));
mProjectiles.push_back(state); mProjectiles.push_back(state);
} }
@ -483,7 +529,7 @@ namespace MWWorld
return true; return true;
} }
createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), false); createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), false, false, osg::Vec4(0,0,0,0));
mProjectiles.push_back(state); mProjectiles.push_back(state);
return true; return true;
@ -518,7 +564,8 @@ namespace MWWorld
return true; return true;
} }
createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, texture); osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(esm.mEffects);
createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();

@ -122,7 +122,8 @@ namespace MWWorld
void moveProjectiles(float dt); void moveProjectiles(float dt);
void moveMagicBolts(float dt); void moveMagicBolts(float dt);
void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, bool rotate, std::string texture = ""); void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient,
bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = "");
void update (State& state, float duration); void update (State& state, float duration);
void operator=(const ProjectileManager&); void operator=(const ProjectileManager&);

@ -1069,7 +1069,7 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam
mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor); mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor);
// The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because // The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because
// MW applied the color to the ambient term as well. After the ambient and emissive terms are added together, the fixed pipeline // MW applied the color to the ambient term as well. After the ambient and emissive terms are added together, the fixed pipeline
// would then clamp the total lighting to (1,1,1). A noticable change in color tone can be observed when only one of the color components gets clamped. // would then clamp the total lighting to (1,1,1). A noticeable change in color tone can be observed when only one of the color components gets clamped.
// Unfortunately that means we can't use the INI color as is, have to replicate the above nonsense. // Unfortunately that means we can't use the INI color as is, have to replicate the above nonsense.
mResult.mSunDiscColor = mResult.mSunDiscColor + osg::componentMultiply(mResult.mSunDiscColor, mResult.mAmbientColor); mResult.mSunDiscColor = mResult.mSunDiscColor + osg::componentMultiply(mResult.mSunDiscColor, mResult.mAmbientColor);
for (int i=0; i<3; ++i) for (int i=0; i<3; ++i)

@ -198,8 +198,8 @@ bool Wizard::IniSettings::parseInx(const QString &path)
const QString section(array.left(index)); const QString section(array.left(index));
// Figure how many characters to read for the key // Figure how many characters to read for the key
int lenght = array.indexOf("\x06", section.length() + 3) - (section.length() + 3); int length = array.indexOf("\x06", section.length() + 3) - (section.length() + 3);
const QString key(array.mid(section.length() + 3, lenght)); const QString key(array.mid(section.length() + 3, length));
QString value(array.mid(section.length() + key.length() + 6)); QString value(array.mid(section.length() + key.length() + 6));

@ -196,6 +196,7 @@ void ESM::CellRef::blank()
mFaction.clear(); mFaction.clear();
mFactionRank = -2; mFactionRank = -2;
mChargeInt = -1; mChargeInt = -1;
mChargeIntRemainder = 0.0f;
mEnchantmentCharge = -1; mEnchantmentCharge = -1;
mGoldValue = 0; mGoldValue = 0;
mDestCell.clear(); mDestCell.clear();

@ -69,6 +69,7 @@ namespace ESM
int mChargeInt; // Used by everything except lights int mChargeInt; // Used by everything except lights
float mChargeFloat; // Used only by lights float mChargeFloat; // Used only by lights
}; };
float mChargeIntRemainder; // Stores amount of charge not subtracted from mChargeInt
// Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full).
float mEnchantmentCharge; float mEnchantmentCharge;

@ -87,6 +87,15 @@ namespace Nif
nif->skip(17); nif->skip(17);
} }
void NiSphericalCollider::read(NIFStream* nif)
{
Controlled::read(nif);
mBounceFactor = nif->getFloat();
mRadius = nif->getFloat();
mCenter = nif->getVector3();
}

@ -111,6 +111,16 @@ public:
float mPlaneDistance; float mPlaneDistance;
}; };
class NiSphericalCollider : public Controlled
{
public:
float mBounceFactor;
float mRadius;
osg::Vec3f mCenter;
void read(NIFStream *nif);
};
class NiParticleRotation : public Controlled class NiParticleRotation : public Controlled
{ {
public: public:

@ -89,6 +89,7 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
newFactory.insert(makeEntry("NiStringExtraData", &construct <NiStringExtraData> , RC_NiStringExtraData )); newFactory.insert(makeEntry("NiStringExtraData", &construct <NiStringExtraData> , RC_NiStringExtraData ));
newFactory.insert(makeEntry("NiGravity", &construct <NiGravity> , RC_NiGravity )); newFactory.insert(makeEntry("NiGravity", &construct <NiGravity> , RC_NiGravity ));
newFactory.insert(makeEntry("NiPlanarCollider", &construct <NiPlanarCollider> , RC_NiPlanarCollider )); newFactory.insert(makeEntry("NiPlanarCollider", &construct <NiPlanarCollider> , RC_NiPlanarCollider ));
newFactory.insert(makeEntry("NiSphericalCollider", &construct <NiSphericalCollider> , RC_NiSphericalCollider ));
newFactory.insert(makeEntry("NiParticleGrowFade", &construct <NiParticleGrowFade> , RC_NiParticleGrowFade )); newFactory.insert(makeEntry("NiParticleGrowFade", &construct <NiParticleGrowFade> , RC_NiParticleGrowFade ));
newFactory.insert(makeEntry("NiParticleColorModifier", &construct <NiParticleColorModifier> , RC_NiParticleColorModifier )); newFactory.insert(makeEntry("NiParticleColorModifier", &construct <NiParticleColorModifier> , RC_NiParticleColorModifier ));
newFactory.insert(makeEntry("NiParticleRotation", &construct <NiParticleRotation> , RC_NiParticleRotation )); newFactory.insert(makeEntry("NiParticleRotation", &construct <NiParticleRotation> , RC_NiParticleRotation ));

@ -92,7 +92,8 @@ enum RecordType
RC_NiSequenceStreamHelper, RC_NiSequenceStreamHelper,
RC_NiSourceTexture, RC_NiSourceTexture,
RC_NiSkinInstance, RC_NiSkinInstance,
RC_RootCollisionNode RC_RootCollisionNode,
RC_NiSphericalCollider
}; };
/// Base class for all records /// Base class for all records

@ -871,6 +871,13 @@ namespace NifOsg
const Nif::NiPlanarCollider* planarcollider = static_cast<const Nif::NiPlanarCollider*>(colliders.getPtr()); const Nif::NiPlanarCollider* planarcollider = static_cast<const Nif::NiPlanarCollider*>(colliders.getPtr());
program->addOperator(new PlanarCollider(planarcollider)); program->addOperator(new PlanarCollider(planarcollider));
} }
else if (colliders->recType == Nif::RC_NiSphericalCollider)
{
const Nif::NiSphericalCollider* sphericalcollider = static_cast<const Nif::NiSphericalCollider*>(colliders.getPtr());
program->addOperator(new SphericalCollider(sphericalcollider));
}
else
std::cerr << "Unhandled particle collider " << colliders->recName << " in " << mFilename << std::endl;
} }
} }
@ -1152,10 +1159,11 @@ namespace NifOsg
morphGeom->setUpdateCallback(NULL); morphGeom->setUpdateCallback(NULL);
morphGeom->setCullCallback(new UpdateMorphGeometry); morphGeom->setCullCallback(new UpdateMorphGeometry);
morphGeom->setUseVertexBufferObjects(true); morphGeom->setUseVertexBufferObjects(true);
morphGeom->getOrCreateVertexBufferObject()->setUsage(GL_DYNAMIC_DRAW_ARB);
triShapeToGeometry(triShape, morphGeom, parentNode, composite, boundTextures, animflags); triShapeToGeometry(triShape, morphGeom, parentNode, composite, boundTextures, animflags);
morphGeom->getOrCreateVertexBufferObject()->setUsage(GL_DYNAMIC_DRAW_ARB);
const std::vector<Nif::NiMorphData::MorphData>& morphs = morpher->data.getPtr()->mMorphs; const std::vector<Nif::NiMorphData::MorphData>& morphs = morpher->data.getPtr()->mMorphs;
if (morphs.empty()) if (morphs.empty())
return morphGeom; return morphGeom;

@ -376,4 +376,73 @@ void PlanarCollider::operate(osgParticle::Particle *particle, double dt)
} }
} }
SphericalCollider::SphericalCollider(const Nif::NiSphericalCollider* collider)
: mBounceFactor(collider->mBounceFactor),
mSphere(collider->mCenter, collider->mRadius)
{
}
SphericalCollider::SphericalCollider()
: mBounceFactor(1.0f)
{
}
SphericalCollider::SphericalCollider(const SphericalCollider& copy, const osg::CopyOp& copyop)
: osgParticle::Operator(copy, copyop)
, mBounceFactor(copy.mBounceFactor)
, mSphere(copy.mSphere)
, mSphereInParticleSpace(copy.mSphereInParticleSpace)
{
}
void SphericalCollider::beginOperate(osgParticle::Program* program)
{
mSphereInParticleSpace = mSphere;
if (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF)
mSphereInParticleSpace.center() = program->transformLocalToWorld(mSphereInParticleSpace.center());
}
void SphericalCollider::operate(osgParticle::Particle* particle, double dt)
{
osg::Vec3f cent = (particle->getPosition() - mSphereInParticleSpace.center()); // vector from sphere center to particle
bool insideSphere = cent.length2() <= mSphereInParticleSpace.radius2();
if (insideSphere
|| (cent * particle->getVelocity() < 0.0f)) // if outside, make sure the particle is flying towards the sphere
{
// Collision test (finding point of contact) is performed by solving a quadratic equation:
// ||vec(cent) + vec(vel)*k|| = R /^2
// k^2 + 2*k*(vec(cent)*vec(vel))/||vec(vel)||^2 + (||vec(cent)||^2 - R^2)/||vec(vel)||^2 = 0
float b = -(cent * particle->getVelocity()) / particle->getVelocity().length2();
osg::Vec3f u = cent + particle->getVelocity() * b;
if (insideSphere
|| (u.length2() < mSphereInParticleSpace.radius2()))
{
float d = (mSphereInParticleSpace.radius2() - u.length2()) / particle->getVelocity().length2();
float k = insideSphere ? (std::sqrt(d) + b) : (b - std::sqrt(d));
if (k < dt)
{
// collision detected; reflect off the tangent plane
osg::Vec3f contact = particle->getPosition() + particle->getVelocity() * k;
osg::Vec3 normal = (contact - mSphereInParticleSpace.center());
normal.normalize();
float dotproduct = particle->getVelocity() * normal;
osg::Vec3 reflectedVelocity = particle->getVelocity() - normal * (2 * dotproduct);
reflectedVelocity *= mBounceFactor;
particle->setVelocity(reflectedVelocity);
}
}
}
}
} }

@ -16,6 +16,7 @@ namespace Nif
{ {
class NiGravity; class NiGravity;
class NiPlanarCollider; class NiPlanarCollider;
class NiSphericalCollider;
class NiColorData; class NiColorData;
} }
@ -110,6 +111,23 @@ namespace NifOsg
osg::Plane mPlaneInParticleSpace; osg::Plane mPlaneInParticleSpace;
}; };
class SphericalCollider : public osgParticle::Operator
{
public:
SphericalCollider(const Nif::NiSphericalCollider* collider);
SphericalCollider();
SphericalCollider(const SphericalCollider& copy, const osg::CopyOp& copyop);
META_Object(NifOsg, SphericalCollider)
virtual void beginOperate(osgParticle::Program* program);
virtual void operate(osgParticle::Particle* particle, double dt);
private:
float mBounceFactor;
osg::BoundingSphere mSphere;
osg::BoundingSphere mSphereInParticleSpace;
};
class GrowFadeAffector : public osgParticle::Operator class GrowFadeAffector : public osgParticle::Operator
{ {
public: public:

@ -120,13 +120,17 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry)
setVertexArray(vertexArray); setVertexArray(vertexArray);
} }
osg::ref_ptr<osg::Array> normalArray = osg::clone(from.getNormalArray(), osg::CopyOp::DEEP_COPY_ALL); if (osg::Array* normals = from.getNormalArray())
if (normalArray)
{ {
normalArray->setVertexBufferObject(vbo); osg::ref_ptr<osg::Array> normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL);
setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX); if (normalArray)
{
normalArray->setVertexBufferObject(vbo);
setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX);
}
} }
if (osg::Vec4Array* tangents = dynamic_cast<osg::Vec4Array*>(from.getTexCoordArray(7))) if (osg::Vec4Array* tangents = dynamic_cast<osg::Vec4Array*>(from.getTexCoordArray(7)))
{ {
mSourceTangents = tangents; mSourceTangents = tangents;
@ -273,7 +277,8 @@ void RigGeometry::update(osg::NodeVisitor* nv)
{ {
unsigned short vertex = *vertexIt; unsigned short vertex = *vertexIt;
(*positionDst)[vertex] = resultMat.preMult((*positionSrc)[vertex]); (*positionDst)[vertex] = resultMat.preMult((*positionSrc)[vertex]);
(*normalDst)[vertex] = osg::Matrix::transform3x3((*normalSrc)[vertex], resultMat); if (normalDst)
(*normalDst)[vertex] = osg::Matrix::transform3x3((*normalSrc)[vertex], resultMat);
if (tangentDst) if (tangentDst)
{ {
osg::Vec4f srcTangent = (*tangentSrc)[vertex]; osg::Vec4f srcTangent = (*tangentSrc)[vertex];
@ -284,7 +289,8 @@ void RigGeometry::update(osg::NodeVisitor* nv)
} }
positionDst->dirty(); positionDst->dirty();
normalDst->dirty(); if (normalDst)
normalDst->dirty();
if (tangentDst) if (tangentDst)
tangentDst->dirty(); tangentDst->dirty();
} }
@ -314,11 +320,14 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv)
box.expandBy(bs); box.expandBy(bs);
} }
_boundingBox = box; if (box != _boundingBox)
_boundingSphere = osg::BoundingSphere(_boundingBox); {
_boundingSphereComputed = true; _boundingBox = box;
for (unsigned int i=0; i<getNumParents(); ++i) _boundingSphere = osg::BoundingSphere(_boundingBox);
getParent(i)->dirtyBound(); _boundingSphereComputed = true;
for (unsigned int i=0; i<getNumParents(); ++i)
getParent(i)->dirtyBound();
}
} }
void RigGeometry::updateGeomToSkelMatrix(const osg::NodePath& nodePath) void RigGeometry::updateGeomToSkelMatrix(const osg::NodePath& nodePath)

@ -148,6 +148,8 @@ void Skeleton::markDirty()
{ {
mTraversedEvenFrame = false; mTraversedEvenFrame = false;
mTraversedOddFrame = false; mTraversedOddFrame = false;
mBoneCache.clear();
mBoneCacheInit = false;
} }
void Skeleton::traverse(osg::NodeVisitor& nv) void Skeleton::traverse(osg::NodeVisitor& nv)
@ -160,6 +162,16 @@ void Skeleton::traverse(osg::NodeVisitor& nv)
osg::Group::traverse(nv); osg::Group::traverse(nv);
} }
void Skeleton::childInserted(unsigned int)
{
markDirty();
}
void Skeleton::childRemoved(unsigned int, unsigned int)
{
markDirty();
}
Bone::Bone() Bone::Bone()
: mNode(NULL) : mNode(NULL)
{ {

@ -53,10 +53,12 @@ namespace SceneUtil
bool getActive() const; bool getActive() const;
/// If a new RigGeometry is added after the Skeleton has already been rendered, you must call markDirty(). void traverse(osg::NodeVisitor& nv);
void markDirty(); void markDirty();
void traverse(osg::NodeVisitor& nv); virtual void childInserted(unsigned int);
virtual void childRemoved(unsigned int, unsigned int);
private: private:
// The root bone is not a "real" bone, it has no corresponding node in the scene graph. // The root bone is not a "real" bone, it has no corresponding node in the scene graph.

@ -102,10 +102,17 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> v
if (evt.key.keysym.sym == SDLK_F3) if (evt.key.keysym.sym == SDLK_F3)
mViewer->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_F3); mViewer->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_F3);
break;
case SDL_TEXTEDITING:
break; break;
case SDL_TEXTINPUT: case SDL_TEXTINPUT:
mKeyboardListener->textInput(evt.text); mKeyboardListener->textInput(evt.text);
break; break;
#if SDL_VERSION_ATLEAST(2, 0, 4)
case SDL_KEYMAPCHANGED:
break;
#endif
case SDL_JOYHATMOTION: //As we manage everything with GameController, don't even bother with these. case SDL_JOYHATMOTION: //As we manage everything with GameController, don't even bother with these.
case SDL_JOYAXISMOTION: case SDL_JOYAXISMOTION:
case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONDOWN:

@ -6,6 +6,7 @@
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <SDL_events.h> #include <SDL_events.h>
#include <SDL_version.h>
#include "OISCompat.hpp" #include "OISCompat.hpp"
#include "events.hpp" #include "events.hpp"

@ -3,9 +3,9 @@ A Tour through OpenMW CS: making a magic ring
In this first chapter we will create a mod that adds a new ring with a simple In this first chapter we will create a mod that adds a new ring with a simple
enchantment to the game. The ring will give its wearer a permanent Night Vision enchantment to the game. The ring will give its wearer a permanent Night Vision
effect while being worn. You don't need prior knowledge about modding effect while being worn. You do not need previous Morrowind modding experience,
Morrowind, but you should be familiar with the game itself. There will be no but you should be familiar with the game itself. There will be no
scripting necessary, we chan achieve everything using just what the base game scripting necessary, we can achieve everything using just what the base game
offers out of the box. Before continuing make sure that OpenMW is properly offers out of the box. Before continuing make sure that OpenMW is properly
installed and playable. installed and playable.
@ -13,7 +13,7 @@ installed and playable.
Adding the ring to the game's records Adding the ring to the game's records
************************************* *************************************
In this first section we will define what our new ring is, what it looks like In this first section we will define what our new ring is, what it looks like,
and what it does. Getting it to work is the first step before we go further. and what it does. Getting it to work is the first step before we go further.
@ -28,11 +28,11 @@ options: create a new game, create a new addon, edit a content file.
:alt: Opening dialogue with three option and setting button (the wrench) :alt: Opening dialogue with three option and setting button (the wrench)
The first option is for creating an entirely new game, that's not what we want. The first option is for creating an entirely new game, that's not what we want.
We want to edit an existing game, so choose the second one. When you save your We want to edit an existing game, so choose the second option. When you save your
addon you can use the third option to open it again. addon you can use the third option to open it again.
You will be presented with another window where you get to chose the content to You will be presented with another window where you get to choose the content to
edit and the name of your project. We have to chose at least a base game, and edit and the name of your project. Then we have to select at least the base game and
optionally a number of other addons we want to depend on. The name of the optionally a number of other addons we want to depend on. The name of the
project is arbitrary, it will be used to identify the addon later in the OpenMW project is arbitrary, it will be used to identify the addon later in the OpenMW
launcher. launcher.
@ -41,7 +41,7 @@ launcher.
:alt: Creation dialogue for a new project, pick content modules and name :alt: Creation dialogue for a new project, pick content modules and name
Choose Morrowind as your content file and enter `Ring of Night Vision` as the Choose Morrowind as your content file and enter `Ring of Night Vision` as the
name. We could also chose further content files as dependencies if we wanted name. We could also choose further content files as dependencies if we wanted
to, but for this mod the base game is enough. to, but for this mod the base game is enough.
Once the addon has been created you will be presented with a table. If you see Once the addon has been created you will be presented with a table. If you see
@ -67,7 +67,7 @@ of the table are the attributes of each object.
Morrowind uses something called a *relational database* for game data. If you Morrowind uses something called a *relational database* for game data. If you
are not familiar with the term, it means that every type of thing can be are not familiar with the term, it means that every type of thing can be
expressed as a *table*: there is a table for objects, a table for enchantments, expressed as a *table*: there is a table for objects, a table for enchantments,
a table for icons, one for meshes, and so on. Properties of an entry must be a table for icons, one for meshes and so on. Properties of an entry must be
simple values, like numbers or text strings. If we want a more complicated simple values, like numbers or text strings. If we want a more complicated
property we need to reference an entry from another table. There are a few property we need to reference an entry from another table. There are a few
exceptions to this though, some tables do have subtables. The effects of exceptions to this though, some tables do have subtables. The effects of
@ -95,7 +95,7 @@ holding Shift to edit it (this is a configurable shortcut), but there is a
better way: right-click the row of our new record and chose *Edit Record*, a better way: right-click the row of our new record and chose *Edit Record*, a
new panel will open. new panel will open.
We can right-click the row of our new record and chose *Edit Record*, a We can right-click the row of our new record and select *Edit Record*, a
new panel will open. Alternatively we can also define a configurable shortcut new panel will open. Alternatively we can also define a configurable shortcut
instead of using the context menu; the default is double-clicking while instead of using the context menu; the default is double-clicking while
holding down the shift key. holding down the shift key.

@ -188,13 +188,13 @@ clamp lighting = true
# If this option is enabled, normal maps are automatically recognized and used if they are named appropriately # If this option is enabled, normal maps are automatically recognized and used if they are named appropriately
# (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). # (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds).
# If this option is disabled, normal maps are only used if they are explicitely listed within the mesh file (.nif or .osg file). # If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file).
# Affects objects. # Affects objects.
auto use object normal maps = false auto use object normal maps = false
# If this option is enabled, specular maps are automatically recognized and used if they are named appropriately # If this option is enabled, specular maps are automatically recognized and used if they are named appropriately
# (see 'specular map pattern', e.g. for a base texture foo.dds, the specular map texture would have to be named foo_spec.dds). # (see 'specular map pattern', e.g. for a base texture foo.dds, the specular map texture would have to be named foo_spec.dds).
# If this option is disabled, normal maps are only used if they are explicitely listed within the mesh file (.osg file, not supported in .nif files). # If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.osg file, not supported in .nif files).
# Affects objects. # Affects objects.
auto use object specular maps = false auto use object specular maps = false

Loading…
Cancel
Save