From c822b1fa35d0289ad188b8bdd2bb9dc1417ca8f2 Mon Sep 17 00:00:00 2001 From: HiPhish Date: Mon, 8 Feb 2016 09:56:51 +0100 Subject: [PATCH 01/16] Write chapters about tables and record types. The "Tables", "Record Types" and "Record Filters" chapters have been adapted from the already existing manual. --- docs/cs-manual/source/record-filters.rst | 293 +++++++++++++++++++++++ docs/cs-manual/source/record-types.rst | 62 +++++ docs/cs-manual/source/tables.rst | 168 +++++++++++++ docs/source/openmw-cs/index.rst | 3 + 4 files changed, 526 insertions(+) create mode 100644 docs/cs-manual/source/record-filters.rst create mode 100644 docs/cs-manual/source/record-types.rst create mode 100644 docs/cs-manual/source/tables.rst diff --git a/docs/cs-manual/source/record-filters.rst b/docs/cs-manual/source/record-filters.rst new file mode 100644 index 000000000..3379f557f --- /dev/null +++ b/docs/cs-manual/source/record-filters.rst @@ -0,0 +1,293 @@ +Record Filters +############## + +Filters are a key element of the OpenMW CS user interface, they allow rapid and +easy access to records presented in all tables. In order to use this +application effectively you need to familiarise yourself with all the concepts +and instructions explained in this chapter. The filter system is somewhat +unusual at first glance, but once you understand the basics it will be fairly +intuitive and easy to use + +Filters are a key element to using the OpenMW CS efficiently by allowing you to +narrow down the table entries very quickly and find what you are looking for. +The filter system might appear unusual at first, you don't just type in a word +and get all instances where it occurs, instead filters are first-class objects +in the CS with their own table. This allows you to define very specific filters +for your project and store them on disc to use in the next session. The CS +allows you fine-grained control, you can choose whether to make a filter +persistent between session, only for one session or use a one-off filter by +typing it directly into the filter field. + + + +Terms used +********** + +Filter + A Filter is generally speaking a tool able to filter the elements of a + table, that is select some elements while discarding others, according to + some criteria. These criteria are written using their own syntax. + +Criterion + A criterion describes some condition a record needs to satisfy in order to + be selected. They are written using a special syntax which is explained + below. We can logically combine multiple criteria in a filter for finer + control. + +Expression + Expressions are how we perform filtering. They look like functions in a + programming language: they have a name and accept a number of arguments. + The expression evaluates to either ``true`` or ``false`` for every record in + the table. The arguments are expressions themselves. + +Arity + The arity of an expression tells us how many arguments it takes. Expressions + taking no arguments are called *nullary*, those taking one argument are + known as *unary* expressions and those taking two arguments are called + *binary*. + + + +Interface +********* + +Above each table there is a text field which is used to enter a filter: either +one predefined by the OpenMW CS developers or one made by you. Another +important element is the filter table found under *View* → *Filters*. You +should see the default filters made by the OpenMW team in the table. The table +has the columns *Filter*, *Description* and *Modified*. + +ID + A unique name used to refer to this filter. Note that every ID has a + scope prefix, we will explain these soon. + +Modified + This is the same as for all the other records, it tells us whether the + filter is *added* or *removed*. Filters are specific to a project instead of + a content file, they have no effect on the game itself. + +Filter + The actual contents of the filter are given here using the filter syntax. + Change the expressions to modify what the filter returns. + +Description + A textual description of what the filter does. + + + +Using predefined filters +************************ + +To use a filter you have to type its ID into the filter field above a table. + +For instance, try to opening the objects table (under the world menu) and type +into the filters field ``project::weapons``. As soon as you complete the text +the table will show only the weapons. The string ``project::weapons`` is the ID +of one of the predefined filters. This means that in order to use the filter +inside the table you type its name inside the filter field. + +Filter IDs follow these general conventions: + +- IDs of filters for a specific record type contain usually the name of a + specific group. For instance the ``project::weapons`` filter contains the + term ``weapons``. Plural form is always used. + +- When filtering a specific subgroup the ID is prefixed with the name of the + more general filter. For instance ``project::weaponssilver`` will filter only + silver weapons and ``project::weaponsmagical`` will filter only magical + weapons. + +- There are few exceptions from the above rule. For instance there are + ``project::added``, ``project::removed``, ``project::modified`` and + ``project::base``. You might except something more like + ``project::statusadded`` but in this case requiring these extra characters + would not improve readability. + +We strongly recommend you take a look at the filters table right now to see +what you can filter with the defaults. Try using the default filters first +before writing you own. + + + +Writing your own filters +************************ + +As mentioned before, filters are just another type of record in the OpenMW CS. +To create a new filter you will have to add a new record to the *Filters* table +and set its properties to your liking. Filters are created by combining +existing filters into more complex ones. + + +Scopes +====== + +Every default filter has the prefix ``project``. This is a *scpoe*, a mechanism +that determines the lifetime of the filter. These are the supported scopes: + +``project::`` + Indicates that the filter is to be used throughout the project in multiple + sessions. You can restart the CS and the filter will still be there. + +``session::`` + Indicates that the filter is not stored between multiple sessions and once + you quit the OpenMW CS application the filter will be gone. Until then it + can be found inside the filters table. + +Project-filters are stored in an internal project file, not final content file +meant for the player. Keep in mind when collaborating with other modders that +you need to share the same project file. + + + +Writing expressions +=================== + +The syntax for expressions is as follows: + +.. code-block:: + + + () + (, , ..., ) + +Where ```` is the name of the expression, such as ``string`` and the +```` are expressions themselves. A nullary expression consists only of its +name. A unary expression contains its argument within a pair of parentheses +following the name. If there is more than one argument they are separated by +commas inside the parentheses. + +An example of a binary expression is ``string("Record Type", weapon)``; the +name is ``string``, and it takes two arguments which are strings of string +type. The meaning of arguments depends on the expression itself. In this case +the first argument is the name of a record column and the second field is the +values we want to test it against. + +Strings are sequences of characters and are case-insensitive. If a string +contains spaces it must be quoted, otherwise the quotes are optional and +ignored. + + +Constant Expressions +-------------------- + +These expressions take no arguments and always return the same result. + +``true`` + Always evaluates to ``true``. + +``false`` + Always evaluates to ``false``. + + +Comparison Expressions +---------------------- + +``string(, )`` + The ```` is a regular expression pattern. The expressions evaluates + to ``true`` when the value of a record in ```` matches the pattern. + Since the majority of the columns contain string values, ``string`` is among + the most often used expressions. Examples: + + ``string("Record Type", "Weapon")`` + Will evaluate to ``true`` for all records containing ``Weapon`` in the + *Record Type* column cell. + + ``string("Portable", "true")`` + Will evaluate to ``true`` [#]_ for all records containing word ``true`` inside + *Portable* column cell. + +.. [#] There is no Boolean (``true`` or ``false``) value in the OpenMW CS. You + should use a string for those. + + +``value(, (, ))`` + Match a value type, such as a number, with a range of possible values. The + argument ```` is the string name of the value we want to compare, the + second argument is a pair of lower and upper bounds for the range interval. + + One can use either parentheses ``()`` or brackets ``[]`` to surround the + pair. Brackets are inclusive and parentheses are exclusive. We can also mix + both styles: + + .. code:: + + value("Weight", [20, 50)) + + This will match any objects with a weight greater or equal to 20 and + strictly less than 50. + + +Logical Expressions +------------------- + +``not `` + Logically negates the result of an expression. If ```` evaluates + to ``true`` the negation is ``false``, and if ```` evaluates to + ``false`` the negation is ``true``. Note that there are no parentheses + around the argument. + +``or(, , ..., )`` + Logical disjunction, evaluates to ``true`` if at least one argument + evaluates to ``true`` as well, otherwise the expression evaluates to + ``false``. + + As an example assume we want to filter for both NPCs and creatures; the + expression for that use-case is + + .. code:: + + or(string("record type", "npc"), string("record type", "creature")) + + In this particular case only one argument can evaluate to ``true``, but one + can write expressions where multiple arguments can be ``true`` at a time. + +``or(, , ..., )`` + Logical conjunction, evaluates to ``true`` if and only if all arguments + evaluate to ``true`` as well, otherwise the expression evaluates to + ``false``. + + As an example assume we want to filter for weapons weighting less than a hundred + units The expression for that use-case is + + .. code:: + + and(string("record type", "weapon"), value("weight", (0, 100))) + + +Anonymous filters +================= + +Creating a whole new filter when you only intend to use it once can be +cumbersome. For that reason the OpenMW CS supports *anonymous* filters which +can be typed directly into the filters field of a table. They are not stored +anywhere, when you clear the field the filter is gone forever. + +In order to define an anonymous filter you type an exclamation mark as the +first character into the field followed by the filter definition (e.g. +``!string("Record Type", weapon)`` to filter only for weapons). + + + +Creating and saving filters +*************************** + +Filters are managed the same way as other records: go to the filters table, +right click and select the option *Add Record* from the context menu. You are +given a choice between project- or session scope. Choose the scope from the +dropdown and type in your desired ID for the filter. A newly created filter +does nothing since it still lacks expressions. In order to add your queries you +have to edit the filter record. + + +Replacing the default filters set +================================= + +OpenMW CS allows you to substitute the default filter set for the entire +application. This will affect the default filters for all content files that +have not been edited on this computer and user account. + +Create a new content file, add the desired filters, remove the undesired ones +and save. Now rename the *project* file to ``defaultfilters`` and make sure the +``.omwaddon.project`` file extension is removed. This file will act as a +template for all new files from now on. If you wish to go back to the +old default set rename or remove this custom file. diff --git a/docs/cs-manual/source/record-types.rst b/docs/cs-manual/source/record-types.rst new file mode 100644 index 000000000..3742cc9e8 --- /dev/null +++ b/docs/cs-manual/source/record-types.rst @@ -0,0 +1,62 @@ +Record Types +############ + +A game world contains many items, such as chests, weapons and monsters. All +these items are merely instances of templates we call *Objects*. The OpenMW CS +*Objects* table contains information about each of these template objects, such +as its value and weight in the case of items, or an aggression level in the +case of NPCs. + +The following is a list of all Record Types and what you can tell OpenMW CS +about each of them. + +Activator + Activators can have a script attached to them. As long as the cell this + object is in is active the script will be run once per frame. + +Potion + This is a potion which is not self-made. It has an Icon for your inventory, + weight, coin value, and an attribute called *Auto Calc* set to ``False``. + This means that the effects of this potion are pre-configured. This does not + happen when the player makes their own potion. + +Apparatus + This is a tool to make potions. Again there’s an icon for your inventory as + well as a weight and a coin value. It also has a *Quality* value attached to + it: the higher the number, the better the effect on your potions will be. + The *Apparatus Type* describes if the item is a *Calcinator*, *Retort*, + *Alembic* or *Mortar & Pestle*. + +Armor + This type of item adds *Enchantment Points* to the mix. Every piece of + clothing or armor has a "pool" of potential *Magicka* that gets unlocked + when the player enchants it. Strong enchantments consume more magicka from + this pool: the stronger the enchantment, the more *Enchantment Points* each + cast will take up. *Health* means the amount of hit points this piece of + armor has. If it sustains enough damage, the armor will be destroyed. + Finally, *Armor Value* tells the game how much points to add to the player + character’s *Armor Rating*. + +Book + This includes scrolls and notes. For the game to make the distinction + between books and scrolls, an extra property, *Scroll*, has been added. + Under the *Skill* column a scroll or book can have an in-game skill listed. + Reading this item will raise the player’s level in that specific skill. + +Clothing + These items work just like armors, but confer no protective properties. + Rather than *Armor Type*, these items have a *Clothing Type*. + +Container + This is all the stuff that stores items, from chests to sacks to plants. Its + *Capacity* shows how much stuff you can put in the container. You can + compare it to the maximum allowed load a player character can carry. A + container, however, will just refuse to take the item in question when it + gets "over-encumbered". Organic Containers are containers such as plants. + Containers that respawn are not safe to store stuff in. After a certain + amount of time they will reset to their default contents, meaning that + everything in them is gone forever. + +Creature + These can be monsters, animals and the like. + diff --git a/docs/cs-manual/source/tables.rst b/docs/cs-manual/source/tables.rst new file mode 100644 index 000000000..43da03f07 --- /dev/null +++ b/docs/cs-manual/source/tables.rst @@ -0,0 +1,168 @@ +Tables +###### + +If you have launched OpenMW CS already and played around with it for a bit, you +will have noticed that the interface is made entirely of tables. This does not +mean it works just like a spreadsheet application though, it would be more +accurate to think of databases instead. Due to the vast amounts of information +involved with Morrowind tables made the most sense. You have to be able to spot +information quickly and be able to change them on the fly. + + +Used Terms +********** + +Record + An entry in OpenMW CS representing an item, location, sound, NPC or anything + else. + +Instance, Object + When an item is placed in the world, it does not create a whole new record + each time, but an *instance* of the *object*. + + For example, the game world might contain a lot of exquisite belts on + different NPCs and in many crates, but they all refer to one specific + instance: the Exquisite Belt record. In this case, all those belts in crates + and on NPCs are instances. The central Exquisite Belt instance is called an + *object*. This allows modders to make changes to all items of the same type + in one place. + + If you wanted all exquisite belts to have 4000 enchantment points rather + than 400, you would only need to change the object Exquisite Belt rather + than all exquisite belt instances individually. + +Some columns are recurring throughout OpenMW CS, they show up in (nearly) every +table. + +ID + Each item, location, sound, etc. gets the same unique identifier in both + OpenMW CS and Morrowind. This is usually a very self-explanatory name. For + example, the ID for the (unique) black pants of Caius Cosades is + ``Caius_pants``. This allows players to manipulate the game in many ways. + For example, they could add these pants to their inventory by opening the + console and entering: ``player- >addItem Caius_pants``. In both Morrowind + and OpenMW CS the ID is the primary way to identify all these different + parts of the game. + +Modified + This column shows what has happened (if anything) to this record. There are + four possible states in which it can exist: + + Base + The record is unmodified and from a content file other than the one + currently being edited. + + Added + This record has been added in the currently content file. + + Modified + Similar to *base*, but has been changed in some way. + + Deleted + Similar to *base*, but has been removed as an entry. This does not mean, + however, that the occurrences in the game itself have been removed! For + example, if you were to remove the ``CharGen_Bed`` entry from + ``morrowind.esm``, it does not mean the bedroll in the basement of the + Census and Excise Office in Seyda Neen will be gone. You will have to + delete that instance yourself or make sure that that object is replaced + by something that still exists otherwise the player will get crashes in + the worst case scenario. + + + +World Screens +************* + +The contents of the game world can be changed by choosing one of the options in +the appropriate menu at the top of the screen. + + +Regions +======= + +This describes the general areas of Vvardenfell. Each of these areas has +different rules about things such as encounters and weather. + +Name + This is how the game will show the player's location in-game. + +MapColour + This is a six-digit hexadecimal representation of the colour used to + identify the region on the map available in *World* → *Region Map*. + +Sleep Encounter + These are the rules for what kinds of enemies the player might encounter + when sleeping outside in the wilderness. + + +Cells +===== + +Expansive worlds such as Vvardenfell, with all its items, NPCs, etc. have a lot +going on simultaneously. But if the player is in Balmora, why would the +computer need to keep track the exact locations of NPCs walking through the +corridors in a Vivec canton? All that work would be quite useless and bring +the player's system down to its knees! So the world has been divided up into +squares we call *cells*. Once your character enters a cell, the game will load +everything that is going on in that cell so the player can interact with it. + +In the original Morrowind this could be seen when a small loading bar would +appear near the bottom of the screen while travelling; the player had just +entered a new cell and the game had to load all the items and NPCs. The *Cells* +screen in OpenMW CS provides you with a list of cells in the game, both the +interior cells (houses, dungeons, mines, etc.) and the exterior cells (the +outside world). + +Sleep Forbidden + Can the player sleep on the floor? In most cities it is forbidden to sleep + outside. Sleeping in the wilderness carries its own risks of attack, though, + and this entry lets you decide if a player should be allowed to sleep on the + floor in this cell or not. + +Interior Water + Should water be rendered in this interior cell? The game world consists of + an endless ocean at height 0, then the landscape is added. If part of the + landscape goes below height 0, the player will see water. + + Setting the cell’s Interior Water to true tells the game that this cell that + there needs to be water at height 0. This is useful for dungeons or mines + that have water in them. + + Setting the cell’s Interior Water to ``false`` tells the game that the water + at height 0 should not be used. This flag is useless for outside cells. + +Interior Sky + Should this interior cell have a sky? This is a rather unique case. The + Tribunal expansion took place in a city on the mainland. Normally this would + require the city to be composed of exterior cells so it has a sky, weather + and the like. But if the player is in an exterior cell and were to look at + their in-game map, they would see Vvardenfell with an overview of all + exterior cells. The player would have to see the city’s very own map, as if + they were walking around in an interior cell. + + So the developers decided to create a workaround and take a bit of both: The + whole city would technically work exactly like an interior cell, but it + would need a sky as if it was an exterior cell. That is what this is. This + is why the vast majority of the cells you will find in this screen will have + this option set to false: It is only meant for these "fake exteriors". + +Region + To which Region does this cell belong? This has an impact on the way the + game handles weather and encounters in this area. It is also possible for a + cell not to belong to any region. + + +Objects +======= + +This is a library of all the items, triggers, containers, NPCs, etc. in the +game. There are several kinds of Record Types. Depending on which type a record +is, it will need specific information to function. For example, an NPC needs a +value attached to its aggression level. A chest, of course, does not. All +Record Types contain at least a 3D model or else the player would not see them. +Usually they also have a *Name*, which is what the players sees when they hover +their reticle over the object during the game. + +Please refer to the Record Types chapter for an overview of what each type of +object does and what you can tell OpenMW CS about these objects. + diff --git a/docs/source/openmw-cs/index.rst b/docs/source/openmw-cs/index.rst index dcd28081a..b9c03980b 100644 --- a/docs/source/openmw-cs/index.rst +++ b/docs/source/openmw-cs/index.rst @@ -22,4 +22,7 @@ few chapters to familiarise yourself with the new interface. tour files-and-directories starting-dialog + tables + record-types + record-filters From 22fb1f3403b7f97c9f381001580bebd457b0276d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 30 Apr 2018 14:26:36 +0400 Subject: [PATCH 02/16] Play spellcasting effects from objects --- apps/openmw/mwbase/world.hpp | 7 +- apps/openmw/mwmechanics/character.cpp | 9 +++ apps/openmw/mwmechanics/spellcasting.cpp | 83 ++++++++++++++++++++--- apps/openmw/mwrender/animation.cpp | 14 ++-- apps/openmw/mwrender/animation.hpp | 3 +- apps/openmw/mwrender/effectmanager.cpp | 9 ++- apps/openmw/mwrender/effectmanager.hpp | 1 + apps/openmw/mwrender/objects.cpp | 8 +++ apps/openmw/mwrender/objects.hpp | 2 + apps/openmw/mwrender/renderingmanager.cpp | 13 +++- apps/openmw/mwrender/renderingmanager.hpp | 2 + apps/openmw/mwscript/miscextensions.cpp | 2 + apps/openmw/mwworld/worldimp.cpp | 9 ++- apps/openmw/mwworld/worldimp.hpp | 4 +- 14 files changed, 143 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 47502fd71..68d4ac85d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -5,6 +5,9 @@ #include #include +#include +#include + #include #include "../mwworld/ptr.hpp" @@ -534,7 +537,7 @@ namespace MWBase /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0; - virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) = 0; + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) = 0; virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, @@ -576,6 +579,8 @@ namespace MWBase /// Preload VFX associated with this effect list virtual void preloadEffects(const ESM::EffectList* effectList) = 0; + + virtual osg::ref_ptr getInstance (const std::string& modelName) = 0; }; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2ef5e07d7..7e3744c30 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -833,6 +833,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); mAnimation->runAnimation(0.f); + mAnimation->updateEffects(0.f); unpersistAnimationState(); } @@ -1997,6 +1998,13 @@ void CharacterController::update(float duration) } osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); + + // treat player specifically since he is not in rendering mObjects + if (mPtr == getPlayer()) + { + mAnimation->updateEffects(mSkipAnim ? 0.f : duration); + } + if(duration > 0.0f) moved /= duration; else @@ -2192,6 +2200,7 @@ void CharacterController::forceStateUpdate() } mAnimation->runAnimation(0.f); + mAnimation->updateEffects(0.f); } CharacterController::KillResult CharacterController::kill() diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 7570cb9ba..802020b0e 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -6,8 +6,12 @@ #include +#include +#include + #include #include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -24,6 +28,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" +#include "../mwrender/vismask.hpp" #include "npcstats.hpp" #include "actorutil.hpp" @@ -996,11 +1001,13 @@ namespace MWMechanics return true; } - void CastSpell::playSpellCastingEffects(const std::string &spellid){ - + void CastSpell::playSpellCastingEffects(const std::string &spellid) + { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell *spell = store.get().find(spellid); + std::vector addedEffects; + for (std::vector::const_iterator iter = spell->mEffects.mList.begin(); iter != spell->mEffects.mList.end(); ++iter) { @@ -1009,18 +1016,72 @@ namespace MWMechanics MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); - if (animation && mCaster.getClass().isActor()) // TODO: Non-actors should also create a spell cast vfx even if they are disabled (animation == NULL) + const ESM::Static* castStatic; + + if (!effect->mCasting.empty()) + castStatic = store.get().find (effect->mCasting); + else + castStatic = store.get().find ("VFX_DefaultCast"); + + // check if the effect was already added + if (std::find(addedEffects.begin(), addedEffects.end(), "meshes\\" + castStatic->mModel) != addedEffects.end()) + continue; + + std::string texture = effect->mParticle; + + float scale = 1.0f; + osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3()); + + if (animation && mCaster.getClass().isNpc()) { - const ESM::Static* castStatic; + // For NOC we should take race height as scaling factor + const ESM::NPC *npc = mCaster.get()->mBase; + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); - if (!effect->mCasting.empty()) - castStatic = store.get().find (effect->mCasting); - else - castStatic = store.get().find ("VFX_DefaultCast"); + const ESM::Race *race = + esmStore.get().find(npc->mRace); - std::string texture = effect->mParticle; + scale = npc->isMale() ? race->mData.mHeight.mMale : race->mData.mHeight.mFemale; + } + else + { + std::string casterModel = mCaster.getClass().getModel(mCaster); + osg::ref_ptr model = MWBase::Environment::get().getWorld()->getInstance(casterModel); - animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture); + osg::ComputeBoundsVisitor computeBoundsVisitor; + computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect)); + model->accept(computeBoundsVisitor); + osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); + + if (bounds.valid()) + { + float meshSizeX = std::abs(bounds.xMax() - bounds.xMin()); + float meshSizeY = std::abs(bounds.yMax() - bounds.yMin()); + float meshSizeZ = std::abs(bounds.zMax() - bounds.zMin()); + + // TODO: take a size of particle or NPC with height and weight = 1.0 as scale = 1.0 + float scaleX = meshSizeX/60.f; + float scaleY = meshSizeY/60.f; + float scaleZ = meshSizeZ/120.f; + + scale = std::max({ scaleX, scaleY, scaleZ }); + + //pos = bounds.center(); + //pos[2] = bounds.zMin(); + } + } + + // If the caster has no animation, add the effect directly to the effectManager + if (animation) + { + animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture, scale); + } + else + { + // We should set scale for effect manager manually + float meshScale = !mCaster.getClass().isActor() ? mCaster.getCellRef().getScale() : 1.0f; + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + castStatic->mModel, effect->mParticle, pos, scale * meshScale); } if (animation && !mCaster.getClass().isActor()) @@ -1030,6 +1091,8 @@ namespace MWMechanics "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; + addedEffects.push_back("meshes\\" + castStatic->mModel); + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!effect->mCastSound.empty()) sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1bd839ead..1b508e593 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -1115,8 +1117,6 @@ namespace MWRender ++stateiter; } - updateEffects(duration); - if (mHeadController) { const float epsilon = 0.001f; @@ -1366,7 +1366,7 @@ namespace MWRender useQuadratic, quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); } - void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) + void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture, float scale) { if (!mObjectRoot.get()) return; @@ -1417,7 +1417,13 @@ namespace MWRender overrideFirstRootTexture(texture, mResourceSystem, node); - // TODO: in vanilla morrowind the effect is scaled based on the host object's bounding box. + osg::Vec3f scale3f (scale, scale, scale); + + osg::ref_ptr trans = new osg::PositionAttitudeTransform; + trans->setScale(scale3f); + trans->addChild(node); + parentNode->removeChild(node); + parentNode->addChild(trans); mEffects.push_back(params); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index cff98a4b7..74224c6bd 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -364,7 +364,7 @@ public: * @param texture override the texture specified in the model's materials - if empty, do not override * @note Will not add an effect twice. */ - void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = ""); + void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = "", float scale = 1.0f); void removeEffect (int effectId); void getLoopingEffects (std::vector& out) const; @@ -446,7 +446,6 @@ public: void setLoopingEnabled(const std::string &groupname, bool enabled); - /// This is typically called as part of runAnimation, but may be called manually if needed. void updateEffects(float duration); /// Return a node with the specified name, or NULL if not existing. diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 3e785a769..c44c5428a 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -26,6 +26,13 @@ EffectManager::~EffectManager() } void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) +{ + osg::Vec3f scale3f (scale, scale, scale); + + addEffect(model, textureOverride, worldPosition, scale3f, isMagicVFX); +} + +void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, const osg::Vec3f &scale, bool isMagicVFX) { osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model); @@ -40,7 +47,7 @@ void EffectManager::addEffect(const std::string &model, const std::string& textu osg::ref_ptr trans = new osg::PositionAttitudeTransform; trans->setPosition(worldPosition); - trans->setScale(osg::Vec3f(scale, scale, scale)); + trans->setScale(scale); trans->addChild(node); SceneUtil::AssignControllerSourcesVisitor assignVisitor(effect.mAnimTime); diff --git a/apps/openmw/mwrender/effectmanager.hpp b/apps/openmw/mwrender/effectmanager.hpp index 5873c00dd..cc1c1b42e 100644 --- a/apps/openmw/mwrender/effectmanager.hpp +++ b/apps/openmw/mwrender/effectmanager.hpp @@ -33,6 +33,7 @@ namespace MWRender /// Add an effect. When it's finished playing, it will be removed automatically. void addEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPosition, float scale, bool isMagicVFX = true); + void addEffect (const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, const osg::Vec3f &scale3f, bool isMagicVFX); void update(float dt); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index f0a3d2e38..523f1774c 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -138,6 +138,14 @@ bool Objects::removeObject (const MWWorld::Ptr& ptr) return false; } +void Objects::updateEffects(float duration) +{ + for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();) + { + iter->second->updateEffects(duration); + ++iter; + } +} void Objects::removeCell(const MWWorld::CellStore* store) { diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 659853763..e97395213 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -88,6 +88,8 @@ public: bool removeObject (const MWWorld::Ptr& ptr); ///< \return found? + void updateEffects(float duration); + void removeCell(const MWWorld::CellStore* store); /// Updates containing cell for object rendering data diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6f0ddba3a..e5933a72a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -557,6 +557,7 @@ namespace MWRender mEffectManager->update(dt); mSky->update(dt); mWater->update(dt); + mObjects->updateEffects(dt); } mCamera->update(dt, paused); @@ -841,9 +842,11 @@ namespace MWRender mObjects->updatePtr(old, updated); } - void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) + void RenderingManager::spawnEffect(const std::string &model, const std::string& texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) { - mEffectManager->addEffect(model, texture, worldPosition, scale, isMagicVFX); + osg::Vec3f scale3f (scale, scale, scale); + + mEffectManager->addEffect(model, texture, worldPosition, scale3f, isMagicVFX); } void RenderingManager::notifyWorldSpaceChanged() @@ -1122,6 +1125,12 @@ namespace MWRender updateProjectionMatrix(); } } + + osg::ref_ptr RenderingManager::getInstance(const std::string& modelName) + { + return mResourceSystem->getSceneManager()->getInstance(modelName); + } + void RenderingManager::exportSceneGraph(const MWWorld::Ptr &ptr, const std::string &filename, const std::string &format) { osg::Node* node = mViewer->getSceneData(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 1c689d29f..7a570f249 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -205,6 +205,8 @@ namespace MWRender LandManager* getLandManager() const; + osg::ref_ptr getInstance(const std::string& modelName); + private: void updateProjectionMatrix(); void updateTextureFiltering(); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 59f2cc9c6..243ba7b7e 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -14,6 +14,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/world.hpp" @@ -1051,6 +1052,7 @@ namespace MWScript MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); MWMechanics::CastSpell cast(ptr, target); + cast.playSpellCastingEffects(spell); cast.mHitPosition = target.getRefData().getPosition().asVec3(); cast.mAlwaysSucceed = true; cast.cast(spell); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 13bfc32b3..bd3ff6e5f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1535,6 +1535,11 @@ namespace MWWorld } } + osg::ref_ptr World::getInstance (const std::string& modelName) + { + return mRendering->getInstance(modelName); + } + const ESM::Potion *World::createRecord (const ESM::Potion& record) { return mStore.insert(record); @@ -3324,9 +3329,9 @@ namespace MWWorld mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); } - void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos) + void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos, float scale, bool isMagicVFX) { - mRendering->spawnEffect(model, textureOverride, worldPos); + mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX); } void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType, diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 40a22af5d..c366e51d0 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -640,7 +640,7 @@ namespace MWWorld /// Spawn a blood effect for \a ptr at \a worldPosition void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override; - void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) override; + void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) override; void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName, @@ -680,6 +680,8 @@ namespace MWWorld /// Preload VFX associated with this effect list void preloadEffects(const ESM::EffectList* effectList) override; + + osg::ref_ptr getInstance (const std::string& modelName); }; } From 6cc740519461e326f95da8f04432712378817d77 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 30 Apr 2018 18:29:26 +0400 Subject: [PATCH 03/16] Remove unnecessary command from QuadTreeWorld destructor (bug #4408) --- components/terrain/quadtreeworld.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index f31064805..cd51efe69 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -246,7 +246,6 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour QuadTreeWorld::~QuadTreeWorld() { - ensureQuadTreeBuilt(); mViewDataMap->clear(); } From f5b7a230fc1fcf69deb0ecd7e73b45f0488f95e2 Mon Sep 17 00:00:00 2001 From: tri4ng1e Date: Sat, 5 May 2018 13:30:45 +0300 Subject: [PATCH 04/16] ESMReader::close now clears mHeader --- components/esm/esmreader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 2a716427e..67b9d6a38 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -57,6 +57,7 @@ void ESMReader::close() mCtx.subCached = false; mCtx.recName.clear(); mCtx.subName.clear(); + mHeader.blank(); } void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) From 3636cf20150476b491387251f2918aeef52168fa Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 4 May 2018 12:56:28 +0400 Subject: [PATCH 05/16] Do not apply queue movement for standing actors --- apps/openmw/mwbase/world.hpp | 1 + apps/openmw/mwmechanics/aiwander.cpp | 3 +-- apps/openmw/mwmechanics/character.cpp | 3 ++- apps/openmw/mwphysics/actor.cpp | 7 +++++- apps/openmw/mwphysics/actor.hpp | 8 +++++++ apps/openmw/mwphysics/physicssystem.cpp | 29 +++++++++++++++++++++++++ apps/openmw/mwphysics/physicssystem.hpp | 3 +++ apps/openmw/mwscript/miscextensions.cpp | 2 ++ apps/openmw/mwstate/statemanagerimp.cpp | 5 ++++- apps/openmw/mwworld/worldimp.cpp | 7 ++++-- apps/openmw/mwworld/worldimp.hpp | 1 + 11 files changed, 62 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 47502fd71..86e2a0281 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -388,6 +388,7 @@ namespace MWBase virtual bool isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const = 0; virtual bool isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const = 0; virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0; + virtual bool isIdle(const MWWorld::Ptr &ptr) const = 0; virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 8199170dc..511a47a49 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -866,8 +866,7 @@ namespace MWMechanics } // place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be underground. - // Adding 20 in adjustPosition() is not enough. - dest.mZ += 60; + dest.mZ += 80; ToWorldCoordinates(dest, actor.getCell()->getCell()); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2ef5e07d7..d7a038b9e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2029,7 +2029,8 @@ void CharacterController::update(float duration) moved.z() = 1.0; // Update movement - if(mMovementAnimationControlled && mPtr.getClass().isActor()) + // We should not apply movement for standing actors + if(mMovementAnimationControlled && mPtr.getClass().isActor() && (movement.length2() > 0.f || !world->isIdle(mPtr))) world->queueMovement(mPtr, moved); mSkipAnim = false; diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 79c6dcabf..6de0d1984 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -18,7 +18,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr shape, btCollisionWorld* world) : mCanWaterWalk(false), mWalkingOnWater(false) - , mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) + , mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false), mIdle(true) , mInternalCollisionMode(true) , mExternalCollisionMode(true) , mCollisionWorld(world) @@ -195,6 +195,11 @@ void Actor::setOnSlope(bool slope) mOnSlope = slope; } +void Actor::setIdle(bool idle) +{ + mIdle = idle; +} + bool Actor::isWalkingOnWater() const { return mWalkingOnWater; diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 8ec94200f..bdafc1235 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -139,6 +139,13 @@ namespace MWPhysics return mInternalCollisionMode && mOnSlope; } + void setIdle(bool idle); + + bool getIdle() const + { + return mIdle; + } + btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); @@ -179,6 +186,7 @@ namespace MWPhysics osg::Vec3f mForce; bool mOnGround; bool mOnSlope; + bool mIdle; bool mInternalCollisionMode; bool mExternalCollisionMode; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f3c34bc4e..1cff6b522 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -683,6 +683,7 @@ namespace MWPhysics , mWaterEnabled(false) , mParentNode(parentNode) , mPhysicsDt(1.f / 60.f) + , mIdleUpdateTimer(0) { mResourceSystem->addResourceManager(mShapeManager.get()); @@ -739,6 +740,18 @@ namespace MWPhysics delete mBroadphase; } + void PhysicsSystem::updateIdle() + { + for (ActorMap::iterator it = mActors.begin(); it != mActors.end(); ++it) + { + osg::Vec3f pos(it->second->getCollisionObjectPosition()); + + RayResult result = castRay(pos, pos - osg::Vec3f(0, 0, it->second->getHalfExtents().z() + 2), it->second->getPtr(), std::vector(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door); + + it->second->setIdle(result.mHit); + } + } + void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) { mUnrefQueue = unrefQueue; @@ -1045,6 +1058,11 @@ namespace MWPhysics return physactor && physactor->getOnGround(); } + bool PhysicsSystem::isIdle(const MWWorld::Ptr &actor) + { + Actor* physactor = getActor(actor); + return physactor && physactor->getIdle(); + } bool PhysicsSystem::canMoveToWaterSurface(const MWWorld::ConstPtr &actor, const float waterlevel) { const Actor* physicActor = getActor(actor); @@ -1337,6 +1355,10 @@ namespace MWPhysics cmode = !cmode; found->second->enableCollisionMode(cmode); found->second->enableCollisionBody(cmode); + + if (cmode) + queueObjectMovement(MWMechanics::getPlayer(), osg::Vec3f(0, 0, -0.1f)); + return cmode; } @@ -1456,6 +1478,13 @@ namespace MWPhysics for (std::set::iterator it = mAnimatedObjects.begin(); it != mAnimatedObjects.end(); ++it) (*it)->animateCollisionShapes(mCollisionWorld); + mIdleUpdateTimer -= dt; + if (mIdleUpdateTimer <= 0.f) + { + mIdleUpdateTimer = 0.5f; + updateIdle(); + } + #ifndef BT_NO_PROFILE CProfileManager::Reset(); CProfileManager::Increment_Frame_Counter(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 3ef9990f5..fe2433ce0 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -124,6 +124,7 @@ namespace MWPhysics bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const; bool isOnGround (const MWWorld::Ptr& actor); + bool isIdle (const MWWorld::Ptr& actor); bool canMoveToWaterSurface (const MWWorld::ConstPtr &actor, const float waterlevel); @@ -173,6 +174,7 @@ namespace MWPhysics private: void updateWater(); + void updateIdle(); osg::ref_ptr mUnrefQueue; @@ -221,6 +223,7 @@ namespace MWPhysics osg::ref_ptr mParentNode; float mPhysicsDt; + float mIdleUpdateTimer; PhysicsSystem (const PhysicsSystem&); PhysicsSystem& operator= (const PhysicsSystem&); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 59f2cc9c6..83ecaff33 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -700,6 +700,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { + MWWorld::Ptr ptr = R()(runtime); + MWBase::Environment::get().getWorld()->queueMovement(ptr, osg::Vec3f(0, 0, -0.1f)); } }; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index c1bb589e8..911e0ebdc 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -506,7 +506,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str if (firstPersonCam != MWBase::Environment::get().getWorld()->isFirstPerson()) MWBase::Environment::get().getWorld()->togglePOV(); - MWWorld::ConstPtr ptr = MWMechanics::getPlayer(); + const MWWorld::Ptr ptr = MWMechanics::getPlayer(); if (ptr.isInCell()) { @@ -538,6 +538,9 @@ void MWState::StateManager::loadGame (const Character *character, const std::str // Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag. // But make sure the flag is cleared anyway in case it was set from an earlier game. MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + + // Workaround to fix camera position upon game load + MWBase::Environment::get().getWorld()->queueMovement(ptr, osg::Vec3f(0, 0, 0)); } catch (const std::exception& e) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 13bfc32b3..5b11dd370 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1314,8 +1314,6 @@ namespace MWWorld if (pos.z() < terrainHeight) pos.z() = terrainHeight; - pos.z() += 20; // place slightly above. will snap down to ground with code below - if (force || !isFlying(ptr)) { osg::Vec3f traced = mPhysics->traceDown(ptr, pos, 500); @@ -2157,6 +2155,11 @@ namespace MWWorld return mPhysics->isOnGround(ptr); } + bool World::isIdle(const MWWorld::Ptr &ptr) const + { + return mPhysics->isIdle(ptr); + } + void World::togglePOV() { mRendering->togglePOV(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 40a22af5d..133e6d8fe 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -489,6 +489,7 @@ namespace MWWorld bool isWading(const MWWorld::ConstPtr &object) const override; bool isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const override; bool isOnGround(const MWWorld::Ptr &ptr) const override; + bool isIdle(const MWWorld::Ptr &ptr) const override; osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const override; From 905cde10dbefa4ac43ed3ea3921173a847ea523a Mon Sep 17 00:00:00 2001 From: tri4ng1e Date: Mon, 23 Apr 2018 22:21:23 +0300 Subject: [PATCH 06/16] Smart-sorting in iniimporter (time + dependency) --- apps/mwiniimporter/importer.cpp | 98 +++++++++++++++++++++++++++++---- apps/mwiniimporter/importer.hpp | 9 ++- 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 24646b844..66b56f8a6 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -824,33 +825,73 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con } } -void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const { - std::vector > contentFiles; +void MwIniImporter::dependencySortStep(std::string& el, MwIniImporter::deplist& src, std::vector& ret) +{ + auto it = std::find_if(src.begin(), src.end(), [&el](std::pair< std::string, std::vector >& o) + { + return o.first == el; + }); + if (it != src.end()) + { + auto o = std::move(*it); + src.erase(it); + for (auto name : o.second) + { + MwIniImporter::dependencySortStep(name, src, ret); + } + ret.push_back(std::move(o.first)); + } +} + +std::vector MwIniImporter::dependencySort(MwIniImporter::deplist src) +{ + std::vector ret; + while (!src.empty()) + { + MwIniImporter::dependencySortStep(src.begin()->first, src, ret); + } + return ret; +} + +std::vector::iterator MwIniImporter::findString(std::vector& v, const std::string& s) +{ + return std::find_if(v.begin(), v.end(), [&s](const std::string& str) + { + return Misc::StringUtils::ciEqual(str, s); + }); +} + +void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const +{ + std::vector> contentFiles; std::string baseGameFile("Game Files:GameFile"); std::string gameFile(""); std::time_t defaultTime = 0; + ToUTF8::Utf8Encoder encoder(mEncoding); // assume the Game Files are all in a "Data Files" directory under the directory holding Morrowind.ini const boost::filesystem::path gameFilesDir(iniFilename.parent_path() /= "Data Files"); multistrmap::const_iterator it = ini.begin(); - for(int i=0; it != ini.end(); i++) { + for (int i=0; it != ini.end(); i++) + { gameFile = baseGameFile; gameFile.append(this->numberToString(i)); it = ini.find(gameFile); - if(it == ini.end()) { + if(it == ini.end()) break; - } - for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { + for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) + { std::string filetype(entry->substr(entry->length()-3)); Misc::StringUtils::lowerCaseInPlace(filetype); - if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { + if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) + { boost::filesystem::path filepath(gameFilesDir); filepath /= *entry; - contentFiles.push_back(std::make_pair(lastWriteTime(filepath, defaultTime), *entry)); + contentFiles.push_back({lastWriteTime(filepath, defaultTime), filepath}); } } } @@ -858,11 +899,46 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co cfg.erase("content"); cfg.insert( std::make_pair("content", std::vector() ) ); - // this will sort files by time order first, then alphabetical (maybe), I suspect non ASCII filenames will be stuffed. + // sort by timestamp sort(contentFiles.begin(), contentFiles.end()); - for(std::vector >::const_iterator iter=contentFiles.begin(); iter!=contentFiles.end(); ++iter) { - cfg["content"].push_back(iter->second); + + MwIniImporter::deplist unsortedFiles; + + ESM::ESMReader reader; + reader.setEncoder(&encoder); + for (auto& file : contentFiles) + { + reader.open(file.second.string()); + std::vector deps; + for (auto& depFile : reader.getGameFiles()) + { + deps.push_back(depFile.name); + } + unsortedFiles.emplace_back(boost::filesystem::path(reader.getName()).filename().string(), deps); + reader.close(); } + + auto sortedFiles = dependencySort(unsortedFiles); + + // hard-coded dependency Morrowind - Tribunal - Bloodmoon + if(findString(sortedFiles, "Morrowind.esm") != sortedFiles.end()) + { + auto foundTribunal = findString(sortedFiles, "Tribunal.esm"); + auto foundBloodmoon = findString(sortedFiles, "Bloodmoon.esm"); + + if (foundBloodmoon != sortedFiles.end() && foundTribunal != sortedFiles.end()) + { + size_t dstBloodmoon = std::distance(sortedFiles.begin(), foundBloodmoon); + size_t dstTribunal = std::distance(sortedFiles.begin(), foundTribunal); + if (dstBloodmoon < dstTribunal) + dstTribunal++; + sortedFiles.insert(foundBloodmoon, *foundTribunal); + sortedFiles.erase(sortedFiles.begin() + dstTribunal); + } + } + + for (auto& file : sortedFiles) + cfg["content"].push_back(file); } void MwIniImporter::writeToFile(std::ostream &out, const multistrmap &cfg) { diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index c73cc65b5..44b2af83c 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -14,6 +14,7 @@ class MwIniImporter { public: typedef std::map strmap; typedef std::map > multistrmap; + typedef std::vector< std::pair< std::string, std::vector > > deplist; MwIniImporter(); void setInputEncoding(const ToUTF8::FromType& encoding); @@ -22,12 +23,17 @@ class MwIniImporter { static multistrmap loadCfgFile(const boost::filesystem::path& filename); void merge(multistrmap &cfg, const multistrmap &ini) const; void mergeFallback(multistrmap &cfg, const multistrmap &ini) const; - void importGameFiles(multistrmap &cfg, const multistrmap &ini, + void importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const; void importArchives(multistrmap &cfg, const multistrmap &ini) const; static void writeToFile(std::ostream &out, const multistrmap &cfg); + static std::vector dependencySort(MwIniImporter::deplist src); + private: + static void dependencySortStep(std::string& el, MwIniImporter::deplist& src, std::vector& ret); + static std::vector::iterator findString(std::vector& v, const std::string& s); + static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value); static std::string numberToString(int n); @@ -40,5 +46,4 @@ class MwIniImporter { ToUTF8::FromType mEncoding; }; - #endif From 103a07b7441fd3635be6fd42dfdfa669239e9dbe Mon Sep 17 00:00:00 2001 From: tri4ng1e Date: Tue, 8 May 2018 18:32:06 +0300 Subject: [PATCH 07/16] Less cryptic abbreviations --- apps/mwiniimporter/importer.cpp | 72 +++++++++++++++++---------------- apps/mwiniimporter/importer.hpp | 8 ++-- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 66b56f8a6..74c21da29 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -825,39 +825,43 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con } } -void MwIniImporter::dependencySortStep(std::string& el, MwIniImporter::deplist& src, std::vector& ret) +void MwIniImporter::dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector& result) { - auto it = std::find_if(src.begin(), src.end(), [&el](std::pair< std::string, std::vector >& o) - { - return o.first == el; - }); - if (it != src.end()) - { - auto o = std::move(*it); - src.erase(it); - for (auto name : o.second) + auto iter = std::find_if( + source.begin(), + source.end(), + [&element](std::pair< std::string, std::vector >& sourceElement) { - MwIniImporter::dependencySortStep(name, src, ret); + return sourceElement.first == element; } - ret.push_back(std::move(o.first)); + ); + if (iter != source.end()) + { + auto foundElement = std::move(*iter); + source.erase(iter); + for (auto name : foundElement.second) + { + MwIniImporter::dependencySortStep(name, source, result); + } + result.push_back(std::move(foundElement.first)); } } -std::vector MwIniImporter::dependencySort(MwIniImporter::deplist src) +std::vector MwIniImporter::dependencySort(MwIniImporter::dependencyList source) { - std::vector ret; - while (!src.empty()) + std::vector result; + while (!source.empty()) { - MwIniImporter::dependencySortStep(src.begin()->first, src, ret); + MwIniImporter::dependencySortStep(source.begin()->first, source, result); } - return ret; + return result; } -std::vector::iterator MwIniImporter::findString(std::vector& v, const std::string& s) +std::vector::iterator MwIniImporter::findString(std::vector& source, const std::string& string) { - return std::find_if(v.begin(), v.end(), [&s](const std::string& str) + return std::find_if(source.begin(), source.end(), [&string](const std::string& sourceString) { - return Misc::StringUtils::ciEqual(str, s); + return Misc::StringUtils::ciEqual(sourceString, string); }); } @@ -902,19 +906,19 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co // sort by timestamp sort(contentFiles.begin(), contentFiles.end()); - MwIniImporter::deplist unsortedFiles; + MwIniImporter::dependencyList unsortedFiles; ESM::ESMReader reader; reader.setEncoder(&encoder); for (auto& file : contentFiles) { reader.open(file.second.string()); - std::vector deps; - for (auto& depFile : reader.getGameFiles()) + std::vector dependencies; + for (auto& gameFile : reader.getGameFiles()) { - deps.push_back(depFile.name); + dependencies.push_back(gameFile.name); } - unsortedFiles.emplace_back(boost::filesystem::path(reader.getName()).filename().string(), deps); + unsortedFiles.emplace_back(boost::filesystem::path(reader.getName()).filename().string(), dependencies); reader.close(); } @@ -923,17 +927,17 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co // hard-coded dependency Morrowind - Tribunal - Bloodmoon if(findString(sortedFiles, "Morrowind.esm") != sortedFiles.end()) { - auto foundTribunal = findString(sortedFiles, "Tribunal.esm"); - auto foundBloodmoon = findString(sortedFiles, "Bloodmoon.esm"); + auto tribunalIter = findString(sortedFiles, "Tribunal.esm"); + auto bloodmoonIter = findString(sortedFiles, "Bloodmoon.esm"); - if (foundBloodmoon != sortedFiles.end() && foundTribunal != sortedFiles.end()) + if (bloodmoonIter != sortedFiles.end() && tribunalIter != sortedFiles.end()) { - size_t dstBloodmoon = std::distance(sortedFiles.begin(), foundBloodmoon); - size_t dstTribunal = std::distance(sortedFiles.begin(), foundTribunal); - if (dstBloodmoon < dstTribunal) - dstTribunal++; - sortedFiles.insert(foundBloodmoon, *foundTribunal); - sortedFiles.erase(sortedFiles.begin() + dstTribunal); + size_t bloodmoonIndex = std::distance(sortedFiles.begin(), bloodmoonIter); + size_t tribunalIndex = std::distance(sortedFiles.begin(), tribunalIter); + if (bloodmoonIndex < tribunalIndex) + tribunalIndex++; + sortedFiles.insert(bloodmoonIter, *tribunalIter); + sortedFiles.erase(sortedFiles.begin() + tribunalIndex); } } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 44b2af83c..e1595ad96 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -14,7 +14,7 @@ class MwIniImporter { public: typedef std::map strmap; typedef std::map > multistrmap; - typedef std::vector< std::pair< std::string, std::vector > > deplist; + typedef std::vector< std::pair< std::string, std::vector > > dependencyList; MwIniImporter(); void setInputEncoding(const ToUTF8::FromType& encoding); @@ -28,11 +28,11 @@ class MwIniImporter { void importArchives(multistrmap &cfg, const multistrmap &ini) const; static void writeToFile(std::ostream &out, const multistrmap &cfg); - static std::vector dependencySort(MwIniImporter::deplist src); + static std::vector dependencySort(MwIniImporter::dependencyList source); private: - static void dependencySortStep(std::string& el, MwIniImporter::deplist& src, std::vector& ret); - static std::vector::iterator findString(std::vector& v, const std::string& s); + static void dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector& result); + static std::vector::iterator findString(std::vector& source, const std::string& string); static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value); static std::string numberToString(int n); From 9126e844bfeb12b4f68e56e88476d536a0ae4156 Mon Sep 17 00:00:00 2001 From: tri4ng1e Date: Fri, 11 May 2018 17:24:36 +0300 Subject: [PATCH 08/16] Use data paths from config (bug #4412) --- apps/mwiniimporter/importer.cpp | 37 +++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 74c21da29..3601f0e9b 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -873,8 +873,20 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co std::time_t defaultTime = 0; ToUTF8::Utf8Encoder encoder(mEncoding); - // assume the Game Files are all in a "Data Files" directory under the directory holding Morrowind.ini - const boost::filesystem::path gameFilesDir(iniFilename.parent_path() /= "Data Files"); + std::vector dataPaths; + if (cfg.count("data")) + { + for (std::string filePathString : cfg["data"]) + { + if (filePathString.front() == '"') + { + filePathString.erase(filePathString.begin()); + filePathString.erase(filePathString.end() - 1); + } + dataPaths.emplace_back(filePathString); + } + } + dataPaths.push_back(iniFilename.parent_path() /= "Data Files"); multistrmap::const_iterator it = ini.begin(); for (int i=0; it != ini.end(); i++) @@ -893,9 +905,20 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { - boost::filesystem::path filepath(gameFilesDir); - filepath /= *entry; - contentFiles.push_back({lastWriteTime(filepath, defaultTime), filepath}); + bool found = false; + for (auto & dataPath : dataPaths) + { + boost::filesystem::path path = dataPath / *entry; + std::time_t time = lastWriteTime(path, defaultTime); + if (time != defaultTime) + { + contentFiles.push_back({time, path}); + found = true; + break; + } + } + if (!found) + std::cout << "Warning: " << *entry << " not found, ignoring" << std::endl; } } } @@ -981,9 +1004,5 @@ std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename std::cout << "content file: " << resolved << " timestamp = (" << writeTime << ") " << timeStrBuffer << std::endl; } - else - { - std::cout << "content file: " << filename << " not found" << std::endl; - } return writeTime; } From 1c9fba9a8cc1353708e3ebb26f67c8fa9d332a05 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 12 May 2018 19:02:39 +0300 Subject: [PATCH 09/16] Fix jumping encumbrance calculation --- apps/openmw/mwclass/npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index f3f763897..8e8b5c3ad 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -988,7 +988,7 @@ namespace MWClass const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); const float encumbranceTerm = gmst.fJumpEncumbranceBase->getFloat() + gmst.fJumpEncumbranceMultiplier->getFloat() * - (1.0f - Npc::getEncumbrance(ptr)/Npc::getCapacity(ptr)); + (1.0f - Npc::getNormalizedEncumbrance(ptr)); float a = static_cast(npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified()); float b = 0.0f; From ba077e72910c59a04e1945108769060c9e3ad2f9 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 12 May 2018 19:06:18 +0300 Subject: [PATCH 10/16] Fix movement fatigue loss encumbrance calculation (fixes #4413) --- apps/openmw/mwmechanics/character.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2ef5e07d7..68dc17915 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1751,8 +1751,7 @@ void CharacterController::update(float duration) if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr)) { - const float encumbrance = cls.getEncumbrance(mPtr) / cls.getCapacity(mPtr); - + const float encumbrance = cls.getNormalizedEncumbrance(mPtr); if (sneak) fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult; else From 409d466e42cfa87eb47a2d3526438112e3fe2f5a Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 12 May 2018 20:50:18 +0300 Subject: [PATCH 11/16] Make 0/0 encumbrance 0% encumbrance --- apps/openmw/mwworld/class.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index e59dde7b1..5425c2bd3 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -462,10 +462,15 @@ namespace MWWorld float Class::getNormalizedEncumbrance(const Ptr &ptr) const { float capacity = getCapacity(ptr); + float encumbrance = getEncumbrance(ptr); + + if (encumbrance == 0) + return 0.f; + if (capacity == 0) return 1.f; - return getEncumbrance(ptr) / capacity; + return encumbrance / capacity; } std::string Class::getSound(const MWWorld::ConstPtr&) const From f8655d2425a8d8a86b8c916b00f5327b1c816228 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 14 May 2018 20:38:53 +0400 Subject: [PATCH 12/16] Use actor's physics position as a ray origin in tracer --- apps/openmw/mwphysics/physicssystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 1cff6b522..412e5e5b3 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -249,7 +249,7 @@ namespace MWPhysics // Check if we actually found a valid spawn point (use an infinitely thin ray this time). // Required for some broken door destinations in Morrowind.esm, where the spawn point // intersects with other geometry if the actor's base is taken into account - btVector3 from = toBullet(position); + btVector3 from = toBullet(position + offset); btVector3 to = from - btVector3(0,0,maxHeight); btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); From 3b86f73ae71eb6129c3f7209e01f6ad2c2f725fb Mon Sep 17 00:00:00 2001 From: tri4ng1e Date: Thu, 17 May 2018 21:04:40 +0300 Subject: [PATCH 13/16] Replace MwIniImporter::numberToString with std::to_string --- apps/mwiniimporter/importer.cpp | 10 ++-------- apps/mwiniimporter/importer.hpp | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 3601f0e9b..3433fe5c7 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -654,12 +654,6 @@ void MwIniImporter::setVerbose(bool verbose) { mVerbose = verbose; } -std::string MwIniImporter::numberToString(int n) { - std::stringstream str; - str << n; - return str.str(); -} - MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::path& filename) const { std::cout << "load ini file: " << filename << std::endl; @@ -801,7 +795,7 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con multistrmap::const_iterator it = ini.begin(); for(int i=0; it != ini.end(); i++) { archive = baseArchive; - archive.append(this->numberToString(i)); + archive.append(std::to_string(i)); it = ini.find(archive); if(it == ini.end()) { @@ -892,7 +886,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co for (int i=0; it != ini.end(); i++) { gameFile = baseGameFile; - gameFile.append(this->numberToString(i)); + gameFile.append(std::to_string(i)); it = ini.find(gameFile); if(it == ini.end()) diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index e1595ad96..d99866533 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -35,7 +35,6 @@ class MwIniImporter { static std::vector::iterator findString(std::vector& source, const std::string& string); static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value); - static std::string numberToString(int n); /// \return file's "last modified time", used in original MW to determine plug-in load order static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime); From 7e03dd0f1247f152e10bf6e09ec7478b2972da00 Mon Sep 17 00:00:00 2001 From: tri4ng1e Date: Thu, 17 May 2018 21:07:20 +0300 Subject: [PATCH 14/16] Read data paths from `data-local` section too --- apps/mwiniimporter/importer.cpp | 27 ++++++++++++++++----------- apps/mwiniimporter/importer.hpp | 1 + 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 3433fe5c7..6f5c2e2cd 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -859,6 +859,17 @@ std::vector::iterator MwIniImporter::findString(std::vector& output, std::vector input) { + for (auto& path : input) { + if (path.front() == '"') + { + path.erase(path.begin()); + path.erase(path.end() - 1); + } + output.emplace_back(path); + } +} + void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const { std::vector> contentFiles; @@ -869,17 +880,11 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co std::vector dataPaths; if (cfg.count("data")) - { - for (std::string filePathString : cfg["data"]) - { - if (filePathString.front() == '"') - { - filePathString.erase(filePathString.begin()); - filePathString.erase(filePathString.end() - 1); - } - dataPaths.emplace_back(filePathString); - } - } + addPaths(dataPaths, cfg["data"]); + + if (cfg.count("data-local")) + addPaths(dataPaths, cfg["data-local"]); + dataPaths.push_back(iniFilename.parent_path() /= "Data Files"); multistrmap::const_iterator it = ini.begin(); diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index d99866533..7b710a4a4 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -35,6 +35,7 @@ class MwIniImporter { static std::vector::iterator findString(std::vector& source, const std::string& string); static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value); + static void addPaths(std::vector& output, std::vector input); /// \return file's "last modified time", used in original MW to determine plug-in load order static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime); From f2613a74b1fb329cd66f1ffdbb47a6e102d378ec Mon Sep 17 00:00:00 2001 From: tri4ng1e Date: Thu, 17 May 2018 21:20:04 +0300 Subject: [PATCH 15/16] Write settings before invoking openmw-iniimporter --- apps/wizard/mainwizard.cpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 0f8fb0c49..57d080cf8 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -231,29 +231,13 @@ void Wizard::MainWizard::setupInstallations() void Wizard::MainWizard::runSettingsImporter() { + writeSettings(); + QString path(field(QLatin1String("installation.path")).toString()); - // Create the file if it doesn't already exist, else the importer will fail QString userPath(toQString(mCfgMgr.getUserConfigPath())); QFile file(userPath + QLatin1String("openmw.cfg")); - if (!file.exists()) { - if (!file.open(QIODevice::ReadWrite)) { - // File cannot be created - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Error writing OpenMW configuration file")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("

