diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f5e21c61..0d380aea0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,10 @@ endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) # Macros include(OpenMWMacros) +if (ANDROID) + set(CMAKE_FIND_ROOT_PATH ${OPENMW_DEPENDENCIES_DIR} "${CMAKE_FIND_ROOT_PATH}") +endif (ANDROID) + # doxygen main page configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/docs/mainpage.hpp") @@ -102,7 +106,6 @@ set(LIBDIR ${CMAKE_SOURCE_DIR}/libs) set(OENGINE_OGRE ${LIBDIR}/openengine/ogre/renderer.cpp - ${LIBDIR}/openengine/ogre/fader.cpp ${LIBDIR}/openengine/ogre/lights.cpp ${LIBDIR}/openengine/ogre/selectionbuffer.cpp ${LIBDIR}/openengine/ogre/imagerotate.cpp @@ -233,7 +236,12 @@ if(OGRE_STATIC) list(APPEND OGRE_STATIC_PLUGINS ${Cg_LIBRARIES}) endif(Cg_FOUND) +if (ANDROID) + add_static_ogre_plugin(RenderSystem_GLES2) +else () add_static_ogre_plugin(RenderSystem_GL) +endif () + if(WIN32) add_static_ogre_plugin(RenderSystem_Direct3D9) endif(WIN32) diff --git a/apps/opencs/model/world/resourcetable.cpp b/apps/opencs/model/world/resourcetable.cpp index 86de0a6a6..58e03ff6d 100644 --- a/apps/opencs/model/world/resourcetable.cpp +++ b/apps/opencs/model/world/resourcetable.cpp @@ -50,7 +50,7 @@ QVariant CSMWorld::ResourceTable::headerData (int section, Qt::Orientation orien return QVariant(); if (role==ColumnBase::Role_Flags) - return ColumnBase::Flag_Table; + return section==0 ? ColumnBase::Flag_Table : 0; switch (section) { diff --git a/apps/opencs/view/widget/pushbutton.cpp b/apps/opencs/view/widget/pushbutton.cpp index 1d53d2393..307ea1a75 100644 --- a/apps/opencs/view/widget/pushbutton.cpp +++ b/apps/opencs/view/widget/pushbutton.cpp @@ -26,6 +26,8 @@ void CSVWidget::PushButton::setExtendedToolTip() "

(left click to activate," "
shift-left click to activate and keep panel open)"; + break; + case Type_Toggle: tooltip += "

(left click to "; diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index 23bc76000..89c27970f 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -6,14 +6,35 @@ #include #include +#include "../../model/doc/document.hpp" + #include "../../model/world/universalid.hpp" #include "../../model/world/tablemimedata.hpp" -CSVWorld::ScriptEdit::ScriptEdit (QWidget* parent, const CSMDoc::Document& document) : +#include "scripthighlighter.hpp" + +CSVWorld::ScriptEdit::ChangeLock::ChangeLock (ScriptEdit& edit) : mEdit (edit) +{ + ++mEdit.mChangeLocked; +} + +CSVWorld::ScriptEdit::ChangeLock::~ChangeLock() +{ + --mEdit.mChangeLocked; +} + + +CSVWorld::ScriptEdit::ScriptEdit (const CSMDoc::Document& document, QWidget* parent) : QTextEdit (parent), mDocument (document), - mWhiteListQoutes("^[a-z|_]{1}[a-z|0-9|_]{0,}$", Qt::CaseInsensitive) + mWhiteListQoutes("^[a-z|_]{1}[a-z|0-9|_]{0,}$", Qt::CaseInsensitive), + mChangeLocked (0) { + setAcceptRichText (false); + setLineWrapMode (QTextEdit::NoWrap); + setTabStopWidth (4); + setUndoRedoEnabled (false); // we use OpenCS-wide undo/redo instead + mAllowedTypes <invalidateIds(); + + if (!mUpdateTimer.isActive()) + mUpdateTimer.start (0); +} + +void CSVWorld::ScriptEdit::updateHighlighting() +{ + if (isChangeLocked()) + return; + + ChangeLock lock (*this); + + mHighlighter->rehighlight(); +} \ No newline at end of file diff --git a/apps/opencs/view/world/scriptedit.hpp b/apps/opencs/view/world/scriptedit.hpp index b4627c2fe..5713f2426 100644 --- a/apps/opencs/view/world/scriptedit.hpp +++ b/apps/opencs/view/world/scriptedit.hpp @@ -1,8 +1,9 @@ #ifndef SCRIPTEDIT_H #define SCRIPTEDIT_H -#include +#include #include +#include #include "../../model/world/universalid.hpp" @@ -16,11 +17,43 @@ namespace CSMDoc namespace CSVWorld { + class ScriptHighlighter; + class ScriptEdit : public QTextEdit { Q_OBJECT + public: - ScriptEdit (QWidget* parent, const CSMDoc::Document& document); + + class ChangeLock + { + ScriptEdit& mEdit; + + ChangeLock (const ChangeLock&); + ChangeLock& operator= (const ChangeLock&); + + public: + + ChangeLock (ScriptEdit& edit); + ~ChangeLock(); + }; + + friend class ChangeLock; + + private: + + int mChangeLocked; + ScriptHighlighter *mHighlighter; + QTimer mUpdateTimer; + + public: + + ScriptEdit (const CSMDoc::Document& document, QWidget* parent); + + /// Should changes to the data be ignored (i.e. not cause updated)? + /// + /// \note This mechanism is used to avoid infinite update recursions + bool isChangeLocked() const; private: QVector mAllowedTypes; @@ -34,6 +67,12 @@ namespace CSVWorld void dragMoveEvent (QDragMoveEvent* event); bool stringNeedsQuote(const std::string& id) const; + + private slots: + + void idListChanged(); + + void updateHighlighting(); }; } #endif // SCRIPTEDIT_H \ No newline at end of file diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index fa41151ca..d2cde1db2 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -3,8 +3,6 @@ #include -#include - #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/data.hpp" @@ -12,28 +10,12 @@ #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" -#include "scripthighlighter.hpp" #include "scriptedit.hpp" -CSVWorld::ScriptSubView::ChangeLock::ChangeLock (ScriptSubView& view) : mView (view) -{ - ++mView.mChangeLocked; -} - -CSVWorld::ScriptSubView::ChangeLock::~ChangeLock() -{ - --mView.mChangeLocked; -} - CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id), mDocument (document), mColumn (-1), mChangeLocked (0) +: SubView (id), mDocument (document), mColumn (-1) { - setWidget (mEditor = new ScriptEdit (this, mDocument)); - - mEditor->setAcceptRichText (false); - mEditor->setLineWrapMode (QTextEdit::NoWrap); - mEditor->setTabStopWidth (4); - mEditor->setUndoRedoEnabled (false); // we use OpenCS-wide undo/redo instead + setWidget (mEditor = new ScriptEdit (mDocument, this)); mModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); @@ -58,14 +40,6 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: connect (mModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (rowsAboutToBeRemoved (const QModelIndex&, int, int))); - - connect (&document.getData(), SIGNAL (idListChanged()), this, SLOT (idListChanged())); - - mHighlighter = new ScriptHighlighter (document.getData(), mEditor->document()); - - connect (&mUpdateTimer, SIGNAL (timeout()), this, SLOT (updateHighlighting())); - - mUpdateTimer.setSingleShot (true); } void CSVWorld::ScriptSubView::setEditLock (bool locked) @@ -73,20 +47,12 @@ void CSVWorld::ScriptSubView::setEditLock (bool locked) mEditor->setReadOnly (locked); } -void CSVWorld::ScriptSubView::idListChanged() -{ - mHighlighter->invalidateIds(); - - if (!mUpdateTimer.isActive()) - mUpdateTimer.start (0); -} - void CSVWorld::ScriptSubView::textChanged() { - if (mChangeLocked) + if (mEditor->isChangeLocked()) return; - ChangeLock lock (*this); + ScriptEdit::ChangeLock lock (*mEditor); mDocument.getUndoStack().push (new CSMWorld::ModifyCommand (*mModel, mModel->getModelIndex (getUniversalId().getId(), mColumn), mEditor->toPlainText())); @@ -94,10 +60,10 @@ void CSVWorld::ScriptSubView::textChanged() void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - if (mChangeLocked) + if (mEditor->isChangeLocked()) return; - ChangeLock lock (*this); + ScriptEdit::ChangeLock lock (*mEditor); QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); @@ -118,12 +84,3 @@ void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, i deleteLater(); } -void CSVWorld::ScriptSubView::updateHighlighting() -{ - if (mChangeLocked) - return; - - ChangeLock lock (*this); - - mHighlighter->rehighlight(); -} \ No newline at end of file diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 7ceab70ba..77127d9be 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -3,9 +3,6 @@ #include "../doc/subview.hpp" -#include - -class QTextEdit; class QModelIndex; namespace CSMDoc @@ -20,34 +17,16 @@ namespace CSMWorld namespace CSVWorld { - class ScriptHighlighter; + class ScriptEdit; class ScriptSubView : public CSVDoc::SubView { Q_OBJECT - QTextEdit *mEditor; + ScriptEdit *mEditor; CSMDoc::Document& mDocument; CSMWorld::IdTable *mModel; int mColumn; - int mChangeLocked; - ScriptHighlighter *mHighlighter; - QTimer mUpdateTimer; - - class ChangeLock - { - ScriptSubView& mView; - - ChangeLock (const ChangeLock&); - ChangeLock& operator= (const ChangeLock&); - - public: - - ChangeLock (ScriptSubView& view); - ~ChangeLock(); - }; - - friend class ChangeLock; public: @@ -57,17 +36,11 @@ namespace CSVWorld public slots: - void idListChanged(); - void textChanged(); void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); - - private slots: - - void updateHighlighting(); }; } diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index d8966bdcf..6df6210ee 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -1,9 +1,17 @@ # local files -set(GAME - main.cpp - engine.cpp -) -if(NOT WIN32) +if (NOT ANDROID) + set(GAME + main.cpp + engine.cpp + ) +else() + set(GAME + main.cpp + android_main.c + engine.cpp + ) +endif() +if(NOT WIN32 AND NOT ANDROID) set(GAME ${GAME} crashcatcher.cpp) endif() set(GAME_HEADER @@ -15,7 +23,7 @@ add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows characterpreview globalmap videoplayer ripplesimulation refraction - terrainstorage renderconst effectmanager weaponanimation terraingrid + terrainstorage renderconst effectmanager weaponanimation ) add_openmw_dir (mwinput @@ -33,7 +41,7 @@ add_openmw_dir (mwgui merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog - recharge mode videowidget backgroundimage itemwidget + recharge mode videowidget backgroundimage itemwidget screenfader ) add_openmw_dir (mwdialogue @@ -82,19 +90,33 @@ add_openmw_dir (mwbase ) # Main executable -set(BOOST_COMPONENTS system filesystem program_options thread wave) +if (ANDROID) + set(BOOST_COMPONENTS system filesystem program_options thread wave atomic) +else () + set(BOOST_COMPONENTS system filesystem program_options thread wave) +endif () + if(WIN32) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) endif(WIN32) find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) -add_executable(openmw - ${OPENMW_LIBS} ${OPENMW_LIBS_HEADER} - ${OPENMW_FILES} - ${GAME} ${GAME_HEADER} - ${APPLE_BUNDLE_RESOURCES} -) +if (NOT ANDROID) + add_executable(openmw + ${OPENMW_LIBS} ${OPENMW_LIBS_HEADER} + ${OPENMW_FILES} + ${GAME} ${GAME_HEADER} + ${APPLE_BUNDLE_RESOURCES} + ) +else () + add_library(openmw + SHARED + ${OPENMW_LIBS} ${OPENMW_LIBS_HEADER} + ${OPENMW_FILES} + ${GAME} ${GAME_HEADER} + ) +endif () # Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING # when we change the backend. @@ -116,6 +138,23 @@ target_link_libraries(openmw components ) +if (ANDROID) + target_link_libraries(openmw + ${OGRE_STATIC_PLUGINS} + EGL + android + log + dl + MyGUI.OgrePlatform + MyGUIEngineStatic + Plugin_StrangeButtonStatic + cpufeatures + BulletCollision + BulletDynamics + LinearMath + ) +endif (ANDROID) + if (USE_SYSTEM_TINYXML) target_link_libraries(openmw ${TINYXML_LIBRARIES}) endif() diff --git a/apps/openmw/android_main.c b/apps/openmw/android_main.c new file mode 100644 index 000000000..76da91c4f --- /dev/null +++ b/apps/openmw/android_main.c @@ -0,0 +1,43 @@ + +#include "../../SDL_internal.h" + +#ifdef __ANDROID__ +#include "SDL_main.h" + + +/******************************************************************************* + Functions called by JNI +*******************************************************************************/ +#include + +/* Called before to initialize JNI bindings */ + + + +extern void SDL_Android_Init(JNIEnv* env, jclass cls); + + +int Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj) +{ + + SDL_Android_Init(env, cls); + + SDL_SetMainReady(); + + +/* Run the application code! */ + + int status; + char *argv[2]; + argv[0] = SDL_strdup("openmw"); + argv[1] = NULL; + status = main(1, argv); + + /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */ + /* exit(status); */ + + return status; +} + +#endif /* __ANDROID__ */ + diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 66eae5f5d..2e5689fa8 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -181,7 +181,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mActivationDistanceOverride(-1) , mGrab(true) , mScriptBlacklistUse (true) - + , mExportFonts(false) { std::srand ( std::time(NULL) ); MWClass::registerClasses(); @@ -372,7 +372,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) MWGui::WindowManager* window = new MWGui::WindowManager( mExtensions, mFpsLevel, mOgre, mCfgMgr.getLogPath().string() + std::string("/"), - mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding); + mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts); mEnvironment.setWindowManager (window); // Create sound system @@ -576,4 +576,9 @@ void OMW::Engine::setScriptBlacklist (const std::vector& list) void OMW::Engine::setScriptBlacklistUse (bool use) { mScriptBlacklistUse = use; -} \ No newline at end of file +} + +void OMW::Engine::enableFontExport(bool exportFonts) +{ + mExportFonts = exportFonts; +} diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 203379a93..10c1f6ff2 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -83,6 +83,8 @@ namespace OMW // Grab mouse? bool mGrab; + bool mExportFonts; + Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; @@ -187,6 +189,8 @@ namespace OMW void setScriptBlacklistUse (bool use); + void enableFontExport(bool exportFonts); + private: Files::ConfigurationManager& mCfgMgr; }; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 6c699bed1..57b3e045e 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -168,6 +168,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("no-grab", "Don't grab mouse cursor") + ("export-fonts", bpo::value()->implicit_value(true) + ->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory") + ("activate-dist", bpo::value ()->default_value (-1), "activation distance override"); bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) @@ -268,6 +271,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setSoundUsage(!variables["no-sound"].as()); engine.setFallbackValues(variables["fallback"].as().mMap); engine.setActivationDistanceOverride (variables["activate-dist"].as()); + engine.enableFontExport(variables["export-fonts"].as()); return true; } diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index f718972b9..532c1c3fa 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -128,6 +128,7 @@ namespace MWBase OffenseType type, int arg=0) = 0; virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0) = 0; + virtual void actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 0; /// Utility to check if taking this item is illegal and calling commitCrime if so virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, int count) = 0; /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 8b407c9ba..f1c69e622 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -331,6 +331,15 @@ namespace MWBase virtual void removeCurrentModal(MWGui::WindowModal* input) = 0; virtual void pinWindow (MWGui::GuiWindow window) = 0; + + /// Fade the screen in, over \a time seconds + virtual void fadeScreenIn(const float time) = 0; + /// Fade the screen out to black, over \a time seconds + virtual void fadeScreenOut(const float time) = 0; + /// Fade the screen to a specified percentage of black, over \a time seconds + virtual void fadeScreenTo(const int percent, const float time) = 0; + /// Darken the screen by \a factor (1.0 = no darkening). Works independently from screen fading. + virtual void setScreenFactor (float factor) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 62da2682d..a3a127d63 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -17,11 +17,6 @@ namespace Ogre namespace OEngine { - namespace Render - { - class Fader; - } - namespace Physic { class PhysicEngine; @@ -113,9 +108,6 @@ namespace MWBase virtual void readRecord (ESM::ESMReader& reader, int32_t type, const std::map& contentFileMap) = 0; - virtual OEngine::Render::Fader* getFader() = 0; - ///< \todo remove this function. Rendering details should not be exposed. - virtual MWWorld::CellStore *getExterior (int x, int y) = 0; virtual MWWorld::CellStore *getInterior (const std::string& name) = 0; @@ -271,8 +263,9 @@ namespace MWBase /// use the "Head" node, or alternatively the "Bip01 Head" node as a basis. virtual std::pair getHitContact(const MWWorld::Ptr &ptr, float distance) = 0; - virtual void adjustPosition (const MWWorld::Ptr& ptr) = 0; + virtual void adjustPosition (const MWWorld::Ptr& ptr, bool force) = 0; ///< Adjust position after load to be on ground. Must be called after model load. + /// @param force do this even if the ptr is flying virtual void fixPosition (const MWWorld::Ptr& actor) = 0; ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index b3fc5890b..f842528d3 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -157,15 +157,19 @@ namespace MWClass float iWeight = gmst.find (typeGmst)->getInt(); - if (iWeight * gmst.find ("fLightMaxMod")->getFloat()>= - ref->mBase->mData.mWeight) + float epsilon = 5e-4; + + if (ref->mBase->mData.mWeight == 0) + return ESM::Skill::Unarmored; + + if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fLightMaxMod")->getFloat() + epsilon) return ESM::Skill::LightArmor; - if (iWeight * gmst.find ("fMedMaxMod")->getFloat()>= - ref->mBase->mData.mWeight) + if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fMedMaxMod")->getFloat() + epsilon) return ESM::Skill::MediumArmor; - return ESM::Skill::HeavyArmor; + else + return ESM::Skill::HeavyArmor; } int Armor::getValue (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index c175ce3eb..36937ac7e 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -148,9 +148,9 @@ namespace MWClass return ref->mBase->mId; } - void Creature::adjustPosition(const MWWorld::Ptr& ptr) const + void Creature::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { - MWBase::Environment::get().getWorld()->adjustPosition(ptr); + MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const @@ -255,23 +255,7 @@ namespace MWClass if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false); - - // Weapon health is reduced by 1 even if the attack misses - const bool weaphashealth = !weapon.isEmpty() && weapon.getClass().hasItemHealth(weapon); - if(weaphashealth) - { - int weaphealth = weapon.getClass().getItemHealth(weapon); - - if (!MWBase::Environment::get().getWorld()->getGodModeState()) - { - weaphealth -= std::min(1, weaphealth); - weapon.getCellRef().setCharge(weaphealth); - } - - // Weapon broken? unequip it - if (weapon.getCellRef().getCharge() == 0) - weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr); - } + MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } @@ -298,7 +282,6 @@ namespace MWClass if (!weapon.isEmpty()) { - const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); const unsigned char *attack = NULL; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; @@ -309,26 +292,10 @@ namespace MWClass if(attack) { damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); - damage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f); - if(weaphashealth) - { - int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); - int weaphealth = weapon.getClass().getItemHealth(weapon); - damage *= float(weaphealth) / weapmaxhealth; - - if (!MWBase::Environment::get().getWorld()->getGodModeState()) - { - // Reduce weapon charge by at least one, but cap at 0 - weaphealth -= std::min(std::max(1, - (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weaphealth); - - weapon.getCellRef().setCharge(weaphealth); - } - - // Weapon broken? unequip it - if (weapon.getCellRef().getCharge() == 0) - weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr); - } + damage *= gmst.find("fDamageStrengthBase")->getFloat() + + (stats.getAttribute(ESM::Attribute::Strength).getModified() * gmst.find("fDamageStrengthMult")->getFloat() * 0.1); + MWMechanics::adjustWeaponDamage(damage, weapon); + MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); } // Apply "On hit" enchanted weapons @@ -366,16 +333,11 @@ namespace MWClass getCreatureStats(ptr).setAttacked(true); // Self defense - if ( ((!attacker.isEmpty() && attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr)) - || attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) - && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker) - && (canWalk(ptr) || canFly(ptr) || canSwim(ptr)) // No retaliation for totally static creatures + if ((canWalk(ptr) || canFly(ptr) || canSwim(ptr)) // No retaliation for totally static creatures // (they have no movement or attacks anyway) - ) + && !attacker.isEmpty()) { - // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. - // Note: accidental or collateral damage attacks are ignored. - MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); + MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } if(!successful) @@ -406,18 +368,21 @@ namespace MWClass if (damage > 0.f) { - // Check for knockdown - float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); - float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() - * getGmst().iKnockDownOddsMult->getInt() * 0.01 + getGmst().iKnockDownOddsBase->getInt(); - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) + if (!attacker.isEmpty()) { - getCreatureStats(ptr).setKnockedDown(true); + // Check for knockdown + float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); + float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() + * getGmst().iKnockDownOddsMult->getInt() * 0.01 + getGmst().iKnockDownOddsBase->getInt(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) + { + getCreatureStats(ptr).setKnockedDown(true); + } + else + getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? } - else - getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? damage = std::max(1.f, damage); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 6920a4b1d..1820d4ea4 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -49,7 +49,9 @@ namespace MWClass virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; - virtual void adjustPosition(const MWWorld::Ptr& ptr) const; + virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; + ///< Adjust position to stand on ground. Must be called post model load + /// @param force do this even if the ptr is flying virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 464c78e9f..8c2decb72 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -56,8 +56,11 @@ namespace MWClass { const std::string model = getModel(ptr); + MWWorld::LiveCellRef *ref = + ptr.get(); + // Insert even if model is empty, so that the light is added - renderingInterface.getObjects().insertModel(ptr, model); + renderingInterface.getObjects().insertModel(ptr, model, false, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } void Light::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d087febd0..662672204 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -403,9 +403,9 @@ namespace MWClass return ref->mBase->mId; } - void Npc::adjustPosition(const MWWorld::Ptr& ptr) const + void Npc::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { - MWBase::Environment::get().getWorld()->adjustPosition(ptr); + MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const @@ -529,24 +529,7 @@ namespace MWClass if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { othercls.onHit(victim, 0.0f, false, weapon, ptr, false); - - // Weapon health is reduced by 1 even if the attack misses - const bool weaphashealth = !weapon.isEmpty() && weapon.getClass().hasItemHealth(weapon); - if(weaphashealth) - { - int weaphealth = weapon.getClass().getItemHealth(weapon); - - if (!MWBase::Environment::get().getWorld()->getGodModeState()) - { - weaphealth -= std::min(1, weaphealth); - weapon.getCellRef().setCharge(weaphealth); - } - - // Weapon broken? unequip it - if (weaphealth == 0) - weapon = *inv.unequipItem(weapon, ptr); - } - + MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } @@ -555,7 +538,6 @@ namespace MWClass MWMechanics::NpcStats &stats = getNpcStats(ptr); if(!weapon.isEmpty()) { - const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); const unsigned char *attack = NULL; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; @@ -568,27 +550,9 @@ namespace MWClass damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); damage *= gmst.fDamageStrengthBase->getFloat() + (stats.getAttribute(ESM::Attribute::Strength).getModified() * gmst.fDamageStrengthMult->getFloat() * 0.1); - if(weaphashealth) - { - int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); - int weaphealth = weapon.getClass().getItemHealth(weapon); - - damage *= float(weaphealth) / weapmaxhealth; - - if (!MWBase::Environment::get().getWorld()->getGodModeState()) - { - // Reduce weapon charge by at least one, but cap at 0 - weaphealth -= std::min(std::max(1, - (int)(damage * store.find("fWeaponDamageMult")->getFloat())), weaphealth); - - weapon.getCellRef().setCharge(weaphealth); - } - - // Weapon broken? unequip it - if (weaphealth == 0) - weapon = *inv.unequipItem(weapon, ptr); - } } + MWMechanics::adjustWeaponDamage(damage, weapon); + MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); healthdmg = true; } else @@ -675,26 +639,13 @@ namespace MWClass // NOTE: 'object' and/or 'attacker' may be empty. - if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) - { - // Attacking peaceful NPCs is a crime - if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker) - && !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker)) - MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); - - if (!attacker.isEmpty() && attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr) - && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker)) - { - // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. - // Note: accidental or collateral damage attacks are ignored. - MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); - } - } - bool wasDead = getCreatureStats(ptr).isDead(); getCreatureStats(ptr).setAttacked(true); + if (!attacker.isEmpty()) + MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); + if(!successful) { // TODO: Handle HitAttemptOnMe script function @@ -721,7 +672,7 @@ namespace MWClass if (damage < 0.001f) damage = 0; - if(damage > 0.0f) + if(damage > 0.0f && !attacker.isEmpty()) { // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying // something, alert the character controller, scripts, etc. @@ -749,7 +700,7 @@ namespace MWClass else getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? - if(damage > 0 && ishealth && !attacker.isEmpty()) // Don't use armor mitigation for fall damage + if(damage > 0 && ishealth) { // Hit percentages: // cuirass = 30% @@ -967,8 +918,6 @@ namespace MWClass float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() * gmst.fAthleticsRunBonus->getFloat() + gmst.fBaseRunMultiplier->getFloat()); - if(npcdata->mNpcStats.isWerewolf()) - runSpeed *= gmst.fWereWolfRunMult->getFloat(); float moveSpeed; if(normalizedEncumbrance >= 1.0f) @@ -1000,6 +949,9 @@ namespace MWClass if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0) moveSpeed *= 0.75f; + if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing) + moveSpeed *= gmst.fWereWolfRunMult->getFloat(); + return moveSpeed; } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index b0ca0a658..71e34e498 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -55,7 +55,9 @@ namespace MWClass virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; - virtual void adjustPosition(const MWWorld::Ptr& ptr) const; + virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; + ///< Adjust position to stand on ground. Must be called post model load + /// @param force do this even if the ptr is flying virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index e5d0d279e..2da213c8d 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -145,11 +145,8 @@ namespace MWClass MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); for (MWGui::Widgets::SpellEffectList::iterator it = info.effects.begin(); it != info.effects.end(); ++it) { - it->mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue) - || (i == 1 && alchemySkill >= fWortChanceValue*2) - || (i == 2 && alchemySkill >= fWortChanceValue*3) - || (i == 3 && alchemySkill >= fWortChanceValue*4)); - + it->mKnown = (i <= 1 && alchemySkill >= fWortChanceValue) + || (i <= 3 && alchemySkill >= fWortChanceValue*2); ++i; } diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index f0212031e..0bdabb222 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -28,6 +28,16 @@ namespace MWGui { + DragAndDrop::DragAndDrop() + : mDraggedWidget(NULL) + , mDraggedCount(0) + , mSourceModel(NULL) + , mSourceView(NULL) + , mSourceSortModel(NULL) + , mIsOnDragAndDrop(NULL) + { + } + void DragAndDrop::startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count) { mItem = sourceModel->getItem(index); @@ -36,7 +46,6 @@ namespace MWGui mSourceView = sourceView; mSourceSortModel = sortModel; mIsOnDragAndDrop = true; - mDragAndDropWidget->setVisible(true); // 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, @@ -73,8 +82,13 @@ namespace MWGui mSourceSortModel->addDragItem(mItem.mBase, count); } - ItemWidget* baseWidget = mDragAndDropWidget->createWidget - ("MW_ItemIcon", MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); + ItemWidget* baseWidget = MyGUI::Gui::getInstance().createWidget("MW_ItemIcon", 0, 0, 42, 42, MyGUI::Align::Default, "DragAndDrop"); + + Controllers::ControllerFollowMouse* controller = + MyGUI::ControllerManager::getInstance().createItem(Controllers::ControllerFollowMouse::getClassTypeName()) + ->castType(); + MyGUI::ControllerManager::getInstance().addItem(baseWidget, controller); + mDraggedWidget = baseWidget; baseWidget->setItem(mItem.mBase); baseWidget->setNeedMouseFocus(false); @@ -99,8 +113,6 @@ namespace MWGui std::string sound = mItem.mBase.getClass().getDownSoundId(mItem.mBase); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - mDragAndDropWidget->setVisible(false); - // If item is dropped where it was taken from, we don't need to do anything - // otherwise, do the transfer if (targetModel != mSourceModel) diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 79951f70e..e32c6efaf 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -33,13 +33,14 @@ namespace MWGui public: bool mIsOnDragAndDrop; MyGUI::Widget* mDraggedWidget; - MyGUI::Widget* mDragAndDropWidget; ItemModel* mSourceModel; ItemView* mSourceView; SortFilterItemModel* mSourceSortModel; ItemStack mItem; int mDraggedCount; + DragAndDrop(); + void startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count); void drop (ItemModel* targetModel, ItemView* targetView); diff --git a/apps/openmw/mwgui/controllers.cpp b/apps/openmw/mwgui/controllers.cpp index e62fb3fce..a50e6496c 100644 --- a/apps/openmw/mwgui/controllers.cpp +++ b/apps/openmw/mwgui/controllers.cpp @@ -1,5 +1,7 @@ #include "controllers.hpp" +#include + namespace MWGui { namespace Controllers @@ -50,5 +52,17 @@ namespace MWGui { } + // ------------------------------------------------------------- + + void ControllerFollowMouse::prepareItem(MyGUI::Widget *_widget) + { + } + + bool ControllerFollowMouse::addTime(MyGUI::Widget *_widget, float _time) + { + _widget->setPosition(MyGUI::InputManager::getInstance().getMousePosition()); + return true; + } + } } diff --git a/apps/openmw/mwgui/controllers.hpp b/apps/openmw/mwgui/controllers.hpp index 798acde62..937855606 100644 --- a/apps/openmw/mwgui/controllers.hpp +++ b/apps/openmw/mwgui/controllers.hpp @@ -40,6 +40,17 @@ namespace MWGui bool mEnabled; float mTimeLeft; }; + + /// Automatically positions a widget below the mouse cursor. + class ControllerFollowMouse : + public MyGUI::ControllerItem + { + MYGUI_RTTI_DERIVED( ControllerFollowMouse ) + + private: + bool addTime(MyGUI::Widget* _widget, float _time); + void prepareItem(MyGUI::Widget* _widget); + }; } } diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index e89777bee..8612617c1 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -338,7 +338,7 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft, item.getClass().getValue(item)); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); return; } } diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp index 92d9a25b6..b1cb7fccf 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/apps/openmw/mwgui/fontloader.cpp @@ -134,7 +134,7 @@ namespace MWGui mEncoding = encoding; } - void FontLoader::loadAllFonts() + void FontLoader::loadAllFonts(bool exportToFile) { Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) @@ -142,7 +142,7 @@ namespace MWGui Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "*.fnt"); for (Ogre::StringVector::iterator resource = resourcesInThisGroup->begin(); resource != resourcesInThisGroup->end(); ++resource) { - loadFont(*resource); + loadFont(*resource, exportToFile); } } } @@ -168,7 +168,7 @@ namespace MWGui float ascent; } GlyphInfo; - void FontLoader::loadFont(const std::string &fileName) + void FontLoader::loadFont(const std::string &fileName, bool exportToFile) { Ogre::DataStreamPtr file = Ogre::ResourceGroupManager::getSingleton().openResource(fileName); @@ -221,6 +221,9 @@ namespace MWGui width, height, 0, Ogre::PF_BYTE_RGBA); texture->loadImage(image); + if (exportToFile) + image.save(resourceName + ".png"); + // Register the font with MyGUI MyGUI::ResourceManualFont* font = static_cast( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); @@ -240,10 +243,10 @@ namespace MWGui for(int i = 0; i < 256; i++) { - int x1 = data[i].top_left.x*width; - int y1 = data[i].top_left.y*height; - int w = data[i].top_right.x*width - x1; - int h = data[i].bottom_left.y*height - y1; + float x1 = data[i].top_left.x*width; + float y1 = data[i].top_left.y*height; + float w = data[i].top_right.x*width - x1; + float h = data[i].bottom_left.y*height - y1; ToUTF8::Utf8Encoder encoder(mEncoding); unsigned long unicodeVal = utf8ToUnicode(getUtf8(i, encoder, mEncoding)); @@ -257,16 +260,38 @@ namespace MWGui code->addAttribute("advance", data[i].width); code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + code->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); // More hacks! The french game uses several win1252 characters that are not included // in the cp437 encoding of the font. Fall back to similar available characters. if (mEncoding == ToUTF8::CP437) { - std::multimap additional; + std::multimap additional; // additional.insert(std::make_pair(39, 0x2019)); // apostrophe additional.insert(std::make_pair(45, 0x2013)); // dash + additional.insert(std::make_pair(45, 0x2014)); // dash additional.insert(std::make_pair(34, 0x201D)); // right double quotation mark additional.insert(std::make_pair(34, 0x201C)); // left double quotation mark + additional.insert(std::make_pair(44, 0x201A)); + additional.insert(std::make_pair(44, 0x201E)); + additional.insert(std::make_pair(43, 0x2020)); + additional.insert(std::make_pair(94, 0x02C6)); + additional.insert(std::make_pair(37, 0x2030)); + additional.insert(std::make_pair(83, 0x0160)); + additional.insert(std::make_pair(60, 0x2039)); + additional.insert(std::make_pair(79, 0x0152)); + additional.insert(std::make_pair(90, 0x017D)); + additional.insert(std::make_pair(39, 0x2019)); + additional.insert(std::make_pair(126, 0x02DC)); + additional.insert(std::make_pair(84, 0x2122)); + additional.insert(std::make_pair(83, 0x0161)); + additional.insert(std::make_pair(62, 0x203A)); + additional.insert(std::make_pair(111, 0x0153)); + additional.insert(std::make_pair(122, 0x017E)); + additional.insert(std::make_pair(89, 0x0178)); + additional.insert(std::make_pair(156, 0x00A2)); + additional.insert(std::make_pair(46, 0x2026)); + for (std::multimap::iterator it = additional.begin(); it != additional.end(); ++it) { if (it->first != i) @@ -281,6 +306,7 @@ namespace MWGui code->addAttribute("advance", data[i].width); code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + code->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); } } @@ -296,6 +322,7 @@ namespace MWGui cursorCode->addAttribute("advance", data[i].width); cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + cursorCode->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); } // Question mark, use for NotDefined marker (used for glyphs not existing in the font) @@ -310,6 +337,7 @@ namespace MWGui cursorCode->addAttribute("advance", data[i].width); cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + cursorCode->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); } } @@ -327,7 +355,13 @@ namespace MWGui cursorCode->addAttribute("coord", "0 0 0 0"); cursorCode->addAttribute("advance", "0"); cursorCode->addAttribute("bearing", "0 0"); + cursorCode->addAttribute("size", "0 0"); + } + if (exportToFile) + { + xmlDocument.createDeclaration(); + xmlDocument.save(resourceName + ".xml"); } font->deserialization(root, MyGUI::Version(3,2,0)); diff --git a/apps/openmw/mwgui/fontloader.hpp b/apps/openmw/mwgui/fontloader.hpp index 7954b0875..619717840 100644 --- a/apps/openmw/mwgui/fontloader.hpp +++ b/apps/openmw/mwgui/fontloader.hpp @@ -12,12 +12,15 @@ namespace MWGui { public: FontLoader (ToUTF8::FromType encoding); - void loadAllFonts (); + + /// @param exportToFile export the converted fonts (Images and XML with glyph metrics) to files? + void loadAllFonts (bool exportToFile); private: ToUTF8::FromType mEncoding; - void loadFont (const std::string& fileName); + /// @param exportToFile export the converted font (Image and XML with glyph metrics) to files? + void loadFont (const std::string& fileName, bool exportToFile); }; } diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 4d3d04ced..aac5ccbbf 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -1,6 +1,7 @@ #include "formatting.hpp" #include +#include #include "../mwscript/interpretercontext.hpp" @@ -269,15 +270,6 @@ namespace MWGui void BookTextParser::parseImage(std::string tag, bool createWidget) { int src_start = tag.find("SRC=")+5; - std::string image = tag.substr(src_start, tag.find('"', src_start)-src_start); - - // fix texture extension to .dds - if (image.size() > 4) - { - image[image.size()-3] = 'd'; - image[image.size()-2] = 'd'; - image[image.size()-1] = 's'; - } int width_start = tag.find("WIDTH=")+7; int width = boost::lexical_cast(tag.substr(width_start, tag.find('"', width_start)-width_start)); @@ -291,16 +283,8 @@ namespace MWGui MyGUI::IntCoord(0, mHeight, width, height), MyGUI::Align::Left | MyGUI::Align::Top, mParent->getName() + boost::lexical_cast(mParent->getChildCount())); - // Apparently a bug with some morrowind versions, they reference the image without the size suffix. - // So if the image isn't found, try appending the size. - if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("bookart\\"+image)) - { - std::stringstream str; - str << image.substr(0, image.rfind(".")) << "_" << width << "_" << height << image.substr(image.rfind(".")); - image = str.str(); - } - - box->setImageTexture("bookart\\" + image); + std::string image = Misc::ResourceHelpers::correctBookartPath(tag.substr(src_start, tag.find('"', src_start)-src_start), width, height); + box->setImageTexture(image); box->setProperty("NeedMouse", "false"); } @@ -406,7 +390,7 @@ namespace MWGui box->setTextAlign(mTextStyle.mTextAlign); box->setTextColour(mTextStyle.mColour); box->setFontName(mTextStyle.mFont); - box->setCaption(realText); + box->setCaption(MyGUI::TextIterator::toTagsString(realText)); box->setSize(box->getSize().width, box->getTextSize().height); mHeight += box->getTextSize().height; diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 3e753b7f9..3a9aa7dcd 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -59,7 +59,7 @@ namespace MWGui }; - HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) + HUD::HUD(int fpsLevel, DragAndDrop* dragAndDrop) : Layout("openmw_hud.layout") , mHealth(NULL) , mMagicka(NULL) @@ -97,7 +97,7 @@ namespace MWGui , mWeaponSpellTimer(0.f) , mDrowningFlashTheta(0.f) { - setCoord(0,0, width, height); + mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); // Energy bars getWidget(mHealthFrame, "HealthFrame"); @@ -405,11 +405,6 @@ namespace MWGui mDrowningFlashTheta += dt * Ogre::Math::TWO_PI; } - void HUD::onResChange(int width, int height) - { - setCoord(0, 0, width, height); - } - void HUD::setSelectedSpell(const std::string& spellId, int successChancePercent) { const ESM::Spell* spell = diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 56b0ef7b5..bf0419aae 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -15,7 +15,7 @@ namespace MWGui class HUD : public OEngine::GUI::Layout, public LocalMapBase { public: - HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop); + HUD(int fpsLevel, DragAndDrop* dragAndDrop); virtual ~HUD(); void setValue (const std::string& id, const MWMechanics::DynamicStat& value); void setFPS(float fps); @@ -47,7 +47,6 @@ namespace MWGui void setCrosshairVisible(bool visible); void onFrame(float dt); - void onResChange(int width, int height); void setCellName(const std::string& cellName); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index abea68185..e2048a34b 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -37,6 +37,7 @@ namespace MWGui , mLastYSize(0) , mPreview(new MWRender::InventoryPreview(MWBase::Environment::get().getWorld ()->getPlayerPtr())) , mPreviewDirty(true) + , mPreviewResize(true) , mDragAndDrop(dragAndDrop) , mSelectedItem(-1) , mGuiMode(GM_Inventory) @@ -91,8 +92,18 @@ namespace MWGui mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr()); mSortModel = new SortFilterItemModel(mTradeModel); mItemView->setModel(mSortModel); + + mPreview.reset(NULL); + mAvatarImage->setImageTexture(""); + MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().getTexture("CharacterPreview"); + if (tex) + MyGUI::RenderManager::getInstance().destroyTexture(tex); + mPreview.reset(new MWRender::InventoryPreview(mPtr)); mPreview->setup(); + + mPreviewDirty = true; + mPreviewResize = true; } void InventoryWindow::setGuiMode(GuiMode mode) @@ -125,7 +136,7 @@ namespace MWGui Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height); if (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()) - mPreviewDirty = true; + mPreviewResize = true; mMainWidget->setPosition(pos); mMainWidget->setSize(size); @@ -333,7 +344,7 @@ namespace MWGui { mLastXSize = mMainWidget->getSize().width; mLastYSize = mMainWidget->getSize().height; - mPreviewDirty = true; + mPreviewResize = true; } } @@ -366,6 +377,12 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setWeaponVisibility(!mPinned); } + void InventoryWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory); + } + void InventoryWindow::useItem(const MWWorld::Ptr &ptr) { const std::string& script = ptr.getClass().getScript(ptr); @@ -403,9 +420,13 @@ namespace MWGui else mSkippedToEquip = ptr; - mItemView->update(); + if (isVisible()) + { + mItemView->update(); - notifyContentChanged(); + notifyContentChanged(); + } + // else: will be updated in open() } void InventoryWindow::onAvatarClicked(MyGUI::Widget* _sender) @@ -491,16 +512,23 @@ namespace MWGui void InventoryWindow::doRenderUpdate () { - if (mPreviewDirty) + mPreview->onFrame(); + if (mPreviewResize) { - mPreviewDirty = false; + mPreviewResize = false; MyGUI::IntSize size = mAvatarImage->getSize(); - - mPreview->update (size.width, size.height); + mPreview->resize(size.width, size.height); mAvatarImage->setImageTexture("CharacterPreview"); mAvatarImage->setImageCoord(MyGUI::IntCoord(0, 0, std::min(512, size.width), std::min(1024, size.height))); mAvatarImage->setImageTile(MyGUI::IntSize(std::min(512, size.width), std::min(1024, size.height))); + } + if (mPreviewDirty) + { + mPreviewDirty = false; + mPreview->update (); + + mAvatarImage->setImageTexture("CharacterPreview"); mArmorRating->setCaptionWithReplacing ("#{sArmor}: " + boost::lexical_cast(static_cast(mPtr.getClass().getArmorRating(mPtr)))); diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index ae7af5719..6fd6ece4a 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -53,6 +53,7 @@ namespace MWGui DragAndDrop* mDragAndDrop; bool mPreviewDirty; + bool mPreviewResize; int mSelectedItem; MWWorld::Ptr mPtr; @@ -98,6 +99,7 @@ namespace MWGui void onFilterChanged(MyGUI::Widget* _sender); void onAvatarClicked(MyGUI::Widget* _sender); void onPinToggled(); + void onTitleDoubleClicked(); void updateEncumbranceBar(); void notifyContentChanged(); diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index 7c79f8027..3e109384b 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "../mwworld/class.hpp" namespace MWGui @@ -63,15 +65,7 @@ namespace MWGui void ItemWidget::setIcon(const MWWorld::Ptr &ptr) { - // image - std::string path = std::string("icons\\"); - path += ptr.getClass().getInventoryIcon(ptr); - - std::string::size_type pos = path.rfind("."); - if(pos != std::string::npos) - path.erase(pos); - path.append(".dds"); - setIcon(path); + setIcon(Misc::ResourceHelpers::correctIconPath(ptr.getClass().getInventoryIcon(ptr))); } diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index b588ba3e1..3e2006e57 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -123,6 +123,9 @@ namespace getPage (LeftBookPage)->adviseLinkClicked (callback); getPage (RightBookPage)->adviseLinkClicked (callback); + + getPage (LeftBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); + getPage (RightBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); } { @@ -488,6 +491,14 @@ namespace MWBase::Environment::get().getWindowManager ()->popGuiMode (); } + void notifyMouseWheel(MyGUI::Widget* sender, int rel) + { + if (rel < 0) + notifyNextPage(sender); + else + notifyPrevPage(sender); + } + void notifyNextPage(MyGUI::Widget* _sender) { if (!mStates.empty ()) @@ -509,7 +520,7 @@ namespace { unsigned int & page = mStates.top ().mPage; - if(page > 0) + if(page >= 2) { page -= 2; updateShowingPages (); diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index f6d6b8135..034163469 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -30,6 +30,8 @@ namespace MWGui , mProgress(0) , mVSyncWasEnabled(false) { + mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); + getWidget(mLoadingText, "LoadingText"); getWidget(mProgressBar, "ProgressBar"); @@ -56,13 +58,6 @@ namespace MWGui mBackgroundImage->setVisible(visible); } - void LoadingScreen::onResChange(int w, int h) - { - setCoord(0,0,w,h); - - mBackgroundImage->setCoord(MyGUI::IntCoord(0,0,w,h)); - } - void LoadingScreen::loadingOn() { // Early-out if already on diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index f198d625d..310a6df3c 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -35,8 +35,6 @@ namespace MWGui void setLoadingProgress (const std::string& stage, int depth, int current, int total); void loadingDone(); - void onResChange(int w, int h); - void updateWindow(Ogre::RenderWindow* rw) { mWindow = rw; } private: diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index af26456f2..39df4d03e 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -536,6 +536,12 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setMinimapVisibility(!mPinned); } + void MapWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map); + } + void MapWindow::open() { // force markers to foreground diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index c73e5d7a1..7021a5d62 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -150,6 +150,7 @@ namespace MWGui protected: virtual void onPinToggled(); + virtual void onTitleDoubleClicked(); virtual void notifyPlayerUpdate(); virtual void notifyMapChanged(); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 84150c322..12defc631 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" @@ -226,12 +227,9 @@ namespace MWGui esmStore.get().find(spell->mEffects.mList.front().mEffectID); std::string path = effect->mIcon; - int slashPos = path.find("\\"); + int slashPos = path.rfind('\\'); path.insert(slashPos+1, "b_"); - path = std::string("icons\\") + path; - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); + path = Misc::ResourceHelpers::correctIconPath(path); button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); button->setIcon(path); diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 1b994d584..c45c2566e 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -181,6 +181,10 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage51")->getString(); message = boost::str(boost::format(message) % gem.getClass().getName(gem)); MWBase::Environment::get().getWindowManager()->messageBox(message); + + // special case: readd Azura's Star + if (Misc::StringUtils::ciEqual(gem.get()->mBase->mId, "Misc_SoulGem_Azura")) + player.getClass().getContainerStore(player).add("Misc_SoulGem_Azura", 1, player); } updateView(); diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp new file mode 100644 index 000000000..c27737285 --- /dev/null +++ b/apps/openmw/mwgui/screenfader.cpp @@ -0,0 +1,111 @@ +#include "screenfader.hpp" + +namespace MWGui +{ + + ScreenFader::ScreenFader() + : WindowBase("openmw_screen_fader.layout") + , mMode(FadingMode_In) + , mRemainingTime(0.f) + , mTargetTime(0.f) + , mTargetAlpha(0.f) + , mCurrentAlpha(0.f) + , mStartAlpha(0.f) + , mFactor(1.f) + { + mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); + + setVisible(false); + } + + void ScreenFader::update(float dt) + { + if (mRemainingTime > 0) + { + if (mMode == FadingMode_In) + { + mCurrentAlpha -= dt/mTargetTime * (mStartAlpha-mTargetAlpha); + if (mCurrentAlpha < mTargetAlpha) mCurrentAlpha = mTargetAlpha; + } + else if (mMode == FadingMode_Out) + { + mCurrentAlpha += dt/mTargetTime * (mTargetAlpha-mStartAlpha); + if (mCurrentAlpha > mTargetAlpha) mCurrentAlpha = mTargetAlpha; + } + + mRemainingTime -= dt; + } + + if (1.f-((1.f-mCurrentAlpha) * mFactor) == 0.f) + mMainWidget->setVisible(false); + else + applyAlpha(); + } + + void ScreenFader::applyAlpha() + { + setVisible(true); + mMainWidget->setAlpha(1.f-((1.f-mCurrentAlpha) * mFactor)); + } + + void ScreenFader::fadeIn(float time) + { + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = 0.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = 0.f; + mMode = FadingMode_In; + mTargetTime = time; + mRemainingTime = time; + } + + void ScreenFader::fadeOut(const float time) + { + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = 1.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = 1.f; + mMode = FadingMode_Out; + mTargetTime = time; + mRemainingTime = time; + } + + void ScreenFader::fadeTo(const int percent, const float time) + { + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = percent/100.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = percent/100.f; + + if (mTargetAlpha == mStartAlpha) return; + else if (mTargetAlpha > mStartAlpha) mMode = FadingMode_Out; + else mMode = FadingMode_In; + + mTargetTime = time; + mRemainingTime = time; + } + + void ScreenFader::setFactor(float factor) + { + mFactor = factor; + } + +} diff --git a/apps/openmw/mwgui/screenfader.hpp b/apps/openmw/mwgui/screenfader.hpp new file mode 100644 index 000000000..975006a85 --- /dev/null +++ b/apps/openmw/mwgui/screenfader.hpp @@ -0,0 +1,44 @@ +#ifndef OPENMW_MWGUI_SCREENFADER_H +#define OPENMW_MWGUI_SCREENFADER_H + +#include "windowbase.hpp" + +namespace MWGui +{ + + class ScreenFader : public WindowBase + { + public: + ScreenFader(); + + void update(float dt); + + void fadeIn(const float time); + void fadeOut(const float time); + void fadeTo(const int percent, const float time); + + void setFactor (float factor); + + private: + enum FadingMode + { + FadingMode_In, + FadingMode_Out + }; + + void applyAlpha(); + + FadingMode mMode; + + float mRemainingTime; + float mTargetTime; + float mTargetAlpha; + float mCurrentAlpha; + float mStartAlpha; + + float mFactor; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 63cbdb567..b72962125 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -2,6 +2,8 @@ #include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -143,13 +145,7 @@ namespace MWGui void EditEffectDialog::setMagicEffect (const ESM::MagicEffect *effect) { - std::string icon = effect->mIcon; - icon[icon.size()-3] = 'd'; - icon[icon.size()-2] = 'd'; - icon[icon.size()-1] = 's'; - icon = "icons\\" + icon; - - mEffectImage->setImageTexture (icon); + mEffectImage->setImageTexture(Misc::ResourceHelpers::correctIconPath(effect->mIcon)); mEffectName->setCaptionWithReplacing("#{"+ESM::MagicEffect::effectIdToString (effect->mIndex)+"}"); @@ -391,6 +387,14 @@ namespace MWGui void SpellCreationDialog::notifyEffectsChanged () { + if (mEffects.empty()) + { + mMagickaCost->setCaption("0"); + mPriceLabel->setCaption("0"); + mSuccessChance->setCaption("0"); + return; + } + float y = 0; const MWWorld::ESMStore &store = @@ -398,7 +402,7 @@ namespace MWGui for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) { - float x = 0.5 * it->mMagnMin + it->mMagnMax; + float x = 0.5 * (it->mMagnMin + it->mMagnMax); const ESM::MagicEffect* effect = store.get().find(it->mEffectID); @@ -413,7 +417,7 @@ namespace MWGui y += x * fEffectCostMult; y = std::max(1.f,y); - if (effect->mData.mFlags & ESM::MagicEffect::CastTarget) + if (it->mRange == ESM::RT_Target) y *= 1.5; } @@ -520,16 +524,24 @@ namespace MWGui void EffectEditorBase::onSelectAttribute () { - mAddEffectDialog.setVisible(true); + const ESM::MagicEffect* effect = + MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); + + mAddEffectDialog.newEffect(effect); mAddEffectDialog.setAttribute (mSelectAttributeDialog->getAttributeId()); + mAddEffectDialog.setVisible(true); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog); mSelectAttributeDialog = 0; } void EffectEditorBase::onSelectSkill () { + const ESM::MagicEffect* effect = + MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); + + mAddEffectDialog.newEffect(effect); + mAddEffectDialog.setSkill (mSelectSkillDialog->getSkillId()); mAddEffectDialog.setVisible(true); - mAddEffectDialog.setSkill (mSelectSkillDialog->getSkillId ()); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog); mSelectSkillDialog = 0; } @@ -554,11 +566,10 @@ namespace MWGui } int buttonId = *sender->getUserData(); - short effectId = mButtonMapping[buttonId]; - + mSelectedKnownEffectId = mButtonMapping[buttonId]; for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) { - if (it->mEffectID == effectId) + if (it->mEffectID == mSelectedKnownEffectId) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sOnetypeEffectMessage}"); return; @@ -566,9 +577,7 @@ namespace MWGui } const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld()->getStore().get().find(effectId); - - mAddEffectDialog.newEffect (effect); + MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) { @@ -588,6 +597,7 @@ namespace MWGui } else { + mAddEffectDialog.newEffect(effect); mAddEffectDialog.setVisible(true); } } diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index 25c615678..f5d6f0616 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -99,6 +99,7 @@ namespace MWGui SelectSkillDialog* mSelectSkillDialog; int mSelectedEffect; + short mSelectedKnownEffectId; std::vector mEffects; diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 1a9e418de..dbd91ab75 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -133,13 +135,7 @@ namespace MWGui ("ImageBox", MyGUI::IntCoord(w,2,16,16), MyGUI::Align::Default); mWidgetMap[it->first] = image; - std::string icon = effect->mIcon; - icon[icon.size()-3] = 'd'; - icon[icon.size()-2] = 'd'; - icon[icon.size()-1] = 's'; - icon = "icons\\" + icon; - - image->setImageTexture(icon); + image->setImageTexture(Misc::ResourceHelpers::correctIconPath(effect->mIcon)); std::string name = ESM::MagicEffect::effectIdToString (it->first); diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 15b5bf280..b558025d1 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -45,6 +45,7 @@ namespace MWGui , NoDrop(drag, mMainWidget) , mHeight(0) , mWidth(0) + , mWindowSize(mMainWidget->getSize()) { mSpellIcons = new SpellIcons(); @@ -66,6 +67,12 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setSpellVisibility(!mPinned); } + void SpellWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Magic); + } + void SpellWindow::open() { updateSpells(); @@ -302,7 +309,11 @@ namespace MWGui void SpellWindow::onWindowResize(MyGUI::Window* _sender) { - updateSpells(); + if (mMainWidget->getSize() != mWindowSize) + { + mWindowSize = mMainWidget->getSize(); + updateSpells(); + } } void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index 53eed1ba1..a74847b90 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -29,6 +29,8 @@ namespace MWGui int mHeight; int mWidth; + MyGUI::IntSize mWindowSize; + std::string mSpellToDelete; void addGroup(const std::string& label, const std::string& label2); @@ -42,6 +44,7 @@ namespace MWGui void onDeleteSpellAccept(); virtual void onPinToggled(); + virtual void onTitleDoubleClicked(); virtual void open(); SpellIcons* mSpellIcons; diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 7f3b66d83..2a07a1624 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -591,4 +591,10 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->setHMSVisibility(!mPinned); } + + void StatsWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Stats); + } } diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index d90c16be9..1a62b6d6b 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -74,6 +74,7 @@ namespace MWGui protected: virtual void onPinToggled(); + virtual void onTitleDoubleClicked(); }; } #endif diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index e09ff2487..2cd86c7b7 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -4,6 +4,8 @@ #include +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -319,20 +321,6 @@ namespace MWGui return tooltipSize; } - void ToolTips::findImageExtension(std::string& image) - { - int len = image.size(); - if (len < 4) return; - - if (!Ogre::ResourceGroupManager::getSingleton().resourceExists(Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME, image)) - { - // Change texture extension to .dds - image[len-3] = 'd'; - image[len-2] = 'd'; - image[len-1] = 's'; - } - } - MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) { mDynamicToolTipBox->setVisible(true); @@ -371,8 +359,7 @@ namespace MWGui const int imageCaptionHPadding = (caption != "" ? 8 : 0); const int imageCaptionVPadding = (caption != "" ? 4 : 0); - std::string realImage = "icons\\" + image; - findImageExtension(realImage); + std::string realImage = Misc::ResourceHelpers::correctIconPath(image); MyGUI::EditBox* captionWidget = mDynamicToolTipBox->createWidget("NormalText", MyGUI::IntCoord(0, 0, 300, 300), MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipCaption"); captionWidget->setProperty("Static", "true"); @@ -644,9 +631,7 @@ namespace MWGui widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "BirthSignToolTip"); - std::string image = sign->mTexture; - image.replace(image.size()-3, 3, "dds"); - widget->setUserString("ImageTexture_BirthSignImage", "textures\\" + image); + widget->setUserString("ImageTexture_BirthSignImage", Misc::ResourceHelpers::correctTexturePath(sign->mTexture)); std::string text; text += sign->mName; @@ -739,15 +724,9 @@ namespace MWGui const std::string &name = ESM::MagicEffect::effectIdToString (id); std::string icon = effect->mIcon; - - int slashPos = icon.find("\\"); + int slashPos = icon.rfind('\\'); icon.insert(slashPos+1, "b_"); - - icon[icon.size()-3] = 'd'; - icon[icon.size()-2] = 'd'; - icon[icon.size()-1] = 's'; - - icon = "icons\\" + icon; + icon = Misc::ResourceHelpers::correctIconPath(icon); std::vector schools; schools.push_back ("#{sSchoolAlteration}"); diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 8b6174b87..62ac69343 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -84,8 +84,6 @@ namespace MWGui MWWorld::Ptr mFocusObject; - void findImageExtension(std::string& image); - MyGUI::IntSize getToolTipViaPtr (bool image=true); ///< @return requested tooltip size diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index f94e39b2f..ccfe6b1eb 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -308,7 +308,7 @@ namespace MWGui it->mBase.getClass().getValue(it->mBase) * it->mCount); onCancelButtonClicked(mCancelButton); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); return; } } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index babfe099f..f67376c93 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -2,12 +2,11 @@ #include -#include - #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -164,14 +163,14 @@ namespace MWGui // go back to game mode MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); // advance time MWBase::Environment::get().getWorld ()->advanceTime (2); MWBase::Environment::get().getMechanicsManager()->rest(false); MWBase::Environment::get().getMechanicsManager()->rest(false); - MWBase::Environment::get().getWorld ()->getFader()->fadeOut(0.25); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.25); mFadeTimeRemaining = 0.5; } @@ -183,6 +182,6 @@ namespace MWGui mFadeTimeRemaining -= dt; if (mFadeTimeRemaining <= 0) - MWBase::Environment::get().getWorld ()->getFader()->fadeIn(0.25); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.25); } } diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index ae191e764..9f3cc949d 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -4,13 +4,12 @@ #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -156,7 +155,7 @@ namespace MWGui MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); npcStats.setGoldPool(npcStats.getGoldPool() + price); - MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(1); ESM::Position pos = *_sender->getUserData(); std::string cellname = _sender->getUserString("Destination"); bool interior = _sender->getUserString("interior") == "y"; @@ -173,14 +172,15 @@ namespace MWGui MWBase::Environment::get().getWorld()->advanceTime(hours); } + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + // Teleports any followers, too. MWWorld::ActionTeleport action(interior ? cellname : "", pos); action.execute(player); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); - MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0); - MWBase::Environment::get().getWorld ()->getFader ()->fadeIn(1); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(1); } void TravelWindow::onCancelButtonClicked(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 9c7757af9..434c6926e 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -2,8 +2,6 @@ #include -#include - #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -128,7 +126,7 @@ namespace MWGui MWBase::Environment::get().getStateManager()->quickSave("Autosave"); MWBase::World* world = MWBase::Environment::get().getWorld(); - world->getFader ()->fadeOut(0.2); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2); setVisible(false); mProgressBar.setVisible (true); @@ -179,8 +177,7 @@ namespace MWGui { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - bool full = (stats.getFatigue().getCurrent() >= stats.getFatigue().getModified()) - && (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) + bool full = (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) && (stats.getMagicka().getCurrent() >= stats.getMagicka().getModified()); MWMechanics::NpcStats& npcstats = player.getClass().getNpcStats(player); bool werewolf = npcstats.isWerewolf(); @@ -243,7 +240,7 @@ namespace MWGui void WaitDialog::stopWaiting () { - MWBase::Environment::get().getWorld ()->getFader ()->fadeIn(0.2); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2); mProgressBar.setVisible (false); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Rest); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_RestBed); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 5cd8899d8..2cd044cf4 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -65,6 +65,7 @@ #include "videowidget.hpp" #include "backgroundimage.hpp" #include "itemwidget.hpp" +#include "screenfader.hpp" namespace MWGui { @@ -72,7 +73,7 @@ namespace MWGui WindowManager::WindowManager( const Compiler::Extensions& extensions, int fpsLevel, OEngine::Render::OgreRenderer *ogre, const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, - Translation::Storage& translationDataStorage, ToUTF8::FromType encoding) + Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts) : mConsoleOnlyScripts(consoleOnlyScripts) , mGuiManager(NULL) , mRendering(ogre) @@ -112,6 +113,7 @@ namespace MWGui , mCompanionWindow(NULL) , mVideoBackground(NULL) , mVideoWidget(NULL) + , mScreenFader(NULL) , mTranslationDataStorage (translationDataStorage) , mCharGen(NULL) , mInputBlocker(NULL) @@ -146,7 +148,7 @@ namespace MWGui // Load fonts FontLoader fontLoader (encoding); - fontLoader.loadAllFonts(); + fontLoader.loadAllFonts(exportFonts); //Register own widgets with MyGUI MyGUI::FactoryManager::getInstance().registerFactory("Widget"); @@ -171,18 +173,14 @@ namespace MWGui ItemWidget::registerComponents(); MyGUI::FactoryManager::getInstance().registerFactory("Controller"); + MyGUI::FactoryManager::getInstance().registerFactory("Controller"); MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); MyGUI::ResourceManager::getInstance().load("core.xml"); MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); - // Get size info from the Gui object - int w = MyGUI::RenderManager::getInstance().getViewSize().width; - int h = MyGUI::RenderManager::getInstance().getViewSize().height; - mLoadingScreen = new LoadingScreen(mRendering->getScene (), mRendering->getWindow ()); - mLoadingScreen->onResChange (w,h); //set up the hardware cursor manager mCursorManager = new SFO::SDLCursorManager(); @@ -192,7 +190,6 @@ namespace MWGui MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); - //SDL_ShowCursor(false); mCursorManager->setEnabled(true); @@ -217,13 +214,7 @@ namespace MWGui int w = MyGUI::RenderManager::getInstance().getViewSize().width; int h = MyGUI::RenderManager::getInstance().getViewSize().height; - MyGUI::Widget* dragAndDropWidget = mGui->createWidgetT("Widget","",0,0,w,h,MyGUI::Align::Default,"DragAndDrop","DragAndDropWidget"); - dragAndDropWidget->setVisible(false); - mDragAndDrop = new DragAndDrop(); - mDragAndDrop->mIsOnDragAndDrop = false; - mDragAndDrop->mDraggedWidget = 0; - mDragAndDrop->mDragAndDropWidget = dragAndDropWidget; mRecharge = new Recharge(); mMenu = new MainMenu(w,h); @@ -245,7 +236,7 @@ namespace MWGui trackWindow(mDialogueWindow, "dialogue"); mContainerWindow = new ContainerWindow(mDragAndDrop); trackWindow(mContainerWindow, "container"); - mHud = new HUD(w,h, mShowFPSLevel, mDragAndDrop); + mHud = new HUD(mShowFPSLevel, mDragAndDrop); mToolTips = new ToolTips(); mScrollWindow = new ScrollWindow(); mBookWindow = new BookWindow(); @@ -267,8 +258,9 @@ namespace MWGui mSoulgemDialog = new SoulgemDialog(mMessageBoxManager); mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); trackWindow(mCompanionWindow, "companion"); + mScreenFader = new ScreenFader(); - mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Overlay"); + mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Stretch,"Overlay"); mHud->setVisible(mHudEnabled); @@ -357,6 +349,7 @@ namespace MWGui delete mCursorManager; delete mRecharge; delete mCompanionWindow; + delete mScreenFader; cleanupGarbage(); @@ -858,6 +851,8 @@ namespace MWGui mCompanionWindow->checkReferenceAvailable(); mConsole->checkReferenceAvailable(); mCompanionWindow->onFrame(); + + mScreenFader->update(frameDuration); } void WindowManager::changeCell(MWWorld::CellStore* cell) @@ -1019,7 +1014,6 @@ namespace MWGui { sizeVideo(x, y); mGuiManager->windowResized(); - mLoadingScreen->onResChange (x,y); if (!mHud) return; // UI not initialized yet @@ -1033,7 +1027,6 @@ namespace MWGui it->first->setSize(size); } - mHud->onResChange(x, y); mConsole->onResChange(x, y); mMenu->onResChange(x, y); mSettingsWindow->center(); @@ -1042,8 +1035,6 @@ namespace MWGui mBookWindow->center(); mQuickKeysMenu->center(); mSpellBuyingWindow->center(); - mDragAndDrop->mDragAndDropWidget->setSize(MyGUI::IntSize(x, y)); - mInputBlocker->setSize(MyGUI::IntSize(x,y)); } void WindowManager::pushGuiMode(GuiMode mode) @@ -1688,4 +1679,24 @@ namespace MWGui updateVisible(); } + + void WindowManager::fadeScreenIn(const float time) + { + mScreenFader->fadeIn(time); + } + + void WindowManager::fadeScreenOut(const float time) + { + mScreenFader->fadeOut(time); + } + + void WindowManager::fadeScreenTo(const int percent, const float time) + { + mScreenFader->fadeTo(percent, time); + } + + void WindowManager::setScreenFactor(float factor) + { + mScreenFader->setFactor(factor); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 8093d637e..598ecd1ee 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -86,6 +86,7 @@ namespace MWGui class CompanionWindow; class VideoWidget; class WindowModal; + class ScreenFader; class WindowManager : public MWBase::WindowManager { @@ -96,7 +97,7 @@ namespace MWGui WindowManager(const Compiler::Extensions& extensions, int fpsLevel, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, - Translation::Storage& translationDataStorage, ToUTF8::FromType encoding); + Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts); virtual ~WindowManager(); void initUI(); @@ -324,6 +325,15 @@ namespace MWGui virtual void pinWindow (MWGui::GuiWindow window); + /// Fade the screen in, over \a time seconds + virtual void fadeScreenIn(const float time); + /// Fade the screen out to black, over \a time seconds + virtual void fadeScreenOut(const float time); + /// Fade the screen to a specified percentage of black, over \a time seconds + virtual void fadeScreenTo(const int percent, const float time); + /// Darken the screen by \a factor (1.0 = no darkening). Works independently from screen fading. + virtual void setScreenFactor (float factor); + private: bool mConsoleOnlyScripts; @@ -373,6 +383,7 @@ namespace MWGui CompanionWindow* mCompanionWindow; MyGUI::ImageBox* mVideoBackground; VideoWidget* mVideoWidget; + ScreenFader* mScreenFader; Translation::Storage& mTranslationDataStorage; Cursor* mSoftwareCursor; diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp index 919d315f2..92ba5876d 100644 --- a/apps/openmw/mwgui/windowpinnablebase.cpp +++ b/apps/openmw/mwgui/windowpinnablebase.cpp @@ -11,6 +11,17 @@ namespace MWGui mPinButton = window->getSkinWidget ("Button"); mPinButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonClicked); + + MyGUI::Button* button = NULL; + MyGUI::VectorWidgetPtr widgets = window->getSkinWidgetsByName("Action"); + for (MyGUI::VectorWidgetPtr::iterator it = widgets.begin(); it != widgets.end(); ++it) + { + if ((*it)->isUserString("HideWindowOnDoubleClick")) + button = (*it)->castType(); + } + + if (button) + button->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WindowPinnableBase::onDoubleClick); } void WindowPinnableBase::onPinButtonClicked(MyGUI::Widget* _sender) @@ -25,6 +36,11 @@ namespace MWGui onPinToggled(); } + void WindowPinnableBase::onDoubleClick(MyGUI::Widget *_sender) + { + onTitleDoubleClicked(); + } + void WindowPinnableBase::setPinned(bool pinned) { if (pinned != mPinned) diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp index 3aad60988..8b7bbefaf 100644 --- a/apps/openmw/mwgui/windowpinnablebase.hpp +++ b/apps/openmw/mwgui/windowpinnablebase.hpp @@ -17,9 +17,11 @@ namespace MWGui private: void onPinButtonClicked(MyGUI::Widget* _sender); + void onDoubleClick(MyGUI::Widget* _sender); protected: virtual void onPinToggled() = 0; + virtual void onTitleDoubleClicked() = 0; MyGUI::Widget* mPinButton; bool mPinned; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 067ad72bb..f60c89681 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -805,16 +805,16 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - if((!MWBase::Environment::get().getWindowManager()->isGuiMode() - || MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Dialogue) - && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) + if(MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Journal + && MWBase::Environment::get().getWindowManager ()->getJournalAllowed() + && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Console) { MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Journal); } - else if(MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Journal) + else if(MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Journal)) { - MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Journal); } } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index bd87f8b4b..92879fe37 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -89,6 +89,58 @@ bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) return false; } +class CheckActorCommanded : public MWMechanics::EffectSourceVisitor +{ + MWWorld::Ptr mActor; +public: + bool mCommanded; + CheckActorCommanded(MWWorld::Ptr actor) + : mActor(actor) + , mCommanded(false){} + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, int casterActorId, + float magnitude, float remainingTime = -1) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if ( ((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc()) + || (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name())) + && casterActorId == player.getClass().getCreatureStats(player).getActorId() + && magnitude >= mActor.getClass().getCreatureStats(mActor).getLevel()) + mCommanded = true; + } +}; + +void adjustCommandedActor (const MWWorld::Ptr& actor) +{ + CheckActorCommanded check(actor); + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + stats.getActiveSpells().visitEffectSources(check); + + bool hasCommandPackage = false; + + std::list::const_iterator it; + for (it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) + { + if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow && + dynamic_cast(*it)->isCommanded()) + { + hasCommandPackage = true; + break; + } + } + + if (check.mCommanded && !hasCommandPackage) + { + MWMechanics::AiFollow package("player", true); + stats.getAiSequence().stack(package, actor); + } + else if (!check.mCommanded && hasCommandPackage) + { + stats.getAiSequence().erase(it); + } +} + void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka) { MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); @@ -107,6 +159,30 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float } } +void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId) +{ + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); + if (!ptr.isEmpty()) + { + // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation + // plays though, which is a rather lame exploit in vanilla. + MWBase::Environment::get().getWorld()->deleteObject(ptr); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); + } + else + { + // We didn't find the creature. It's probably in an inactive cell. + // Add to graveyard so we can delete it when the cell becomes active. + std::vector& graveyard = casterStats.getSummonedCreatureGraveyard(); + graveyard.push_back(creatureActorId); + } +} + } namespace MWMechanics @@ -268,7 +344,8 @@ namespace MWMechanics void Actors::adjustMagicEffects (const MWWorld::Ptr& creature) { CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); - + if (creatureStats.isDead()) + return; MagicEffects now = creatureStats.getSpells().getMagicEffects(); if (creature.getTypeName()==typeid (ESM::NPC).name()) @@ -290,17 +367,17 @@ namespace MWMechanics { CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); - int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); - int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getBase(); - int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getBase(); - int agility = creatureStats.getAttribute(ESM::Attribute::Agility).getBase(); - int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); + int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getModified(); + int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); + int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getModified(); + int agility = creatureStats.getAttribute(ESM::Attribute::Agility).getModified(); + int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getModified(); double magickaFactor = - creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude * 0.1 + 0.5; + creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude * 0.1 + 1; DynamicStat magicka = creatureStats.getMagicka(); - float diff = (static_cast(intelligence + magickaFactor*intelligence)) - magicka.getBase(); + float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); magicka.modify(diff); creatureStats.setMagicka(magicka); @@ -612,9 +689,9 @@ namespace MWMechanics summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID"; } + std::map& creatureMap = creatureStats.getSummonedCreatureMap(); for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) { - std::map& creatureMap = creatureStats.getSummonedCreatureMap(); bool found = creatureMap.find(it->first) != creatureMap.end(); int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude; if (found != (magnitude > 0)) @@ -665,34 +742,31 @@ namespace MWMechanics } else { - // Summon lifetime has expired. Try to delete the creature. - int actorId = creatureMap[it->first]; - creatureMap.erase(it->first); - - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(actorId); - if (!ptr.isEmpty()) - { - // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation - // plays though, which is a rather lame exploit in vanilla. - MWBase::Environment::get().getWorld()->deleteObject(ptr); - - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_End"); - if (fx) - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); - } - else - { - // We didn't find the creature. It's probably in an inactive cell. - // Add to graveyard so we can delete it when the cell becomes active. - std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); - graveyard.push_back(actorId); - } + // Effect has ended + std::map::iterator foundCreature = creatureMap.find(it->first); + cleanupSummonedCreature(creatureStats, foundCreature->second); + creatureMap.erase(foundCreature); } } } + for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); + if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead()) + { + // Purge the magic effect so a new creature can be summoned if desired + creatureStats.getActiveSpells().purgeEffect(it->first); + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first); + + cleanupSummonedCreature(creatureStats, it->second); + creatureMap.erase(it++); + } + else + ++it; + } + std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); for (std::vector::iterator it = graveyard.begin(); it != graveyard.end(); ) { @@ -1005,7 +1079,10 @@ namespace MWMechanics if (MWBase::Environment::get().getMechanicsManager()->isAIActive()) { if (timerUpdateAITargets == 0) - { + { + if (iter->first != player) + adjustCommandedActor(iter->first); + for(PtrControllerMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { if (it->first == iter->first || iter->first == player) // player is not AI-controlled @@ -1053,10 +1130,12 @@ namespace MWMechanics //KnockedOutOneFrameLogic //Used for "OnKnockedOut" command //Put here to ensure that it's run for PRECISELY one frame. - if (stats.getKnockedDown() && !stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) { //Start it for one frame if nessesary + if (stats.getKnockedDown() && !stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) + { //Start it for one frame if nessesary stats.setKnockedDownOneFrame(true); } - else if (stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) { //Turn off KnockedOutOneframe + else if (stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) + { //Turn off KnockedOutOneframe stats.setKnockedDownOneFrame(false); stats.setKnockedDownOverOneFrame(true); } diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index bab8bca28..eea1b4850 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -51,14 +51,17 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor,float duration ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door float x = pos.pos[0] - tPos.pos[0]; float y = pos.pos[1] - tPos.pos[1]; - float dirToDoor = std::atan2(x,y) + pos.rot[2] + mAdjAngle; //Calculates the direction to the door, relative to the direction of the NPC - // For example, if the NPC is directly facing the door this will be pi/2 - // Make actor move away from the door - actor.getClass().getMovementSettings(actor).mPosition[1] = -1 * std::sin(dirToDoor); //I knew I'd use trig someday - actor.getClass().getMovementSettings(actor).mPosition[0] = -1 * std::cos(dirToDoor); + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); - //Make all nearby actors also avoid the door + // Turn away from the door and move when turn completed + if (zTurn(actor, Ogre::Radian(std::atan2(x,y) + mAdjAngle), Ogre::Degree(5))) + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + else + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + actor.getClass().getMovementSettings(actor).mPosition[0] = 0; + + // Make all nearby actors also avoid the door std::vector actors; MWBase::Environment::get().getMechanicsManager()->getActorsInRange(Ogre::Vector3(pos.pos[0],pos.pos[1],pos.pos[2]),100,actors); for(std::vector::iterator it = actors.begin(); it != actors.end(); it++) { diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index cc8dead8a..a0f8bffbd 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -75,6 +75,7 @@ namespace MWMechanics } actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); const float* const leaderPos = actor.getRefData().getPosition().pos; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 27d944657..623306411 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -16,16 +16,16 @@ #include "steering.hpp" MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("") +: mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("") { } MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId) +: mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId) { } -MWMechanics::AiFollow::AiFollow(const std::string &actorId) -: mAlwaysFollow(true), mRemainingDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("") +MWMechanics::AiFollow::AiFollow(const std::string &actorId, bool commanded) +: mAlwaysFollow(true), mCommanded(commanded), mRemainingDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("") { } @@ -99,6 +99,11 @@ int MWMechanics::AiFollow::getTypeId() const return TypeIdFollow; } +bool MWMechanics::AiFollow::isCommanded() const +{ + return mCommanded; +} + void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const { std::auto_ptr follow(new ESM::AiSequence::AiFollow()); @@ -109,6 +114,7 @@ void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) co follow->mRemainingDuration = mRemainingDuration; follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; + follow->mCommanded = mCommanded; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Follow; @@ -120,6 +126,7 @@ MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) : mAlwaysFollow(follow->mAlwaysFollow), mRemainingDuration(follow->mRemainingDuration) , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) , mActorId(follow->mTargetId), mCellId(follow->mCellId) + , mCommanded(follow->mCommanded) { } diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 744a9c505..c0eef8008 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -27,7 +27,7 @@ namespace MWMechanics /// Follow Actor for duration or until you arrive at a position in a cell AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); /// Follow Actor indefinitively - AiFollow(const std::string &ActorId); + AiFollow(const std::string &ActorId, bool commanded=false); AiFollow(const ESM::AiSequence::AiFollow* follow); @@ -44,10 +44,13 @@ namespace MWMechanics virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + bool isCommanded() const; + private: /// This will make the actor always follow. /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ bool mAlwaysFollow; + bool mCommanded; float mRemainingDuration; // Seconds float mX; float mY; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 1d0065679..f15b357e5 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -82,6 +82,20 @@ std::list::const_iterator AiSequence::end() const return mPackages.end(); } +void AiSequence::erase(std::list::const_iterator package) +{ + // Not sure if manually terminated packages should trigger mDone, probably not? + for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + if (package == it) + { + mPackages.erase(it); + return; + } + } + throw std::runtime_error("can't find package to erase"); +} + bool AiSequence::isInCombat() const { for(std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) @@ -245,6 +259,9 @@ void AiSequence::clear() void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) { + if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("Can't add AI packages to player"); + if (package.getTypeId() == AiPackage::TypeIdCombat || package.getTypeId() == AiPackage::TypeIdPursue) { // Notify AiWander of our current position so we can return to it after combat finished diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index af14cce59..c7d176661 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -54,6 +54,8 @@ namespace MWMechanics std::list::const_iterator begin() const; std::list::const_iterator end() const; + void erase (std::list::const_iterator package); + /// Returns currently executing AiPackage type /** \see enum AiPackage::TypeId **/ int getTypeId() const; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 0809cd640..e0e8057f2 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -395,22 +395,35 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat CharacterState walkState = runStateToWalkState(mMovementState); const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); anim = stateinfo->groupname; - } - if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) - { - speedmult = mMovementSpeed / vel; + if (mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) + speedmult = mMovementSpeed / vel; + else + // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), + // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist + // we will play without any scaling. + // Makes the speed attribute of most water creatures totally useless. + // And again, this can not be fixed without patching game data. + speedmult = 1.f; } - else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) - speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed - else if (mMovementSpeed > 0.0f) + else { - // The first person anims don't have any velocity to calculate a speed multiplier from. - // We use the third person velocities instead. - // FIXME: should be pulled from the actual animation, but it is not presently loaded. - speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); - mMovementAnimationControlled = false; + if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) + { + speedmult = mMovementSpeed / vel; + } + else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) + speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed + else if (mMovementSpeed > 0.0f) + { + // The first person anims don't have any velocity to calculate a speed multiplier from. + // We use the third person velocities instead. + // FIXME: should be pulled from the actual animation, but it is not presently loaded. + speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); + mMovementAnimationControlled = false; + } } + mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); } @@ -1174,6 +1187,36 @@ void CharacterController::update(float duration) bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run); bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak); bool flying = world->isFlying(mPtr); + CreatureStats &stats = cls.getCreatureStats(mPtr); + + //Force Jump Logic + + bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5); + if(!inwater && !flying) + { + //Force Jump + if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) + { + if(onground) + { + cls.getMovementSettings(mPtr).mPosition[2] = 1; + } + else + cls.getMovementSettings(mPtr).mPosition[2] = 0; + } + //Force Move Jump, only jump if they're otherwise moving + if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) + { + + if(onground) + { + cls.getMovementSettings(mPtr).mPosition[2] = 1; + } + else + cls.getMovementSettings(mPtr).mPosition[2] = 0; + } + } + //Ogre::Vector3 vec = cls.getMovementVector(mPtr); Ogre::Vector3 vec(cls.getMovementSettings(mPtr).mPosition); if(vec.z > 0.0f) // to avoid slow-down when jumping @@ -1183,7 +1226,7 @@ void CharacterController::update(float duration) vec.x = vecXY.x; vec.y = vecXY.y; } - else + else vec.normalise(); if(mHitState != CharState_None && mJumpState == JumpState_None) @@ -1352,8 +1395,7 @@ void CharacterController::update(float duration) } else { - if(!(vec.z > 0.0f)) - mJumpState = JumpState_None; + mJumpState = JumpState_None; vec.z = 0.0f; inJump = false; @@ -1482,6 +1524,8 @@ void CharacterController::update(float duration) else if (mAnimation) mAnimation->updateEffects(duration); mSkipAnim = false; + + mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead()); } diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index c13eac98c..c04e2c62d 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -169,12 +169,12 @@ namespace MWMechanics MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); - const MWWorld::Class &othercls = victim.getClass(); - if(!othercls.isActor()) // Can't hit non-actors - return; - MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim); - if(otherstats.isDead()) // Can't hit dead actors + if(victim.isEmpty() || !victim.getClass().isActor() || victim.getClass().getCreatureStats(victim).isDead()) + // Can't hit non-actors or dead actors + { + reduceWeaponCondition(0.f, false, weapon, attacker); return; + } if(attacker.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->setEnemy(victim); @@ -189,6 +189,7 @@ namespace MWMechanics if((::rand()/(RAND_MAX+1.0)) > getHitChance(attacker, victim, skillValue)/100.0f) { victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); + MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); return; } @@ -209,6 +210,8 @@ namespace MWMechanics damage *= fDamageStrengthBase + (attackerStats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1); + adjustWeaponDamage(damage, weapon); + reduceWeaponCondition(damage, true, weapon, attacker); if(attacker.getRefData().getHandle() == "player") attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); @@ -295,4 +298,42 @@ namespace MWMechanics } } + void reduceWeaponCondition(float damage, bool hit, MWWorld::Ptr &weapon, const MWWorld::Ptr &attacker) + { + if (weapon.isEmpty()) + return; + + if (!hit) + damage = 0.f; + + const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); + if(weaphashealth) + { + int weaphealth = weapon.getClass().getItemHealth(weapon); + + const float fWeaponDamageMult = MWBase::Environment::get().getWorld()->getStore().get().find("fWeaponDamageMult")->getFloat(); + float x = std::max(1.f, fWeaponDamageMult * damage); + + weaphealth -= std::min(int(x), weaphealth); + weapon.getCellRef().setCharge(weaphealth); + + // Weapon broken? unequip it + if (weaphealth == 0) + weapon = *attacker.getClass().getInventoryStore(attacker).unequipItem(weapon, attacker); + } + } + + void adjustWeaponDamage(float &damage, const MWWorld::Ptr &weapon) + { + if (weapon.isEmpty()) + return; + + const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); + if(weaphashealth) + { + int weaphealth = weapon.getClass().getItemHealth(weapon); + int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); + damage *= (float(weaphealth) / weapmaxhealth); + } + } } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index eb89cb820..0d3009510 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -13,6 +13,7 @@ bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage); /// @note for a thrown weapon, \a weapon == \a projectile, for bows/crossbows, \a projectile is the arrow/bolt +/// @note \a victim may be empty (e.g. for a hit on terrain), a non-actor (environment objects) or an actor void projectileHit (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, const Ogre::Vector3& hitPosition); @@ -22,6 +23,15 @@ float getHitChance (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, in /// Applies damage to attacker based on the victim's elemental shields. void applyElementalShields(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); +/// @param damage Unmitigated weapon damage of the attack +/// @param hit Was the attack successful? +/// @param weapon The weapon used. +/// @note if the weapon is unequipped as result of condition damage, a new Ptr will be assigned to \a weapon. +void reduceWeaponCondition (float damage, bool hit, MWWorld::Ptr& weapon, const MWWorld::Ptr& attacker); + +/// Adjust weapon damage based on its condition. A used weapon will be less effective. +void adjustWeaponDamage (float& damage, const MWWorld::Ptr& weapon); + } #endif diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 4ee994de5..a36d28fd3 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -227,7 +227,9 @@ namespace MWMechanics Flag_ForceRun = 1, Flag_ForceSneak = 2, Flag_Run = 4, - Flag_Sneak = 8 + Flag_Sneak = 8, + Flag_ForceJump = 16, + Flag_ForceMoveJump = 32 }; enum Stance { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 199dc488d..ed21fa8a8 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -973,6 +973,9 @@ namespace MWMechanics if (!it->getClass().isNpc()) continue; + if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) + continue; + // Will the witness report the crime? if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { @@ -1071,6 +1074,9 @@ namespace MWMechanics if (*it != victim && type == OT_Assault) aggression = iFightAttacking; + if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) + continue; + if (it->getClass().isClass(*it, "guard")) { // Mark as Alarmed for dialogue @@ -1100,6 +1106,35 @@ namespace MWMechanics } } + void MechanicsManager::actorAttacked(const MWWorld::Ptr &ptr, const MWWorld::Ptr &attacker) + { + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + return; + + std::list followers = getActorsFollowing(attacker); + if (std::find(followers.begin(), followers.end(), ptr) != followers.end()) + { + ptr.getClass().getCreatureStats(ptr).friendlyHit(); + + if (ptr.getClass().getCreatureStats(ptr).getFriendlyHits() < 4) + return; + } + + // Attacking peaceful NPCs is a crime + if (ptr.getClass().isNpc() && !attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker) + && !isAggressive(ptr, attacker)) + commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); + + if (!attacker.isEmpty() && (attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(ptr) + || attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) + && !ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat(attacker)) + { + // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. + // Note: accidental or collateral damage attacks are ignored. + startCombat(ptr, attacker); + } + } + bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) { if (observer.getClass().getCreatureStats(observer).isDead()) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 365e24067..7dbc6da74 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -119,6 +119,7 @@ namespace MWMechanics OffenseType type, int arg=0); virtual void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0); + virtual void actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); /// Utility to check if taking this item is illegal and calling commitCrime if so virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, int count); /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 579969f9d..87fb835a7 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -34,7 +34,6 @@ MWMechanics::NpcStats::NpcStats() , mProfit(0) , mTimeToStartDrowning(20.0) , mLastDrowningHit(0) -, mLevelHealthBonus(0) { mSkillIncreases.resize (ESM::Attribute::Length, 0); } @@ -262,8 +261,7 @@ void MWMechanics::NpcStats::levelUp() // "When you gain a level, in addition to increasing three primary attributes, your Health // will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level, // the Health increase is calculated from the increased Endurance" - mLevelHealthBonus += endurance * gmst.find("fLevelUpHealthEndMult")->getFloat(); - updateHealth(); + setHealth(getHealth().getBase() + endurance * gmst.find("fLevelUpHealthEndMult")->getFloat()); setLevel(getLevel()+1); } @@ -273,7 +271,7 @@ void MWMechanics::NpcStats::updateHealth() const int endurance = getAttribute(ESM::Attribute::Endurance).getBase(); const int strength = getAttribute(ESM::Attribute::Strength).getBase(); - setHealth(static_cast (0.5 * (strength + endurance)) + mLevelHealthBonus); + setHealth(static_cast (0.5 * (strength + endurance))); } int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const @@ -497,7 +495,6 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const state.mTimeToStartDrowning = mTimeToStartDrowning; state.mLastDrowningHit = mLastDrowningHit; - state.mLevelHealthBonus = mLevelHealthBonus; } void MWMechanics::NpcStats::readState (const ESM::NpcStats& state) @@ -549,5 +546,4 @@ void MWMechanics::NpcStats::readState (const ESM::NpcStats& state) mTimeToStartDrowning = state.mTimeToStartDrowning; mLastDrowningHit = state.mLastDrowningHit; - mLevelHealthBonus = state.mLevelHealthBonus; } diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index a066760d0..c78900287 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -52,8 +52,6 @@ namespace MWMechanics /// time since last hit from drowning float mLastDrowningHit; - float mLevelHealthBonus; - public: NpcStats(); @@ -104,7 +102,7 @@ namespace MWMechanics void updateHealth(); ///< Calculate health based on endurance and strength. - /// Called at character creation and at level up. + /// Called at character creation. void flagAsUsed (const std::string& id); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 94b479007..f53aceaef 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -72,7 +72,7 @@ namespace MWMechanics return schoolSkillMap[school]; } - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool) + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap) { CreatureStats& stats = actor.getClass().getCreatureStats(actor); @@ -123,14 +123,17 @@ namespace MWMechanics if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player") castChance = 100; - return std::max(0.f, std::min(100.f, castChance)); + if (!cap) + return std::max(0.f, castChance); + else + return std::max(0.f, std::min(100.f, castChance)); } - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool) + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return getSpellSuccessChance(spell, actor, effectiveSchool); + return getSpellSuccessChance(spell, actor, effectiveSchool, cap); } @@ -184,6 +187,10 @@ namespace MWMechanics float resisted = 0; if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) { + // Effects with no resistance attribute belonging to them can not be resisted + if (ESM::MagicEffect::getResistanceEffect(effectId) == -1) + return 0.f; + float resistance = getEffectResistanceAttribute(effectId, magicEffects); float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); @@ -191,12 +198,13 @@ namespace MWMechanics float x = (willpower + 0.1 * luck) * stats.getFatigueTerm(); // This makes spells that are easy to cast harder to resist and vice versa + float castChance = 100.f; if (spell != NULL && !caster.isEmpty() && caster.getClass().isActor()) { - float castChance = getSpellSuccessChance(spell, caster); - if (castChance > 0) - x *= 50 / castChance; + castChance = getSpellSuccessChance(spell, caster, NULL, false); // Uncapped casting chance } + if (castChance > 0) + x *= 50 / castChance; float roll = static_cast(std::rand()) / RAND_MAX * 100; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) @@ -230,6 +238,44 @@ namespace MWMechanics return -(resistance-100) / 100.f; } + /// Check if the given affect can be applied to the target. If \a castByPlayer, emits a message box on failure. + bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, bool castByPlayer) + { + switch (effectId) + { + case ESM::MagicEffect::Levitate: + if (!MWBase::Environment::get().getWorld()->isLevitationEnabled()) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + return false; + } + break; + case ESM::MagicEffect::Soultrap: + if (!target.getClass().isNpc() // no messagebox for NPCs + && (target.getTypeName() == typeid(ESM::Creature).name() && target.get()->mBase->mData.mSoul == 0)) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); + return true; // must still apply to get visual effect and have target regard it as attack + } + break; + case ESM::MagicEffect::AlmsiviIntervention: + case ESM::MagicEffect::DivineIntervention: + case ESM::MagicEffect::Mark: + case ESM::MagicEffect::Recall: + if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled()) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + return false; + } + break; + } + + return true; + } + CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target) : mCaster(caster) , mTarget(target) @@ -318,23 +364,8 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); - if (!MWBase::Environment::get().getWorld()->isLevitationEnabled() && effectIt->mEffectID == ESM::MagicEffect::Levitate) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); - continue; - } - - if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled() && - (effectIt->mEffectID == ESM::MagicEffect::AlmsiviIntervention || - effectIt->mEffectID == ESM::MagicEffect::DivineIntervention || - effectIt->mEffectID == ESM::MagicEffect::Mark || - effectIt->mEffectID == ESM::MagicEffect::Recall)) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + if (!checkEffectTarget(effectIt->mEffectID, target, castByPlayer)) continue; - } // If player is healing someone, show the target's HP bar if (castByPlayer && target != caster diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 2de187ae4..9381b605e 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -26,11 +26,12 @@ namespace MWMechanics * @param spell spell to cast * @param actor calculate spell success chance for this actor (depends on actor's skills) * @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here - * @attention actor has to be an NPC and not a creature! - * @return success chance from 0 to 100 (in percent) + * @param cap cap the result to 100%? + * @note actor can be an NPC or a creature + * @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned. */ - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL); - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL); + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true); + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL, bool cap=true); int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 872740d74..7a5a45832 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -467,7 +468,13 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node const std::string stop = groupname+": stop"; float starttime = std::numeric_limits::max(); float stoptime = 0.0f; - // Have to find keys in reverse (see reset method) + + // Pick the last Loop Stop key and the last Loop Start key. + // This is required because of broken text keys in AshVampire.nif. + // It has *two* WalkForward: Loop Stop keys at different times, the first one is used for stopping playback + // but the animation velocity calculation uses the second one. + // As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated, + // because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough. NifOgre::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin()); while(keyiter != keys.rend()) { @@ -476,8 +483,18 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node starttime = keyiter->first; break; } - else if(keyiter->second == loopstop || keyiter->second == stop) + ++keyiter; + } + keyiter = keys.rbegin(); + while(keyiter != keys.rend()) + { + if (keyiter->second == stop) + stoptime = keyiter->first; + else if (keyiter->second == loopstop) + { stoptime = keyiter->first; + break; + } ++keyiter; } @@ -1191,13 +1208,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) return; - // fix texture extension to .dds - if (texture.size() > 4) - { - texture[texture.size()-3] = 'd'; - texture[texture.size()-2] = 'd'; - texture[texture.size()-1] = 's'; - } + std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture); EffectParams params; params.mModelName = model; @@ -1255,7 +1266,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con for (int tex=0; texgetNumTextureUnitStates(); ++tex) { Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); - tus->setTextureName("textures\\" + texture); + tus->setTextureName(correctedTexture); } } } @@ -1285,7 +1296,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con for (int tex=0; texgetNumTextureUnitStates(); ++tex) { Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); - tus->setTextureName("textures\\" + texture); + tus->setTextureName(correctedTexture); } } } @@ -1415,6 +1426,15 @@ void ObjectAnimation::addLight(const ESM::Light *light) addExtraLight(mInsert->getCreator(), mObjectRoot, light); } +void ObjectAnimation::removeParticles() +{ + for (unsigned int i=0; imParticles.size(); ++i) + { + mObjectRoot->mSceneMgr->destroyParticleSystem(mObjectRoot->mParticles[i]); + } + mObjectRoot->mParticles.clear(); +} + class FindEntityTransparency { public: diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index e15bd6ecb..dc4244c39 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -306,6 +306,7 @@ public: virtual void attachArrow() {} virtual void releaseArrow() {} void enableLights(bool enable); + virtual void enableHeadAnimation(bool enable) {} Ogre::AxisAlignedBox getWorldBounds(); @@ -323,6 +324,7 @@ public: ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model); void addLight(const ESM::Light *light); + void removeParticles(); bool canBatch() const; void fillBatch(Ogre::StaticGeometry *sg); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 5e88b2250..fd07f1a19 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -37,6 +37,7 @@ namespace MWRender , mViewport(NULL) , mCamera(NULL) , mNode(NULL) + , mRecover(false) { mCharacter.mCell = NULL; } @@ -46,6 +47,16 @@ namespace MWRender } + void CharacterPreview::onFrame() + { + if (mRecover) + { + setupRenderTarget(); + mRenderTarget->update(); + mRecover = false; + } + } + void CharacterPreview::setup () { mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); @@ -83,19 +94,10 @@ namespace MWRender mCamera->setNearClipDistance (0.01); mCamera->setFarClipDistance (1000); - mTexture = Ogre::TextureManager::getSingleton().getByName (mName); - if (mTexture.isNull ()) - mTexture = Ogre::TextureManager::getSingleton().createManual(mName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mSizeX, mSizeY, 0, Ogre::PF_A8R8G8B8, Ogre::TU_RENDERTARGET); + mTexture = Ogre::TextureManager::getSingleton().createManual(mName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mSizeX, mSizeY, 0, Ogre::PF_A8R8G8B8, Ogre::TU_RENDERTARGET, this); - mRenderTarget = mTexture->getBuffer()->getRenderTarget(); - mRenderTarget->removeAllViewports (); - mViewport = mRenderTarget->addViewport(mCamera); - mViewport->setOverlaysEnabled(false); - mViewport->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); - mViewport->setShadowsEnabled(false); - mRenderTarget->setActive(true); - mRenderTarget->setAutoUpdated (false); + setupRenderTarget(); onSetup (); } @@ -107,6 +109,7 @@ namespace MWRender mSceneMgr->destroyAllCameras(); delete mAnimation; Ogre::Root::getSingleton().destroySceneManager(mSceneMgr); + Ogre::TextureManager::getSingleton().remove(mName); } } @@ -127,12 +130,39 @@ namespace MWRender onSetup(); } + void CharacterPreview::loadResource(Ogre::Resource *resource) + { + Ogre::Texture* tex = dynamic_cast(resource); + if (!tex) + return; + + tex->createInternalResources(); + + mRenderTarget = NULL; + mViewport = NULL; + mRecover = true; + } + + void CharacterPreview::setupRenderTarget() + { + mRenderTarget = mTexture->getBuffer()->getRenderTarget(); + mRenderTarget->removeAllViewports (); + mViewport = mRenderTarget->addViewport(mCamera); + mViewport->setOverlaysEnabled(false); + mViewport->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); + mViewport->setShadowsEnabled(false); + mRenderTarget->setActive(true); + mRenderTarget->setAutoUpdated (false); + } + // -------------------------------------------------------------------------------------------------- InventoryPreview::InventoryPreview(MWWorld::Ptr character) : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 65, -180), Ogre::Vector3(0,65,0)) , mSelectionBuffer(NULL) + , mSizeX(0) + , mSizeY(0) { } @@ -141,7 +171,21 @@ namespace MWRender delete mSelectionBuffer; } - void InventoryPreview::update(int sizeX, int sizeY) + void InventoryPreview::resize(int sizeX, int sizeY) + { + mSizeX = sizeX; + mSizeY = sizeY; + + mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); + mTexture->load(); + + if (!mRenderTarget) + setupRenderTarget(); + + mRenderTarget->update(); + } + + void InventoryPreview::update() { mAnimation->updateParts(); @@ -197,15 +241,25 @@ namespace MWRender mAnimation->runAnimation(0.0f); - mViewport->setDimensions (0, 0, std::min(1.f, float(sizeX) / float(512)), std::min(1.f, float(sizeY) / float(1024))); - mNode->setOrientation (Ogre::Quaternion::IDENTITY); + mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); + mTexture->load(); + + if (!mRenderTarget) + setupRenderTarget(); + mRenderTarget->update(); mSelectionBuffer->update(); } + void InventoryPreview::setupRenderTarget() + { + CharacterPreview::setupRenderTarget(); + mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); + } + int InventoryPreview::getSlotSelected (int posX, int posY) { return mSelectionBuffer->getSelected (posX, posY); @@ -243,6 +297,7 @@ namespace MWRender void RaceSelectionPreview::render() { + mTexture->load(); mRenderTarget->update(); } diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 60312455f..711de0d15 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -22,7 +22,7 @@ namespace MWRender class NpcAnimation; - class CharacterPreview + class CharacterPreview : public Ogre::ManualResourceLoader { public: CharacterPreview(MWWorld::Ptr character, int sizeX, int sizeY, const std::string& name, @@ -34,6 +34,13 @@ namespace MWRender virtual void rebuild(); + void onFrame(); + + void loadResource(Ogre::Resource *resource); + + private: + bool mRecover; // Texture content was lost and needs to be re-rendered + private: CharacterPreview(const CharacterPreview&); CharacterPreview& operator=(const CharacterPreview&); @@ -41,6 +48,8 @@ namespace MWRender protected: virtual bool renderHeadOnly() { return false; } + virtual void setupRenderTarget(); + Ogre::TexturePtr mTexture; Ogre::RenderTarget* mRenderTarget; Ogre::Viewport* mViewport; @@ -72,11 +81,17 @@ namespace MWRender virtual ~InventoryPreview(); virtual void onSetup(); - void update(int sizeX, int sizeY); + void update(); // Render preview again, e.g. after changed equipment + void resize(int sizeX, int sizeY); int getSlotSelected(int posX, int posY); + protected: + virtual void setupRenderTarget(); + private: + int mSizeX; + int mSizeY; OEngine::Render::SelectionBuffer* mSelectionBuffer; }; diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 968be0f9e..a48dea8d5 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -1,5 +1,7 @@ #include "effectmanager.hpp" +#include + #include #include #include @@ -21,15 +23,6 @@ void EffectManager::addEffect(const std::string &model, std::string textureOverr Ogre::SceneNode* sceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(worldPosition); sceneNode->setScale(scale,scale,scale); - // fix texture extension to .dds - if (textureOverride.size() > 4) - { - textureOverride[textureOverride.size()-3] = 'd'; - textureOverride[textureOverride.size()-2] = 'd'; - textureOverride[textureOverride.size()-1] = 's'; - } - - NifOgre::ObjectScenePtr scene = NifOgre::Loader::createObjects(sceneNode, model); // TODO: turn off shadow casting @@ -44,6 +37,7 @@ void EffectManager::addEffect(const std::string &model, std::string textureOverr if (!textureOverride.empty()) { + std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(textureOverride); for(size_t i = 0;i < scene->mParticles.size(); ++i) { Ogre::ParticleSystem* partSys = scene->mParticles[i]; @@ -59,7 +53,7 @@ void EffectManager::addEffect(const std::string &model, std::string textureOverr for (int tex=0; texgetNumTextureUnitStates(); ++tex) { Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); - tus->setTextureName("textures\\" + textureOverride); + tus->setTextureName(correctedTexture); } } } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 271cbbfe1..50dec8950 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -98,6 +98,7 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) return; Ogre::Image image; + tex->load(); tex->convertToImage(image); Ogre::DataStreamPtr encoded = image.encode("tga"); @@ -137,6 +138,7 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) return; Ogre::Image image; + tex->load(); tex->convertToImage(image); fog->mFogTextures.push_back(ESM::FogTexture()); @@ -581,6 +583,8 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni } } + tex->load(); + // copy to the texture // NOTE: Could be optimized later. We actually only need to update the region that changed. // Not a big deal at the moment, the FoW is only 32x32 anyway. diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 0a9b56b33..23b761646 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -67,11 +67,16 @@ namespace MWRender { HeadAnimationTime::HeadAnimationTime(MWWorld::Ptr reference) - : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mValue(0) + : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mValue(0), mEnabled(true) { resetBlinkTimer(); } +void HeadAnimationTime::setEnabled(bool enabled) +{ + mEnabled = enabled; +} + void HeadAnimationTime::resetBlinkTimer() { mBlinkTimer = -(2 + (std::rand() / double(RAND_MAX*1.0)) * 6); @@ -79,6 +84,9 @@ void HeadAnimationTime::resetBlinkTimer() void HeadAnimationTime::update(float dt) { + if (!mEnabled) + return; + if (MWBase::Environment::get().getSoundManager()->sayDone(mReference)) { mBlinkTimer += dt; @@ -864,6 +872,11 @@ void NpcAnimation::setAlpha(float alpha) } } +void NpcAnimation::enableHeadAnimation(bool enable) +{ + mHeadAnimationTime->setEnabled(enable); +} + void NpcAnimation::preRender(Ogre::Camera *camera) { Animation::preRender(camera); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 95cd35ddb..979210591 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -26,6 +26,8 @@ private: float mBlinkTimer; + bool mEnabled; + float mValue; private: void resetBlinkTimer(); @@ -34,6 +36,8 @@ public: void update(float dt); + void setEnabled(bool enabled); + void setTalkStart(float value); void setTalkStop(float value); void setBlinkStart(float value); @@ -125,6 +129,8 @@ public: ViewMode viewMode=VM_Normal); virtual ~NpcAnimation(); + virtual void enableHeadAnimation(bool enable); + virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } virtual Ogre::Vector3 runAnimation(float timepassed); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index d9e20e1f8..96decbb36 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -73,14 +73,19 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) ptr.getRefData().setBaseNode(insert); } -void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch) +void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch, bool addLight) { insertBegin(ptr); std::auto_ptr anim(new ObjectAnimation(ptr, mesh)); if(ptr.getTypeName() == typeid(ESM::Light).name()) - anim->addLight(ptr.get()->mBase); + { + if (addLight) + anim->addLight(ptr.get()->mBase); + else + anim->removeParticles(); + } if (!mesh.empty()) { diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 02e974e2d..7f740dbab 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -41,7 +41,7 @@ public: , mRootNode(NULL) {} ~Objects(){} - void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false); + void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false, bool addLight=false); ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 23edb3a7f..ed6b56f37 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -23,6 +23,7 @@ #include #include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -45,7 +46,6 @@ #include "globalmap.hpp" #include "terrainstorage.hpp" #include "effectmanager.hpp" -#include "terraingrid.hpp" using namespace MWRender; using namespace Ogre; @@ -212,11 +212,6 @@ MWRender::Actors& RenderingManager::getActors(){ return *mActors; } -OEngine::Render::Fader* RenderingManager::getFader() -{ - return mRendering.getFader(); -} - MWRender::Camera* RenderingManager::getCamera() const { return mCamera; @@ -345,7 +340,7 @@ void RenderingManager::update (float duration, bool paused) MWWorld::Ptr player = world->getPlayerPtr(); int blind = player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Blind).mMagnitude; - mRendering.getFader()->setFactor(std::max(0.f, 1.f-(blind / 100.f))); + MWBase::Environment::get().getWindowManager()->setScreenFactor(std::max(0.f, 1.f-(blind / 100.f))); setAmbientMode(); // player position @@ -1050,7 +1045,7 @@ void RenderingManager::enableTerrain(bool enable) mTerrain = new Terrain::DefaultWorld(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); else - mTerrain = new MWRender::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + mTerrain = new Terrain::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY); mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index ea7905cf5..ef436931d 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -4,8 +4,6 @@ #include "sky.hpp" #include "debugging.hpp" -#include - #include #include @@ -98,8 +96,6 @@ public: void toggleLight(); bool toggleRenderMode(int mode); - OEngine::Render::Fader* getFader(); - void removeCell (MWWorld::CellStore *store); /// \todo this function should be removed later. Instead the rendering subsystems should track diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 8354cca5d..229bf92e7 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -19,6 +19,7 @@ #include #include +#include #include @@ -294,7 +295,12 @@ void SkyManager::create() // Stars mAtmosphereNight = mRootNode->createChildSceneNode(); - NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif"); + NifOgre::ObjectScenePtr objects; + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("meshes\\sky_night_02.nif")) + objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_02.nif"); + else + objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif"); + for(size_t i = 0, matidx = 0;i < objects->mEntities.size();i++) { Entity* night1_ent = objects->mEntities[i]; @@ -584,13 +590,13 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) if (mClouds != weather.mCloudTexture) { - sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", "textures\\"+weather.mCloudTexture); + sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", Misc::ResourceHelpers::correctTexturePath(weather.mCloudTexture)); mClouds = weather.mCloudTexture; } if (mNextClouds != weather.mNextCloudTexture) { - sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", "textures\\"+weather.mNextCloudTexture); + sh::Factory::getInstance().setTextureAlias ("cloud_texture_2", Misc::ResourceHelpers::correctTexturePath(weather.mNextCloudTexture)); mNextClouds = weather.mNextCloudTexture; } diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 2558c95c5..cbd9e2444 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,21 +1,11 @@ #include "terrainstorage.hpp" -#include -#include -#include -#include -#include -#include -#include - #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" -#include - namespace MWRender { @@ -59,515 +49,4 @@ namespace MWRender return esmStore.get().find(index, plugin); } - bool TerrainStorage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) - { - assert (size <= 1 && "TerrainStorage::getMinMaxHeights, chunk size should be <= 1 cell"); - - /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead - - Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); - - int cellX = origin.x; - int cellY = origin.y; - - const ESM::Land* land = getLand(cellX, cellY); - if (!land) - return false; - - min = std::numeric_limits().max(); - max = -std::numeric_limits().max(); - for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; - if (h > max) - max = h; - if (h < min) - min = h; - } - } - return true; - } - - void TerrainStorage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) - { - while (col >= ESM::Land::LAND_SIZE-1) - { - ++cellY; - col -= ESM::Land::LAND_SIZE-1; - } - while (row >= ESM::Land::LAND_SIZE-1) - { - ++cellX; - row -= ESM::Land::LAND_SIZE-1; - } - while (col < 0) - { - --cellY; - col += ESM::Land::LAND_SIZE-1; - } - while (row < 0) - { - --cellX; - row += ESM::Land::LAND_SIZE-1; - } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mHasData) - { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalise(); - } - else - normal = Ogre::Vector3(0,0,1); - } - - void TerrainStorage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) - { - Ogre::Vector3 n1,n2,n3,n4; - fixNormal(n1, cellX, cellY, col+1, row); - fixNormal(n2, cellX, cellY, col-1, row); - fixNormal(n3, cellX, cellY, col, row+1); - fixNormal(n4, cellX, cellY, col, row-1); - normal = (n1+n2+n3+n4); - normal.normalise(); - } - - void TerrainStorage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) - { - if (col == ESM::Land::LAND_SIZE-1) - { - ++cellY; - col = 0; - } - if (row == ESM::Land::LAND_SIZE-1) - { - ++cellX; - row = 0; - } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mLandData->mUsingColours) - { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; - } - else - { - color.r = 1; - color.g = 1; - color.b = 1; - } - - } - - void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - std::vector& positions, - std::vector& normals, - std::vector& colours) - { - // LOD level n means every 2^n-th vertex is kept - size_t increment = 1 << lodLevel; - - Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); - - int startX = origin.x; - int startY = origin.y; - - size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; - - colours.resize(numVerts*numVerts*4); - positions.resize(numVerts*numVerts*3); - normals.resize(numVerts*numVerts*3); - - Ogre::Vector3 normal; - Ogre::ColourValue color; - - float vertY; - float vertX; - - float vertY_ = 0; // of current cell corner - for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) - { - float vertX_ = 0; // of current cell corner - for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) - { - ESM::Land* land = getLand(cellX, cellY); - if (land && !land->mHasData) - land = NULL; - bool hasColors = land && land->mLandData->mUsingColours; - - int rowStart = 0; - int colStart = 0; - // Skip the first row / column unless we're at a chunk edge, - // since this row / column is already contained in a previous cell - if (colStart == 0 && vertY_ != 0) - colStart += increment; - if (rowStart == 0 && vertX_ != 0) - rowStart += increment; - - vertY = vertY_; - for (int col=colStart; colmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; - else - positions[vertX*numVerts*3 + vertY*3 + 2] = -2048; - - if (land) - { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalise(); - } - else - normal = Ogre::Vector3(0,0,1); - - // Normals apparently don't connect seamlessly between cells - if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixNormal(normal, cellX, cellY, col, row); - - // some corner normals appear to be complete garbage (z < 0) - if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) - averageNormal(normal, cellX, cellY, col, row); - - assert(normal.z > 0); - - normals[vertX*numVerts*3 + vertY*3] = normal.x; - normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; - normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; - - if (hasColors) - { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; - } - else - { - color.r = 1; - color.g = 1; - color.b = 1; - } - - // Unlike normals, colors mostly connect seamlessly between cells, but not always... - if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixColour(color, cellX, cellY, col, row); - - color.a = 1; - Ogre::uint32 rsColor; - Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); - memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); - - ++vertX; - } - ++vertY; - } - vertX_ = vertX; - } - vertY_ = vertY; - - assert(vertX_ == numVerts); // Ensure we covered whole area - } - assert(vertY_ == numVerts); // Ensure we covered whole area - } - - TerrainStorage::UniqueTextureId TerrainStorage::getVtexIndexAt(int cellX, int cellY, - int x, int y) - { - // For the first/last row/column, we need to get the texture from the neighbour cell - // to get consistent blending at the borders - --x; - if (x < 0) - { - --cellX; - x += ESM::Land::LAND_TEXTURE_SIZE; - } - if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? - { - ++cellY; - y -= ESM::Land::LAND_TEXTURE_SIZE; - } - - assert(xmLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; - if (tex == 0) - return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin - return std::make_pair(tex, land->mPlugin); - } - else - return std::make_pair(0,0); - } - - std::string TerrainStorage::getTextureName(UniqueTextureId id) - { - if (id.first == 0) - return "_land_default.dds"; // Not sure if the default texture floatly is hardcoded? - - // NB: All vtex ids are +1 compared to the ltex ids - const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); - - std::string texture = ltex->mTexture; - //TODO this is needed due to MWs messed up texture handling - texture = texture.substr(0, texture.rfind(".")) + ".dds"; - - return texture; - } - - void TerrainStorage::getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) - { - for (std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) - { - out.push_back(Terrain::LayerCollection()); - out.back().mTarget = *it; - getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers); - } - } - - void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) - { - getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList); - } - - void TerrainStorage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) - { - // TODO - blending isn't completely right yet; the blending radius appears to be - // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap - // and interpolate the rest of the cell by hand? :/ - - Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); - int cellX = origin.x; - int cellY = origin.y; - - // Save the used texture indices so we know the total number of textures - // and number of required blend maps - std::set textureIndices; - // Due to the way the blending works, the base layer will always shine through in between - // blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible). - // To get a consistent look, we need to make sure to use the same base layer in all cells. - // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. - textureIndices.insert(std::make_pair(0,0)); - - for (int y=0; y textureIndicesMap; - for (std::set::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it) - { - int size = textureIndicesMap.size(); - textureIndicesMap[*it] = size; - layerList.push_back(getLayerInfo(getTextureName(*it))); - } - - int numTextures = textureIndices.size(); - // numTextures-1 since the base layer doesn't need blending - int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1); - - int channels = pack ? 4 : 1; - - // Second iteration - create and fill in the blend maps - const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; - - for (int i=0; isecond; - int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1); - int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; - - if (blendIndex == i) - pData[y*blendmapSize*channels + x*channels + channel] = 255; - else - pData[y*blendmapSize*channels + x*channels + channel] = 0; - } - } - blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData)); - } - } - - float TerrainStorage::getHeightAt(const Ogre::Vector3 &worldPos) - { - int cellX = std::floor(worldPos.x / 8192.f); - int cellY = std::floor(worldPos.y / 8192.f); - - ESM::Land* land = getLand(cellX, cellY); - if (!land) - return -2048; - - // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition - - // Normalized position in the cell - float nX = (worldPos.x - (cellX * 8192))/8192.f; - float nY = (worldPos.y - (cellY * 8192))/8192.f; - - // get left / bottom points (rounded down) - float factor = ESM::Land::LAND_SIZE - 1.0f; - float invFactor = 1.0f / factor; - - int startX = static_cast(nX * factor); - int startY = static_cast(nY * factor); - int endX = startX + 1; - int endY = startY + 1; - - assert(endX < ESM::Land::LAND_SIZE); - assert(endY < ESM::Land::LAND_SIZE); - - // now get points in terrain space (effectively rounding them to boundaries) - float startXTS = startX * invFactor; - float startYTS = startY * invFactor; - float endXTS = endX * invFactor; - float endYTS = endY * invFactor; - - // get parametric from start coord to next point - float xParam = (nX - startXTS) * factor; - float yParam = (nY - startYTS) * factor; - - /* For even / odd tri strip rows, triangles are this shape: - even odd - 3---2 3---2 - | / | | \ | - 0---1 0---1 - */ - - // Build all 4 positions in normalized cell space, using point-sampled height - Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); - Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); - Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); - Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); - // define this plane in terrain space - Ogre::Plane plane; - // (At the moment, all rows have the same triangle alignment) - if (true) - { - // odd row - bool secondTri = ((1.0 - yParam) > xParam); - if (secondTri) - plane.redefine(v0, v1, v3); - else - plane.redefine(v1, v2, v3); - } - else - { - // even row - bool secondTri = (yParam > xParam); - if (secondTri) - plane.redefine(v0, v2, v3); - else - plane.redefine(v0, v1, v2); - } - - // Solve plane equation for z - return (-plane.normal.x * nX - -plane.normal.y * nY - - plane.d) / plane.normal.z * 8192; - - } - - float TerrainStorage::getVertexHeight(const ESM::Land *land, int x, int y) - { - assert(x < ESM::Land::LAND_SIZE); - assert(y < ESM::Land::LAND_SIZE); - return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; - } - - Terrain::LayerInfo TerrainStorage::getLayerInfo(const std::string& texture) - { - // Already have this cached? - if (mLayerInfoMap.find(texture) != mLayerInfoMap.end()) - return mLayerInfoMap[texture]; - - Terrain::LayerInfo info; - info.mParallax = false; - info.mSpecular = false; - info.mDiffuseMap = "textures\\" + texture; - std::string texture_ = texture; - boost::replace_last(texture_, ".", "_nh."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) - { - info.mNormalMap = "textures\\" + texture_; - info.mParallax = true; - } - else - { - texture_ = texture; - boost::replace_last(texture_, ".", "_n."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) - info.mNormalMap = "textures\\" + texture_; - } - - texture_ = texture; - boost::replace_last(texture_, ".", "_diffusespec."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) - { - info.mDiffuseMap = "textures\\" + texture_; - info.mSpecular = true; - } - - // This wasn't cached, so the textures are probably not loaded either. - // Background load them so they are hopefully already loaded once we need them! - Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General"); - if (!info.mNormalMap.empty()) - Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General"); - - mLayerInfoMap[texture] = info; - - return info; - } - - Terrain::LayerInfo TerrainStorage::getDefaultLayer() - { - Terrain::LayerInfo info; - info.mDiffuseMap = "textures\\_land_default.dds"; - info.mParallax = false; - info.mSpecular = false; - return info; - } - - float TerrainStorage::getCellWorldSize() - { - return ESM::Land::REAL_SIZE; - } - - int TerrainStorage::getCellVertices() - { - return ESM::Land::LAND_SIZE; - } - } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index c1fb74445..30a2a61ac 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -1,15 +1,13 @@ #ifndef MWRENDER_TERRAINSTORAGE_H #define MWRENDER_TERRAINSTORAGE_H -#include -#include - -#include +#include namespace MWRender { - class TerrainStorage : public Terrain::Storage + /// @brief Connects the ESM Store used in OpenMW with the ESMTerrain storage. + class TerrainStorage : public ESMTerrain::Storage { private: virtual ESM::Land* getLand (int cellX, int cellY); @@ -18,92 +16,6 @@ namespace MWRender /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); - - /// Get the minimum and maximum heights of a terrain region. - /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. - /// Larger chunks can simply merge AABB of children. - /// @param size size of the chunk in cell units - /// @param center center of the chunk in cell units - /// @param min min height will be stored here - /// @param max max height will be stored here - /// @return true if there was data available for this terrain chunk - virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); - - /// Fill vertex buffers for a terrain chunk. - /// @note May be called from background threads. Make sure to only call thread-safe functions from here! - /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. - /// @param lodLevel LOD level, 0 = most detailed - /// @param size size of the terrain chunk in cell units - /// @param center center of the chunk in cell units - /// @param positions buffer to write vertices - /// @param normals buffer to write vertex normals - /// @param colours buffer to write vertex colours - virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - std::vector& positions, - std::vector& normals, - std::vector& colours); - - /// Create textures holding layer blend values for a terrain chunk. - /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might - /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from background threads. - /// @param chunkSize size of the terrain chunk in cell units - /// @param chunkCenter center of the chunk in cell units - /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - - /// otherwise, each texture contains blend values for one layer only. Shader-based rendering - /// can utilize packing, FFP can't. - /// @param blendmaps created blendmaps will be written here - /// @param layerList names of the layer textures used will be written here - virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, - std::vector& layerList); - - /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. - /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. - /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might - /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from background threads. - /// @param nodes A collection of nodes for which to retrieve the aforementioned data - /// @param out Output vector - /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - - /// otherwise, each texture contains blend values for one layer only. Shader-based rendering - /// can utilize packing, FFP can't. - virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack); - - virtual float getHeightAt (const Ogre::Vector3& worldPos); - - virtual Terrain::LayerInfo getDefaultLayer(); - - /// Get the transformation factor for mapping cell units to world units. - virtual float getCellWorldSize(); - - /// Get the number of vertices on one side for each cell. Should be (power of two)+1 - virtual int getCellVertices(); - - private: - void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); - void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row); - void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); - - float getVertexHeight (const ESM::Land* land, int x, int y); - - // Since plugins can define new texture palettes, we need to know the plugin index too - // in order to retrieve the correct texture name. - // pair - typedef std::pair UniqueTextureId; - - UniqueTextureId getVtexIndexAt(int cellX, int cellY, - int x, int y); - std::string getTextureName (UniqueTextureId id); - - std::map mLayerInfoMap; - - Terrain::LayerInfo getLayerInfo(const std::string& texture); - - // Non-virtual - void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, - std::vector& layerList); }; } diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index d2e774859..fb6b73be6 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -121,6 +121,34 @@ namespace MWScript } }; + template + class OpGetForceJump : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + } + }; + + template + class OpGetForceMoveJump : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); + runtime.push (stats.getMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + } + }; + template class OpGetForceSneak : public Interpreter::Opcode0 { @@ -169,27 +197,54 @@ namespace MWScript interpreter.installSegment5 (Compiler::Control::opcodeToggleCollision, new OpToggleCollision); + //Force Run interpreter.installSegment5 (Compiler::Control::opcodeClearForceRun, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); - interpreter.installSegment5 (Compiler::Control::opcodeForceRun, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneak, - new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - interpreter.installSegment5 (Compiler::Control::opcodeForceSneak, - new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); - interpreter.installSegment5 (Compiler::Control::opcodeClearForceRunExplicit, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); + interpreter.installSegment5 (Compiler::Control::opcodeForceRun, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); interpreter.installSegment5 (Compiler::Control::opcodeForceRunExplicit, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceRun)); + + //Force Jump + interpreter.installSegment5 (Compiler::Control::opcodeClearForceJump, + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + interpreter.installSegment5 (Compiler::Control::opcodeClearForceJumpExplicit, + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + interpreter.installSegment5 (Compiler::Control::opcodeForceJump, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + interpreter.installSegment5 (Compiler::Control::opcodeForceJumpExplicit, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceJump)); + + //Force MoveJump + interpreter.installSegment5 (Compiler::Control::opcodeClearForceMoveJump, + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + interpreter.installSegment5 (Compiler::Control::opcodeClearForceMoveJumpExplicit, + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + interpreter.installSegment5 (Compiler::Control::opcodeForceMoveJump, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + interpreter.installSegment5 (Compiler::Control::opcodeForceMoveJumpExplicit, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceMoveJump)); + + //Force Sneak + interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneak, + new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeClearForceSneakExplicit, new OpClearMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); + interpreter.installSegment5 (Compiler::Control::opcodeForceSneak, + new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); interpreter.installSegment5 (Compiler::Control::opcodeForceSneakExplicit, new OpSetMovementFlag (MWMechanics::CreatureStats::Flag_ForceSneak)); + interpreter.installSegment5 (Compiler::Control::opcodeGetPcRunning, new OpGetPcRunning); interpreter.installSegment5 (Compiler::Control::opcodeGetPcSneaking, new OpGetPcSneaking); interpreter.installSegment5 (Compiler::Control::opcodeGetForceRun, new OpGetForceRun); interpreter.installSegment5 (Compiler::Control::opcodeGetForceRunExplicit, new OpGetForceRun); + interpreter.installSegment5 (Compiler::Control::opcodeGetForceJump, new OpGetForceJump); + interpreter.installSegment5 (Compiler::Control::opcodeGetForceJumpExplicit, new OpGetForceJump); + interpreter.installSegment5 (Compiler::Control::opcodeGetForceMoveJump, new OpGetForceMoveJump); + interpreter.installSegment5 (Compiler::Control::opcodeGetForceMoveJumpExplicit, new OpGetForceMoveJump); interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneak, new OpGetForceSneak); interpreter.installSegment5 (Compiler::Control::opcodeGetForceSneakExplicit, new OpGetForceSneak); } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 4b501b20f..f600117a9 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -413,5 +413,13 @@ op 0x2000254: HurtStandingActor op 0x2000255: HurtStandingActor, explicit op 0x2000256: HurtCollidingActor op 0x2000257: HurtCollidingActor, explicit +op 0x2000258: ClearForceJump +op 0x2000259: ClearForceJump, explicit reference +op 0x200025a: ForceJump +op 0x200025b: ForceJump, explicit reference +op 0x200025c: ClearForceMoveJump +op 0x200025d: ClearForceMoveJump, explicit reference +op 0x200025e: ForceMoveJump +op 0x200025f: ForceMoveJump, explicit reference -opcodes 0x2000258-0x3ffffff unused +opcodes 0x2000260-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 36848d44c..b487ac70f 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -3,8 +3,6 @@ #include -#include - #include #include #include @@ -248,7 +246,7 @@ namespace MWScript Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getWorld()->getFader()->fadeIn(time); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(time); } }; @@ -261,7 +259,7 @@ namespace MWScript Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getWorld()->getFader()->fadeOut(time); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(time); } }; @@ -277,7 +275,7 @@ namespace MWScript Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getWorld()->getFader()->fadeTo(alpha, time); + MWBase::Environment::get().getWindowManager()->fadeScreenTo(alpha, time); } }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 07d137ce2..7150704ca 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -637,8 +637,6 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime); - std::string factionID = ""; if(arg0 >0) { @@ -647,6 +645,8 @@ namespace MWScript } else { + MWWorld::Ptr ptr = R()(runtime); + if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) { factionID = ""; @@ -913,8 +913,6 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) { - MWWorld::Ptr ptr = R()(runtime); - std::string factionID = ""; if(arg0 >0 ) { @@ -923,6 +921,8 @@ namespace MWScript } else { + MWWorld::Ptr ptr = R()(runtime); + if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) { factionID = ""; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index a041049ca..3355e705f 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -327,7 +327,7 @@ namespace MWScript } MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); - ptr.getClass().adjustPosition(ptr); + ptr.getClass().adjustPosition(ptr, false); } else { @@ -363,8 +363,19 @@ namespace MWScript runtime.pop(); int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); - MWBase::Environment::get().getWorld()->moveObject(ptr, - MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); + + // another morrowind oddity: player will be moved to the exterior cell at this location, + // non-player actors will move within the cell they are in. + if (ptr.getRefData().getHandle() == "player") + { + MWBase::Environment::get().getWorld()->moveObject(ptr, + MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); + } + else + { + MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z); + } + float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity @@ -374,7 +385,7 @@ namespace MWScript zRot = zRot/60.; } MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); - ptr.getClass().adjustPosition(ptr); + ptr.getClass().adjustPosition(ptr, false); } }; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 446519b87..d241278d1 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -322,7 +322,7 @@ namespace MWWorld return std::make_pair (1, ""); } - void Class::adjustPosition(const MWWorld::Ptr& ptr) const + void Class::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index e880030f9..64e83179d 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -90,8 +90,9 @@ namespace MWWorld ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. - virtual void adjustPosition(const MWWorld::Ptr& ptr) const; + virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; ///< Adjust position to stand on ground. Must be called post model load + /// @param force do this even if the ptr is flying virtual MWMechanics::CreatureStats& getCreatureStats (const Ptr& ptr) const; ///< Return creature stats or throw an exception, if class does not have creature stats diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 3188c1cd5..085b079d9 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -701,7 +701,8 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& state) for (std::vector >::const_iterator iter (state.mLights.begin()); iter!=state.mLights.end(); ++iter) { - getState (lights, iter->first); + int slot = iter->second; + setSlot (getState (lights, iter->first), slot); } mLevelledItemMap = state.mLevelledItemMap; diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 02a198f3d..3c0e67729 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -612,9 +612,9 @@ namespace MWWorld } } - std::vector PhysicsSystem::getCollisions(const Ptr &ptr) + std::vector PhysicsSystem::getCollisions(const Ptr &ptr, int collisionGroup, int collisionMask) { - return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName()); + return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName(), collisionGroup, collisionMask); } Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight) diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index ac60a80b5..f0522426d 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -55,7 +55,7 @@ namespace MWWorld void stepSimulation(float dt); - std::vector getCollisions(const MWWorld::Ptr &ptr); ///< get handles this object collides with + std::vector getCollisions(const MWWorld::Ptr &ptr, int collisionGroup, int collisionMask); ///< get handles this object collides with Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, float maxHeight); std::pair getFacedHandle(float queryDistance); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 280761215..b3996f756 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -244,6 +244,8 @@ namespace MWWorld mPlayer.load (player.mObject); + getPlayer().getClass().getCreatureStats(getPlayer()).getAiSequence().clear(); + MWBase::World& world = *MWBase::Environment::get().getWorld(); try diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index fb376bb93..ff1743c64 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -247,29 +247,23 @@ namespace MWWorld if (obstacle == caster) continue; - if (obstacle.isEmpty()) + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mId); + + // Try to get a Ptr to the bow that was used. It might no longer exist. + MWWorld::Ptr bow = projectileRef.getPtr(); + if (!caster.isEmpty()) { - // Terrain - } - else if (obstacle.getClass().isActor()) - { - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mId); - - // Try to get a Ptr to the bow that was used. It might no longer exist. - MWWorld::Ptr bow = projectileRef.getPtr(); - if (!caster.isEmpty()) - { - MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); - MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) - bow = *invIt; - } - - if (caster.isEmpty()) - caster = obstacle; - - MWMechanics::projectileHit(caster, obstacle, bow, projectileRef.getPtr(), pos + (newPos - pos) * cIt->first); + MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); + MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) + bow = *invIt; } + + if (caster.isEmpty()) + caster = obstacle; + + MWMechanics::projectileHit(caster, obstacle, bow, projectileRef.getPtr(), pos + (newPos - pos) * cIt->first); + hit = true; } if (hit) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index b2faa1a01..ee107e1f2 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -4,8 +4,6 @@ #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" /// FIXME #include "../mwbase/soundmanager.hpp" @@ -86,7 +84,7 @@ namespace updateObjectLocalRotation(ptr, mPhysics, mRendering); MWBase::Environment::get().getWorld()->scaleObject (ptr, ptr.getCellRef().getScale()); - ptr.getClass().adjustPosition (ptr); + ptr.getClass().adjustPosition (ptr, false); } catch (const std::exception& e) { @@ -232,7 +230,7 @@ namespace MWWorld float z = Ogre::Radian(pos.rot[2]).valueDegrees(); world->rotateObject(player, x, y, z); - player.getClass().adjustPosition(player); + player.getClass().adjustPosition(player, true); } MWBase::MechanicsManager *mechMgr = @@ -405,21 +403,20 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { + CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); + bool loadcell = (mCurrentCell == NULL); + if(!loadcell) + loadcell = *mCurrentCell != *cell; + Nif::NIFFile::CacheLock lock; - MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.5); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - Loading::ScopedLoad load(loadingListener); - - mRendering.enableTerrain(false); - std::string loadingInteriorText = "#{sLoadingMessage2}"; loadingListener->setLabel(loadingInteriorText); + Loading::ScopedLoad load(loadingListener); - CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); - bool loadcell = (mCurrentCell == NULL); - if(!loadcell) - loadcell = *mCurrentCell != *cell; + mRendering.enableTerrain(false); if(!loadcell) { @@ -431,8 +428,8 @@ namespace MWWorld float z = Ogre::Radian(position.rot[2]).valueDegrees(); world->rotateObject(world->getPlayerPtr(), x, y, z); - world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr()); - world->getFader()->fadeIn(0.5f); + world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr(), true); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); return; } @@ -480,7 +477,7 @@ namespace MWWorld MWBase::Environment::get().getWorld()->adjustSky(); mCellChanged = true; - MWBase::Environment::get().getWorld ()->getFader ()->fadeIn(0.5); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); } void Scene::changeToExteriorCell (const ESM::Position& position) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index fb45cb034..675a3e86e 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -73,11 +73,6 @@ Rain Height Max=700 ? Rain Threshold=0.6 ? Max Raindrops=650 ? */ - - size_t offset = weather.mCloudTexture.find(".tga"); - if (offset != std::string::npos) - weather.mCloudTexture.replace(offset, weather.mCloudTexture.length() - offset, ".dds"); - weather.mIsStorm = (name == "ashstorm" || name == "blight"); mWeatherSettings[name] = weather; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f6aa6fec7..a84e4781d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1151,7 +1151,7 @@ namespace MWWorld } } - void World::adjustPosition(const Ptr &ptr) + void World::adjustPosition(const Ptr &ptr, bool force) { ESM::Position pos (ptr.getRefData().getPosition()); @@ -1170,7 +1170,7 @@ namespace MWWorld ptr.getRefData().setPosition(pos); - if (!isFlying(ptr)) + if (force || !isFlying(ptr)) { Ogre::Vector3 traced = mPhysics->traceDown(ptr, 200); if (traced.z < pos.pos[2]) @@ -1284,7 +1284,8 @@ namespace MWWorld bool reached = (targetRot == 90.f && it->second) || targetRot == 0.f; /// \todo should use convexSweepTest here - std::vector collisions = mPhysics->getCollisions(it->first); + std::vector collisions = mPhysics->getCollisions(it->first, OEngine::Physic::CollisionType_Actor + , OEngine::Physic::CollisionType_Actor); for (std::vector::iterator cit = collisions.begin(); cit != collisions.end(); ++cit) { MWWorld::Ptr ptr = getPtrViaHandle(*cit); @@ -1300,7 +1301,6 @@ namespace MWWorld // we need to undo the rotation localRotateObject(it->first, 0, 0, oldRot); reached = false; - //break; //Removed in case multiple actors are touching } } @@ -1555,11 +1555,6 @@ namespace MWWorld mWeatherManager->modRegion(regionid, chances); } - OEngine::Render::Fader* World::getFader() - { - return mRendering->getFader(); - } - Ogre::Vector2 World::getNorthVector (CellStore* cell) { MWWorld::CellRefList& statics = cell->get(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index c4c8b588e..fad3dfd97 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -169,9 +169,6 @@ namespace MWWorld virtual void readRecord (ESM::ESMReader& reader, int32_t type, const std::map& contentFileMap); - virtual OEngine::Render::Fader* getFader(); - ///< \todo remove this function. Rendering details should not be exposed. - virtual CellStore *getExterior (int x, int y); virtual CellStore *getInterior (const std::string& name); @@ -260,8 +257,9 @@ namespace MWWorld virtual Ptr searchPtrViaActorId (int actorId); ///< Search is limited to the active cells. - virtual void adjustPosition (const Ptr& ptr); + virtual void adjustPosition (const Ptr& ptr, bool force); ///< Adjust position after load to be on ground. Must be called after model load. + /// @param force do this even if the ptr is flying virtual void fixPosition (const Ptr& actor); ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake index 81b52b1b7..5608c528c 100644 --- a/cmake/FindOGRE.cmake +++ b/cmake/FindOGRE.cmake @@ -127,7 +127,7 @@ endif () set(OGRE_COMPONENTS Paging Terrain Plugin_BSPSceneManager Plugin_CgProgramManager Plugin_OctreeSceneManager Plugin_OctreeZone Plugin_PCZSceneManager Plugin_ParticleFX - RenderSystem_Direct3D10 RenderSystem_Direct3D9 RenderSystem_GL RenderSystem_GLES) + RenderSystem_Direct3D10 RenderSystem_Direct3D9 RenderSystem_GL RenderSystem_GLES2) set(OGRE_RESET_VARS OGRE_CONFIG_INCLUDE_DIR OGRE_INCLUDE_DIR OGRE_LIBRARY_FWK OGRE_LIBRARY_REL OGRE_LIBRARY_DBG @@ -234,10 +234,10 @@ if (OGRE_STATIC) find_package(FreeImage QUIET) find_package(Freetype QUIET) find_package(OpenGL QUIET) - find_package(OpenGLES QUIET) + find_package(OpenGLES2 QUIET) find_package(ZLIB QUIET) find_package(ZZip QUIET) - if (UNIX AND NOT APPLE) + if (UNIX AND (NOT APPLE AND NOT ANDROID)) find_package(X11 QUIET) find_library(XAW_LIBRARY NAMES Xaw Xaw7 PATHS ${DEP_LIB_SEARCH_DIR} ${X11_LIB_SEARCH_PATH}) if (NOT XAW_LIBRARY OR NOT X11_Xt_FOUND) @@ -258,10 +258,16 @@ if (OGRE_STATIC) endif () endif () - set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${OGRE_LIBRARY_FWK} ${ZZip_LIBRARIES} ${ZLIB_LIBRARIES} +if (ANDROID) + set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${OGRE_LIBRARY_FWK} ${ZZip_LIBRARIES} ${ZLIB_LIBRARIES} ${FreeImage_LIBRARIES} ${FREETYPE_LIBRARIES} - ${X11_LIBRARIES} ${X11_Xt_LIBRARIES} ${XAW_LIBRARY} ${X11_Xrandr_LIB} ${Cocoa_LIBRARIES} ${Carbon_LIBRARIES}) +else () + set(OGRE_LIBRARIES ${OGRE_LIBRARIES} ${OGRE_LIBRARY_FWK} ${ZZip_LIBRARIES} ${ZLIB_LIBRARIES} + ${FreeImage_LIBRARIES} ${FREETYPE_LIBRARIES} + ${X11_LIBRARIES} ${X11_Xt_LIBRARIES} ${XAW_LIBRARY} ${X11_Xrandr_LIB} + ${Cocoa_LIBRARIES} ${Carbon_LIBRARIES}) +endif() if (NOT ZLIB_FOUND OR NOT ZZip_FOUND) set(OGRE_DEPS_FOUND FALSE) @@ -272,7 +278,7 @@ if (OGRE_STATIC) if (NOT FREETYPE_FOUND) set(OGRE_DEPS_FOUND FALSE) endif () - if (UNIX AND NOT APPLE) + if (UNIX AND NOT APPLE AND NOT ANDROID) if (NOT X11_FOUND) set(OGRE_DEPS_FOUND FALSE) endif () @@ -486,7 +492,7 @@ ogre_find_plugin(Plugin_CgProgramManager OgreCgProgram.h PlugIns/CgProgramManage ogre_find_plugin(Plugin_OctreeSceneManager OgreOctreeSceneManager.h PlugIns/OctreeSceneManager/include) ogre_find_plugin(Plugin_ParticleFX OgreParticleFXPrerequisites.h PlugIns/ParticleFX/include) ogre_find_plugin(RenderSystem_GL OgreGLRenderSystem.h RenderSystems/GL/include) -ogre_find_plugin(RenderSystem_GLES OgreGLESRenderSystem.h RenderSystems/GLES/include) +ogre_find_plugin(RenderSystem_GLES2 OgreGLES2RenderSystem.h RenderSystems/GLES2/include) ogre_find_plugin(RenderSystem_Direct3D9 OgreD3D9RenderSystem.h RenderSystems/Direct3D9/include) ogre_find_plugin(RenderSystem_Direct3D10 OgreD3D10RenderSystem.h RenderSystems/Direct3D10/include) ogre_find_plugin(RenderSystem_Direct3D11 OgreD3D11RenderSystem.h RenderSystems/Direct3D11/include) @@ -528,8 +534,8 @@ if (OGRE_STATIC) set(OGRE_RenderSystem_GL_LIBRARIES ${OGRE_RenderSystem_GL_LIBRARIES} ${OPENGL_LIBRARIES} ) - set(OGRE_RenderSystem_GLES_LIBRARIES ${OGRE_RenderSystem_GLES_LIBRARIES} - ${OPENGLES_LIBRARIES} + set(OGRE_RenderSystem_GLES2_LIBRARIES ${OGRE_RenderSystem_GLES2_LIBRARIES} + ${OPENGLES2_LIBRARIES} ) set(OGRE_Plugin_CgProgramManager_LIBRARIES ${OGRE_Plugin_CgProgramManager_LIBRARIES} ${Cg_LIBRARIES} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index c8758ca9e..8cd99b6af 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -45,12 +45,16 @@ add_component_dir (esm aisequence ) +add_component_dir (esmterrain + storage + ) + add_component_dir (misc - utf8stream stringops + utf8stream stringops resourcehelpers ) add_component_dir (files - linuxpath windowspath macospath fixedpath multidircollection collections configurationmanager + linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager constrainedfiledatastream lowlevelfile ) @@ -72,7 +76,7 @@ add_component_dir (translation add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain - quadtreenode chunk world defaultworld storage material buffercache defs + quadtreenode chunk world defaultworld terraingrid storage material buffercache defs ) add_component_dir (loadinglistener diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index bb377a0dc..73877f459 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -113,7 +113,7 @@ namespace Compiler { void registerExtensions (Extensions& extensions) { - extensions.registerInstruction ("additem", "cl", opcodeAddItem, opcodeAddItemExplicit); + extensions.registerInstruction ("additem", "clX", opcodeAddItem, opcodeAddItemExplicit); extensions.registerFunction ("getitemcount", 'l', "c", opcodeGetItemCount, opcodeGetItemCountExplicit); extensions.registerInstruction ("removeitem", "cl", opcodeRemoveItem, @@ -148,6 +148,16 @@ namespace Compiler extensions.registerInstruction ("forcerun", "", opcodeForceRun, opcodeForceRunExplicit); + extensions.registerInstruction ("clearforcejump", "", opcodeClearForceJump, + opcodeClearForceJumpExplicit); + extensions.registerInstruction ("forcejump", "", opcodeForceJump, + opcodeForceJumpExplicit); + + extensions.registerInstruction ("clearforcemovejump", "", opcodeClearForceMoveJump, + opcodeClearForceMoveJumpExplicit); + extensions.registerInstruction ("forcemovejump", "", opcodeForceMoveJump, + opcodeForceMoveJumpExplicit); + extensions.registerInstruction ("clearforcesneak", "", opcodeClearForceSneak, opcodeClearForceSneakExplicit); extensions.registerInstruction ("forcesneak", "", opcodeForceSneak, @@ -155,6 +165,8 @@ namespace Compiler extensions.registerFunction ("getpcrunning", 'l', "", opcodeGetPcRunning); extensions.registerFunction ("getpcsneaking", 'l', "", opcodeGetPcSneaking); extensions.registerFunction ("getforcerun", 'l', "", opcodeGetForceRun, opcodeGetForceRunExplicit); + extensions.registerFunction ("getforcejump", 'l', "", opcodeGetForceJump, opcodeGetForceJumpExplicit); + extensions.registerFunction ("getforcemovejump", 'l', "", opcodeGetForceMoveJump, opcodeGetForceMoveJumpExplicit); extensions.registerFunction ("getforcesneak", 'l', "", opcodeGetForceSneak, opcodeGetForceSneakExplicit); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 828d736c4..c09ec9f94 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -123,6 +123,14 @@ namespace Compiler const int opcodeClearForceRunExplicit = 0x2000155; const int opcodeForceRun = 0x2000156; const int opcodeForceRunExplicit = 0x2000157; + const int opcodeClearForceJump = 0x2000258; + const int opcodeClearForceJumpExplicit = 0x2000259; + const int opcodeForceJump = 0x200025a; + const int opcodeForceJumpExplicit = 0x200025b; + const int opcodeClearForceMoveJump = 0x200025c; + const int opcodeClearForceMoveJumpExplicit = 0x200025d; + const int opcodeForceMoveJump = 0x200025e; + const int opcodeForceMoveJumpExplicit = 0x200025f; const int opcodeClearForceSneak = 0x2000158; const int opcodeClearForceSneakExplicit = 0x2000159; const int opcodeForceSneak = 0x200015a; @@ -134,6 +142,10 @@ namespace Compiler const int opcodeGetForceSneak = 0x20001cc; const int opcodeGetForceRunExplicit = 0x20001cd; const int opcodeGetForceSneakExplicit = 0x20001ce; + const int opcodeGetForceJump = 0x2000260; + const int opcodeGetForceMoveJump = 0x2000262; + const int opcodeGetForceJumpExplicit = 0x2000261; + const int opcodeGetForceMoveJumpExplicit = 0x2000263; } namespace Dialogue diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index 80440bdd3..0e3e54102 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -58,6 +58,8 @@ namespace AiSequence esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); esm.getHNT (mAlwaysFollow, "ALWY"); + mCommanded = false; + esm.getHNOT (mCommanded, "CMND"); } void AiFollow::save(ESMWriter &esm) const @@ -68,6 +70,7 @@ namespace AiSequence if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); esm.writeHNT ("ALWY", mAlwaysFollow); + esm.writeHNT ("CMND", mCommanded); } void AiActivate::load(ESMReader &esm) diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index bf0e17fa0..da16bf867 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -96,6 +96,7 @@ namespace ESM float mRemainingDuration; bool mAlwaysFollow; + bool mCommanded; void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index c9860dcef..7c98cc35b 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -10,6 +10,9 @@ namespace ESM void DialInfo::load(ESMReader &esm) { + mQuestStatus = QS_None; + mFactionLess = false; + mPrev = esm.getHNString("PNAM"); mNext = esm.getHNString("NNAM"); @@ -49,7 +52,6 @@ void DialInfo::load(ESMReader &esm) return; } - mFactionLess = false; if (subName.val == REC_FNAM) { mFaction = esm.getHString(); @@ -104,8 +106,6 @@ void DialInfo::load(ESMReader &esm) return; } - mQuestStatus = QS_None; - if (subName.val == REC_QSTN) mQuestStatus = QS_Name; else if (subName.val == REC_QSTF) diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index 74eb37197..ffc291609 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -25,7 +25,7 @@ struct Light Negative = 0x004, // Negative light - i.e. darkness Flicker = 0x008, Fire = 0x010, - OffDefault = 0x020, // Off by default + OffDefault = 0x020, // Off by default - does not burn while placed in a cell, but can burn when equipped by an NPC FlickerSlow = 0x040, Pulse = 0x080, PulseSlow = 0x100 diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index aee8628bd..2e8f31cd2 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -38,13 +38,28 @@ void Script::load(ESMReader &esm) char* str = &tmp[0]; for (size_t i = 0; i < mVarNames.size(); i++) { + // Support '\r' terminated strings like vanilla. See Bug #1324. char *termsym = strchr(str, '\r'); if(termsym) *termsym = '\0'; mVarNames[i] = std::string(str); str += mVarNames[i].size() + 1; if (str - &tmp[0] > s) - esm.fail("String table overflow"); + { + // Apparently SCVR subrecord is not used and variable names are + // determined on the fly from the script text. Therefore don't throw + // an exeption, just log an error and continue. + std::stringstream ss; + + ss << "ESM Error: " << "String table overflow"; + ss << "\n File: " << esm.getName(); + ss << "\n Record: " << esm.getContext().recName.toString(); + ss << "\n Subrecord: " << "SCVR"; + ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); + std::cerr << ss.str() << std::endl; + break; + } + } } diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index 26d8f9596..3e6aed99d 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -78,8 +78,9 @@ void ESM::NpcStats::load (ESMReader &esm) mLastDrowningHit = 0; esm.getHNOT (mLastDrowningHit, "DRLH"); - mLevelHealthBonus = 0; - esm.getHNOT (mLevelHealthBonus, "LVLH"); + // No longer used + float levelHealthBonus = 0; + esm.getHNOT (levelHealthBonus, "LVLH"); mCrimeId = -1; esm.getHNOT (mCrimeId, "CRID"); @@ -148,9 +149,6 @@ void ESM::NpcStats::save (ESMWriter &esm) const if (mLastDrowningHit) esm.writeHNT ("DRLH", mLastDrowningHit); - if (mLevelHealthBonus) - esm.writeHNT ("LVLH", mLevelHealthBonus); - if (mCrimeId != -1) esm.writeHNT ("CRID", mCrimeId); } diff --git a/components/esm/npcstats.hpp b/components/esm/npcstats.hpp index ce7c75d2a..0061fc05f 100644 --- a/components/esm/npcstats.hpp +++ b/components/esm/npcstats.hpp @@ -46,7 +46,6 @@ namespace ESM std::vector mUsedIds; float mTimeToStartDrowning; float mLastDrowningHit; - float mLevelHealthBonus; int mCrimeId; void load (ESMReader &esm); diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp new file mode 100644 index 000000000..585c1495d --- /dev/null +++ b/components/esmterrain/storage.cpp @@ -0,0 +1,530 @@ +#include "storage.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace ESMTerrain +{ + + bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) + { + assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); + + /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int cellX = origin.x; + int cellY = origin.y; + + const ESM::Land* land = getLand(cellX, cellY); + if (!land) + return false; + + min = std::numeric_limits().max(); + max = -std::numeric_limits().max(); + for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + if (h > max) + max = h; + if (h < min) + min = h; + } + } + return true; + } + + void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) + { + while (col >= ESM::Land::LAND_SIZE-1) + { + ++cellY; + col -= ESM::Land::LAND_SIZE-1; + } + while (row >= ESM::Land::LAND_SIZE-1) + { + ++cellX; + row -= ESM::Land::LAND_SIZE-1; + } + while (col < 0) + { + --cellY; + col += ESM::Land::LAND_SIZE-1; + } + while (row < 0) + { + --cellX; + row += ESM::Land::LAND_SIZE-1; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mHasData) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + } + + void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) + { + Ogre::Vector3 n1,n2,n3,n4; + fixNormal(n1, cellX, cellY, col+1, row); + fixNormal(n2, cellX, cellY, col-1, row); + fixNormal(n3, cellX, cellY, col, row+1); + fixNormal(n4, cellX, cellY, col, row-1); + normal = (n1+n2+n3+n4); + normal.normalise(); + } + + void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) + { + if (col == ESM::Land::LAND_SIZE-1) + { + ++cellY; + col = 0; + } + if (row == ESM::Land::LAND_SIZE-1) + { + ++cellX; + row = 0; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mLandData->mUsingColours) + { + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + + } + + void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, + std::vector& positions, + std::vector& normals, + std::vector& colours) + { + // LOD level n means every 2^n-th vertex is kept + size_t increment = 1 << lodLevel; + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int startX = origin.x; + int startY = origin.y; + + size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; + + colours.resize(numVerts*numVerts*4); + positions.resize(numVerts*numVerts*3); + normals.resize(numVerts*numVerts*3); + + Ogre::Vector3 normal; + Ogre::ColourValue color; + + float vertY; + float vertX; + + float vertY_ = 0; // of current cell corner + for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) + { + float vertX_ = 0; // of current cell corner + for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) + { + ESM::Land* land = getLand(cellX, cellY); + if (land && !land->mHasData) + land = NULL; + bool hasColors = land && land->mLandData->mUsingColours; + + int rowStart = 0; + int colStart = 0; + // Skip the first row / column unless we're at a chunk edge, + // since this row / column is already contained in a previous cell + if (colStart == 0 && vertY_ != 0) + colStart += increment; + if (rowStart == 0 && vertX_ != 0) + rowStart += increment; + + vertY = vertY_; + for (int col=colStart; colmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + else + positions[vertX*numVerts*3 + vertY*3 + 2] = -2048; + + if (land) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + + // Normals apparently don't connect seamlessly between cells + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixNormal(normal, cellX, cellY, col, row); + + // some corner normals appear to be complete garbage (z < 0) + if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) + averageNormal(normal, cellX, cellY, col, row); + + assert(normal.z > 0); + + normals[vertX*numVerts*3 + vertY*3] = normal.x; + normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; + normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; + + if (hasColors) + { + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + + // Unlike normals, colors mostly connect seamlessly between cells, but not always... + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixColour(color, cellX, cellY, col, row); + + color.a = 1; + Ogre::uint32 rsColor; + Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); + memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); + + ++vertX; + } + ++vertY; + } + vertX_ = vertX; + } + vertY_ = vertY; + + assert(vertX_ == numVerts); // Ensure we covered whole area + } + assert(vertY_ == numVerts); // Ensure we covered whole area + } + + Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY, + int x, int y) + { + // For the first/last row/column, we need to get the texture from the neighbour cell + // to get consistent blending at the borders + --x; + if (x < 0) + { + --cellX; + x += ESM::Land::LAND_TEXTURE_SIZE; + } + if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? + { + ++cellY; + y -= ESM::Land::LAND_TEXTURE_SIZE; + } + + assert(xmLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + if (tex == 0) + return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin + return std::make_pair(tex, land->mPlugin); + } + else + return std::make_pair(0,0); + } + + std::string Storage::getTextureName(UniqueTextureId id) + { + if (id.first == 0) + return "textures\\_land_default.dds"; // Not sure if the default texture really is hardcoded? + + // NB: All vtex ids are +1 compared to the ltex ids + const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); + + //TODO this is needed due to MWs messed up texture handling + std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture); + + return texture; + } + + void Storage::getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) + { + for (std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) + { + out.push_back(Terrain::LayerCollection()); + out.back().mTarget = *it; + getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers); + } + } + + void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) + { + getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList); + } + + void Storage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) + { + // TODO - blending isn't completely right yet; the blending radius appears to be + // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap + // and interpolate the rest of the cell by hand? :/ + + Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); + int cellX = origin.x; + int cellY = origin.y; + + // Save the used texture indices so we know the total number of textures + // and number of required blend maps + std::set textureIndices; + // Due to the way the blending works, the base layer will always shine through in between + // blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible). + // To get a consistent look, we need to make sure to use the same base layer in all cells. + // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. + textureIndices.insert(std::make_pair(0,0)); + + for (int y=0; y textureIndicesMap; + for (std::set::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it) + { + int size = textureIndicesMap.size(); + textureIndicesMap[*it] = size; + layerList.push_back(getLayerInfo(getTextureName(*it))); + } + + int numTextures = textureIndices.size(); + // numTextures-1 since the base layer doesn't need blending + int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1); + + int channels = pack ? 4 : 1; + + // Second iteration - create and fill in the blend maps + const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; + + for (int i=0; isecond; + int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1); + int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; + + if (blendIndex == i) + pData[y*blendmapSize*channels + x*channels + channel] = 255; + else + pData[y*blendmapSize*channels + x*channels + channel] = 0; + } + } + blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData)); + } + } + + float Storage::getHeightAt(const Ogre::Vector3 &worldPos) + { + int cellX = std::floor(worldPos.x / 8192.f); + int cellY = std::floor(worldPos.y / 8192.f); + + ESM::Land* land = getLand(cellX, cellY); + if (!land) + return -2048; + + // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition + + // Normalized position in the cell + float nX = (worldPos.x - (cellX * 8192))/8192.f; + float nY = (worldPos.y - (cellY * 8192))/8192.f; + + // get left / bottom points (rounded down) + float factor = ESM::Land::LAND_SIZE - 1.0f; + float invFactor = 1.0f / factor; + + int startX = static_cast(nX * factor); + int startY = static_cast(nY * factor); + int endX = startX + 1; + int endY = startY + 1; + + assert(endX < ESM::Land::LAND_SIZE); + assert(endY < ESM::Land::LAND_SIZE); + + // now get points in terrain space (effectively rounding them to boundaries) + float startXTS = startX * invFactor; + float startYTS = startY * invFactor; + float endXTS = endX * invFactor; + float endYTS = endY * invFactor; + + // get parametric from start coord to next point + float xParam = (nX - startXTS) * factor; + float yParam = (nY - startYTS) * factor; + + /* For even / odd tri strip rows, triangles are this shape: + even odd + 3---2 3---2 + | / | | \ | + 0---1 0---1 + */ + + // Build all 4 positions in normalized cell space, using point-sampled height + Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); + Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); + Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); + Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); + // define this plane in terrain space + Ogre::Plane plane; + // (At the moment, all rows have the same triangle alignment) + if (true) + { + // odd row + bool secondTri = ((1.0 - yParam) > xParam); + if (secondTri) + plane.redefine(v0, v1, v3); + else + plane.redefine(v1, v2, v3); + } + else + { + // even row + bool secondTri = (yParam > xParam); + if (secondTri) + plane.redefine(v0, v2, v3); + else + plane.redefine(v0, v1, v2); + } + + // Solve plane equation for z + return (-plane.normal.x * nX + -plane.normal.y * nY + - plane.d) / plane.normal.z * 8192; + + } + + float Storage::getVertexHeight(const ESM::Land *land, int x, int y) + { + assert(x < ESM::Land::LAND_SIZE); + assert(y < ESM::Land::LAND_SIZE); + return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; + } + + Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) + { + // Already have this cached? + if (mLayerInfoMap.find(texture) != mLayerInfoMap.end()) + return mLayerInfoMap[texture]; + + Terrain::LayerInfo info; + info.mParallax = false; + info.mSpecular = false; + info.mDiffuseMap = texture; + std::string texture_ = texture; + boost::replace_last(texture_, ".", "_nh."); + + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) + { + info.mNormalMap = texture_; + info.mParallax = true; + } + else + { + texture_ = texture; + boost::replace_last(texture_, ".", "_n."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) + info.mNormalMap = texture_; + } + + texture_ = texture; + boost::replace_last(texture_, ".", "_diffusespec."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) + { + info.mDiffuseMap = texture_; + info.mSpecular = true; + } + + // This wasn't cached, so the textures are probably not loaded either. + // Background load them so they are hopefully already loaded once we need them! + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General"); + if (!info.mNormalMap.empty()) + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General"); + + mLayerInfoMap[texture] = info; + + return info; + } + + Terrain::LayerInfo Storage::getDefaultLayer() + { + Terrain::LayerInfo info; + info.mDiffuseMap = "textures\\_land_default.dds"; + info.mParallax = false; + info.mSpecular = false; + return info; + } + + float Storage::getCellWorldSize() + { + return ESM::Land::REAL_SIZE; + } + + int Storage::getCellVertices() + { + return ESM::Land::LAND_SIZE; + } + +} diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp new file mode 100644 index 000000000..d25f7552b --- /dev/null +++ b/components/esmterrain/storage.hpp @@ -0,0 +1,117 @@ +#ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H +#define COMPONENTS_ESM_TERRAIN_STORAGE_H + +#include + +#include +#include + +namespace ESMTerrain +{ + + /// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) + /// into the terrain component, converting it on the fly as needed. + class Storage : public Terrain::Storage + { + private: + + // Not implemented in this class, because we need different Store implementations for game and editor + virtual ESM::Land* getLand (int cellX, int cellY) = 0; + virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; + + public: + + // Not implemented in this class, because we need different Store implementations for game and editor + /// Get bounds of the whole terrain in cell units + virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; + + /// Get the minimum and maximum heights of a terrain region. + /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. + /// Larger chunks can simply merge AABB of children. + /// @param size size of the chunk in cell units + /// @param center center of the chunk in cell units + /// @param min min height will be stored here + /// @param max max height will be stored here + /// @return true if there was data available for this terrain chunk + virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); + + /// Fill vertex buffers for a terrain chunk. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! + /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. + /// @param lodLevel LOD level, 0 = most detailed + /// @param size size of the terrain chunk in cell units + /// @param center center of the chunk in cell units + /// @param positions buffer to write vertices + /// @param normals buffer to write vertex normals + /// @param colours buffer to write vertex colours + virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, + std::vector& positions, + std::vector& normals, + std::vector& colours); + + /// Create textures holding layer blend values for a terrain chunk. + /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. + /// @param chunkSize size of the terrain chunk in cell units + /// @param chunkCenter center of the chunk in cell units + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + /// @param blendmaps created blendmaps will be written here + /// @param layerList names of the layer textures used will be written here + virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); + + /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. + /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. + /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. + /// @param nodes A collection of nodes for which to retrieve the aforementioned data + /// @param out Output vector + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack); + + virtual float getHeightAt (const Ogre::Vector3& worldPos); + + virtual Terrain::LayerInfo getDefaultLayer(); + + /// Get the transformation factor for mapping cell units to world units. + virtual float getCellWorldSize(); + + /// Get the number of vertices on one side for each cell. Should be (power of two)+1 + virtual int getCellVertices(); + + private: + void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row); + void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + + float getVertexHeight (const ESM::Land* land, int x, int y); + + // Since plugins can define new texture palettes, we need to know the plugin index too + // in order to retrieve the correct texture name. + // pair + typedef std::pair UniqueTextureId; + + UniqueTextureId getVtexIndexAt(int cellX, int cellY, + int x, int y); + std::string getTextureName (UniqueTextureId id); + + std::map mLayerInfoMap; + + Terrain::LayerInfo getLayerInfo(const std::string& texture); + + // Non-virtual + void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); + }; + +} + +#endif diff --git a/components/files/androidpath.cpp b/components/files/androidpath.cpp new file mode 100644 index 000000000..52ae73803 --- /dev/null +++ b/components/files/androidpath.cpp @@ -0,0 +1,94 @@ +#include "androidpath.hpp" + +#if defined(__ANDROID__) + +#include +#include +#include +#include +#include + +namespace +{ + boost::filesystem::path getUserHome() + { + const char* dir = getenv("HOME"); + if (dir == NULL) + { + struct passwd* pwd = getpwuid(getuid()); + if (pwd != NULL) + { + dir = pwd->pw_dir; + } + } + if (dir == NULL) + return boost::filesystem::path(); + else + return boost::filesystem::path(dir); + } + + boost::filesystem::path getEnv(const std::string& envVariable, const boost::filesystem::path& fallback) + { + const char* result = getenv(envVariable.c_str()); + if (!result) + return fallback; + boost::filesystem::path dir(result); + if (dir.empty()) + return fallback; + else + return dir; + } +} + +/** + * \namespace Files + */ +namespace Files +{ + +AndroidPath::AndroidPath(const std::string& application_name) + : mName(application_name) +{ +} + +boost::filesystem::path AndroidPath::getUserConfigPath() const +{ + return getEnv("XDG_CONFIG_HOME", "/sdcard/morrowind/config") / mName; +} + +boost::filesystem::path AndroidPath::getUserDataPath() const +{ + return getEnv("XDG_DATA_HOME", "/sdcard/morrowind/share") / mName; +} + +boost::filesystem::path AndroidPath::getCachePath() const +{ + return getEnv("XDG_CACHE_HOME", "/sdcard/morrowind/cache") / mName; +} + +boost::filesystem::path AndroidPath::getGlobalConfigPath() const +{ + boost::filesystem::path globalPath("/sdcard/morrowind/"); + return globalPath / mName; +} + +boost::filesystem::path AndroidPath::getLocalPath() const +{ + return boost::filesystem::path("./"); +} + +boost::filesystem::path AndroidPath::getGlobalDataPath() const +{ + boost::filesystem::path globalDataPath("/sdcard/morrowind/data"); + return globalDataPath / mName; +} + +boost::filesystem::path AndroidPath::getInstallPath() const +{ + return boost::filesystem::path(); +} + + +} /* namespace Files */ + +#endif /* defined(__Android__) */ diff --git a/components/files/androidpath.hpp b/components/files/androidpath.hpp new file mode 100644 index 000000000..792462fc6 --- /dev/null +++ b/components/files/androidpath.hpp @@ -0,0 +1,54 @@ +#ifndef COMPONENTS_FILES_ANDROIDPATH_H +#define COMPONENTS_FILES_ANDROIDPATH_H + +#if defined(__ANDROID__) + +#include +/** + * \namespace Files + */ +namespace Files +{ + +struct AndroidPath +{ + AndroidPath(const std::string& application_name); + + /** + * \brief Return path to the user directory. + */ + boost::filesystem::path getUserConfigPath() const; + + boost::filesystem::path getUserDataPath() const; + + /** + * \brief Return path to the global (system) directory where config files can be placed. + */ + boost::filesystem::path getGlobalConfigPath() const; + + /** + * \brief Return path to the runtime configuration directory which is the + * place where an application was started. + */ + boost::filesystem::path getLocalPath() const; + + /** + * \brief Return path to the global (system) directory where game files can be placed. + */ + boost::filesystem::path getGlobalDataPath() const; + + /** + * \brief + */ + boost::filesystem::path getCachePath() const; + + boost::filesystem::path getInstallPath() const; + + std::string mName; +}; + +} /* namespace Files */ + +#endif /* defined(__Android__) */ + +#endif /* COMPONENTS_FILES_ANDROIDPATH_H */ diff --git a/components/files/fixedpath.hpp b/components/files/fixedpath.hpp index cfd3458ce..86571442a 100644 --- a/components/files/fixedpath.hpp +++ b/components/files/fixedpath.hpp @@ -4,10 +4,14 @@ #include #include -#if defined(__linux__) || defined(__FreeBSD__) +#if defined(__linux__) || defined(__FreeBSD__) +#ifndef ANDROID #include namespace Files { typedef LinuxPath TargetPathType; } - +#else + #include + namespace Files { typedef AndroidPath TargetPathType; } +#endif #elif defined(__WIN32) || defined(__WINDOWS__) || defined(_WIN32) #include namespace Files { typedef WindowsPath TargetPathType; } @@ -87,6 +91,7 @@ struct FixedPath return mLocalPath; } + const boost::filesystem::path& getInstallPath() const { return mInstallPath; diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp new file mode 100644 index 000000000..9eaf441ef --- /dev/null +++ b/components/misc/resourcehelpers.cpp @@ -0,0 +1,90 @@ +#include "resourcehelpers.hpp" + +#include + +#include + +bool Misc::ResourceHelpers::changeExtensionToDds(std::string &path) +{ + Ogre::String::size_type pos = path.rfind('.'); + if(pos != Ogre::String::npos && path.compare(pos, path.length() - pos, ".dds") != 0) + { + path.replace(pos, path.length(), ".dds"); + return true; + } + return false; +} + +std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLevelDirectory, const std::string &resPath) +{ + /* Bethesda at some point converted all their BSA + * textures from tga to dds for increased load speed, but all + * texture file name references were kept as .tga. + */ + + std::string prefix1 = topLevelDirectory + '\\'; + std::string prefix2 = topLevelDirectory + '/'; + + std::string correctedPath = resPath; + Misc::StringUtils::toLower(correctedPath); + + // Apparently, leading separators are allowed + while (correctedPath.size() && (correctedPath[0] == '/' || correctedPath[0] == '\\')) + correctedPath.erase(0, 1); + + if(correctedPath.compare(0, prefix1.size(), prefix1.data()) != 0 && + correctedPath.compare(0, prefix2.size(), prefix2.data()) != 0) + correctedPath = prefix1 + correctedPath; + + std::string origExt = correctedPath; + + // since we know all (GOTY edition or less) textures end + // in .dds, we change the extension + if (changeExtensionToDds(correctedPath)) + { + // if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods) + // verify, and revert if false (this call succeeds quickly, but fails slowly) + if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(correctedPath)) + { + return origExt; + } + } + + return correctedPath; +} + +std::string Misc::ResourceHelpers::correctTexturePath(const std::string &resPath) +{ + static const std::string dir = "textures"; + return correctResourcePath(dir, resPath); +} + +std::string Misc::ResourceHelpers::correctIconPath(const std::string &resPath) +{ + static const std::string dir = "icons"; + return correctResourcePath(dir, resPath); +} + +std::string Misc::ResourceHelpers::correctBookartPath(const std::string &resPath) +{ + static const std::string dir = "bookart"; + std::string image = correctResourcePath(dir, resPath); + + return image; +} + +std::string Misc::ResourceHelpers::correctBookartPath(const std::string &resPath, int width, int height) +{ + std::string image = correctBookartPath(resPath); + + // Apparently a bug with some morrowind versions, they reference the image without the size suffix. + // So if the image isn't found, try appending the size. + if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(image)) + { + std::stringstream str; + str << image.substr(0, image.rfind('.')) << "_" << width << "_" << height << image.substr(image.rfind('.')); + image = Misc::ResourceHelpers::correctBookartPath(str.str()); + } + + return image; +} diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp new file mode 100644 index 000000000..3cf0f4c27 --- /dev/null +++ b/components/misc/resourcehelpers.hpp @@ -0,0 +1,19 @@ +#ifndef MISC_RESOURCEHELPERS_H +#define MISC_RESOURCEHELPERS_H + +#include + +namespace Misc +{ + namespace ResourceHelpers + { + bool changeExtensionToDds(std::string &path); + std::string correctResourcePath(const std::string &topLevelDirectory, const std::string &resPath); + std::string correctTexturePath(const std::string &resPath); + std::string correctIconPath(const std::string &resPath); + std::string correctBookartPath(const std::string &resPath); + std::string correctBookartPath(const std::string &resPath, int width, int height); + } +} + +#endif diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 44831c13b..5ca58da3b 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -1,7 +1,7 @@ #include "material.hpp" #include -#include +#include #include #include @@ -54,49 +54,6 @@ static const char *getTestMode(int mode) return "less_equal"; } - -std::string NIFMaterialLoader::findTextureName(const std::string &filename) -{ - /* Bethesda at some point converted all their BSA - * textures from tga to dds for increased load speed, but all - * texture file name references were kept as .tga. - */ - static const char path[] = "textures\\"; - static const char path2[] = "textures/"; - - std::string texname = filename; - Misc::StringUtils::toLower(texname); - - // Apparently, leading separators are allowed - while (texname.size() && (texname[0] == '/' || texname[0] == '\\')) - texname.erase(0, 1); - - if(texname.compare(0, sizeof(path)-1, path) != 0 && - texname.compare(0, sizeof(path2)-1, path2) != 0) - texname = path + texname; - - Ogre::String::size_type pos = texname.rfind('.'); - if(pos != Ogre::String::npos && texname.compare(pos, texname.length() - pos, ".dds") != 0) - { - // since we know all (GOTY edition or less) textures end - // in .dds, we change the extension - texname.replace(pos, texname.length(), ".dds"); - - // if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods) - // verify, and revert if false (this call succeeds quickly, but fails slowly) - if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texname)) - { - texname = filename; - Misc::StringUtils::toLower(texname); - if(texname.compare(0, sizeof(path)-1, path) != 0 && - texname.compare(0, sizeof(path2)-1, path2) != 0) - texname = path + texname; - } - } - - return texname; -} - Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, const Ogre::String &name, const Ogre::String &group, const Nif::NiTexturingProperty *texprop, @@ -146,7 +103,7 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, const Nif::NiSourceTexture *st = texprop->textures[i].texture.getPtr(); if(st->external) - texName[i] = findTextureName(st->filename); + texName[i] = Misc::ResourceHelpers::correctTexturePath(st->filename); else warn("Found internal texture, ignoring."); } diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp index abe1982eb..fc978549e 100644 --- a/components/nifogre/material.hpp +++ b/components/nifogre/material.hpp @@ -38,8 +38,6 @@ class NIFMaterialLoader { static std::map sMaterialMap; public: - static std::string findTextureName(const std::string &filename); - static Ogre::String getMaterial(const Nif::ShapeData *shapedata, const Ogre::String &name, const Ogre::String &group, const Nif::NiTexturingProperty *texprop, diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 750fa8167..be362e6ac 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -46,6 +46,7 @@ #include #include +#include #include "skeleton.hpp" #include "material.hpp" @@ -169,7 +170,7 @@ public: const Nif::NiSourceTexture* tex = ctrl->mSources[i].getPtr(); if (!tex->external) std::cerr << "Warning: Found internal texture, ignoring." << std::endl; - mTextures.push_back(NIFMaterialLoader::findTextureName(tex->filename)); + mTextures.push_back(Misc::ResourceHelpers::correctTexturePath(tex->filename)); } } diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp index 515c0875a..cd3723354 100644 --- a/components/ogreinit/ogreinit.cpp +++ b/components/ogreinit/ogreinit.cpp @@ -22,6 +22,7 @@ #include "ogreplugin.hpp" + namespace bfs = boost::filesystem; namespace @@ -82,28 +83,34 @@ namespace OgreInit #ifdef ENABLE_PLUGIN_GL , mGLPlugin(NULL) #endif - #ifdef ENABLE_PLUGIN_Direct3D9 + #ifdef ENABLE_PLUGIN_GLES2 + , mGLES2Plugin(NULL) + #endif + + #ifdef ENABLE_PLUGIN_Direct3D9 , mD3D9Plugin(NULL) #endif {} Ogre::Root* OgreInit::init(const std::string &logPath) { + + #ifndef ANDROID // Set up logging first new Ogre::LogManager; Ogre::Log *log = Ogre::LogManager::getSingleton().createLog(logPath); - #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 // Use custom listener only on Windows log->addListener(new LogListener(logPath)); - #endif + #endif // Disable logging to cout/cerr log->setDebugOutputEnabled(false); - + #endif mRoot = new Ogre::Root("", "", ""); - #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) + #if defined(ENABLE_PLUGIN_GL) || (ENABLE_PLUGIN_GLES2) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) loadStaticPlugins(); #else loadPlugins(); @@ -133,6 +140,10 @@ namespace OgreInit delete mGLPlugin; mGLPlugin = NULL; #endif + #ifdef ENABLE_PLUGIN_GLES2 + delete mGLES2Plugin; + mGLES2Plugin = NULL; + #endif #ifdef ENABLE_PLUGIN_Direct3D9 delete mD3D9Plugin; mD3D9Plugin = NULL; @@ -157,6 +168,10 @@ namespace OgreInit mGLPlugin = new Ogre::GLPlugin(); mRoot->installPlugin(mGLPlugin); #endif + #ifdef ENABLE_PLUGIN_GLES2 + mGLES2Plugin = new Ogre::GLES2Plugin(); + mRoot->installPlugin(mGLES2Plugin); + #endif #ifdef ENABLE_PLUGIN_Direct3D9 mD3D9Plugin = new Ogre::D3D9Plugin(); mRoot->installPlugin(mD3D9Plugin); diff --git a/components/ogreinit/ogreinit.hpp b/components/ogreinit/ogreinit.hpp index b6fe4631a..9613421f7 100644 --- a/components/ogreinit/ogreinit.hpp +++ b/components/ogreinit/ogreinit.hpp @@ -17,6 +17,10 @@ #ifdef ENABLE_PLUGIN_GL # include "OgreGLPlugin.h" #endif +#ifdef ENABLE_PLUGIN_GLES2 +# include "OgreGLES2Plugin.h" +#endif + #ifdef ENABLE_PLUGIN_Direct3D9 # include "OgreD3D9Plugin.h" #endif @@ -52,7 +56,6 @@ namespace OgreInit void loadPlugins(); void loadParticleFactories(); - #ifdef ENABLE_PLUGIN_CgProgramManager Ogre::CgPlugin* mCgPlugin; #endif @@ -65,6 +68,9 @@ namespace OgreInit #ifdef ENABLE_PLUGIN_GL Ogre::GLPlugin* mGLPlugin; #endif + #ifdef ENABLE_PLUGIN_GLES2 + Ogre::GLES2Plugin* mGLES2Plugin; + #endif #ifdef ENABLE_PLUGIN_Direct3D9 Ogre::D3D9Plugin* mD3D9Plugin; #endif diff --git a/apps/openmw/mwrender/terraingrid.cpp b/components/terrain/terraingrid.cpp similarity index 95% rename from apps/openmw/mwrender/terraingrid.cpp rename to components/terrain/terraingrid.cpp index f2bd92061..fe322a6bf 100644 --- a/apps/openmw/mwrender/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -4,12 +4,9 @@ #include #include -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" +#include "chunk.hpp" -#include - -namespace MWRender +namespace Terrain { TerrainGrid::TerrainGrid(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align) @@ -145,8 +142,10 @@ void TerrainGrid::setVisible(bool visible) Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center) { - int cellX, cellY; - MWBase::Environment::get().getWorld()->positionToIndex(center.x, center.y, cellX, cellY); + float cellSize = getStorage()->getCellWorldSize(); + + int cellX = std::floor(center.x/cellSize); + int cellY = std::floor(center.y/cellSize); Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY)); if (it == mGrid.end()) diff --git a/apps/openmw/mwrender/terraingrid.hpp b/components/terrain/terraingrid.hpp similarity index 93% rename from apps/openmw/mwrender/terraingrid.hpp rename to components/terrain/terraingrid.hpp index 1b5250dcf..7cbf45576 100644 --- a/apps/openmw/mwrender/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -1,16 +1,12 @@ -#ifndef OPENMW_MWRENDER_TERRAINGRID_H -#define OPENMW_MWRENDER_TERRAINGRID_H +#ifndef COMPONENTS_TERRAIN_TERRAINGRID_H +#define COMPONENTS_TERRAIN_TERRAINGRID_H -#include -#include +#include "world.hpp" +#include "material.hpp" namespace Terrain { class Chunk; -} - -namespace MWRender -{ struct GridElement { diff --git a/credits.txt b/credits.txt index 2c8d86f49..c190a8cc6 100644 --- a/credits.txt +++ b/credits.txt @@ -68,6 +68,7 @@ Pieter van der Kloet (pvdk) Radu-Marius Popovici (rpopovici) Roman Melnik (Kromgart) Roman Proskuryakov (humbug) +sandstranger Sandy Carter (bwrsandman) Sebastian Wick (swick) Sergey Shambir diff --git a/extern/sdl4ogre/CMakeLists.txt b/extern/sdl4ogre/CMakeLists.txt index fb0832f71..86ce7b70e 100644 --- a/extern/sdl4ogre/CMakeLists.txt +++ b/extern/sdl4ogre/CMakeLists.txt @@ -15,6 +15,7 @@ endif () set(SDL4OGRE_HEADER_FILES OISCompat.h cursormanager.hpp + events.h ) add_library(${SDL4OGRE_LIBRARY} STATIC ${SDL4OGRE_SOURCE_FILES} ${SDL4OGRE_HEADER_FILES}) diff --git a/extern/sdl4ogre/sdlwindowhelper.cpp b/extern/sdl4ogre/sdlwindowhelper.cpp index 3ea39cff7..f30362d78 100644 --- a/extern/sdl4ogre/sdlwindowhelper.cpp +++ b/extern/sdl4ogre/sdlwindowhelper.cpp @@ -26,6 +26,7 @@ SDLWindowHelper::SDLWindowHelper (SDL_Window* window, int w, int h, throw std::runtime_error("Couldn't get WM Info!"); Ogre::String winHandle; + Ogre::String winHandleSurface; switch (wmInfo.subsystem) { @@ -39,10 +40,15 @@ SDLWindowHelper::SDLWindowHelper (SDL_Window* window, int w, int h, //required to make OGRE play nice with our window params.insert(std::make_pair("macAPI", "cocoa")); params.insert(std::make_pair("macAPICocoaUseNSView", "true")); - winHandle = Ogre::StringConverter::toString(WindowContentViewHandle(wmInfo)); break; -#else +#elif ANDROID + case SDL_SYSWM_ANDROID: + winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.android.window); + winHandleSurface = Ogre::StringConverter::toString((unsigned long)wmInfo.info.android.surface); + + break; + #else case SDL_SYSWM_X11: winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.x11.window); break; @@ -54,7 +60,11 @@ SDLWindowHelper::SDLWindowHelper (SDL_Window* window, int w, int h, /// \todo externalWindowHandle is deprecated according to the source code. Figure out a way to get parentWindowHandle /// to work properly. On Linux/X11 it causes an occasional GLXBadDrawable error. - params.insert(std::make_pair("externalWindowHandle", winHandle)); + +#ifdef ANDROID + params.insert(std::make_pair("externalSurface", winHandleSurface)); +#endif + params.insert(std::make_pair("externalWindowHandle", winHandle)); mWindow = Ogre::Root::getSingleton().createRenderWindow(title, w, h, fullscreen, ¶ms); } diff --git a/extern/shiny/Extra/core.h b/extern/shiny/Extra/core.h index cba716777..1687d5ed1 100644 --- a/extern/shiny/Extra/core.h +++ b/extern/shiny/Extra/core.h @@ -1,13 +1,16 @@ #if SH_HLSL == 1 || SH_CG == 1 #define shTexture2D sampler2D + #define shTexture3D sampler3D #define shSample(tex, coord) tex2D(tex, coord) #define shCubicSample(tex, coord) texCUBE(tex, coord) #define shLerp(a, b, t) lerp(a, b, t) #define shSaturate(a) saturate(a) #define shSampler2D(name) , uniform sampler2D name : register(s@shCounter(0)) @shUseSampler(name) - + + #define shSampler3D(name) , uniform sampler3D name : register(s@shCounter(0)) @shUseSampler(name) + #define shSamplerCube(name) , uniform samplerCUBE name : register(s@shCounter(0)) @shUseSampler(name) #define shMatrixMult(m, v) mul(m, v) @@ -67,6 +70,7 @@ #define int3 ivec3 #define int4 ivec4 #define shTexture2D sampler2D + #define shTexture3D sampler3D #define shSample(tex, coord) texture2D(tex, coord) #define shCubicSample(tex, coord) textureCube(tex, coord) #define shLerp(a, b, t) mix(a, b, t) @@ -76,6 +80,8 @@ #define shSampler2D(name) uniform sampler2D name; @shUseSampler(name) + #define shSampler3D(name) uniform sampler3D name; @shUseSampler(name) + #define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name) #define shMatrixMult(m, v) (m * v) diff --git a/extern/shiny/Main/Factory.cpp b/extern/shiny/Main/Factory.cpp index eab5c8dfc..48caa225a 100644 --- a/extern/shiny/Main/Factory.cpp +++ b/extern/shiny/Main/Factory.cpp @@ -278,14 +278,15 @@ namespace sh MaterialMap::iterator it = mMaterials.find(name); if (it != mMaterials.end()) return &(it->second); - else + else return NULL; } MaterialInstance* Factory::findInstance (const std::string& name) { - assert (mMaterials.find(name) != mMaterials.end()); - return &mMaterials.find(name)->second; + MaterialInstance* m = searchInstance(name); + assert (m); + return m; } MaterialInstance* Factory::requestMaterial (const std::string& name, const std::string& configuration, unsigned short lodIndex) @@ -297,27 +298,24 @@ namespace sh if (m) { - // make sure all lod techniques below (higher lod) exist - int i = lodIndex; - while (i>0) + if (m->createForConfiguration (configuration, 0)) + { + if (mListener) + mListener->materialCreated (m, configuration, 0); + } + else + return NULL; + + for (LodConfigurationMap::iterator it = mLodConfigurations.begin(); it != mLodConfigurations.end(); ++it) { - --i; - if (m->createForConfiguration (configuration, i)) + if (m->createForConfiguration (configuration, it->first)) { if (mListener) - mListener->materialCreated (m, configuration, i); + mListener->materialCreated (m, configuration, it->first); } else return NULL; } - - if (m->createForConfiguration (configuration, lodIndex)) - { - if (mListener) - mListener->materialCreated (m, configuration, lodIndex); - } - else - return NULL; } return m; } @@ -625,8 +623,14 @@ namespace sh { MaterialInstance* m = searchInstance (name); assert(m); + m->createForConfiguration (configuration, 0); - } + + for (LodConfigurationMap::iterator it = mLodConfigurations.begin(); it != mLodConfigurations.end(); ++it) + { + m->createForConfiguration (configuration, it->first); + } + } bool Factory::removeCache(const std::string& pattern) { diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp index e8562011c..721b4af7d 100644 --- a/extern/shiny/Main/Factory.hpp +++ b/extern/shiny/Main/Factory.hpp @@ -259,8 +259,8 @@ namespace sh Platform* mPlatform; MaterialInstance* findInstance (const std::string& name); - private: MaterialInstance* searchInstance (const std::string& name); + /// @return was anything removed? bool removeCache (const std::string& pattern); diff --git a/extern/shiny/Main/MaterialInstance.cpp b/extern/shiny/Main/MaterialInstance.cpp index 128cc593b..c69d13401 100644 --- a/extern/shiny/Main/MaterialInstance.cpp +++ b/extern/shiny/Main/MaterialInstance.cpp @@ -163,7 +163,8 @@ namespace sh mTexUnits.push_back(texUnit); // set texture unit indices (required by GLSL) - if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && (mFactory->getCurrentLanguage () == Language_GLSL || mFactory->getCurrentLanguage() == Language_GLSLES)) + if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && (mFactory->getCurrentLanguage () == Language_GLSL + || mFactory->getCurrentLanguage() == Language_GLSLES)) { pass->setTextureUnitIndex (foundVertex ? GPT_Vertex : GPT_Fragment, texIt->getName(), i); diff --git a/extern/shiny/Main/Preprocessor.cpp b/extern/shiny/Main/Preprocessor.cpp index c03879d46..26481aa03 100644 --- a/extern/shiny/Main/Preprocessor.cpp +++ b/extern/shiny/Main/Preprocessor.cpp @@ -9,8 +9,7 @@ /* Almost exact copy of load_file_to_string policy found in boost::wave headers with the only change that it uses - boost::filesystem facility to handle UTF-8 paths used - throughout OpenMW (bfs::fstream, bfs::path). + boost::filesystem facility to handle UTF-8 paths properly on windows. Original namespace is used due to required bost::wave internal symbols. diff --git a/extern/shiny/Main/Preprocessor.hpp b/extern/shiny/Main/Preprocessor.hpp index 4eb533499..7ee30ae7f 100644 --- a/extern/shiny/Main/Preprocessor.hpp +++ b/extern/shiny/Main/Preprocessor.hpp @@ -1,8 +1,6 @@ #ifndef SH_PREPROCESSOR_H #define SH_PREPROCESSOR_H -#include - #include #include diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp index 9f309fbcd..eab8f93e2 100644 --- a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp +++ b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp @@ -151,7 +151,7 @@ namespace sh else if (typeid(*value) == typeid(IntValue)) type = Ogre::GCT_INT1; else - throw std::runtime_error("unexpected type"); + throw std::runtime_error("unexpected type"); params->addConstantDefinition(name, type); mSharedParameters[name] = params; } diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 2135f4dde..54a4d0b63 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -80,6 +80,7 @@ set(MYGUI_FILES openmw_companion_window.layout openmw_savegame_dialog.layout openmw_recharge_dialog.layout + openmw_screen_fader.layout DejaVuLGCSansMono.ttf markers.png ../launcher/images/openmw.png diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index e1f8af7bf..be3d17506 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index e66f3fc01..1df4841af 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -1,6 +1,7 @@ + diff --git a/files/mygui/openmw_loading_screen.layout b/files/mygui/openmw_loading_screen.layout index faa0d8637..eda002040 100644 --- a/files/mygui/openmw_loading_screen.layout +++ b/files/mygui/openmw_loading_screen.layout @@ -2,7 +2,7 @@ - + diff --git a/files/mygui/openmw_screen_fader.layout b/files/mygui/openmw_screen_fader.layout new file mode 100644 index 000000000..fffd2e66e --- /dev/null +++ b/files/mygui/openmw_screen_fader.layout @@ -0,0 +1,7 @@ + + + + + + + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index 22586716c..a7092416f 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -829,6 +829,7 @@ window by dragging the caption. --> + diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index c95552cb7..50d82cd37 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -661,12 +661,14 @@ namespace Physic }; - std::vector PhysicEngine::getCollisions(const std::string& name) + std::vector PhysicEngine::getCollisions(const std::string& name, int collisionGroup, int collisionMask) { RigidBody* body = getRigidBody(name); if (!body) // fall back to raycasting body if there is no collision body body = getRigidBody(name, true); ContactTestResultCallback callback; + callback.m_collisionFilterGroup = collisionGroup; + callback.m_collisionFilterMask = collisionMask; mDynamicsWorld->contactTest(body, callback); return callback.mResult; } diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 590b56c01..d846d60b1 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -311,7 +311,7 @@ namespace Physic std::pair sphereCast (float radius, btVector3& from, btVector3& to); ///< @return (hit, relative distance) - std::vector getCollisions(const std::string& name); + std::vector getCollisions(const std::string& name, int collisionGroup, int collisionMask); // Get the nearest object that's inside the given object, filtering out objects of the // provided name diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp index 028192e9f..ca4067ca1 100644 --- a/libs/openengine/gui/manager.cpp +++ b/libs/openengine/gui/manager.cpp @@ -653,7 +653,9 @@ void MyGUIManager::updateWindow (Ogre::RenderWindow *wnd) void MyGUIManager::windowResized() { +#ifndef ANDROID mRenderManager->setActiveViewport(0); +#endif } void MyGUIManager::shutdown() diff --git a/libs/openengine/ogre/fader.cpp b/libs/openengine/ogre/fader.cpp deleted file mode 100644 index 20b9296da..000000000 --- a/libs/openengine/ogre/fader.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "fader.hpp" - -#include -#include -#include -#include -#include -#include -#include - - -using namespace Ogre; -using namespace OEngine::Render; - -Fader::Fader(Ogre::SceneManager* sceneMgr) - : mSceneMgr(sceneMgr) - , mMode(FadingMode_In) - , mRemainingTime(0.f) - , mTargetTime(0.f) - , mTargetAlpha(0.f) - , mCurrentAlpha(0.f) - , mStartAlpha(0.f) - , mFactor(1.f) -{ - // Create the fading material - MaterialPtr material = MaterialManager::getSingleton().create("FadeInOutMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME ); - Pass* pass = material->getTechnique(0)->getPass(0); - pass->setSceneBlending(SBT_TRANSPARENT_ALPHA); - pass->setDepthWriteEnabled (false); - mFadeTextureUnit = pass->createTextureUnitState("black.png"); - mFadeTextureUnit->setColourOperationEx(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, ColourValue(0.f, 0.f, 0.f)); // always black colour - - mRectangle = new Ogre::Rectangle2D(true); - mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0); - mRectangle->setMaterial("FadeInOutMaterial"); - mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY-1); - // Use infinite AAB to always stay visible - Ogre::AxisAlignedBox aabInf; - aabInf.setInfinite(); - mRectangle->setBoundingBox(aabInf); - // Attach background to the scene - Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - node->attachObject(mRectangle); - mRectangle->setVisible(false); - mRectangle->setVisibilityFlags (2048); -} - -Fader::~Fader() -{ - delete mRectangle; -} - -void Fader::update(float dt) -{ - if (mRemainingTime > 0) - { - if (mMode == FadingMode_In) - { - mCurrentAlpha -= dt/mTargetTime * (mStartAlpha-mTargetAlpha); - if (mCurrentAlpha < mTargetAlpha) mCurrentAlpha = mTargetAlpha; - } - else if (mMode == FadingMode_Out) - { - mCurrentAlpha += dt/mTargetTime * (mTargetAlpha-mStartAlpha); - if (mCurrentAlpha > mTargetAlpha) mCurrentAlpha = mTargetAlpha; - } - - mRemainingTime -= dt; - } - - if (1.f-((1.f-mCurrentAlpha) * mFactor) == 0.f) - mRectangle->setVisible(false); - else - applyAlpha(); -} - -void Fader::applyAlpha() -{ - mRectangle->setVisible(true); - mFadeTextureUnit->setAlphaOperation(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, 1.f-((1.f-mCurrentAlpha) * mFactor)); -} - -void Fader::fadeIn(float time) -{ - if (time<0.f) return; - if (time==0.f) - { - mCurrentAlpha = 0.f; - applyAlpha(); - return; - } - - mStartAlpha = mCurrentAlpha; - mTargetAlpha = 0.f; - mMode = FadingMode_In; - mTargetTime = time; - mRemainingTime = time; -} - -void Fader::fadeOut(const float time) -{ - if (time<0.f) return; - if (time==0.f) - { - mCurrentAlpha = 1.f; - applyAlpha(); - return; - } - - mStartAlpha = mCurrentAlpha; - mTargetAlpha = 1.f; - mMode = FadingMode_Out; - mTargetTime = time; - mRemainingTime = time; -} - -void Fader::fadeTo(const int percent, const float time) -{ - if (time<0.f) return; - if (time==0.f) - { - mCurrentAlpha = percent/100.f; - applyAlpha(); - return; - } - - mStartAlpha = mCurrentAlpha; - mTargetAlpha = percent/100.f; - - if (mTargetAlpha == mStartAlpha) return; - else if (mTargetAlpha > mStartAlpha) mMode = FadingMode_Out; - else mMode = FadingMode_In; - - mTargetTime = time; - mRemainingTime = time; -} diff --git a/libs/openengine/ogre/fader.hpp b/libs/openengine/ogre/fader.hpp deleted file mode 100644 index 53124e2f6..000000000 --- a/libs/openengine/ogre/fader.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef OENGINE_OGRE_FADE_H -#define OENGINE_OGRE_FADE_H - -/* - A class that handles fading in the screen from black or fading it out to black. - - To achieve this, it uses a full-screen Rectangle2d - */ - -namespace Ogre -{ - class TextureUnitState; - class Rectangle2D; - class SceneManager; -} - -namespace OEngine { -namespace Render -{ - class Fader - { - public: - Fader(Ogre::SceneManager* sceneMgr); - ~Fader(); - - void update(float dt); - - void fadeIn(const float time); - void fadeOut(const float time); - void fadeTo(const int percent, const float time); - - void setFactor (float factor) { mFactor = factor; } - - private: - enum FadingMode - { - FadingMode_In, - FadingMode_Out - }; - - void applyAlpha(); - - Ogre::TextureUnitState* mFadeTextureUnit; - Ogre::Rectangle2D* mRectangle; - - FadingMode mMode; - - float mRemainingTime; - float mTargetTime; - float mTargetAlpha; - float mCurrentAlpha; - float mStartAlpha; - - float mFactor; - - Ogre::SceneManager* mSceneMgr; - }; - }} -#endif diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 8fcf615ba..78eff6aee 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -1,5 +1,4 @@ #include "renderer.hpp" -#include "fader.hpp" #include @@ -27,9 +26,6 @@ using namespace OEngine::Render; void OgreRenderer::cleanup() { - delete mFader; - mFader = NULL; - if (mWindow) Ogre::Root::getSingleton().destroyRenderTarget(mWindow); mWindow = NULL; @@ -46,7 +42,6 @@ void OgreRenderer::cleanup() void OgreRenderer::update(float dt) { - mFader->update(dt); } void OgreRenderer::screenshot(const std::string &file) @@ -161,8 +156,6 @@ void OgreRenderer::createWindow(const std::string &title, const WindowSettings& mScene = mRoot->createSceneManager(ST_GENERIC); - mFader = new Fader(mScene); - mCamera = mScene->createCamera("cam"); // Create one viewport, entire window diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index ad88b1606..c7ec2ec44 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -43,8 +43,6 @@ namespace OEngine std::string icon; }; - class Fader; - class WindowSizeListener { public: @@ -62,8 +60,6 @@ namespace OEngine OgreInit::OgreInit* mOgreInit; - Fader* mFader; - WindowSizeListener* mWindowListener; int mWindowWidth; @@ -79,7 +75,6 @@ namespace OEngine , mCamera(NULL) , mView(NULL) , mOgreInit(NULL) - , mFader(NULL) , mWindowListener(NULL) , mWindowWidth(0) , mWindowHeight(0) @@ -131,9 +126,6 @@ namespace OEngine /// Get the scene manager Ogre::SceneManager *getScene() { return mScene; } - /// Get the screen colour fader - Fader *getFader() { return mFader; } - /// Camera Ogre::Camera *getCamera() { return mCamera; } diff --git a/libs/openengine/ogre/selectionbuffer.cpp b/libs/openengine/ogre/selectionbuffer.cpp index 5aeb35c28..af95750d5 100644 --- a/libs/openengine/ogre/selectionbuffer.cpp +++ b/libs/openengine/ogre/selectionbuffer.cpp @@ -18,22 +18,42 @@ namespace Render { SelectionBuffer::SelectionBuffer(Ogre::Camera *camera, int sizeX, int sizeY, int visibilityFlags) + : mCamera(camera) + , mVisibilityFlags(visibilityFlags) { mTexture = Ogre::TextureManager::getSingleton().createManual("SelectionBuffer", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, sizeX, sizeY, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, sizeX, sizeY, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET, this); + setupRenderTarget(); + + mCurrentColour = Ogre::ColourValue(0.3, 0.3, 0.3); + } + + void SelectionBuffer::setupRenderTarget() + { mRenderTarget = mTexture->getBuffer()->getRenderTarget(); - Ogre::Viewport* vp = mRenderTarget->addViewport(camera); + Ogre::Viewport* vp = mRenderTarget->addViewport(mCamera); vp->setOverlaysEnabled(false); vp->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); vp->setShadowsEnabled(false); vp->setMaterialScheme("selectionbuffer"); - if (visibilityFlags != 0) - vp->setVisibilityMask (visibilityFlags); + if (mVisibilityFlags != 0) + vp->setVisibilityMask (mVisibilityFlags); mRenderTarget->setActive(true); mRenderTarget->setAutoUpdated (false); + } - mCurrentColour = Ogre::ColourValue(0.3, 0.3, 0.3); + void SelectionBuffer::loadResource(Ogre::Resource *resource) + { + Ogre::Texture* tex = dynamic_cast(resource); + if (!tex) + return; + + tex->createInternalResources(); + + mRenderTarget = NULL; + + // Don't need to re-render texture, because we have a copy in system memory (mBuffer) } SelectionBuffer::~SelectionBuffer() @@ -45,6 +65,9 @@ namespace Render { Ogre::MaterialManager::getSingleton ().addListener (this); + if (mRenderTarget == NULL) + setupRenderTarget(); + mRenderTarget->update(); Ogre::MaterialManager::getSingleton ().removeListener (this); diff --git a/libs/openengine/ogre/selectionbuffer.hpp b/libs/openengine/ogre/selectionbuffer.hpp index b9b4cd9d9..9bdc4c137 100644 --- a/libs/openengine/ogre/selectionbuffer.hpp +++ b/libs/openengine/ogre/selectionbuffer.hpp @@ -20,7 +20,7 @@ namespace Render } }; - class SelectionBuffer : public Ogre::MaterialManager::Listener + class SelectionBuffer : public Ogre::MaterialManager::Listener, public Ogre::ManualResourceLoader { public: SelectionBuffer(Ogre::Camera* camera, int sizeX, int sizeY, int visibilityFlags); @@ -31,6 +31,8 @@ namespace Render void update(); + virtual void loadResource(Ogre::Resource* resource); + virtual Ogre::Technique* handleSchemeNotFound ( unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial, unsigned short lodIndex, const Ogre::Renderable *rend); @@ -46,7 +48,12 @@ namespace Render Ogre::ColourValue mCurrentColour; + Ogre::Camera* mCamera; + int mVisibilityFlags; + void getNextColour(); + + void setupRenderTarget(); }; }