Could not open or create %1 for writing

\ -

Please make sure you have the right permissions \ - and try again.

").arg(file.fileName())); - msgBox.exec(); - return qApp->quit(); - } - - file.close(); - } - // Construct the arguments to run the importer QStringList arguments; From 867a5938caffc9e0ad8a7f6a2ef7fda195a8e82a Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 20 May 2018 10:24:20 +0300 Subject: [PATCH 16/16] Don't reset sneaking camera offset while in GUI (fixes #4420) --- apps/openmw/mwworld/worldimp.cpp | 6 +++--- apps/openmw/mwworld/worldimp.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 13bfc32b3..6cf1ead87 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1621,7 +1621,7 @@ namespace MWWorld if (!paused) doPhysics (duration); - updatePlayer(paused); + updatePlayer(); mPhysics->debugDraw(); @@ -1637,7 +1637,7 @@ namespace MWWorld } } - void World::updatePlayer(bool paused) + void World::updatePlayer() { MWWorld::Ptr player = getPlayerPtr(); @@ -1670,7 +1670,7 @@ namespace MWWorld bool swimming = isSwimming(player); static const float i1stPersonSneakDelta = getStore().get().find("i1stPersonSneakDelta")->getFloat(); - if(!paused && sneaking && !(swimming || inair)) + if (sneaking && !(swimming || inair)) mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); else mRendering->getCamera()->setSneakOffset(0.f); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 40a22af5d..0d168c912 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -129,7 +129,7 @@ namespace MWWorld Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); - void updatePlayer(bool paused); + void updatePlayer(); void preloadSpells();