From da4c55d5adf756ca7cc49a4a80a214f7347105bb Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Sat, 23 Jun 2018 17:51:32 +1000 Subject: [PATCH 01/40] prevent segfalt in QuickKeysMenu when item has been removed from player inventory added a MWWorld::ContainerStore to hold item copies which are then used to find real items with findReplacement(). (storing the RefId could be a better solution but would probably leave tooltips broken...) --- apps/openmw/mwgui/quickkeysmenu.cpp | 147 ++++++++++++++-------------- apps/openmw/mwgui/quickkeysmenu.hpp | 3 + 2 files changed, 78 insertions(+), 72 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 08192625f..2271d2582 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -124,6 +124,13 @@ namespace MWGui void QuickKeysMenu::unassign(ItemWidget* key, int index) { + // cleanup refrance ItemContainer + if( mAssigned[index] == Type_Item || mAssigned[index] == Type_MagicItem) + { + MWWorld::Ptr refItem = *key->getUserData(); + mRefItemContainer.remove(refItem.getCellRef().getRefId(), 1, MWMechanics::getPlayer()); + } + key->clearUserStrings(); key->setItem(MWWorld::Ptr()); while (key->getChildCount()) // Destroy number label @@ -221,9 +228,11 @@ namespace MWGui mAssigned[mSelectedIndex] = Type_Item; - button->setItem(item, ItemWidget::Barter); + MWWorld::Ptr itemCopy = *mRefItemContainer.add(item, 1, MWMechanics::getPlayer()); + + button->setItem(itemCopy, ItemWidget::Barter); button->setUserString ("ToolTipType", "ItemPtr"); - button->setUserData(MWWorld::Ptr(item)); + button->setUserData(itemCopy); if (mItemSelectionDialog) mItemSelectionDialog->setVisible(false); @@ -334,29 +343,78 @@ namespace MWGui if (type == Type_Item || type == Type_MagicItem) { - MWWorld::Ptr item = *button->getUserData(); - // Make sure the item is available and is not broken - if (item.getRefData().getCount() < 1 || - (item.getClass().hasItemHealth(item) && - item.getClass().getItemHealth(item) <= 0)) + MWWorld::Ptr refItem = *button->getUserData(); + MWWorld::Ptr item = store.findReplacement(refItem.getCellRef().getRefId()); + + // check the item is available and not broken + if (!item || item.getRefData().getCount() < 1 || + (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) + { + if (!item || item.getRefData().getCount() < 1) + { + // item not in plater inventory found + MWBase::Environment::get().getWindowManager()->messageBox( + "#{sQuickMenu5} " + refItem.getClass().getName(refItem)); + + return; + } + } + + if (type == Type_Item) { - // Try searching for a compatible replacement - std::string id = item.getCellRef().getRefId(); + bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); + bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || + item.getTypeName() == typeid(ESM::Lockpick).name(); - item = store.findReplacement(id); - button->setUserData(MWWorld::Ptr(item)); + // delay weapon switching if player is busy + if (isDelayNeeded && (isWeapon || isTool)) + { + mActivatedIndex = index; + return; + } - if (item.getRefData().getCount() < 1) + // disable weapon switching if player is dead or paralyzed + if (isReturnNeeded && (isWeapon || isTool)) { - // No replacement was found - MWBase::Environment::get().getWindowManager ()->messageBox ( - "#{sQuickMenu5} " + item.getClass().getName(item)); return; } + + MWBase::Environment::get().getWindowManager()->useItem(item); + MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + // change draw state only if the item is in player's right hand + if (rightHand != store.end() && item == *rightHand) + { + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + } } - } + else if (type == Type_MagicItem) + { + // retrieve ContainerStoreIterator to the item + MWWorld::ContainerStoreIterator it = store.begin(); + for (; it != store.end(); ++it) + { + if (*it == item) + { + break; + } + } + assert(it != store.end()); + + // equip, if it can be equipped + if (!item.getClass().getEquipmentSlots(item).first.empty()) + { + MWBase::Environment::get().getWindowManager()->useItem(item); + + // make sure that item was successfully equipped + if (!store.isEquipped(item)) + return; + } - if (type == Type_Magic) + store.setSelectedEnchantItem(it); + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); + } + } + else if (type == Type_Magic) { std::string spellId = button->getUserString("Spell"); @@ -374,61 +432,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); } - else if (type == Type_Item) - { - MWWorld::Ptr item = *button->getUserData(); - bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); - bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || item.getTypeName() == typeid(ESM::Lockpick).name(); - - // delay weapon switching if player is busy - if (isDelayNeeded && (isWeapon || isTool)) - { - mActivatedIndex = index; - return; - } - - // disable weapon switching if player is dead or paralyzed - if (isReturnNeeded && (isWeapon || isTool)) - { - return; - } - - MWBase::Environment::get().getWindowManager()->useItem(item); - MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - // change draw state only if the item is in player's right hand - if (rightHand != store.end() && item == *rightHand) - { - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); - } - } - else if (type == Type_MagicItem) - { - MWWorld::Ptr item = *button->getUserData(); - - // retrieve ContainerStoreIterator to the item - MWWorld::ContainerStoreIterator it = store.begin(); - for (; it != store.end(); ++it) - { - if (*it == item) - { - break; - } - } - assert(it != store.end()); - - // equip, if it can be equipped - if (!item.getClass().getEquipmentSlots(item).first.empty()) - { - MWBase::Environment::get().getWindowManager()->useItem(item); - - // make sure that item was successfully equipped - if (!store.isEquipped(item)) - return; - } - - store.setSelectedEnchantItem(it); - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); - } else if (type == Type_HandToHand) { store.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, player); diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index b5bc60b19..5e2305df8 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -2,6 +2,7 @@ #define MWGUI_QUICKKEYS_H #include "../mwworld/ptr.hpp" +#include "../mwworld/containerstore.hpp" #include "windowbase.hpp" @@ -58,6 +59,8 @@ namespace MWGui MyGUI::EditBox* mInstructionLabel; MyGUI::Button* mOkButton; + MWWorld::ContainerStore mRefItemContainer; + std::vector mQuickKeyButtons; std::vector mAssigned; From 46c6abcf54f8e969e784089c0bd8ec99e9e58afa Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Mon, 25 Jun 2018 16:02:28 +1000 Subject: [PATCH 02/40] add string vectors for name/id in QuickKeysMenu for item lookups --- apps/openmw/mwgui/quickkeysmenu.cpp | 41 ++++++++++++++++------------- apps/openmw/mwgui/quickkeysmenu.hpp | 2 ++ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 2271d2582..7d3e2cbc7 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -60,6 +60,9 @@ namespace MWGui mAssigned.push_back(Type_Unassigned); + mAssignedId.push_back(std::string("")); + mAssignedName.push_back(std::string("")); + unassign(button, i); } } @@ -131,6 +134,9 @@ namespace MWGui mRefItemContainer.remove(refItem.getCellRef().getRefId(), 1, MWMechanics::getPlayer()); } + mAssignedName[index] = ""; + mAssignedId[index] = ""; + key->clearUserStrings(); key->setItem(MWWorld::Ptr()); while (key->getChildCount()) // Destroy number label @@ -227,12 +233,14 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); mAssigned[mSelectedIndex] = Type_Item; + mAssignedId[mSelectedIndex] = item.getCellRef().getRefId(); + mAssignedName[mSelectedIndex] = item.getClass().getName(item); - MWWorld::Ptr itemCopy = *mRefItemContainer.add(item, 1, MWMechanics::getPlayer()); + MWWorld::Ptr refItem = *mRefItemContainer.add(item, 1, MWMechanics::getPlayer()); - button->setItem(itemCopy, ItemWidget::Barter); + button->setItem(refItem, ItemWidget::Barter); button->setUserString ("ToolTipType", "ItemPtr"); - button->setUserData(itemCopy); + button->setUserData(item); if (mItemSelectionDialog) mItemSelectionDialog->setVisible(false); @@ -343,18 +351,26 @@ namespace MWGui if (type == Type_Item || type == Type_MagicItem) { - MWWorld::Ptr refItem = *button->getUserData(); - MWWorld::Ptr item = store.findReplacement(refItem.getCellRef().getRefId()); + MWWorld::Ptr item = *button->getUserData(); + + MWWorld::ContainerStoreIterator it = store.begin(); + for (; it != store.end(); ++it) + { + if (*it == item) + break; + } + if (it == store.end()) + item = NULL; // check the item is available and not broken if (!item || item.getRefData().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { + item = store.findReplacement(mAssignedId[index-1]); if (!item || item.getRefData().getCount() < 1) { - // item not in plater inventory found MWBase::Environment::get().getWindowManager()->messageBox( - "#{sQuickMenu5} " + refItem.getClass().getName(refItem)); + "#{sQuickMenu5} " + mAssignedName[index-1]); return; } @@ -389,17 +405,6 @@ namespace MWGui } else if (type == Type_MagicItem) { - // retrieve ContainerStoreIterator to the item - MWWorld::ContainerStoreIterator it = store.begin(); - for (; it != store.end(); ++it) - { - if (*it == item) - { - break; - } - } - assert(it != store.end()); - // equip, if it can be equipped if (!item.getClass().getEquipmentSlots(item).first.empty()) { diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index 5e2305df8..8df0ae239 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -60,6 +60,8 @@ namespace MWGui MyGUI::Button* mOkButton; MWWorld::ContainerStore mRefItemContainer; + std::vector mAssignedId; + std::vector mAssignedName; std::vector mQuickKeyButtons; std::vector mAssigned; From 186ec8c50fa205ae731031934589091d3f96c3b0 Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Tue, 26 Jun 2018 13:35:04 +1000 Subject: [PATCH 03/40] rm ContainerStore/refItem --- apps/openmw/mwgui/quickkeysmenu.cpp | 11 +---------- apps/openmw/mwgui/quickkeysmenu.hpp | 6 ++---- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 7d3e2cbc7..e19df2bbb 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -127,13 +127,6 @@ namespace MWGui void QuickKeysMenu::unassign(ItemWidget* key, int index) { - // cleanup refrance ItemContainer - if( mAssigned[index] == Type_Item || mAssigned[index] == Type_MagicItem) - { - MWWorld::Ptr refItem = *key->getUserData(); - mRefItemContainer.remove(refItem.getCellRef().getRefId(), 1, MWMechanics::getPlayer()); - } - mAssignedName[index] = ""; mAssignedId[index] = ""; @@ -236,9 +229,7 @@ namespace MWGui mAssignedId[mSelectedIndex] = item.getCellRef().getRefId(); mAssignedName[mSelectedIndex] = item.getClass().getName(item); - MWWorld::Ptr refItem = *mRefItemContainer.add(item, 1, MWMechanics::getPlayer()); - - button->setItem(refItem, ItemWidget::Barter); + button->setItem(item, ItemWidget::Barter); button->setUserString ("ToolTipType", "ItemPtr"); button->setUserData(item); diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index 8df0ae239..29506ab58 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -59,12 +59,10 @@ namespace MWGui MyGUI::EditBox* mInstructionLabel; MyGUI::Button* mOkButton; - MWWorld::ContainerStore mRefItemContainer; - std::vector mAssignedId; - std::vector mAssignedName; - std::vector mQuickKeyButtons; std::vector mAssigned; + std::vector mAssignedId; + std::vector mAssignedName; QuickKeysMenuAssign* mAssignDialog; ItemSelectionDialog* mItemSelectionDialog; From 43c9fd4cec3e9c060ca524a048bf63ba8f0f1021 Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Tue, 26 Jun 2018 13:41:53 +1000 Subject: [PATCH 04/40] check MWWorld::Ptr != NULL for MWGui ItemPtr tooltips --- apps/openmw/mwgui/tooltips.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index b2991a034..20814aac5 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -191,6 +191,9 @@ namespace MWGui else if (type == "ItemPtr") { mFocusObject = *focus->getUserData(); + if (!mFocusObject) + return; + tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false, checkOwned()); } else if (type == "ItemModelIndex") From 8cda355af6c90cddfcd87f7693a9e230a420d146 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 27 Jun 2018 12:14:34 +0200 Subject: [PATCH 05/40] last minute changes to design doc --- docs/openmw-stage1.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/openmw-stage1.md b/docs/openmw-stage1.md index 42fe8a2aa..b082759bf 100644 --- a/docs/openmw-stage1.md +++ b/docs/openmw-stage1.md @@ -231,6 +231,8 @@ Therefore we will develop our own cross-platform package format for OpenMW conte # Scripting +*Important: The following is a draft for a scripting language titles oldscript+ in the forum discussions. We will not go down this implementation route. But this section is still relevant because newscript has to cover all functionality listed here.* + Note: Extensions to the scripting language in form of new instructions and functions are distributed over this entire document. In some cases features may require additional functions that return the value of certain fields of certain records. These functions are usually not listed explicitly and are left as an exercise to the reader. ## Language Version @@ -1159,11 +1161,11 @@ All effects described in these sub-records affect active cells only. ### Sky -TODO all existing sky effects and possibly new ones; bugger scrawl to fill in the details +TODO all existing sky effects and possibly new ones ### Particles -TODO all existing particle effects and possibly new ones; bugger scrawl to fill in the details +TODO all existing particle effects and possibly new ones ### Event @@ -1210,7 +1212,7 @@ A liquid record consists of the following fields: We need to support at least one liquid type (ID 0: Water) from the start. Other types can be added. -TODO check with scrawl about other ways to handle visuals for liquid types that requires less hard-coding. Shaders? +TODO check about other ways to handle visuals for liquid types that requires less hard-coding. Shaders? ## Magic Effects @@ -2112,7 +2114,7 @@ If the GMST is not 0 we show skill progression in two places: # Graphics -TODO bugger scrawl until he agrees to fill in this section +TODO Random collection of ideas that may be feasible or not: From 8bc6c853963ac2b837f3f7a869885a115c1e2423 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 27 Jun 2018 12:24:21 +0200 Subject: [PATCH 06/40] last minute changes (this time for real; forgot to merge in the last update from the private repo) --- docs/openmw-stage1.md | 1046 +++++++++++++++++++++-------------------- 1 file changed, 538 insertions(+), 508 deletions(-) diff --git a/docs/openmw-stage1.md b/docs/openmw-stage1.md index b082759bf..9458b66bb 100644 --- a/docs/openmw-stage1.md +++ b/docs/openmw-stage1.md @@ -10,7 +10,7 @@ At the same time we want to stay true to our origins. While we want to broaden t Our goal here is to make OpenMW into a general purpose engine, but a general purpose 1st/3rd person real-time RPG engine. We do not attempt to support other genres or other flavours of RPG. -The development of OpenMW will hopefully continue for a long time and we can not reasonable hope to sketch out its entire future development in a single design document. Therefore this document should be seen as stage 1 of the post 1.0 development only. It may last us 6 months or a year or several years, depending on how much development activity we can achieve. But eventually there will be a stage 2 design document. +The development of OpenMW will hopefully continue for a long time and we can not reasonably hope to sketch out its entire future development in a single design document. Therefore this document should be seen as stage 1 of the post 1.0 development only. It may last us 6 months or a year or several years, depending on how much development activity we can achieve. But eventually there will be a stage 2 design document. # Definitions @@ -68,7 +68,7 @@ However this approach means additional work and would be of limited usefulness i We will continue with the revised roadmap scheme. This means we add a new milestone (openmw-stage1). After we are finished discussing this design document and have made necessary adjustments we will cut it up into individual tasks and add them to openmw-stage1. This milestone will then take the role of the current openmw-1.0 and openmw-cs-1.0 milestones. Confirmed bug reports also go into openmw-stage1. Other issues (feature requests and tasks) only after we have approved them for near future development. -We will most like not have a separate openmw-stage1 for the editor, since for the bulk of the changes (The Grand Dehardcoding) most tasks we will require changes to both OpenMW and the editor. +We will most like not have a separate openmw-stage1 for the editor, since for the bulk of the changes (The Grand De-hardcoding) most tasks we will require changes to both OpenMW and the editor. # Content File Format & Namespaces @@ -92,7 +92,7 @@ The following cases of changes to the record format have been identified: * Existing record that is switched over from integer-based to ID-based indexing (this is a special case of the case above) * Existing records is split into two records: Split needs to be performed on load. -There have been some concerns regarding the efficiency of string based IDs (vs integer). While there hasn't been an evidence indicating that this is actually a problem in OpenMW, it is important to point out that the move from integer to string does not mean that such a problem could not be addressed. +There have been some concerns regarding the efficiency of string based IDs (vs. integer). While there hasn't been an evidence indicating that this is actually a problem in OpenMW, it is important to point out that the move from integer to string does not mean that such a problem could not be addressed. We already have plans to introduce a new string type for IDs, which deals with the case-issue (current std::string based implementation is error-prone and results in some very verbose code). @@ -202,13 +202,13 @@ Most of the current content exists in a form that is not compatible with the new In most cases this can easily be resolved by adding an importer tool to the launcher, that takes the content and copies it into the desired organisation scheme (archive or directory). -For other content (preexisting data directories and Morrowind, since Morrowind and its add-ons are installed into the same directory) we need to maintain a level of compatibility with the old approach, that can be switched on and off as needed. +For other content (pre-existing data directories and Morrowind, since Morrowind and its add-ons are installed into the same directory) we need to maintain a level of compatibility with the old approach, that can be switched on and off as needed. ### Resources Packages -During the discussion about the new content file organisation scheme (omwgame, omwaddon) there was some criticism about the scheme not allowing plugins with no dependencies. +During the discussion about the new content file organisation scheme (omwgame, omwaddon) there was some criticism about the scheme not allowing plug-ins with no dependencies. -This is not a problem, because such plugins are generally useless. A plugin without any dependency can not modify or reference any record. That was not such a large issue with vanilla Morrowind, because in this case the plugin could have still have depended on things like attributes or dynamic stats. However in OpenMW these will all turn into records and therefore can not be accessed from within an omwaddon file without dependencies. This will only get more extreme as the de-hardcoding progresses further. An omwaddon file without dependencies can do literally nothing, which makes it useless by definition. +This is not a problem, because such plug-ins are generally useless. A plug-in without any dependency can not modify or reference any record. That was not such a large issue with vanilla Morrowind, because in this case the plug-in could have still have depended on things like attributes or dynamic stats. However in OpenMW these will all turn into records and therefore can not be accessed from within an omwaddon file without dependencies. This will only get more extreme as the de-hardcoding progresses further. An omwaddon file without dependencies can do literally nothing, which makes it useless by definition. But there is one exception. Some members of the mod community have provided resources packages (meshes and similar) that can be used by other content developers. Under our new resources scheme these would have to be accompanied by an omwaddon file. This is not a good solution because of two reasons: @@ -231,40 +231,38 @@ Therefore we will develop our own cross-platform package format for OpenMW conte # Scripting -*Important: The following is a draft for a scripting language titles oldscript+ in the forum discussions. We will not go down this implementation route. But this section is still relevant because newscript has to cover all functionality listed here.* - -Note: Extensions to the scripting language in form of new instructions and functions are distributed over this entire document. In some cases features may require additional functions that return the value of certain fields of certain records. These functions are usually not listed explicitly and are left as an exercise to the reader. +**Note**: Extensions to the scripting language in form of new instructions and functions are distributed over this entire document. In some cases, features may require additional functions that return the value of certain fields of certain records. These functions are usually not listed explicitly and are left as an exercise to the reader. ## Language Version -The version of the language used in a script is determined by a single integer number which is stored in a new sub-record within the script record. Note that this is different from earlier concepts which used a more complicated version identifier and stored it within the actual script. +The version of the language used in a script is determined by a single integer number which is stored in a new subrecord within the script record. Note that this is different from earlier concepts, which used a more complicated version identifier and stored it within the actual script. -Scripts that are missing this sub-record are considered version 0 (legacy). New scripts (when created in OpenMW-CS) default to the most up-to-date version. The version can be selected from a combo box in the script sub-view. We may add a user setting that hides the combo box UI if the version is the most up to date one. +Scripts that are missing this subrecord are considered version 0 (legacy). New scripts (when created in OpenMW-CS) default to the most up-to-date version. The version can be selected from a combo box in the script subview. We may add a user setting that hides the combo box UI if the version is the most up to date one. The version number is incremented whenever an addition to the scripting language is made (at most once per feature release). A new feature release is not required to increment the version number, if no feature changes have been made to the language in this release. -From version 1 on all workarounds for bad scripts will be disabled. This should not break anything for well written scripts. In general moving to a new language version should usually at most require minor fixes. +From version 1 on, all workarounds for bad scripts will be disabled. This should not break anything for well-written scripts. In general, moving to a new language version should usually at most require minor fixes. Since old scripts will remain with the old language version (unless there is a need to modify them and use newer features), this scheme will not break compatibility for existing content. ## Error Handling -We will continue with the existing runtime error handling scheme (meaning stopping the script execution on a runtime error). Introducing exception handling instead would be overkill. +We will continue with the existing runtime error handling scheme (i.e., stopping the script execution on a runtime error). Introducing exception handling instead would be overkill. -We will do our best to avoid crashes as result of broken script. That means (among others) that we need to put an artificial (configurable) limit on function call depth to avoid crashes and lockups from endless script recursion. We may also decide to put a similar limit on loops. +We will do our best to avoid crashes as a result of broken scripts. That means (among others) that we need to put an artificial (configurable) limit on function call depth to avoid crashes and lock-ups from endless script recursion. We may also decide to put a similar limit on loops. ## Variable Types -The types *long* and *float* remain unchanged. +The types `long` and `float` remain unchanged. -We will flag *short* as deprecated. Currently *short* has no advantage over *long* (it uses the same amount of memory in OpenMW). For anything but a very specific kind of bit cutting the *short* type has no use at all. -If we ever decide that we do want a shorter integer type we can always de-deprecate *short*. +We will flag `short` as deprecated. Currently `short` has no advantage over `long` (it uses the same amount of memory in OpenMW). For anything but a very specific kind of bit-cutting the `short` type has no use at all. +If we ever decide that we do want a shorter integer type we can always de-deprecate `short`. We will introduce several new types: ### Strings -The name of the new string type is **String** and string literals are marked by placing them in quotation marks. Example: +The name of the new string type is `String`, and string literals are marked by placing them in quotation marks. Example: ```` String a @@ -273,7 +271,7 @@ Set a to "Some Text" Comparison operators for strings are provided in the usual way. -Since we discontinue bad script workarounds most ambiguousness regarding string literals should have been removed already (we will not accept local variable names and instructions within quotation marks anymore). Only a single problem case remains: +Because we are discontinuing bad script workarounds, most ambiguity regarding string literals should have been removed already (we will not accept local variable names and instructions within quotation marks any more). Only a single problem case remains: If a function requires an ID, is it a literal ID or is it the name of a string variable that contains an ID? Example: @@ -283,13 +281,13 @@ Set Fargoth to "player" Fargoth -> AddItem "Gold_001", 100 ```` -We can not make a rule that forbids the use of local variable names that are also IDs, because that would allow random content files to break scripts in other, unrelated content files. +We cannot make a rule that forbids the use of local variable names that are also IDs, because that would allow random content files to break scripts in other, unrelated content files. -Therefore the solution here is to simply give the local variable precedence over IDs. If the script author wanted to give money to Fargoth he should not have created a local variable with this name. +Therefore, the solution here is to simply give the local variable precedence over the ID. If the script author intended to give money to `Fargoth`, he should not have created a local variable with the same name. Note that global variables are not an issue here, because global variables are IDs and IDs need to be unique (with certain exceptions that are not relevant here). -To remove even the last remaining potential problems with ambiguity we will also introduce a new string literal that can only be a literal and never a string variable name. These literals are marked by a L prefix. Utilising this feature the code block above could be rewritten to the following if the script author absolutely insists on having a local variable named Fargoth: +To remove even the last remaining potential problems with ambiguity, we will also introduce a new string literal that can only be a literal and never a string variable name. These literals are preceded by an `L` character. Utilising this feature, the aforementioned code block could be rewritten as is shown below, if the script author absolutely insists on having a local variable named `Fargoth`: ```` String Fargoth @@ -297,93 +295,93 @@ Set Fargoth to "player" L"Fargoth" -> AddItem "Gold_001", 100 ```` -### Instance-References +### Instance References -The name of the new reference type is **Ref**. It can reference an instance in the world or in a container or be a null reference. +The name of the new reference type is `Ref`. It can reference an instance in the world or in a container or be a null reference. -Since we currently have no way to reference an instance persistently the use of the ref type is limited for the time being (see section about variable scope for further details). +Since we currently have no way to reference an instance persistently, the use of the `Ref` type is limited for the time being (see section *Variable Scope* for further details). -References can be used in any place that would otherwise allow an ID that stand for an existing reference. The rules for strings regarding ambiguousness apply to references in the same way. +References can be used in any place that would otherwise allow an ID that stand for an existing reference. The rules for strings regarding ambiguity apply to references in the same way. A reference will implicitly cast to 0 (a null reference) or 1 (not a null reference) when used in a numeric expression. -Note: A reference pointing into the contents of a container points to a stack of items and not a single item. +**Note**: A reference pointing to the contents of a container points to a stack of items and not a single item. We introduce the following keywords: -* Self: a value of type Ref (the instance the script is currently running on, only available in local scripts and dialogue scripts) -* NullRef: a literal of type Ref (null reference) -* GetId (r): return ID of reference as a string value -* GetContainer (r): returns the reference of the container r is in (or a null-reference if r is not in a container) -* GetCell (r): returns the ID string of the cell r is in (either directly or via a container) -* SearchActive (id, container=0): return a reference with the given ID within the active cells. If container!=0, also check in containers; return a null-reference if no reference is found -* SearchIn (id, id2): return a reference with the given ID within something (id2) that can contain references; this can be a cell, a worldspace, a container, a creature or a NPC. If id2 represents an instance, a ref variable can be given instead; return a null-reference if no reference is found +* `Self`: A value of type `Ref` (the instance the script is currently running on, only available in local scripts and dialogue scripts) +* `NullRef`: A literal of type `Ref` (null reference) +* `GetId(r)`: Returns the ID of the reference `r` as a string value +* ``GetContainer(r)`: Returns the reference of the container `r` is in (or a null reference if `r` is not in a container) +* `GetCell(r)`: Returns the ID string of the cell `r` is in (either directly or via a container) +* `SearchActive(id, container = 0)`: Returns a reference with the given ID within the active cells. If `container != 0`, also check in containers; returns a null reference if no reference is found +* `SearchIn(id, id2)`: Returns a reference with the given ID within something (`id2`) that can contain references; this can be a cell, a worldspace, a container, a creature or an NPC. If `id2` represents an instance, a `Ref` variable can be given instead; returns a null reference if no reference is found ### Lists -A single type list available for the following types: +A single-type list will be available for the following types: -* Long -* Float -* String -* Ref +* `long` +* `float` +* `String` +* `Ref` -We will not support mixed type list because that would require a major change to the type system. Internally and functionally a list will work like an array. The type names for lists are: +We will not support mixed-type lists because that would require a major change to the type system. Internally and functionally, a list will work like an array. The type names for lists are: -* LongList -* FloatList -* StringList -* RefList +* `LongList` +* `FloatList` +* `StringList` +* `RefList` -List literals are given as a comma separated list of values in square brackets. Empty list literals are allowed. In the context of lists we generally allow implicit promotion of integer to float types. +List literals are given as a comma-separated list of values in square brackets. Empty list literals are allowed. In the context of lists, we generally allow implicit promotion of integer to float types. We support the following list operations: -* l1[i]: Return member i (indexing begins at 0) -* GetSize(l1): Returns the number of elements in l1 as an *Integer* -* l1 + l2: Returns a concatenated list consisting of the members of l1 followed by the members of l2 -* GetSubset (l1, l2): Return the common subset of the elements of l1 and the elements of l2 -* HasElement (l1, e): Return 0 (if e is not element of l1) or 1 (otherwise) -* GetIndex (l1, e): Return index of first occurrence of element e in l1 (or -1 if none) -* Minimum (l1): Return the minimum of the elements in l1 (only works with numerical types) -* Maximum (l1): Return the minimum of the elements in l1 (only works with numerical types) +* `l1[i]`: Returns member `i` (indexing begins at 0) +* `GetSize(l1)`: Returns the number of elements in `l1` as an integer +* `l1 + l2`: Returns a concatenated list consisting of the members of `l1` followed by the members of `l2` +* `GetSubset(l1, l2)`: Returns the common subset of the elements of `l1` and `l2` +* `HasElement(l1, e)`: Returns 0 (if `e` is not element of `l1`) or 1 (otherwise) +* `GetIndex(l1, e)`: Returns the index of the first occurrence of `e` in `l1` (or -1 if none) +* `Minimum(l1)`: Returns the minimum of the elements in `l1` (only works with numerical types) +* `Maximum(l1)`: Returns the maximum of the elements in `l1` (only works with numerical types) and the following list instructions: -* Set l1[i] To e: Set list element i of list l1 to value e (indexing begins at 0) -* Sort (l1, cf=""): Sort list elements into ascending order (if cf is an empty string) or via comparison function script cf -* Filter (l1, ff): Filter list l1 by filter function ff (only keep elements that do not return 0) -* Reverse (l1): Reverse order of list elements -* Append (l1, e): Append element e to list l1 -* Append (l1, l2): Append elements of list l2 to list l1 -* Remove (l1, e): Remove all elements equal to e from list l1 -* Remove (l1, l2): Remove from list l1 all elements which are also in list l2 -* InsertAt (l1, e, i): Insert element at position i into list l1 (replacing element i on position upwards and extending the size of the list by one) -* RemoveAt (l1, i): Remove element at position i from list l1 (moving elements with an index higher than i down by one and reducing the size of the list by one) -* Resize (l1, n): Set size of l1 to n. If list is extended additional elements are default initialised. +* `Set l1[i] To e`: Sets list element `i` of list `l1` to value `e` (indexing begins at 0) +* `Sort(l1, cf="")`: Sorts list elements into ascending order (if `cf` is an empty string) or via comparison function script `cf` +* `Filter(l1, ff)`: Filters list `l1` by filter function `ff` (only keeps elements that do not return 0) +* `Reverse(l1)`: Reverses order of list elements of list `l1` +* `Append(l1, e)`: Appends element `e` to list `l1` +* `Append(l1, l2)`: Appends elements of list `l2` to list `l1` +* `Remove(l1, e)`: Removes all elements equal to `e` from list `l1` +* `Remove(l1, l2)`: Removes all elements from list `l1` which are also in list `l2` +* `InsertAt(l1, e, i)`: Inserts element `e` into list `l1` at position `i` (moving the element at index `i` one position upwards and extending the size of the list by one) +* `RemoveAt(l1, i)`: Removes the element at position `i` from list `l1` (moving elements with an index higher than `i` down by one and reducing the size of the list by one) +* `Resize(l1, n)`: Set size of list `l1` to `n`; if the list is extended, additional elements are initialised with default values Variable names used in the lists above: -* l1 and l2 are lists/list literals of the same element type -* i is an integer value that functions as list index -* n is an integer value -* e is a variable/literal of the element type of l1 and l2 -* cf: ID of a comparison function script that takes two element type arguments and returns an integer (-1, 0, 1) -* ff: Id of filter function script that takes one element type arguments and returns an integer (0, 1) +* `l1` and `l2` are lists / list literals of the same element type +* `i` is an integer value and is used as list index +* `n` is an integer value +* `e` is a variable/literal of the element type of `l1` and `l2` +* `cf` is the ID of a comparison function script that takes two element-type arguments and returns an integer (-1, 0, 1) +* `ff` is the ID of a filter function script that takes one element-type argument and returns an integer (0, 1) ### List Aliases We define three more type aliases: -* Vector3: A float list of 3 members, used for location or rotation -* Vector4: A float list of 4 members (location and zrot) -* Vector6: A float list of 6 members (location and rotation) +* `Vector3`: A `FloatList` of 3 members, used for location or rotation +* `Vector4`: A `FloatList` of 4 members (location and rotation around z-axis) +* `Vector6`: A `FloatList` of 6 members (location and rotation) These aliases are not separate types. -In the specification of additional functions and instructions values of these types are specified as v3, v4 and v6 respectively. Passing in shorter lists is allowed and will set the missing elements to 0. +In the specification of additional functions and instructions, values of these types are specified as `v3`, `v4`, and `v6` respectively. Passing shorter lists is allowed and will set the missing elements to 0. -Note that all rotations are specified in degrees. +**Note**: All rotations are specified in degrees. ## Variable Scope @@ -393,32 +391,32 @@ The global and the local scope remain unchanged. We will add two more scopes: ### True Local Variables -What the Morrowind scripting language describes as local variables dose not match the concept of local variables in other languages. In case of local scripts Morrowind local variables function as member variables. In case of global scripts Morrowind local variables function as static variables. +What the Morrowind scripting language describes as local variables does not match the concept of local variables in other languages. In case of local scripts, Morrowind local variables function as member variables. In case of global scripts, Morrowind local variables function as static variables. -There is a need for true local variables: +There are several reasons for the introduction of true local variables: * Less pollution of local dialogue variable names * Less state that needs to be saved -* Avoidance of potential scripting errors when the state of a local variable is maintained across multiple script executions and the script does not re-initialise the variable at the beginning of a new run +* Prevention of potential scripting errors when the state of a local variable is maintained across multiple script executions and the script does not re-initialise the variable at the beginning of a new run We will call this scope **true local variable** to distinguish it from old local variables. Declaration of true local variables will look like this: ```` -Local Long a = 1 -Local Long b +Local long a = 1 +Local long b ```` -If no default value is given the variable is default initialised (value 0, empty string, empty list, null reference). +If no default value is given, the variable is default-initialised (value 0, empty string, empty list, null reference). ### Record Variables -A problem (especially relevant for large projects with complex functionality) is that there is no place other then global variables to store additional state that is not specific to an instance. A global function with multiple variables can be used instead but that is just a workaround. +A common problem (especially relevant for large projects with complex functionality) is that there is no place other than global variables to store additional state that is not specific to an instance. A global function with multiple variables can be used instead, but that is just a workaround. -Furthermore there is currently no way to add new state to other objects (object in the OOP sense, not in the Morrowind sense) other then instances. +Furthermore, there is currently no way to add new state to other objects (object in the OOP sense, not in the Morrowind sense) other than instances. -Therefore we introduce record variables which are a generalisation of old local variables in local scripts. +Therefore, we introduce record variables, which are a generalisation of old local variables in local scripts. The respective records will be enhanced by an optional list of subrecords that can declare record variables and their default values. @@ -430,33 +428,33 @@ Obvious candidates for this feature are: * Worldspaces * Factions -Note: Object record variables are essentially the same as old local variables declared in a local script, but declared in the object record instead of the script. +**Note**: Object record variables are essentially the same as old local variables declared in a local script - only that they are declared in an object record instead. We will introduce the following record variable functions: -* Has{Type} (vn): Return 1 if record/reference has a long/float/string/list record variable of name vn, 0 otherwise. -* Get{Type} (vn): Return value of record/reference variable vn. Note that we don't use the x.y syntax here, because we need to explicitly specify the type to allow for proper compile time error checking for reference variables. +* `Has{Type}(vn)`: Returns 1 if record/reference has a long/float/string/list record variable of name `vn`, 0 otherwise +* `Get{Type}(vn)`: Returns the value of record/reference variable `vn`. Note that we don't use the x.y syntax here, because we need to explicitly specify the type to allow proper compile-time error checking for reference variables. -and the following record variable instruction: +and the following record variable instructions: -* Set{Type} (vn, val): Set variable vn to value val. It is an error, if this variable does not exist. +* `Set{Type}(vn, val)`: Sets variable `vn` to value `val`. It is considered an error if this variable does not exist. -With all functions and instructions the record/reference is specified via the -> operator in the usual way. When using object records (via ID) the first instance found is used instead of the object record. +Within all functions and instructions, the record/reference is specified via the `->` operator in the usual way. When using object records (via ID), the first instance found is used instead of the object record. Variable names used in the lists above: -* id: string -* vn: string -* ref: reference -* val: value according to {Type} +* `id` is a `String` +* `vn` is a `String` +* `ref`is a reference of type `Ref` +* `val` is a value according to `{Type}` -and finally {Type} standing for Long, Float, String, LongList, FloatList or StringList +Finally, `{Type}` stands for `long`, `float`, `String`, `LongList`, `FloatList`, or `StringList`. -We can fold the access of local variables in global scripts into the new record variable system, even though global scripts do not have explicit record variables. The new functions for record variable access make the old x.y syntax obsolete and therefore we declare it as deprecated. +We can fold the access of local variables in global scripts into the new record variable system, even though global scripts do not have explicit record variables. The new functions for record variable access make the old x.y syntax obsolete and, therefore, we declare it as deprecated. ## Control Structures -We will add a for loop that works on lists. To avoid unnecessary complexity in the language and to encourage the use of lists we will not have an index based for loop (these can easily be simulated via while). +We will add a `For` loop that works on lists. To avoid unnecessary complexity in the language and to encourage the use of lists, we will not have an index-based `For` loop (these can easily be simulated via `While`). Example: @@ -466,104 +464,104 @@ For x In l EndFor ```` -We will add the break and continue keywords both for for and while loops. +We will add the `break` and `continue` keywords both for `For` and `While` loops. -We may add a switch-case construct, but this is most likely a stage 2 task since there are many different ways to do switch-case and no obviously superior solution. This will most likely require extended discussion and design work. +We may add a `switch-case` construct, but this is most likely a stage-2 task since there are many different ways to do `switch-case` and no obviously superior solution. This will most likely require extended discussion and design work. ## General Additions -The vanilla scripting language is missing a few pieces of basic functionality. Therefore we need to add the following kinds of instructions: +The vanilla scripting language is missing a few pieces of basic functionality. Therefore, we need to add the following kinds of instructions: -* Trigonometric functions (Cos, Sin, Tan, Acos, Asin, Atan, Atan2, DegToRad, RadToDeg) -* Logical boolean function (And, Or, Not, Xor) -* Other mathematical functions (Abs, Floor, Ceil, Clamp, Lerp, Sign) +* Trigonometric functions (`Cos`, `Sin`, `Tan`, `Acos`, `Asin`, `Atan`, `Atan2`, `DegToRad`, `RadToDeg`) +* Logical boolean function (`And`, `Or`, `Not`, `Xor`) +* Other mathematical functions (`Abs`, `Floor`, `Ceil`, `Clamp`, `Lerp`, `Sign`) ## Object Management -We will add a function that moves an instance into the world. This feature (especially the move function) preludes a concept of persistent instance identity, which is most likely a stage 2 or OpenMW 2.0 feature. For now these functions are required to deal with state attached to the instance in the form of record variables/old local variables. +We will add a function that moves an instance into the world. This feature (especially the `move` function) preludes a concept of persistent instance identity, which is most likely a stage-2 or OpenMW-2.0 feature. For now, these functions are required to deal with state attached to the instance in the form of record variables or old local variables respectively. -* MoveObjectToWorld (target, v6): Move instance from current location to world location. -* CloneObjectToWorld (target, v6): Add a copy of instance to world location. +* `MoveObjectToWorld(target, v6)`: Moves the instance from current location to world location `target` at coordinates `v6` +* `CloneObjectToWorld(target, v6)`: Adds a copy of the instance `target` to world location `target` at coordinates `v6` -(in all cases reference specification for objects to be moved/cloned via -> operator in the usual way) +**Note**: In all cases, reference specification for objects to be moved/cloned can be done via the `->` operator in the usual way. -Moving or cloning out of a container will only move or clone a single item, not the whole stack. +**Note**: Moving or cloning out of a container will only move or clone a single item, not the whole stack. -The target value represents the target cell or worldspace (see section "References to cells in data structures" for details) +The `target` value represents the target cell or worldspace (see section *References to Cells in Data Structures* for details) -We will add replacement functions for object placement related functions without declaring the original functions deprecated. +We will add replacement functions for object-placement-related functions without declaring the original functions deprecated. Instructions: -* SetRotation (v3): Set instance's rotation; characters and NPCs ignore first two elements (replaces SetAngle) -* SetPosition (v3): Set instance's position (replaces SetPos) -* Teleport (target, v6, strict=0): Teleport instance to location specified by target and v6 (if the instance is a character or NPC the xrot and yrot values are ignored). If strict!=0, no corrections are made to the specified location. Otherwise the location may be adjusted for safety. (replaces Position and PositionCell) -* Spawn (objectId, target, v6, strict=0): Same as teleport, but spawns a new object (replaces PlaceItem and PlaceItemCell) +* `SetRotation(v3)`: Sets the instance's rotation; actors ignore the first two elements (replaces `SetAngle`) +* `SetPosition(v3)`: Sets the instance's position (replaces `SetPos`) +* `Teleport(target, v6, strict = 0)`: Teleports the instance to location specified by `target` and `v6`; actors ignore the first two elements; if `strict != 0`, no corrections are made to the specified location, otherwise the location may be adjusted for safety (replaces `Position` and `PositionCell`) +* `Spawn(objectId, target, v6, strict = 0)`: Same as `Teleport`, but spawns a new object `objectId` (replaces `PlaceItem` and `PlaceItemCell`) Functions: -* GetRotation: Returns instance's rotation as a Vector3 (replaces GetAngle) -* GetPosition: Returns instance's position as a Vector3 (replaces GetPos) +* `GetRotation`: Returns the instance's rotation as a `Vector3` (replaces `GetAngle`) +* `GetPosition`: Returns the instance's position as a `Vector3` (replaces `GetPos`) -## Object-Type Specific Additions +## Object-Type-Specific Additions We will add functions to modify and query other non-item-specific state that is already present in the cellref class and therefore already written to saved game files. Instructions: -* SetTrap (trapId): Set trap ID -* SetKey (trapId): Set key ID -* SetTeleport (target, v4): Set teleport location (does not affect teleport flag) +* `SetTrap(trapId)`: Sets the instance's trap ID to `trapId` +* `SetKey(trapId)`: Sets the instance's key ID to `keyId` +* `SetTeleport(target, v4)`: Sets the instance's teleport location to the location specified by `target` and `v4` (does not affect teleport flag) Functions: -* GetTrap: Return trap ID -* GetKey: Return key ID -* GetTeleportTarget: Return teleport target -* GetTeleportPosition: Returns teleport location and zrot as Vector4 +* `GetTrap`: Return the instance's trap ID +* `GetKey`: Return the instance's key ID +* `GetTeleportTarget`: Return the instance's teleport target +* `GetTeleportPosition`: Returns the instance's teleport location and zrot as `Vector4` ## Object-Types -We introduce compile time constants for object types (Weapons, Activators and so on). These have integer values. The constants must be named consistently in such a way that name collisions are unlikely (e.g. TypeWeapon, TypeActivator). +We introduce compile-time constants for object types (Weapons, Activators and so on). These have integer values. The constants must be named consistently in such a way that name collisions are unlikely (e.g. `TypeWeapon`, `TypeActivator`). We add a function that returns the type of an object or instance: -* GetObjectType +* `GetObjectType` ## Queries -A query returns a list of instances within a certain area. Along with the description of the area the query takes a list of object types (t) as an additional argument. +A query returns a list of instances within a certain area. Along with the description of the area, the query takes a list of object types (`t`) as an additional argument. -* QuerySphere t, v3, r -* QueryBox t, v3_tl, v3_br (this is an axis-aligned box) -* QueryCylinder t, v3, r, height (the cylinder is aligned along the Z-axis, v3 specifies the middle point of the bottom circle) +* `QuerySphere t, v3, r` +* `QueryBox t, v3_tl, v3_br` (this is an axis-aligned box) +* `QueryCylinder t, v3, r, height` (the cylinder is aligned along the z-axis, `v3` specifies the middle point of the bottom circle) ## Text Formatting -The vanilla scripting language provides text formatting in a single place (MessageBox instruction). This is insufficient, because: +The vanilla scripting language provides text formatting in a single place (`MessageBox` instruction). This is insufficient: -1. We will need text formatting in many places, independently of message boxes -2. The MessageBox text formatting sucks +1. We will need text formatting in many places, independently of message boxes. +2. The `MessageBox` text formatting sucks. -We will deprecate MessageBox. As a replacement we will introduce new instructions for creating message boxes (see following sub-section and GUI section) without a formatting features and also separate text formatting functions. +We will deprecate `MessageBox`. As a replacement, we will introduce new instructions for creating message boxes (see following subsection and *GUI* section) without a formatting feature and separate text formatting functions. -* Str(v, p = -1): Returns a string containing the string representation of the numeric value v. p specifies the number of digits after the decimal point. If p==-1 a suitable value depending on the type of v will be chosen. -* LFormat (s, a): Returns a string equal to the string s with all occurrences of %x replaced with the respective element indexed x from the string list a. -* Join (l, s): Return a string consisting of the members of the string list l separated by the string s +* `Str(v, p = -1)`: Returns a `String` containing the string representation of the numeric value `v`. `p` specifies the number of digits after the decimal point. If `p == -1`, a suitable value depending on the type of `v` will be chosen +* `LFormat(s, a)`: Returns a `String` equal to the `String` `s` with all occurrences of %x replaced with the respective element indexed x from the string list `a` +* `Join(l, s)`: Returns a `String` consisting of the members of the `StringList` `l` separated by the `String` `s` -## Multiple Choice Message Boxes +## Multiple-Choice Message Boxes -We add two new functions for showing multiple choice message boxes: +We add two new functions for showing multiple-choice message boxes: -* MakeChoice t, sl, f -* MakeBranchChoice t, sl, fl +* `MakeChoice t, sl, f` +* `MakeBranchChoice t, sl, fl` Arguments are as follows: -* t: The message box text -* sl: A list of strings, defining the available options -* f: The ID of a script function that is called when an option is selected. The function takes one long argument (the option index, starting at 0). -* fl: A list of script function names, same length as sl. Functions do not take any arguments. Empty strings in the list are allowed, which makes OpenMW ignore the respective choice. +* `t` is the message box's text +* `sl` is a list of strings, defining the available options +* `f` is the ID of a script function that is called when an option is selected; the function takes one `long` argument (the option index, starting at 0) +* `fl` is a list of script function names with the same length as `sl`; the functions do not take any arguments; empty strings in the list are allowed, which makes OpenMW ignore the respective choice ## Functions @@ -577,35 +575,35 @@ The syntax of the begin statement is extended in the following way: Begin { ( { -> return-type } ) } ```` -( {} denoting optional parts ) +**Note**: `{}` denotes optional parts. -Argument lists are allowed to be empty. Argument lists are comma-separated. Elements in argument list are type name pairs. Elements can be given default values in a C++ typical fashion. +Argument lists are allowed to be empty and are comma-separated. Elements in argument lists are type-name pairs. Elements can be given default values in a C++ fashion. -Arguments and return values are for all intents and purposes true local variable. Therefore the ref type is available. +Arguments and return values are for all intents and purposes true local variables. Therefore, the `Ref` type is available. ### Calling -A function can be called by its name followed by parenthesis. Arguments are listed inside the parenthesis separated by comma. +A function can be called by its name followed by parentheses. Arguments are listed inside the parentheses separated by commas. -We may at some point add the use of named arguments for function calling (comparable to Python), but this is most likely a stage 2 feature. +We may at some point add the use of named arguments for function calling (comparable to Python), but this is most likely a stage-2 feature. -If the function has a return type the function is evaluated as an expressions with a type corresponding to the return type. +If the function has a return type, the function is evaluated as an expression with a type corresponding to the return type. -Calling a function pauses the execution of the calling script, executes the called script and then resumes the execution of the calling script. This is different from the StartScript/StopScript instructions. The StartScript instruction is not modified (i.e. no argument passing). +Calling a function pauses the execution of the calling script, executes the called script and then resumes the execution of the calling script. This is different from the `StartScript` and `StopScript` instructions. The `StartScript` instruction is not modified (i.e., no argument passing). -A function call must be performed via a function name literal. The function name can not be given as a string variable, since this would make it impossible to check for the correctness of the function signature at compile time. +A function call must be performed via a function name literal. The function name cannot be given as a string variable, since this would make it impossible to check for the correctness of the function signature at compile time. Local and global scripts must not have any arguments without default values. ## Script Hooks -We introduce two new record type (hook, hooked script). The hooked script is a generalisation of the concept of a startup script. We may decide to fold the startup script record type into the hook record type. +We introduce two new record types (hook, hooked script). The hooked script is a generalisation of the concept of a start-up script. We may decide to fold the start-up script record type into the hook record type. ### Hooked Scripts A hooked script record binds a script to a hook record. More than one hooked script record per hook record is allowed. -The ID of a hook record is build from the hook record ID followed by a $ followed by the script name. +The ID of a hook record is built from the hook record ID followed by a `$` followed by the script name. Example: @@ -613,9 +611,9 @@ Example: Startup$KillFargoth ```` -By building hook IDs in this way we allow content files to delete hooked script records in dependencies without the need for an additional ID that identifies individual hook records. +By building hook IDs in this way, we allow content files to delete hooked script records in dependencies without the need for an additional ID that identifies individual hook records. -If more than one script is attached to a hook the order in which the scripts are executed is implementation defined at this point and scripts must not depend on a specific order. +**Note**: If more than one script is attached to a hook, the order in which the scripts are executed is, at this point, implementation-defined and, thus, scripts must not depend on a specific order. ### Hooks @@ -623,34 +621,34 @@ A hook record declares a new hook. Each hook record contains a list of argument There will be no return values (problematic since there can be multiple scripts per hook). -We provide system hooks within the namespace sys that are called in specific situations (see the section about de-hardcoding for some examples). Content developers may also provide their own hooks (user hooks). -System hooks are not added to content files, since they can not be modified by content developers anyway. We will instead inject the system hook records on content file load. +We provide system hooks within the namespace `sys` that are called in specific situations (see the section about de-hardcoding for some examples). Content developers may also provide their own hooks (user hooks). +System hooks are not added to content files, since they cannot be modified by content developers anyway. We will instead inject the system hook records on content-file load. System hooks are triggered by the engine. System hooks and user hooks can be triggered explicitly with a new script instruction. ## Script Slots -We define the term script slot as an optional sub-record in an object record that contains the name of a script. The default script slot for most object types is the "Run-Once-Per-Frame" slot, a.k.a. local script. +We define the term script slot as an optional subrecord in an object record that contains the name of a script. The default script slot for most object types is the "Run-Once-Per-Frame" slot, a.k.a. local script. -The default slot does not take any function arguments, but other slots can. The function arguments of a script attached to a slot need to match the hard-coded argument list of the slot. +The default slot does not take any function arguments, but other slots can. The function arguments of a script attached to a slot need to match the hardcoded argument list of the slot. -Note that if to scripts attached to the same object both define a (non-true) local variable with the same name, there will be only one variable. It is an error if the type of these variables don't match. +**Note**: If two scripts attached to the same object both define a (non-true) local variable with the same name, there will be only one variable. It is an error if the type of these variables don't match. ### Additional Slots -We add slots for OnX type keywords to object types where applicable The relevant keywords are: +We add slots for `OnX`-type keywords to object types where applicable. The relevant keywords are: -* OnActivate -* OnDeath -* OnKnockout -* OnMurder -* OnPCAdd -* OnPCDrop -* OnPcEquip -* OnHitMe -* OnPCRepair -* OnPCSoulGemUse -* OnRepair +* `OnActivate` +* `OnDeath` +* `OnKnockout` +* `OnMurder` +* `OnPCAdd` +* `OnPCDrop` +* `OnPcEquip` +* `OnHitMe` +* `OnPCRepair` +* `OnPCSoulGemUse` +* `OnRepair` ### Custom Slots @@ -658,221 +656,221 @@ We may allow the addition of custom slots (defined by the content developer), th ## Namespaces -The namespace of a script is determined by its ID. For example a script with the ID foo::Bar would be placed in the namespace foo. +The namespace of a script is determined by its ID. For example a script with the ID `foo::Bar` would be placed in the namespace `foo`. -IDs in a script refer to the local namespace by default, meaning the ID a in the script b::c would refer to b::a if such an ID exists or to ::a otherwise. +IDs in a script refer to the local namespace by default, meaning the ID `a` in the script `b::c` would refer to `b::a` if such an ID exists, or to `::a` otherwise. ## Other Script Instructions This is a collection of script instructions that fit nowhere else: -* GetPcTarget: Return the reference of the instance the player is currently looking at (crosshair). Can be a NullRef. -* GetMultiplayer: Always returns 0 -* GetPlayers: Returns a list of reference to all player controlled instances. This list contains a single reference to the instance of object Player. +* `GetPcTarget`: Returns the reference of the instance the player is currently looking at (crosshair); can be a null reference +* `GetMultiplayer`: Always returns 0 +* `GetPlayers`: Returns a list of references to all player-controlled instances; this list contains a single reference to the instance of object `Player` ## Other Script Hooks This is a collection of script hooks that fit nowhere else: -* sys::NewGameStarted: Executed when a new game is started. +* `sys::NewGameStarted`: Executed when a new game is started # Cells, Worldspaces & Areas -## Interior vs Exterior +## Interior vs. Exterior -The distinction between Interior and Exterior cells stopped making sense with the release of the Tribunal add-on. Therefore we should drop this terminology and replace it with Unpaged Cells (Interior) and Paged Cells (Exterior). +The distinction between interior and exterior cells stopped making sense with the release of the Tribunal add-on. Therefore, we should drop this terminology and replace it with **Unpaged Cells** (interior) and **Paged Cells** (exterior). -## Paged Cells +## Unpaged Cells -Paged cells remain essentially unchanged from old exterior cells, the only major difference being the ID and the worldspace. +Unpaged cells take the place of old interior cells. The only difference between old interior cells and unpaged cells is the worldspace. -Currently there is no uniform ID scheme for cells. Interior cells are named via an ID, exterior cells are named via a cell index (x, y). This needs to be changed, since we need a more consistent way to address cells. +By default, each unpaged cell has an associated worldspace record with the same ID. Furthermore, the unpaged-cell record is enhanced by an optional subrecord that specifies an alternative worldspace (if present). -The editor already gives exterior cells an ID (currently in the form of #x,y). The new scheme (relevant at least for the editor and scripting) will be "worldspace::x,y" (the cell will have the ID "x,y" in the namespace "worldspace"). We may also consider to replace the two integer coordinates in the cell record with the string ID. The performance impact of this change should be insignificant. +Most of the cell configuration data is moved out of the cell record and into the worldspace record. -The default exterior worldspace will be called default (previously called sys::default). Therefore the old exterior cell 0, 0 will be called "default:0,0". We are moving default out of sys to avoid creating an exception to the rule that content files are not allowed to create records in sys. +By moving out the worldspace into a separate record, we can unify worldspace data between interior and exterior cells. We can also associate multiple interior cells with the same worldspace or interior cells with an exterior worldspace. This should reduce duplicated data significantly. -## Unpaged Cells +## Paged Cells -Unpaged cells take the place of old interior cells. The only difference between old interior cells and unpaged cells is the worldspace. +Paged cells remain essentially unchanged from old exterior cells, the only major difference being the ID and the worldspace. -By default each unpaged cell has an associated worldspace record with the same ID. Furthermore the unpaged cell record is enhanced by an optional sub-record that specifies an alternative worldspace (if present). +Currently, there is no uniform ID scheme for cells. Interior cells are named via an ID, exterior cells are named via a cell index (x, y). This needs to be changed, since we need a more consistent way to address cells. -Most of the cell configuration data is moved out of the cell record and into the worldspace record. +The editor already gives exterior cells an ID (currently in the form of `#x,y`). The new scheme (relevant at least for the editor and scripting) will be `worldspace::x,y` (the cell will have the ID `x,y` in the namespace `worldspace`). We may also consider to replace the two integer coordinates in the cell record with the string ID. The performance impact of this change should be insignificant. -By moving out the worldspace into a separate record we can unify worldspace data between interior and exterior cells. We can also associate multiple interior cells with the same worldspace or interior cells with an exterior worldspace. This should reduce duplicated data significantly. +The default exterior worldspace will be called `default` (previously called `sys::default`). Therefore, the old exterior cell 0, 0 will be called `default:0,0`. We are moving `default` out of `sys` to avoid creating an exception to the rule that content files are not allowed to create records in `sys`. ## Worldspace Records -Worldspace records are shared by paged and unpaged cells (i.e. there are no interior and exterior worldspaces). +Worldspace records are shared by paged and unpaged cells (i.e., there are no interior and exterior worldspaces). Worldspace records contain the following fields: -* Water: Default Water level, Water ID (see de-hardcoding). Note: Script instructions that modify the water level will now modify the water level for a worldspace instead of a cell. +* Water: Default Water Level, Water ID (see de-hardcoding). **Note**: Script instructions that modify the water level will now modify the water level of a worldspace instead the one of a cell. * Ambient Light: 4 values -* Sky: Sky ID (analogous to Water ID, most likely not part of stage 1). Note: A worldspace can not have both an ambient light and a sky sub-record. -* Terrain: No data (might be better implemented as a flag). Presence indicate that worldspace has terrain. Ignored by unpaged cells. +* Sky: Sky ID (analogous to Water ID, most likely not part of stage 1). **Note**: A worldspace cannot have both an ambient-light and a sky subrecord. +* Terrain: No data (might be better implemented as a flag); presence indicates that worldspace has terrain; ignored by unpaged cells -All fields are optional. If a field is missing the respective cell/worldspace element is not present. +All fields are optional. If a field is missing, the respective cell/worldspace element is not present. -## References to cells in data structures +## References to Cells in Data Structures -Vanilla references to cells are generally based on the ID of the cell. There are no fields in vanilla Morrowind data structures that reference individual exterior cells. +Vanilla references to cells are generally based on the ID of the cell. There are no fields in vanilla Morrowind's data structures that reference individual exterior cells. We keep these reference fields, but change their meaning: -1. Check if it is the ID of an unpaged cell. If yes, use this cell -2. Check if it is a worldspace. If yes, use paged cells in this worldspace. +1. Check whether it is the ID of an unpaged cell. If yes, use this cell. +2. Check whether it is a worldspace. If yes, use the paged cells in this worldspace. 3. Otherwise, error. ## Scripts ### Water Level -The existing script instructions that deal with water level can be safely extended to the new system. We will add an optional argument at the end of the argument list of each instruction (GetWaterLevel, ModWaterLevel and SetWaterLevel) that specifies the worldspace the instruction is action on. If the argument is missing the instruction effects the current worldspace instead. +The existing script instructions that deal with water level can be safely extended to the new system. We will add an optional argument at the end of the argument list of each instruction (`GetWaterLevel`, `ModWaterLevel`, and `SetWaterLevel`) which specifies the worldspace the instruction is acting on. If the argument is missing, the instruction affects the current worldspace instead. -Note that this is different from vanilla Morrowind in regards to use in exterior cells. +**Note**: This behaviour is different from vanilla Morrowind in regards to exterior cells. ### New Instructions We add the following script instructions: -* EnumerateActiveCells: Returns a list of strings, containing the IDs of all cells currently active. Paged cells are listed individually. -* GetWorldspace (c): Returns the ID of the worldspace the cell with ID c is in -* GetWater (w): Returns the ID of the water used in the worldspace with ID w -* GetSky (w): Returns the ID of the sky used in the worldspace with ID w +* `EnumerateActiveCells`: Returns a `StringList` containing the IDs of all cells currently active; paged cells are listed individually +* `GetWorldspace(c)`: Returns the ID of the worldspace the cell with the ID `c` is in +* `GetWater(w)`: Returns the ID of the water used in the worldspace with the ID `w` +* `GetSky(w)`: Returns the ID of the sky used in the worldspace with the ID `w` ### Script hooks We add four new script hooks: -* RegionEntered -* RegionExited -* WorldspaceEntered -* WorldspaceExited +* `RegionEntered` +* `RegionExited` +* `WorldspaceEntered` +* `WorldspaceExited` -All four hooks take the ID of the region or worldspace as arguments and are executed when the player enters or exists a region or worldspace. +All four hooks take the ID of the region or worldspace as arguments and are executed when the player enters or exits a region or a worldspace. ## Areas The ability to designate a section of a worldspace with a specific ID that can be checked or referenced has many uses. A few examples: -* Give a NPC certain dialogue topics only in specific places +* Give an NPC certain dialogue topics only in specific places * Limit wandering NPCs to an area * Add special/magical effects to a location -Currently our ability to do that is largely limited to cells. This is a problem because of two reasons: +Currently, our ability to do that is largely limited to cells. This is a problem because of two reasons: -* The fixed-size square nature of nature of cells makes areas unreasonably inflexible. +* The fixed-size, square nature of cells makes areas unreasonably inflexible. * We can't have overlapping areas. ### Record -We introduce a new record (Area) that consists of the following fields: +We introduce a new record ("Area") that consists of the following fields: * Worldspace ID -* Polygon (list of 2D coordinates), defining a surface on the x-y-plane -* Min-Height: Z-coordinate at which the area starts -* Max-Height: Z-coordinate at which the area ends +* Polygon: A list of 2D coordinates defining a surface on the xy-plane +* Min-Height: z-coordinate at which the area starts +* Max-Height: z-coordinate at which the area ends * Enter script ID (string, optional): Script function that is called when the player enters the area * Exit script ID (string, optional): Script function that is called when the player exits the area (must also be called in case of teleportation) * Inside script ID (string, optional): Script function that is called while the player is in the area. -* Inside script delta (float, optional): Minimum time between two executions of the inside script function. -* Composite (integer, optional): If this flag is set the area will be ignored by OpenMW except as a part of a joined area. Composite areas are not accessible by script instructions and do not run any scripts of their own. +* Inside script delta (float, optional): Minimum time between two executions of the inside-script function. +* Composite (integer, optional): If this flag is set, the area will be ignored by OpenMW except as a part of a joined area. **Note**: Composite areas are not accessible by script instructions and do not run any scripts of their own. All script functions take the ID of the area as a parameter. -Whenever a cell or a worldspace is referenced for checking or defining locations the ID of an area can be used instead. +Whenever a cell or a worldspace is referenced to check or define locations, the ID of an area can be used instead. ### Script Functions We add the following script functions: -* GetAreas (v3): Returns a string list containing the IDs of all areas v3 is inside of -* InArea (id): Returns 1 or 0 depending on if the instance is in area id or not +* `GetAreas(v3)`: Returns a `StringList` containing the IDs of all areas `v3` is in +* `InArea(id)`: Returns 1 (instance is in area `id`) or 0 (otherwise) ## Joined Areas -A joined area is an area that consists of multiple un-joined areas. Joined areas can contain both composite and non-composite areas. A joined area can stretch across multiple worldspaces. Scripts do not distinguish between areas and joined areas. +A joined area is an area that consists of multiple unjoined areas. Joined areas can contain both composite and non-composite areas. A joined area can stretch across multiple worldspaces. Scripts do not distinguish between areas and joined areas. ### Record -We introduce a new record (JoinedArea) that consists of the following fields: +We introduce a new record ("Joined Area") that consists of the following fields: * Enter script ID (string, optional): Script function that is called when the player enters the area * Exit script ID (string, optional): Script function that is called when the player exits the area (must also be called in case of teleportation) * Inside script ID (string, optional): Script function that is called while the player is in the area. -* Inside script delta (float, optional): Minimum time between two executions of the inside script function. +* Inside script delta (float, optional): Minimum time between two executions of the inside-script function. -# Item-Interactions & -Management +# Item Interaction & Item Management ## Deletion -We will add an instruction to delete instances via a reference variable. Current solutions are insufficient because they can not target specific items (when in a container) and require different approaches depending on if an instance is in a container or in the world. Self-deletion must be safe. For the sake of consistency We extend this instruction to work on non-item objects too. +We will add an instruction to delete instances via a reference variable. Current solutions are insufficient because they can not target specific items (when in a container) and require different approaches depending on whether an instance is in a container or in the world. Self-deletion must be safe. For the sake of consistency, we extend this instruction to work on non-item objects too. -* Delete (reference specification for instance to be deleted via -> operator in the usual way) +* `Delete` (reference specification for instance to be deleted via the `->` operator in the usual way) ## Container -We will add a function that returns the contents of a container as a list of references (the term container includes here creatures and NPCs). +We will add a function that returns the contents of a container as a list of references. **Note**: In this case, the term "container" also includes creatures and NPCs. -* GetContents (reference specification for container via -> operator in the usual way) +* `GetContents` (reference specification for container via the `->` operator in the usual way) -We will add a function that moves/clones an item instance into a container (actual container or NPC). This feature (especially the move function) preludes a concept of persistent instance identity, which is most likely a stage 2 or OpenMW 2.0 feature. For now these functions are required to deal with state attached to the instance in the form of record variables/old local variables. +We will add a function that moves/clones an item instance into a container (actual container or actor). This feature (especially the `move` function) preludes a concept of persistent instance identity, which is most likely a stage-2 or OpenMW-2.0 feature. For now, these functions are required to deal with state attached to the instance in the form of record variables / old local variables. (In all cases, the reference specification for the item to be moved/cloned works via the `->` operator in the usual way.) -* MoveItemToContainer (ref/id, count=1): Move item from current location to container specified by ref/id -* CloneItemToContainer (ref/id, count=1): Add a copy of item to container specified by ref/id +* `MoveItemToContainer(ref/id, count = 1)`: Move item from current location to container specified by `ref/id` +* `CloneItemToContainer(ref/id, count = 1)`: Add a copy of item to container specified by `ref/id` -(in all cases reference specification for item to be moved/cloned work via -> operator in the usual way) - -The count argument is ignored when the original item is not in a container. +The `count` argument is ignored when the original item is not in a container. ## Other Item-Related Instructions -* SetItemHealth (health): Set item current health -* SetItemCharge (charge): Set item current charge -* SetItemOwner (owner): Set item owner ID -* SetItemSoul (soul): Set soul ID (soul gems only) -* SetItemFaction (faction): Set item faction ID -* SetItemFactionRank (rank): Set item faction rank +Instructions: + +* `SetItemHealth(health)`: Sets item's current health +* `SetItemCharge(charge)`: Sets item's current charge +* `SetItemOwner(owner)`: Sets item's owner ID +* `SetItemSoul(soul)`: Sets item's soul ID (soul gems only) +* `SetItemFaction(faction)`: Sets item's faction ID +* `SetItemFactionRank(rank)`: Sets item's faction rank Functions: -* IsItem: Return 1 if reference is an item, 0 otherwise -* GetItemHealth: Return item current health -* GetItemMaxHealth: Return item max health -* GetItemCharge: Return item current charge -* GetItemMaxCharge: Return item max charge -* GetItemOwner: Return item owner ID -* GetSoulItem: Return soul ID (soul gems only) -* GetItemFaction: Return item faction ID -* GetItemFactionRank: Return item faction rank +* `IsItem`: Returns 1 if reference is an item, 0 otherwise +* `GetItemHealth`: Returns item's current health +* `GetItemMaxHealth`: Returns item's maximum health +* `GetItemCharge`: Returns item's current charge +* `GetItemMaxCharge`: Returns item's maximum charge +* `GetItemOwner`: Returns item's owner ID +* `GetSoulItem`: Returns item's soul ID (soul gems only) +* `GetItemFaction`: Returns item's faction ID +* `GetItemFactionRank`: Returns item's faction rank ## Item Tags -Currently there is no customisable way of categorising items. To compensate for this shortcoming we introduce items tags. +Currently, there is no customisable way of categorising items. To compensate for this shortcoming, we introduce item tags. An item tag is a string that is attached to an object record. An object record can have multiple tags. -Objects also have implicit item tags that are determined by their type (e.g. every weapon object has a tag weapon even without the object record explicit containing this tag. We may introduce other kinds of implicit tags (e.g. weapon types, enchanted items). +Objects also have implicit item tags that are determined by their type (e.g., every weapon object has a tag "Weapon" even without the object record explicitly containing this tag). We may introduce other kinds of implicit tags (e.g. weapon types, enchanted items). The de-hardcoding introduces additional item tags. Item tags are immutable at runtime and can be queried via script instructions: -* GetItemTags (Id): Returns a string list of tags for the object Id. As usual instead of an ID a reference variable can be used. -* HasitemTag (Id, Tag): Return 1 or 0 depending on Id has the tag Tag. +* `GetItemTags(id)`: Returns a `StringList` of tags for the object `id`; as usual, instead of an ID a reference variable can be used +* `HasitemTag(id, tag)`: Returns 1 or 0 depending on `id` having the tag `tag` -Using these instructions on non-item objects return an empty list and 0 respectively. +Using these instructions on non-item objects returns an empty list or 0 respectively. -Item tags can be used to organise container windows (see GUI section). +Item tags can be used to organise container windows (see *GUI* section). A few examples of tags that content developers may come up with: -* quest: A quest item -* vendor: An item that has no other use than to sell it to a vendor +* "Quest": A quest item +* "Vendor": An item that has no other use than to sell it to a vendor -We may suggest some default tags in the documentation (or even the editor) to encourage more consistent tag use by content developers. +We may suggest some default tags in the documentation (or even the editor itself) to encourage more consistent tag use by content developers. ## Interactions @@ -882,435 +880,467 @@ We enhance the way how the player interacts with the world, especially via items There are three methods of interaction: -* The player presses the attack button while holding an interaction-item in his hand and targeting an object in the world. This feature exists in vanilla MW only within the security skill. We generalise these kinds of interactions (see Held Items in the De-Hardcoding section). We also allow this feature for weapons held in the main hand. If an interaction is possible the interaction takes the place of the attack. If the interaction is ignored or refused the attack proceeds. -* The player drags an item from a container onto on instance in the world. This kind of interaction does not exist in vanilla MW. -* The player drags an item from a container onto another item in another (or the same) container. This kind of interaction does not exist in vanilla MW. +* The player presses the attack button while holding an interaction item in his hand and targeting an object in the world. This feature exists in vanilla Morrowind only within the Security skill. We generalise this kind of interaction (see *Held Items* in the *De-Hardcoding* section). We also allow this feature for weapons held in the main hand: If an interaction is possible, the interaction takes the place of the attack. If the interaction is ignored or refused, the attack proceeds. +* The player drags an item from a container onto an instance in the world. This kind of interaction does not exist in vanilla Morrowind. +* The player drags an item from a container onto another item in another (or the same) container. This kind of interaction does not exist in vanilla Morrowind. ### Interaction Subrecords -All item object record types are enhanced by a new optional sub-record that holds the name of the ItemItemInteraction script. +All item-object record types are enhanced by a new optional subrecord that holds the name of the ItemItemInteraction script. -All object record types (including items) are enhanced by a new optional sub-record that holds the name of the ItemObjectInteraction script. +All object record types (including items) are enhanced by a new optional subrecord that holds the name of the ItemObjectInteraction script. -### Interaction-Chain +### Interaction Chain -Custom interactions are handled by running through a sequence of scripts. Each script can either ignore the interactions or accept it or refuse it. +Custom interactions are handled by running through a sequence of scripts. Each script can either ignore the interaction, accept it, or refuse it. -If the action is accepted or refused the chain stops. If the action is refused or the chain runs to its end without any script accepting it the player will be notified via a sound-effect. We may either re-purpose an existing sound-effect or acquire a new one. +If the action is accepted or refused, the chain stops. If the action is refused or the chain runs to its end without any script accepting it, the player will be notified via a sound effect. We may either re-purpose an existing sound effect or acquire a new one. We add two new script instructions: -* AcceptInteraction -* RefuseInteraction +* `AcceptInteraction` +* `RefuseInteraction` -Using any of these instructions outside of an interaction chain is an error. We use these instructions instead of return values, because a part of the interaction chain works via hooks which do no provide return values. +Using any of these instructions outside of an interaction chain is an error. We use these instructions instead of return values, because a part of the interaction chain works via hooks which do not provide return values. All interaction chain functions share the following signature: -* ref to Item A/Item -* ref to Item B/Object -* ref to actor performing the interaction (player for now, we may extend that later) -* integer: 0 interaction was initiated via drag & drop, 1 interaction was initiated via attack button +* reference to item A +* reference to item/object B +* reference to actor performing the interaction (player for now, we may extend that later) +* integer: 0 if interaction was initiated via drag & drop, 1 if interaction was initiated via attack button -### Item-Item-Chain: +### Item-Item Chain: -* Hook sys::PreItemItemInteraction -* ItemItemInteraction Script of object A -* ItemItemInteraction Script of object B -* Hook sys::ItemItemInteraction +* Hook `sys::PreItemItemInteraction` +* `ItemItemInteraction script of item A +* `ItemItemInteraction script of item B +* Hook `sys::ItemItemInteraction` -### Item-Object-Chain: +### Item-Object Chain: If the object is also an item the Item-Item-Chain is used instead. -* Hook sys::PreItemObjectInteraction -* ItemObjectInteraction Script of item -* ItemObjectInteraction Script of object -* Hook sys::ItemObjectInteraction +* Hook `sys::PreItemObjectInteraction` +* `ItemObjectInteraction` script of item A +* `ItemObjectInteraction` script of object B +* Hook `sys::ItemObjectInteraction` # De-Hardcoding -This section describes the first batch of de-hardcoding tasks. This is the core part of stage 1 (*The Grand De-Hardcoding*). We are aiming mostly for low hanging but highly profitable fruits here. More complex de-hardcoding (especially tasks that require more extensive script support) will follow in stage 2. +This section describes the first batch of de-hardcoding tasks. This is the core part of stage 1 (*The Grand De-Hardcoding*). We are aiming mostly for low-hanging, but highly profitable fruits here. More complex de-hardcoding (especially tasks that require more extensive script support) will follow in stage 2. ## GMSTs Many GMSTs will be integrated into other records, which will effectively remove the GMST. We may consider actually removing the GMST record during the load process. -We add three new types of GMSTs: +We will add three new types of GMSTs: -* IntegerList -* FloatList -* StringList +* `IntegerList` +* `FloatList` +* `StringList` -We may consider allowing content files to create new GMSTs outside of the sys namespace. This would make the GMST record type more consistent with other record types. To make this addition useful we need to add script functions to read GMSTs: +We may consider allowing content files to create new GMSTs outside of the `sys` namespace. This would make the GMST record type more consistent with other record types. To make this addition useful, we need to add script functions to read GMSTs: -* Keyword: Get{Type}Gmst() +* `Get{Type}Gmst()` -{Type} can be Integer, Float, String, Integerlist, FloatList or StringList. +`{Type}` can be of type `long`, `float`, `String`, `Longlist`, `FloatList`, or `StringList`. ## Fallback Values -The openmw.cfg file contains a set of fallback values. These were extracted from the Morrowind.ini file. As the name indicates we consider these values as a fallback for legacy format content files only. Our goal is to move all these values to content file format. In some cases we also may decide to declare a fallback value obsolete and not use it at all. +The *openmw.cfg* file contains a set of fallback values. These were extracted from the *Morrowind.ini* file. As the name indicates, we consider these values as a fallback for legacy-format content files only. Our goal is to move all these values to content file format. In some cases, we may also decide to declare a fallback value obsolete and not use it at all. All usable values should be migrated to new GMST records, unless they are already covered by other parts of the de-hardcoding. ## General Scripting Enhancements -We introduce new script functions: +We will introduce new script functions: -* Enumerate{x}: {x} is either Skills, Races, Classes, DynamicStats, Attributes, WeaponTypes, ArmorTypes, Specialisations, MagicSchools, Factions or Birthsigns. Returns a string list of the IDs of all records of the respective type. +* `Enumerate{x}`: Returns a `StringList` of the IDs of all records of the respective type; `{x}` is either "Skills", "Races", "Classes", "DynamicStats", "Attributes", "WeaponTypes", "ArmorTypes", "Specialisations", "MagicSchools", "Factions" or "Birthsigns" ## Dynamic Stats -We will unify dynamic stats and make them configurable. For stage 1 we will not allow the deletion of existing dynamic stats (they are used in too many parts of the engine). But we will allow new dynamic stats. +We will unify dynamic stats and make them configurable. For stage 1, we will not allow the deletion of existing dynamic stats (they are used in too many parts of the engine). But we will allow new dynamic stats. -We add a new record type for dynamic stats. Records for Health (sys::Health), Fatigue (sys::Fatigue) and Magicka (sys::Magicka) will be created by the engine when loading older omwgame files (these records are mandatory for newer omwgame files). +We add a new record type for dynamic stats. Records for Health (`sys::Health`), Fatigue (`sys::Fatigue`), and Magicka (`sys::Magicka`) will be created by the engine when loading older omwgame files (these records are mandatory for newer omwgame files). -A dynamic stats record contains the following information: +A dynamic-stat record contains the following information: * Name (taken from GMSTs for default stats) -* Tooltip text (taken from GMSTs for default stats) +* Tooltip Text (taken from GMSTs for default stats) * Colour -* (optional) PrevID: ID of stat the engine will try to sort in this stats after (similarly to info records) -* (optional) NextID: ID of stat the engine will try to sort in this stats before (similarly to info records) -* (optional) Vital-flag (player dies if stat with vital flag drops to 0) -* (optional) ID of level-up function: Takes the reference of the character as argument and returns an integer value representing the max stat change from the level-up. -* (optional) ID of time passed function: Takes the reference of the character and the duration as arguments. Returns a float that is used to modify the current stat value. Not used when waiting or sleeping. -* (optional) ID of wait function: Takes the reference of the character, the duration and a sleep flag (integer) as arguments. Returns a float that is used to modify the current stat value. -* Default value: If an actor does not have this stat (possible if the stat was defined in an addon and not in the base-game) this value is used for the maximum value of the stat. +* (optional) PrevID: ID of the stat the engine will try to subsequently sort this stat in (similar to info records) +* (optional) NextID: ID of the stat the engine will try to antecedently sort this stat in (similar to info records) +* (optional) Vital: Flag which specifies whether the player dies if the stat drops to 0 +* (optional) Level-Up Function: ID of the stat's level-up function, which takes the reference of the character as argument and returns an `long` value representing the maximum stat change achievable with a level-up +* (optional) Time-Passed Function: ID of the stat's time-passed functionm, which takes the reference of the character and the duration as arguments and returns a `float` that is used to modify the current stat value (the function is not used when waiting or sleeping) +* (optional) Wait Function: ID of the stat's wait function, which takes the reference of the character, the duration, and a sleep flag (integer) as arguments and returns a `float` that is used to modify the current stat value +* Default Value: Value that is used as the maximum value of the stat if an actor does not have this stat (possible if the stat was defined in an add-on and not in the base game) -Scripts for default stats are injected as necessary. Some of these scripts require access to GMST values. We need to figure out how to implement this, either grab the value from the GMST when adding the script or adding a script function to read GMST values. The later is easier but will not allow us to remove the redundant GMST record easily. +Scripts for default stats are injected as necessary. Some of these scripts require access to GMST values. We need to figure out how to implement this: either grab the value from the GMST when adding the script or adding a script function to read GMST values. The latter is easier but will not allow us to remove the redundant GMST records easily. -The currently existing Get, Set and Mod instructions are flagged as deprecated and replaced by a single instruction for each operation that takes the stat ID as an argument. We need separate instructions to deal with base, modified and current values. +The currently existing `Get`, `Set`, and `Mod` instructions are flagged as deprecated and replaced by a single instruction for each operation that takes the stat ID as an argument. We need separate instructions to deal with base, modified and current values. ## Attributes We will unify attributes and make them configurable. For stage 1 we will not allow the deletion of existing attributes (they are used in too many parts of the engine). But we will allow new attributes. -We add a new record type for attributes. Records for the existing attributes (sys::NameOfAttribute) will be created by the engine when loading older omwgame files (these records are mandatory for newer omwgame files). +We add a new record type for attributes. Records for the existing attributes (`sys::NameOfAttribute`) will be created by the engine when loading older omwgame files (these records are mandatory for newer omwgame files). -A attribute record contains the following information: +An attribute record contains the following information: * Name (taken from GMSTs for default attributes) -* Tooltip text (taken from GMSTs for default attributes) -* (optional) PrevID: ID of attribute the engine will try to sort in this attribute after (similarly to info records) -* (optional) NextID: ID of attribute the engine will try to sort in this attribute before (similarly to info records) -* Default value: If an actor does not have this attribute (possible if the attribute was defined in an addon and not in the base-game) this value is used for the attribute. +* Tooltip Text (taken from GMSTs for default attributes) +* (optional) PrevID: ID of the attribute the engine will try to subsequently sort this attribute in (similar to info records) +* (optional) NextID: ID of the attribute the engine will try to antecedently sort this attribute in (similar to info records) +* Default Value: Value that is used as the maximum value of the attribute if an actor does not have this attribute (possible if the attribute was defined in an add-on and not in the base game) -Note that all records that reference attributes need to have the respective fields be changed from integers to strings. +**Note**: All records that reference attributes need the respective fields to be changed from integers to strings. -The currently existing Get, Set and Mod instructions are flagged as deprecated and replaced by a single instruction for each operation that takes the attribute ID as an argument. We need separate instructions to deal with base, modified and current values. +The currently existing `Get`, `Set`, and `Mod` instructions are flagged as deprecated and replaced by a single instruction for each operation that takes the attribute ID as an argument. We need separate instructions to deal with base, modified and current values. -Additionally we will add a new GMST sys::ClassAttributes of type integer. This GMST specifies the number of favoured attributes that a class has. The value defaults to 2. +Additionally, we will add a new GMST `sys::ClassAttributes` of type `long`. This GMST specifies the number of favoured attributes that a class has. The value defaults to 2. ## Weapon Types We will unify weapon types and make them configurable. -We add a new record type for weapon types. Records for the existing weapon types (sys::NameOfType) will be created by the engine when loading older omwgame files. +We add a new record type for weapon types. Records for the existing weapon types (`sys::NameOfType`) will be created by the engine when loading older omwgame files. A weapon type record contains the following information: * Name (taken from GMSTs for default types) -* HandlingType (how the weapon is held and what animations to play when attacking) -* Name of HitTestScript (see Combat subsection) +* Handling Type: Defines how the weapon is held and what animations play when attacking +* Hit Test Script: Name of the hit test script used (see *Combat* subsection) + +For stage 1, the weapon type record is still very basic. Its purpose is mostly to allow better de-hardcoding of skills. We may expand on this in the future. However, there are already possible uses, e.g., a weapon type "Ancient Blades" which requires a separate skill. + +We add a new script function: -For stage 1 the weapon type record is still very basic. Its purpose is mostly to allow better de-hardcoding of skills. We may expand on this in the future. However there are already possible uses (for example a type of "ancient blades" that require a separate skill). +* `GetWeaponTypeId id`: Returns the weapon type ID of an instance or an object with the ID `id` -We add another script function that returns the weapon type ID of an instance or an object ID: GetWeapopnTypeId. Note that GetWeaponType is already taken by Tribunal and we can not repurpose this name without breaking compatibility. +**Note**: `GetWeaponType` is already taken by Tribunal and we can not repurpose this name without breaking compatibility. ## Armour Types -In vanilla Morrowind armour types exist only implicitly. There is no record and no option to select armour types. Armour types are determined by weight only. +In vanilla Morrowind, armour types exist only implicitly. There is no record and no option to select armour types. Armour types are determined by weight only. -We will keep implicit armour types as an option, but also add armour types explicitly as a new type of record. +We will keep implicit armour types as an option, but we'll also add armour types explicitly as a new type of record. -Records for the existing types (sys::LightArmorType, sys::MediumArmorType, sys::HeavyArmorType) will be created by the engine when loading older omwgame files (these records are mandatory for newer omwgame files). +Records for the existing types (`sys::LightArmorType`, `sys::MediumArmorType`, `sys::HeavyArmorType`) will be created by the engine when loading older omwgame files (these records are mandatory for newer omwgame files). -A armour type record contains the following information: +An armour type record contains the following information: * Name (taken from GMSTs for default types) * (optional) Minimum weight -Additional armour object records are extended by the optional field "armor type". +Additionally, armour object records are extended by the optional field "Armour type". + +If an armour object has no armour type specified, its type is determined by its weight according to the following algorithm: + +1. Consider all armour types with a minimum weight field. +2. Exclude types that have a minimum weight larger than the weight of the object. +3. Pick the type with the largest minimum weight. -If an armour object does not have this field its type is determined by the weight by the following algorithm: +For stage 1, the armour type record is still very basic. Its purpose is mostly to allow better de-hardcoding of skills. We may expand on this in the future. However, there are already possible uses, e.g., imitating TES IV: Oblivion by eliminating medium armour or adding a new armour type such as "Ancient Armour" which requires a separate skill. -* Consider all armour types with a minimum weight field -* Exclude types that have a minimum weight larger than the weight of the object -* Pick the type with the largest minimum weight +We add another script function: -For stage 1 the armour type record is still very basic. Its purpose is mostly to allow better de-hardcoding of skills. We may expand on this in the future. However there are already possible uses (for example going Oblivion by killing medium armour or adding a new type of armour "ancient armor" that requires a separate skill). +* `GetArmorTypeId id`: Returns the armour type ID of an instance or an object with the ID `id` -We add another script function that returns the armour type ID of an instance or an object ID: GetArmorTypeId. +**Note**: For the sake of consistency, we've adopted the naming scheme introduced for weapon types. ## Specialisation We will unify specialisations and make them configurable. -We add a new record type for specialisations. Records for Combat (sys::CombatSpec), Stealth (sys::StealthSpec) and Magic (sys::MagicSpec) will be created by the engine when loading older omwgame files. +We add a new record type for specialisations. Records for Combat (`sys::CombatSpec`), Stealth (`sys::StealthSpec`), and Magic (`sys::MagicSpec`) will be created by the engine when loading older omwgame files. A specialisation record contains the following information: * Name (taken from GMSTs for default types) -We add a new script instruction GetSpecialization id, that returns the specialisation of a class or a skill with the given ID. +We add a new script instruction: + +* `GetSpecialization id`: Returns the specialisation of a class or a skill with the given ID `id` ## Magic Schools We will unify magic schools and make them configurable. -We add a new record type for magic schools. Records for the existing magic schools will be created by the engine when loading older omwgame files. +We add a new record type for magic schools. Records for the existing magic schools (`sys::AlterationMagicSchool`, `sys::ConjurationMagicSchool`, etc.) will be created by the engine when loading older omwgame files. A magic school record contains the following information: * Name (taken from GMSTs for default schools) -* Specialisation (sys::MagicSpec for default schools) -* Resource (sys::Magicka for default schools) +* Specialisation (`sys::MagicSpec` for default schools) +* Resource (`sys::Magicka` for default schools) -A use for the specialisation field would be (for example) dividing magic up into two categories "divine magic" and "arcane magic" and have characters specialise accordingly. +A use for the specialisation field would be, e.g., dividing magic into the two categories "Divine Magic" and "Arcane Magic" and having characters specialise accordingly. ## Skills -Skills are a complex topic and we won't achieve full de-hardcoding in stage 1 and most like there will never be a 100% complete de-hardcoding. But there is still a lot we can do with skills. +Skills are a complex topic and we won't achieve full de-hardcoding in stage 1 - and most likely there will never be a 100% complete de-hardcoding. But there is still a lot we can do with skills. -Currently skills exist as indexed records. We need to move these over to ID-based records. The exiting indices are translated to IDs of the form sys::NameOfSkill. +Currently, skills exist as indexed records. We need to move these over to ID-based records. The exiting indices are translated to IDs of the form `sys::NameOfSkill`. -We add a new sub-record type (Function Sub-Record, see below). Each skill can have zero, one or multiple function sub-records. +We add a new subrecord type (see subsection *Function Subrecords* below). Each skill can have zero, one, or multiple function subrecords. The following skills are too tightly bound into the engine to allow their easy removal: -* Armorer -* Enchanting -* Alchemy * Acrobatics -* Block -* Sneak +* Alchemy +* Armorer * Athletics +* Block +* Enchant * Hand-to-hand -* Unarmored * Mercantile +* Sneak * Speechcraft +* Unarmored -Therefore we will (for now) forbid the deletion of the respective skill records. All other default skills can be deleted. +Therefore, we will (for now) forbid the deletion of the respective skill records. All other default skills can be deleted. -### Scripts Instructions +### Script Instructions -The currently existing Get, Set and Mod instructions are flagged as deprecated and replaced by a single instruction for each operation that takes the skill ID as an argument. We need separate instructions to deal with base and modified values. +The currently existing `Get`, `Set`, and `Mod` instructions are flagged as deprecated and replaced by a single instruction for each operation that takes the skill ID as an argument. We need separate instructions to deal with base and modified values. We also introduce new script instructions: -* UseSkill id, v: Progress skill id by a value of v (float). -* UseValueSkill Id, i: Progress skill id by the value given by use value field with index i (0, 1, 2 or 3) -* Get{x}SkillLevel s_id: Return the skill level of a function sub-record with TargetId s_id (string). {x} is either Armor, Weapon or Magic. See next sub-section for details. +* `UseSkill id, v`: Progresses the skill `id` by a value of `v` (`float`). +* `UseValueSkill Id, i`: Progresses the skill `id` by the value given by the use-value field with index `i` (0, 1, 2 or 3) +* `Get{x}SkillLevel s_id`: Returns the skill level of a function subrecord with `TargetId` `s_id` (`String`). `{x}` is either "Armor", "Weapon", or "Magic"; see the next subsection for details. -Note that we do not de-hardcode the fixed number of use value fields. The usefulness of these fields is most likely limited for new skills. The UseSkill instruction, which bypasses the use value fields should be sufficient in most cases. We only add the UseValueSkill instructions to better support script interactions with default skills. +**Note**: We do not de-hardcode the fixed number of use-value fields. The usefulness of these fields for new skills is most likely limited. The `UseSkill` instruction, which bypasses the use-value fields, should be sufficient in most cases. We only add the `UseValueSkill` instructions to better support script interactions with default skills. -### Function Sub-Records +### Function Subrecords -A function sub-record describes how a skill interacts with hardcoded engine functions. A function record consists of a function ID (we use an index here, because the list of functions will definitely not be extensible by content files) and additional arguments. +A function subrecord describes how a skill interacts with hardcoded engine functions. A function record consists of a function ID (we use an index here, because the list of functions will definitely not be extensible by content files) and additional arguments. -For stage one we require only one additional argument: +For stage 1, we require only one additional argument: -* TargetId (single string value) +* `TargetId` (single `String` value) -For stage 1 we introduce three function IDs: Armour skill, weapon skill, magic skill. +Stage 1 will introduce three function IDs: -We do not forbid non-unique function sub-records; meaning two skills may have identical function sub-records (e.g. two skills governing light armor). If there is more than one skill with the same function sub-record and the skill level for this function needs to be considered, we use the maximum over all relevant skill levels. +* Armour Skill +* Weapon Skill +* Magic Skill + +We do not forbid non-unique function subrecords, i.e., two skills may have identical function subrecords, e.g., two skills governing light armour. If there is more than one skill with the same function subrecord and the skill level for this function needs to be considered, we use the maximum of all relevant skill levels. ### Armour Skills -We make armour skills fully customisable by introducing a function ID for armour types. Any skill that has a function sub-record with this ID is an armour skill. For default armour skills loaded from older omwgame files we will inject function sub-records during the loading process. +We make armour skills fully customisable by introducing a function ID for armour types. Any skill that has a function subrecord with this ID is an armour skill. For default armour skills loaded from older omwgame files, we will inject function subrecords during the loading process. + +The `TargetId` of an armour function subrecord indicates the armour type that the skill is governing. -The TargetId of a armour function sub-record indicates the armour type that the skill is governing. +The default skills are: -The default skills are: Heavy Armor, Medium Armor, Light Armor. +* "Heavy Armor" +* "Light Armor" +* "Medium Armor" ### Weapon Skills -We make weapon skills fully customisable by introducing a function ID for weapon types. Any skill that has a function sub-record with this ID is a weapon skill. For default weapon skills loaded from older omwgame files we will inject function sub-records during the loading process. +We make weapon skills fully customisable by introducing a function ID for weapon types. Any skill that has a function subrecord with this ID is a weapon skill. For default weapon skills loaded from older omwgame files, we will inject function subrecords during the loading process. + +The `TargetId` of a weapon function subrecord indicates the weapon type that the skill is governing. -The TargetId of a weapon function sub-record indicates the weapon type that the skill is governing. +The default skills are: -The default skills are: Spear, Axe, Blunt Weapon, Long Blade, Marksman, Short Blade +* "Axe" +* "Blunt Weapon" +* "Long Blade" +* "Marksman" +* "Short Blade" +* "Spear" ### Magic Skills -We make magic skills fully customisable by introducing a function ID for magic schools. Any skill that has a function sub-record with this ID is a magic skill. For default magic skills loaded from older omwgame files we will inject function sub-records during the loading process. +We make magic skills fully customisable by introducing a function ID for magic schools. Any skill that has a function subrecord with this ID is a magic skill. For default magic skills loaded from older omwgame files, we will inject function subrecords during the loading process. -The TargetId of a magic school function sub-record indicates the magic school that the skill is governing. +The `TargetId` of a magic school function subrecord indicates the magic school that the skill is governing. -The default skills are: Illusion, Conjuration, Alteration, Destruction, Mysticism, Restoration +The default skills are: + +* "Alteration" +* "Conjuration" +* "Destruction" +* "Illusion" +* "Mysticism" +* "Restoration" ## Weather -We make weather types customisable by moving from hard-coded index based weather effects to ID based weather types stored in content files. To this end we introduce a new record type (Weather). When loading older omwgame files we will inject weather records for the ten default weather types. +We make weather types customisable by moving from hardcoded, index-based weather effects to ID-based weather types stored in content files. To achieve this, we introduce a new record type ("Weather"). When loading older omwgame files, we will inject weather records for the ten default weather types (`sys::WeatherAsh`, `sys::WeatherBlight`, etc.). -A weather record is made up from sub-records, each describing a weather effect. A weather record can have any number of weather effect sub-records. +A weather record is made up of subrecords, each describing a weather effect. A weather record can have any number of weather effect subrecords. -There are three types of effect sub-records: +There are four types of weather effect subrecords: -* Magic effect: A magic effect that is applied to all actors within active cells while the respective weather type is active. -* Sky: Modifications to the visuals of the sky (e.g clouds) -* Particles: Particle effects (e.g. rain) -* Event: Events that can happen with a certain probability while the weather type is active (e.g. lightning). Events happen at random locations. +* "Magic Effect": A magic effect that is applied to all actors within active cells while the respective weather type is active +* "Sky": Modifications to the visuals of the sky (e.g, clouds) +* "Particles": Particle effects (e.g., rain) +* "Event": Events that can happen with a certain probability while the weather type is active (e.g., lightning); events happen at random locations -Magic effect sub-records contain the ID of the respective magic effect. All other sub-records contain an integer ID specifying the effect and (if necessary) additional parameters. +Magic effect subrecords contain the ID of the respective magic effect. All other subrecords contain an integer ID specifying the effect and (if necessary) additional parameters. -All effects described in these sub-records affect active cells only. +All effects described in these subrecords affect active cells only. ### Sky -TODO all existing sky effects and possibly new ones +TODO: +- Should cover all existing sky effects and possibly new ones ### Particles -TODO all existing particle effects and possibly new ones +TODO: +- Should cover all existing particle effects and possibly new ones ### Event We will add the following event IDs: -* 0: Scripted Event: Additional data consists of the name of the script to be run when the event is triggered. The script takes the following arguments: worldspace (string), location (Vector3), weather ID (string) -* 1: Lightning - -In addition to the event ID and event ID specific data event sub-records have an event trigger block. +* "0": Scripted event; additional data consists of the name of the script to be run when the event is triggered; the script takes the following arguments: worldspace (`String`), location (`Vector3`), weather ID (`String`) +* "1": Lightning -### Event, Trigger +In addition to the event ID and event-ID-specific data, event subrecords have an "Event Trigger" block: -* Chance: The chance of the event happening (floating number in the range between 0 and 1) per unit of time. We may want to make the unit of time globally configurable with a new GMST. -* Min-Distance (optional): The minimum distance from the player at which the effect can take place (0 by default). -* Max-Distance (optional): The maximum distance from the player at which the effect can take place (infinite by default): +* Chance: Floating number in the range between 0 and 1; the chance per unit of time that the event happens (we may want to make the unit of time globally configurable with a new GMST) +* (optional) Min-Distance: The minimum distance from the player at which the effect can take place (0 by default) +* (optional) Max-Distance: The maximum distance from the player at which the effect can take place (infinite by default) ### Scripts -We need to introduce new script instructions regarding to weather, since the existing instructions are based on fixed number weather types or integer indexed weather types. These instructions can not be salvaged and we therefore declare them deprecated. +We need to introduce new script instructions regarding the weather, since the existing instructions are based on a fixed number of weather types or integer-indexed weather types. These instructions can not be salvaged and we therefore declare them deprecated. New script instructions: -* SetWeather (region ID, weather ID): Replaces change weather. Set current weather for a region. -* GetWeather (region ID): Replaces GetCurrentWeather. Returns current weather for a region. -* SetRegionWeather (region ID, weather ID, chance): Replaces ModRegion. Modify a single entry in the weather table of a region. Chance is of type long. We relax the requirement that the sum of the chance values need to be 100. -* UpdateRegionWeather (region ID): Forces a new roll on the weather table of a region. +* `SetWeather(regionId, weatherId)`: Sets the current weather for the region with the ID `regionId` to `weatherId` (replaces `ChangeWeather`) +* `GetWeather(regionId)`: Returns the current weather ID for the region with the ID `regionId` (replaces `GetCurrentWeather`) +* `SetRegionWeather(regionId, weatherId, chance)`: Modifies the entry `weatherId` in the weather table of the region with the ID `regionId`; `chance` is of type `long`; we relax the requirement that the sum of the chance values needs to be 100 (replaces `ModRegion`) +* `UpdateRegionWeather(regionId)`: Forces a new roll on the weather table of the region with the ID `regionId` ## Water -We currently have only a single water type that is hard-coded. We name this water type sys::Water and inject a suitable record of the new type (see below) during loading of older omwgame files. +We currently have but a single water type that is hardcoded. We name this water type `sys::Water` and inject a suitable record of the new type (see below) during loading of older omwgame files. -We generalise the concept of water to liquid by introducing a new record type (Liquid). Liquid records can be used both for different looking water type (more swampy water, water with different colouration) but also for completely different liquid types (e.g. Lava). +We generalise the concept of water to the concept of liquid by introducing a new record type ("Liquid"). Liquid records can be used both for different-looking water types (e.g., more swampy water, water with different colouration) but also for completely different liquid types (e.g. lava). -To support the water type sys::water we also need to add a new magic effect (sys::SuffocationEffect). +**Note**: Morrowind only uses one liquid type, which we refer to as "water". (While lava does exist in the game, it is handled with activators, rather than as a liquid type) -Liquid records are referenced both in worldspace records (see Cells & Worldspaces section) and in body of liquid records (see Misc section). +To support the water type `sys::Water`, we also need to add a new magic effect (`sys::SuffocationEffect`). + +Liquid records are referenced both in worldspace records (see *Cells, Worldspaces & Areas* section) and in the body of liquid records (see *Misc* section). A liquid record consists of the following fields: -* Effects: IDs of zero or more magic effects that applied to actors while in the liquid. -* Submerged Effects: IDs of zero or more magic effects that are applied to actors while completely submerged in the liquid. -* Liquid type ID: Integer, hard-coded -* Additional parameter, specific to liquid type +* Effects: IDs of zero or more magic effects which are applied to actors while in the liquid +* Submerged Effects: IDs of zero or more magic effects that are applied to actors while completely submerged in the liquid +* Liquid Type ID: `long`, hardcoded +* Additional parameters, specific to liquid type We need to support at least one liquid type (ID 0: Water) from the start. Other types can be added. -TODO check about other ways to handle visuals for liquid types that requires less hard-coding. Shaders? +TODO: +- Are there other ways to handle visuals for liquid types that require less hardcoding? +- Use of shaders? ## Magic Effects -Currently we have a fixed number of magic effects that are referenced by an integer index. - -We move over the magic effect records to a string ID based scheme. +Currently, we have a fixed number of magic effects that are referenced by an integer index. -We also introduce a new effect (sys::SuffocationEffect) by injecting a record when loading from older omwgame files. This effect triggers the normal procedure for running out of air. Note that this will require creating a new effect icon. +We move over to effect records with a string-ID-based scheme. -Effect records can be deleted without restrictions. A content developer can add new effect records, again without restrictions. +We also introduce a new effect (`sys::SuffocationEffect`) by injecting a record when loading from older omwgame files. This effect triggers the normal procedure for running out of air. -We add a field to the effect record that contains the effect's name, currently stored in a GMST. +**Note**: This will require creating a new effect icon, which will be stored internally along with other resources that are part of the game engine. -We also add an optional field that contains the ID of a script function that is called when the magic effect takes effect. The function takes two arguments: +Effect records can be deleted without restrictions. A content developer can add new effect records - again without restrictions. -* The reference of the effects source. This would be a spellcaster in case of a spell or an item in case of an enchantment. -* A list of target references. +An effect record consists of the following fields: -Furthermore we add a second optional field that contains the ID of a script function that is called when the magic effect ends (only relevant for non-instant effects). The function signature is the same. +* Name (currently stored in a GMST) +* (optional) Effect Function: Contains the ID of a script function that is called when the magic effect takes effect; the function takes two arguments: the reference of the effects source (this would be a spellcaster in case of a spell or an item in case of an enchantment) and a list of target references +* (optional) Wear-Off Function: Contains the ID of a script function that is called when the magic effect ends (only relevant for non-instant effects); the function takes the same two arguments as the "Effect Function" field -For the existing effects we will inject scripts that match the previously hard-coded effects when loading from an older omwgame file. This will require the addition of a significant number of new script instructions that are all trivial, since they will just call existing engine functions. +For the existing effects, we will inject scripts that match the previously hardcoded effects when loading from an older omwgame file. This will require the addition of a significant number of new script instructions that are all trivial, since they will just call existing engine functions. -It is important to generalise as much as possible when creating these new script functions. For example we won't have a DivineIntervention script instruction. Instead we add a GetClosestMarker function and use existing functions for querying position and cell and then use the new Teleport instruction. +It is important to generalise as much as possible when creating these new script functions, e.g., we won't have a `DivineIntervention` script instruction. Instead we add a `GetClosestMarker` function, use existing functions for querying position and cell and, then, use the new `Teleport` instruction. ## Input We allow the addition of customisable input functions that can be bound to keyboard buttons in the usual way and can trigger the execution of scripts. Existing input bindings are not affected by this. -To this end we introduce a new record type (Input): +To this end, we introduce a new record type ("Input"): * Label -* Tooltip (string, optional) -* Default key (integer) -* Key down event script (string, optional): A script that is executed when the key is pressed. -* Key up event script (string, optional): A script that is executed when the key is released. -* Key pressed event script (string, optional): A script that is executed periodically while the key is down. -* Key pressed delta (float): Minimum time between two executions of the key pressed event script. Defaults to a reasonable value in the absence of this field. +* (optional) Tooltip: `String` value +* Default Key: `long` value +* (optional) Key-Press Event Script: Contains the ID of a script that is executed when the key is pressed +* (optional) Key-Release Event Script: Contains the ID of a script that is executed when the key is released +* (optional) Key-Hold Event Script: Contains the ID of a script that is executed when the key is pressed and held +* Key-Hold Delta: `float` value describing the minimum time between two executions of the "Key-Hold Event Script"; defaults to a reasonable value ## Held Items -We merge the probe and lockpick object types into a new object type: Held Item. The security skill related functions are handled via the ItemObjectInteraction script subrecord. When loading older omwgame files a suitable script in the sys namespace is injected for probing and lockpicking each. When transforming probe and lockpick records into Held Item records a ItemObjectInteraction subrecord referencing the respective script is injected. +We merge the probe and lockpick object types into a new object type: "Held Item". The Security-skill-related functions are handled via the `ItemObjectInteraction` script subrecord. When loading older omwgame files, a suitable script in the `sys` namespace is injected for probing and lock picking each. When transforming probe and lockpick records into held-item records, an `ItemObjectInteraction` subrecord referencing the respective script is injected. -If we enhance the animation system we may need to add additional subrecords that specify the pose for holding the item or the animation for using it. +**Note**: If we enhance the animation system, we may need to add additional subrecords that specify the pose for holding the item or the animation for using it. ## Pickpocketing -We add a new GMST (sys::ReversePickpocketing) of type integer that indicates if reverse pickpocketing is allowed (value other than 0) or not (value 0). +We add a new GMST (`sys::ReversePickpocketing`) of type `long` that indicates if reverse pickpocketing is allowed (value other than 0) or not (value 0). -We move the function for checking if pickpocketing succeeded from the C++ code to a new script function (sys::PickpocketTest). We inject this function when loading older omwgame files. +We move the function for checking if pickpocketing succeeded from the C++ code to a new script function (`sys::PickpocketTest`). We inject this function when loading older omwgame files. The function takes the following arguments: -* ref to thief actor -* ref to victim actor -* ref to item to be stolen -* reverse flag: integer, 0 for regular pickpocketing, 1 for reverse +* thiefId: Reference to the thief (actor) +* victimId: Reference to the victim (actor) +* stolenItemId: Reference to the item to be stolen +* reversePickpocket: `long` flag; set to 0 for regular pickpocketing, set to 1 for reverse pickpocketing The function returns 1 if the pickpocket attempt succeeded and 0 if it failed. -Items with the nopick tag are excluded from pickpocketing. +Items with the `noPick` tag are excluded from pickpocketing. ## Combat -De-hardcoding combat is a monumental task that we can not hope to complete within the time frame of stage 1. We will de-hardcode some parts that do not depend on large scale improvements to animation and can be handled with the scripting improvements in stage 1. Any further enhancements are left for stage 2 or more likely OpenMW 2.0. +De-hardcoding combat is a monumental task that we can not hope to complete within the time frame of stage 1. We will de-hardcode some parts that do not depend on large-scale improvements to animation and can be handled with the scripting improvements in stage 1. Any further enhancements are left for stage 2 or, more likely, OpenMW 2.0. ### Hits -We move the hit test check for C++ code to script functions. The following functions will be used: +We move the hit test check from C++ code to script functions. The following functions will be used: -* For melee weapons the HitTest script function defined in the respective WeaponType record; arguments: ref to weapon, ref to target -* For ranged weapons the HitTest script function defined in the respective WeaponType record; arguments: ref to weapon, ref to target, ref to ammunition -* For unarmed attacks the function sys::UnarmedHitTest; arguments: ref to weapon, ref to target +* `(weaponId, targetId)`: Function for melee weapons defined in the corresponding WeaponType record; `weaponId` refers to the weapon used, `targetId` refers to the ID of the target +* `(weaponId, targetId, ammunitionId)`: Function for ramged weapons defined in the corresponding WeaponType record; `weaponId` refers to the weapon used and `targetId` refers to the ID of the target, and `ammunitionId` refers to the ammunition used by the ranged weapon +* `sys::UnarmedHitTest(weaponId, targetId)`: Default function for unarmed attacks; `weaponId` refers to the weapon used and `targetId` refers to the ID of the target -All functions return an integer: 0 (no hit), a value other than 0 (hit) +All functions return 0 if the attack misses and a value other than 0 on a successful hit. -sys::UnarmedHitTest and default functions for armed hit tests (sys::MeleeHitTest, sys::RangedHitTest) are injected when loading older omwgame files. +`sys::UnarmedHitTest` and default functions for armed-hit tests (`sys::MeleeHitTest` and `sys::RangedHitTest`) are injected when loading older omwgame files. ### Script Enhancements We add a new script function: -* GetTargetRef: Returns the current target of an actor. Returns a NullRef if the actor is not in combat. Also returns a NullRef when used on an instance that is not an actor. +* `GetTargetRef`: Returns the current target of an actor; returns a null reference if the actor is not in combat or if it is used on an instance that is not an actor We add two new script instructions: -* DeclareWar l: Similar to StartCombat, but specifies a whole group of enemies. l is a list of references. -* DeclarePeace l: Ends the hostilities resulting from previous combat towards the actors listed in the reference list l. We may need to add a grace period after this instruction during attacks do not cause hostility again, so we can handle non-instant attacks. +* `DeclareWar l`: Similar to `StartCombat`, but specifies a whole group of enemies; `l` is a list of references +* `DeclarePeace l`: Ends the hostilities resulting from previous combat towards the actors listed in the reference list `l`. **Note**: We may need to add a grace period after this instruction during which attacks do not cause hostility again, so we can handle non-instant attacks. ### Script Hooks -We add three new script hooks. - -sys::Kill is executed whenever an actor is killed. Scripts for this hook take the following arguments: - -* Ref to target -* Ref to actor who killed the target (may be a null ref) +We add three new script hooks: -sys::CombatStarted and sys::CombatEnded are executed when the player enters or exits combat, respectively. +* `sys::Kill`: Executed whenever an actor is killed; scripts for this hook take the following two arguments: reference to the target and reference to the actor who killed the target (may be a null reference) +* `sys::CombatStarted`: Executed when the player enters combat +* `sys::CombatEnded`: Executed when the player exits combat ## Music @@ -1326,12 +1356,12 @@ The playlist record has the following fields: The titles on the playlist are all the titles in the music directory and the titles listed explicitly. -When playing a playlist first the probability is considered to randomly decide if a track is played or not. Then a track from the list is chosen. +When playing a playlist, first the probability is considered to randomly decide if a track is played or not. Then a track from the list is chosen. If a playlist is played in loop mode the process above is repeated whenever: * The current track ends -* If no track is playing at reasonably chosen time intervals +* No track is playing at reasonably chosen time intervals ### Background and Event Music @@ -1376,11 +1406,11 @@ We add two playlists (sys::ExplorePlaylist and sys::CombatPlaylist) which are po We add a GMST (sys::DefaultBackgroundMusic) with a default value of "sys::ExplorePlaylist". Whenever starting a new game or loading a game with no background music running the playlist specified in this GMST is played as background music. If the GMST holds an empty string no background music is played by default. -We add a script to the sys::CombatStarted and sys::CombatEnded hook each. The former plays sys::CombatPlaylist looped and forced. The later stops event music. +We add a script to the sys::CombatStarted and sys::CombatEnded hook each. The former plays sys::CombatPlaylist looped and forced. The latter stops event music. ### Location-Based Background Music -We add an optional string sub-record to the records listed below. This field contains a single track or a playlist, that determine the background music for the respective location. +We add an optional string sub-record to the records listed below. This field contains a single track or a playlist that determine the background music for the respective location. * Worldspace * Region @@ -1399,8 +1429,8 @@ The process of character creation currently allows for very little customisation Currently the character creation process runs through these steps: -1. Choose Name -2. Choose Race and appearance +1. Choose name +2. Choose race and appearance 3. Choose a method for class selection and perform class selection (3 methods) 4. Choose birthsign 5. Review @@ -1429,7 +1459,7 @@ We add several new script instructions. * CharacterCreationBack: Go back to previous step * CharacterCreationNext: Go to next step (ignored if the next step had not been accessed before) -n is an integer argument with one of several hard-coded integer values: +n is an integer argument with one of several hardcoded integer values: * 0: Name GUI * 1: Race GUI @@ -1460,11 +1490,11 @@ Likewise the character selection method of answering a set of quests can be impl The vanilla (character-)level-up system allows almost no modification. We keep the existing hard-coded system, but make it more configurable, and we also allow content developers to completely replace the existing system. -### State Un-Hiding +### State Unveiling Checks for a level up are based on the skill progression since the last level up. In vanilla this state is not accessible to content developers or players. -We address this issue by turning these hidden variables into record variables attached to the player character. The variables have the type long, a name matching the ID of the skill and are created on the fly whenever the engine detects a skill level up. Note that regular script instructions that change skill level do not count towards level ups. +We address this issue by turning these hidden variables into record variables attached to the player character. The variables have the type long and a name matching the ID of the skill, and are created on the fly whenever the engine detects a skill level up. Note that regular script instructions that change skill level do not count towards level ups. We also add another record variable (LevelUpCount) that counts the number of major and minor skill level-ups. @@ -1478,7 +1508,7 @@ Some NPCs are flagged as skill trainers. These trainers can train the player in * New script (sys::SkillTrainingTest): Tests if NPC can train player in a skill (returns 0 or 1). This test is performed after the faction standing test and after the skill list is trimmed down via sys::SkillTrainCount. The function takes the following arguments: ref to player, ref to NPC, ID of skill * New script (sys::SkillTrainingApplied): Executed after training has been completed. Arguments are the same as with sys::SkillTrainingTest. -When loading from an old omwgame file GMST and scripts are injected. sys::SkillTrainCount defaults to 3, sys::SkillTrainingTest performing the usual tests on skill level and attributes and sys::SkillTrainingApplied defaults to an empty script. +When loading from an old omwgame file, GMST and scripts are injected. sys::SkillTrainCount defaults to 3, sys::SkillTrainingTest performs the usual tests on skill level and attributes and sys::SkillTrainingApplied defaults to an empty script. ### Level-Up Test @@ -1513,7 +1543,7 @@ The default implementation (injected when loading from an older omwgame file) re ### Level-Up Image -The level up dialogue contains an image that depends on how the skill increase is distributed among specialisations. The function that decides about this image is very simple. However since specialisations won't be hard-coded anymore we can not simply pass these values into the function without first establishing more involved script support than planned for stage 1. +The level up dialogue contains an image that depends on how the skill increase is distributed among specialisations. The function that decides about this image is very simple. However, since specialisations won't be hardcoded any more, we can not simply pass these values into the function without first establishing more involved script support than planned for stage 1. This is actually not a problem since we can simply track the increase values in record variables, which may also prove useful for other purposes. @@ -1547,13 +1577,13 @@ This process is complicated by the existence of levelled item lists, which makes We will make several changes to increase the flexibility of item restocking. -First we move the re-fill operation from after a trade is completed to before a trade is completed. This should not affect existing content. +First we move the refill operation from after a trade is completed to before a trade is completed. This should not affect existing content. -We add three more scripts (optional, specified via new string fields in the actor's record) that hook into the re-fill process. +We add three more scripts (optional, specified via new string fields in the actor's record) that hook into the refill process. -1. Check if a re-fill should be performed; called at the beginning of every trade cycle. Arguments: ref to actor, integer (1 if first trade since talking to the merchant, 0 otherwise). Returns an integer (0 no re-fill required, otherwise re-fill is required). If script is not present a re-fill is performed). -2. Called for every item that the re-fill algorithm flags for removal. Arguments: ref to actor, ref to item. Returns an integer (0 if item should be kept, otherwise item is removed). If script is not present the removal takes place. -3. Called at the end of re-fill procedure. This gives content authors the opportunity to make other modifications. Arguments: ref to actor. No return value. +1. Check if a refill should be performed; called at the beginning of every trade cycle. Arguments: ref to actor, integer (1 if first trade since talking to the merchant, 0 otherwise). Returns an integer (0 no refill required, otherwise refill is required). If this script is not present, a refill is performed). +2. Called for every item that the refill algorithm flags for removal. Arguments: ref to actor, ref to item. Returns an integer (0 if item should be kept, otherwise item is removed). If this script is not present, the removal takes place. +3. Called at the end of refill procedure. This gives content authors the opportunity to make other modifications. Arguments: ref to actor. No return value. ## Fast-Travel @@ -1666,23 +1696,23 @@ NPC schedules are an advanced topic and as such would be a better fit for stage We run schedules only in active cells. But we also need to consider passive cells. -Here are a few examples. For simplicity lets consider a one-dimensional cell grid with the usual pattern of one cell around of the player cell being active. +Here are a few examples. For simplicity let's consider a one-dimensional cell grid with the usual pattern of one cell around of the player cell being active. For this example let the player be in cell 0. Now consider NPC A, who is in cell 2 (not active). He has a schedule that takes him to cell 1 sometimes. As a player we would expect to see him from the distance then. Another example with the same setup as above: While A is moving within cell 2 towards cell 1, the player relocates to cell 2. He would to see A make his way towards cell 1. Moreover if the player moves back to cell 0 and then returns to cell 2, he would expect to see A having made some progress. -Conclusion: We must track all actors who's schedule can take them into active cells. +Conclusion: We must track all actors whose schedules can take them into active cells. -It is not clear yet how to achieve this goal best. In the most simple implementation we could just estimate the time it takes to perform a task in a schedule and spawn the actor into a active cells accordingly. This would however not cover the second example. +It is not clear yet how to achieve this goal best. In the most simple implementation we could just estimate the time it takes to perform a task in a schedule and spawn the actor into active cells accordingly. This would however not cover the second example. -Alternatively we could invent a semi-active cell state and apply it to all cells that contain NPCs who's schedule can touch the active cells. This semi-active state would not run any scripts, nor would it render or run animations. And it may use a simplified/limited form of physics. This approach would cover all cases. However it has the potential for a severe performance impact. +Alternatively we could invent a semi-active cell state and apply it to all cells that contain NPCs whose schedules can touch the active cells. This semi-active state would not run any scripts, nor would it render or run animations, and it may use a simplified/limited form of physics. This approach would cover all cases. However, it has the potential for a severe performance impact. -Again alternatively, we may offer both implementation and switch between them base on settings/available processing power. +Again alternatively, we may offer both implementations and switch between them base on settings/available processing power. ### Actor Tracking -Independently from the chosen implementation we need to track all actors who's schedules are relevant to the active cells. This is a problem because with the default data structures we would at least need to scan every single cell on startup for relevant actors. The performance impact of such an approach is most likely not acceptable. +Independently from the chosen implementation, we need to track all actors whose schedules are relevant to the active cells. This is a problem because with the default data structures we would at least need to scan every single cell on start-up for relevant actors. The performance impact of such an approach is most likely not acceptable. Therefore information about which actor's schedule is relevant to which cell needs to be stored in a separate data structure that exists outside of the cell records. @@ -1698,9 +1728,9 @@ Each task sub-record consists of: The time table of a schedule is allowed to have holes (e.g schedule 1 ending at 10:00 and schedule 2 beginning at 10:15). -Task may also overlap partially, but no two task may start at the same time. +Tasks may also overlap partially, but no two tasks may start at the same time. -When picking a task from the schedule all task which contain the current time are considered. Of these the task with the earliest start time is chosen. +When picking a task from the schedule, all tasks which contain the current time are considered. Of these the task with the earliest start time is chosen. A new task is picked only when the current one has expired. @@ -1708,7 +1738,7 @@ A new task is picked only when the current one has expired. Schedules can be assigned to actors either in the actor record or with a new script instruction. -When explicit switching from one schedule to another, tasks with the ignore flag are ignored. This feature is intended for transitory tasks, that are pointless if the previous task hasn't been performed. +When explicitly switching from one schedule to another, tasks with the ignore flag are ignored. This feature is intended for transitory tasks, that are pointless if the previous task hasn't been performed. We add script instructions that allow for querying the current schedule of an actor. We also add a script instruction to prematurely end a task. @@ -1722,14 +1752,14 @@ This is an early draft of the new system and will almost certainly require more Idle activities are currently bound to the Wander package. This is suboptimal, both because other packages could benefit from it and because storing the idle function in a package instead of the NPC makes it hard to give the NPC a characteristic idle pattern. -We add a list of idle chances for each available idle animation to the NPC record. Ideally this list should be extensible, but first we need to improve the animation system so that the number of idle animations isn't hard-coded anymore. This may be a stage 2 task. +We add a list of idle chances for each available idle animation to the NPC record. Ideally this list should be extensible, but first we need to improve the animation system so that the number of idle animations isn't hard-coded any more. This may be a stage 2 task. Each package contains two fields related to idling behaviour: * Chance: A value between 0 and 1 that specifies the chance (per reasonable unit of time) of an idle animation to be executed. * Dialogue Chance: A value between 0 and 1 that specifies the chance of an idle dialogue if an idle animation is executed. -We may decide to use a scale of 0 to 100 instead of 0 to 1 to increase usability for less mathematical minded content developers. +We may decide to use a scale of 0 to 100 instead of 0 to 1 to increase usability for less mathematically minded content developers. The old wander package keeps its old idle list. When active it disables the new idle system. @@ -1739,7 +1769,7 @@ If a NPC does not have any active packages the idle function is still active. In We categorise package into four roles: -* Legacy: Old packages, generated either via script instructions or via AI package list in actor record. Removed once it runs out (if not flagged for repeat). Deprecated. +* Legacy: Old packages, generated either via script instructions or via AI package list in the actor record. Removed once they run out (if not flagged for repeat). Deprecated. * Schedule: Inserted by the schedule system. Can not be directly manipulated via script instructions. * Manual: New packages, generated via new script instructions. * Situation: Inserted via other game mechanics (e.g. combat, breath). Can not be directly manipulated via script instructions. @@ -1752,7 +1782,7 @@ We add new script instructions to query the package state of an actor and to ins Each package has two fields that describe its duration: -* Duration itself: Package will self-delete once the current time is larger than the start time plus the duration) +* Duration itself: Package will self-delete once the current time is larger than the start time plus the duration * Repeat flag: Package adds itself again at the end of the end of the duration Packages lacking these fields persist indefinitely unless explicitly removed. @@ -1763,7 +1793,7 @@ Some legacy packages already have these fields. Schedule packages will not have We translate the packages AiFollow, AiActivate and AiTravel into the new format. -AiFollow and AiTravel specify a location by given a set of coordinates and an optional cell ID. We will replace part of the package with a different format. +AiFollow and AiTravel specify a location by being given a set of coordinates and an optional cell ID. We will replace part of the package with a different format. There are two possible locations sub-records of which each package may only contain one: @@ -1792,7 +1822,7 @@ This is a new package with no direct equivalent in the old system. The package c * A list of locations (in the new format) * A loop flag -The actor will walk from one location to the next. Once he reached the last location he will either continue with the first location (if the loop flag is set) or walk down the list of locations in opposite direction and then start over (if the loop flag is not set). +The actor will walk from one location to the next. Once he has reached the last location he will either continue with the first location (if the loop flag is set) or walk down the list of locations in opposite direction and then start over (if the loop flag is not set). The obvious use case for this package is a guard patrolling a perimeter or a place. @@ -1821,7 +1851,7 @@ This results in multiple issues: * When localising the developer must hunt down all references to the localised strings and change them manually (cell names and topics). * When updating a localisation to a new version of the content file the translator has to manually pierce together his old translation and the new content file. -* Changing a string that is used as an internal reference breaks the connection to the referenced records in relation to the unlocalised file, which in turn makes it impossible to utilise automated tools that help for the localisation process. +* Changing a string that is used as an internal reference breaks the connection to the referenced records in relation to the non-localised file, which in turn makes it impossible to utilise automated tools that help for the localisation process. * Content files of mismatching languages generally can't be combined in a content file list. Mixing languages is generally not desirable for playing, but may sometimes be unavoidable. The ability to mix languages is highly desirable for developers (e.g. debugging). ## Encoding @@ -1834,7 +1864,7 @@ Localisation will be contained in separate content files (omwlang) that are to b The launcher will treat a content file and its localisation file as a single entity (see section about Identifying Meta-Data). -We may add a launcher language setting the determines the default language chosen by the launcher. This needs to be a separate option from the legacy encoding setting, which is currently also called language setting. +We may add a launcher language setting that determines the default language chosen by the launcher. This needs to be a separate option from the legacy encoding setting, which is currently also called language setting. ## Localisation Records @@ -1868,7 +1898,7 @@ fallback-lp-somekey=Some Text When looking up fallback strings the preferred language is chosen by the default language setting in the launcher (we may decide to provide a separate setting for fallback strings if this doesn't result in an overly complicated settings UI). -If a fallback string with this language-code is available it will be used. If no such fallback string is available, the default fallback string (without language-code prefix) will be used exist. The default fallback string must exist for all fallback strings with language codes. Otherwise the cfg file must be considered defective. +If a fallback string with this language-code is available it will be used. If no such fallback string is available, the default fallback string (without language-code prefix) will be used. The default fallback string must exist for all fallback strings with language codes. Otherwise the cfg file must be considered defective. We add an option to the importer tool to import strings only, which then will be merged (with a user-specified language-code prefix) into an existing cfg file. @@ -1880,7 +1910,7 @@ The handling of cell names in vanilla MW is one of the major localisation proble We turn the name field (the ID) into an ID-only field. The actual user visible name is moved into an optional localisation record. -We allow referencing of cells by both ID and name. If the name is used we emit an unobtrusive warning message. +We allow referencing of cells by both ID and name. If the name is used, we emit an unobtrusive warning message. This change does not fix the problem of already existing localisations that have modified the names of cells. See the section about localisation mapping below for a workaround. @@ -1909,7 +1939,7 @@ Issues #2 and #3 can be enforced at least partially. We declare the use of user ## Localisation Mapping -The new localisation scheme leaves existing localisations un-addressed. Localisations of Morrowind.esm break the connection of cell names and topics in relation to the un-localised Morrowind.esm (English). +The new localisation scheme leaves existing localisations unaddressed. Localisations of Morrowind.esm break the connection of cell names and topics in relation to the un-localised Morrowind.esm (English). We provide a workaround for this problem by introducing localisation mapping. Localisation mapping is a language-specific list of ID/user visible text pairs, provided as a text file. A similar scheme exists for the Russian localisation. We may decide to simple extend the existing implementation of this feature in OpenMW to provide localisation mapping. @@ -1939,7 +1969,7 @@ Note that by going down this route we do not block the addition of a full-scale ## Skinning -Under the term skinning we understand changes to the look of a GUI without affecting the functionality. Vanilla Morrowind already provides a certain level of skinning-capacity though textures and Morrowind.ini settings. We are going to expand on these and clean up the skinning process. After 1.0 all skinning should be done exclusively through content files and associated resources files. Skinning does not belong into ini/cfg files. +Under the term skinning we understand changes to the look of a GUI without affecting the functionality. Vanilla Morrowind already provides a certain level of skinning-capacity though textures and Morrowind.ini settings. We are going to expand on these and clean up the skinning process. After 1.0 all skinning should be done exclusively through content files and associated resources files. Skinning does not belong in ini/cfg files. ### Windows @@ -1981,7 +2011,7 @@ Dynamic stats are more complicated since they merge three elements: The first two go together well enough, but we should provide an option to decouple the enemy health bar and turn it into its own HUD elements. -We also need to consider that after 1.0 we won't always have three dynamic stats. There could be more or (in stage 2) less. The HUD element must be build in a way that can adjust its layout accordingly without manual layouting. +We also need to consider that after 1.0 we won't always have three dynamic stats. There could be more or (in stage 2) less. The HUD element must be built in a way that it can adjust its layout accordingly without manual layouting. ### Notifications @@ -2022,7 +2052,7 @@ We add a couple of new input elements that are each represented as a separate wi * Numerical input: Takes a range, a precision value and a default value and returns a float. * Text input: Takes a default value and returns a string. -Additionally each window has a titlebar, an okay button and and optional cancel button. +Additionally each window has a title bar, an okay button and and optional cancel button. For each element we add a new script instruction that brings up the respective element. These instructions take the following arguments: @@ -2077,10 +2107,10 @@ We may explore an alternative GUI layout consisting of a vertical bar (maybe on The current horizontal text-based category selector is problematic for two reasons: -* Since we hand over the creation of categories to mod developers we can expect a large number of them and therefore scaling becomes an issue. Horizontally arranged text items scale terribly because text items also primarily extend horizontally. There are workarounds for this problem but these are either bad or add alternative interface (e.g. a pulldown menu listing all tabs) which isn't a great solution either. Under no circumstances will we use the workaround that adds two arrow buttons left and right of the tab bar to change the visible section of the tab bar. This is an anti-pattern and everyone who has ever committed the horrendous crime of implementing this GUI-atrocity deserves to end up in usability hell. +* Since we hand over the creation of categories to mod developers we can expect a large number of them and therefore scaling becomes an issue. Horizontally arranged text items scale terribly because text items also primarily extend horizontally. There are workarounds for this problem but these are either bad or add alternative interface (e.g. a pull-down menu listing all tabs) which isn't a great solution either. Under no circumstances will we use the workaround that adds two arrow buttons left and right of the tab bar to change the visible section of the tab bar. This is an anti-pattern and everyone who has ever committed the horrendous crime of implementing this GUI-atrocity deserves to end up in usability hell. * In table mode we already have a bar at the top of the window. Two bars on the same window border give a cluttered, unclean appearance. -If we do use an alternative layout we need to add an icon field to the ItemCategory record and we also need to consider, if we drop the vanilla one completely or let the user choose. Unless there is strong opposition (this is a significant change from Vanilla after all) we should choose the former option. If we opt for a less radical change we still should switch from text to icons to counter the scaling problem, even if we stick with the horizontal layout. +If we do use an alternative layout, we need to add an icon field to the ItemCategory record and we also need to consider whether we drop the vanilla one completely or let the user choose. Unless there is strong opposition (this is a significant change from Vanilla after all) we should choose the former option. If we opt for a less radical change we still should switch from text to icons to counter the scaling problem, even if we stick with the horizontal layout. ## Character State @@ -2151,7 +2181,7 @@ We let scripts to run as operations (exclusively for now). In the context of Ope * An operation runs in a separate thread and has read only access to the content data * Multiple operations can run concurrently, but only one operation of each type (e.g. saving, verifier, python script) at a time -* While at least one operation is running the content data can not be modified +* While at least one operation is running, the content data can not be modified * The operation can be terminated by the user at any time * The operation reports back to the main thread via messages (shown in a table like with the verifier) and progress state (represented as progress bar) @@ -2171,7 +2201,7 @@ Note that all debugging tools will require that OpenMW is started from OpenMW-CS ### Marking -We add a new keyboard-shortcut to OpenMW (only available when started from OpenMW-CS). When used OpenMW will look at the instance under the mouse pointer (or the crosshair if the mouse pointer is hidden). If this instance is part of a content file (i.e. not created during play) OpenMW will send the cell and RefID of the instance to OpenMW-CS. OpenMW-CS inserts the instance info into a table. +We add a new keyboard shortcut to OpenMW (only available when started from OpenMW-CS). When used OpenMW will look at the instance under the mouse pointer (or the crosshair if the mouse pointer is hidden). If this instance is part of a content file (i.e. not created during play) OpenMW will send the cell and RefID of the instance to OpenMW-CS. OpenMW-CS inserts the instance info into a table. This table functions in a similar way to the verifier table; allowing the content developer to jump directly to records that have been identified as being in need of attention. @@ -2191,15 +2221,15 @@ We also add function that produces a warning if the user tries to create an ID o OpenMW-CS currently has very little in-application help. Improving this will be an ongoing process. For stage 1 we will focus on scripting. -We will add a keyboard-shortcut and a context menu item to the script editor. When activated while the cursor is on a keyword we show a help text that explains the syntax and the function. +We will add a keyboard shortcut and a context menu item to the script editor. When activated while the cursor is on a keyword we show a help text that explains the syntax and the function. Ideally we would want to use the same help text within the application and in our documentation. A way to handle this needs to be determined. -The location where we show the help text needs to be determined. For script editor views we could re-use the widget that displays errors. Or we could add a separate subview type for help text. We may decide to add a help main menu item from which the help system can be navigated. +The location where we show the help text needs to be determined. For script editor views we could reuse the widget that displays errors. Or we could add a separate subview type for help text. We may decide to add a help main menu item from which the help system can be navigated. -## Spellchecker +## Spell Checker -We will add a spellchecker by utilising an existing spellcheck solution. All names (e.g. items, cells, races) will be implicitly added to the list of known words. +We will add a spell checker by utilising an existing spell check solution. All names (e.g. items, cells, races) will be implicitly added to the list of known words. ## Porting Tools @@ -2239,19 +2269,19 @@ A user settings record consists of the following fields: The following user setting types are available: -* bool (type 0): GMST is an integer; GUI is a checkbox; additional data: default value -* numeric, integer (type 1): GMST is an integer; GUI is a spinbox; additional data: upper bound, lower bound, default value -* numeric, float (type 2): GMST is a float; GUI is a spinbox; additional data; upper bound, lower bound, precision, default value -* list (type 3) GMST is an integer; GUI is a combobox; additional data; list of strings for combobox text, default value +* bool (type 0): GMST is an integer; GUI is a check box; additional data: default value +* numeric, integer (type 1): GMST is an integer; GUI is a spin box; additional data: upper bound, lower bound, default value +* numeric, float (type 2): GMST is a float; GUI is a spin box; additional data; upper bound, lower bound, precision, default value +* list (type 3) GMST is an integer; GUI is a combo box; additional data; list of strings for combo box text, default value * slider (type 4): GMST is a float; GUI is a slider; additional data; upper bound, lower bound, default value ### Categories User settings categories are represented as separate pages/tabs in the GUI. We specify categories via category IDs (strings). Currently there does not appear to be a need for a separate user settings category record, since this record would have no data. -Content files can create new categories (simply by referencing them in a user settings record) or add to existing categories (including the vanilla ones). We should consider to add a couple of additional default categories (including localised labels, but empty and invisible until populated by content files) to help content developers organise their settings in a coherent way. +Content files can create new categories (simply by referencing them in a user settings record) or add to existing categories (including the vanilla ones). We should consider adding a couple of additional default categories (including localised labels, but empty and invisible until populated by content files) to help content developers organise their settings in a coherent way. -The categories in vanilla MW are General, Audio, Controls and Graphics. Content files can add settings to these too. But we should consider reorganisation. General is not a good category. We should consider splitting it up. We should also consider moving the keybindings into their own category. A separate keybindings category would have to be the only exception to the rule that content files are allowed to add settings to pre-existing categories. +The categories in vanilla MW are General, Audio, Controls and Graphics. Content files can add settings to these too, but we should consider reorganisation. General is not a good category. We should consider splitting it up. We should also consider moving the key bindings into their own category. A separate key bindings category would have to be the only exception to the rule that content files are allowed to add settings to pre-existing categories. ### Hook @@ -2285,13 +2315,13 @@ A LiquidBody object record has the following fields: * Height (float) * Shape -Shape is a closed bezier-curve that can be edited in the editor. This shape defines a surface (the liquid surface). The depths of liquid body is defined by the height value. +Shape is a closed Bézier curve that can be edited in the editor. This shape defines a surface (the liquid surface). The depths of liquid body is defined by the height value. We can imply that only the surface of the LiquidShape instance is exposed. The other sides do not require rendering. -## Dagoth'Ur Un-Fix +## Dagoth Ur Fix Un-Fix -We introduced a fix for a defect in Morrowind.esm by blocking remote access to instances of the object dagoth_ur_1 via mod instructions for dynamic stats. We will now bind this fix to a new integer GMST in the sys namespace. This will allow content developers to disable this fix in their mods (hopefully after given poor old Dagoth'Ur a bit more health). +We introduced a fix for a defect in Morrowind.esm by blocking remote access to instances of the object dagoth_ur_1 via mod instructions for dynamic stats. We will now bind this fix to a new integer GMST in the sys namespace. This will allow content developers to disable this fix in their mods (hopefully after giving poor old Dagoth Ur a bit more health). ## Comment Subrecords @@ -2314,7 +2344,7 @@ We add four new GMSTs of type integer (default value 1): These decide if the game is paused when in status mode (right-click), dialogue mode, journal mode or any of the crafting modes (e.g. alchemy). To handle sys::DialogPause==0 properly we ignore dialogues initiated by NPCs or via script, if the player is already in a dialogue. -## Instance Persistency +## Instance Persistence We add a new optional field to all object records and instance subrecords, a single enum-like integer. From 5ee731d86f942e597ad6976e368d8fb9c0af6236 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 27 Jun 2018 12:33:23 +0200 Subject: [PATCH 07/40] updated roadmap section --- docs/openmw-stage1.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/openmw-stage1.md b/docs/openmw-stage1.md index 9458b66bb..4707a4435 100644 --- a/docs/openmw-stage1.md +++ b/docs/openmw-stage1.md @@ -66,9 +66,9 @@ However this approach means additional work and would be of limited usefulness i ## Roadmap -We will continue with the revised roadmap scheme. This means we add a new milestone (openmw-stage1). After we are finished discussing this design document and have made necessary adjustments we will cut it up into individual tasks and add them to openmw-stage1. This milestone will then take the role of the current openmw-1.0 and openmw-cs-1.0 milestones. -Confirmed bug reports also go into openmw-stage1. Other issues (feature requests and tasks) only after we have approved them for near future development. -We will most like not have a separate openmw-stage1 for the editor, since for the bulk of the changes (The Grand De-hardcoding) most tasks we will require changes to both OpenMW and the editor. +We will continue with the revised roadmap scheme. This means we add a new label (stage1). After we are finished discussing this design document and have made necessary adjustments we will cut it up into individual tasks and add them to the tracker with the stage1 label. This label will then take the role of the current 1.0 label. +Confirmed bug reports also get tagged with stage1. Other issues (feature requests and tasks) only after we have approved them for near future development. +We will most likely not have a separate stage1 label for the editor, since for the bulk of the changes (The Grand De-hardcoding) most tasks we will require changes to both OpenMW and the editor. # Content File Format & Namespaces From 9c78364c45679a2846081f833507935cc4af7fe1 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Wed, 27 Jun 2018 22:19:09 +0200 Subject: [PATCH 08/40] Revert "Merge pull request #1771 from Xenkhan/master" This reverts commit 9667dd051c8f1794cd01605df9c88d10fe0cd514, reversing changes made to f52e06fc19b0d6cf05ad79ca17f29e5835720cc6. --- CHANGELOG.md | 1 - apps/openmw/engine.cpp | 29 ++++++++--------------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39af121aa..c1181b615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,6 @@ Bug #4459: NotCell dialogue condition doesn't support partial matches Bug #4461: "Open" spell from non-player caster isn't a crime Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal - Bug #4471: Retrieve SDL window settings instead of using magic numbers Bug #4474: No fallback when getVampireHead fails Bug #4475: Scripted animations should not cause movement Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8c3c9494c..72dfaa0e4 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -381,33 +381,20 @@ void OMW::Engine::createWindow(Settings::Manager& settings) setWindowIcon(); osg::ref_ptr traits = new osg::GraphicsContext::Traits; - int redSize; - int greenSize; - int blueSize; - int depthSize; - int stencilSize; - int doubleBuffer; - - SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &redSize); - SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &greenSize); - SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &blueSize); - SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthSize); - SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &stencilSize); - SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &doubleBuffer); - SDL_GetWindowPosition(mWindow, &traits->x, &traits->y); SDL_GetWindowSize(mWindow, &traits->width, &traits->height); - traits->red = redSize; - traits->green = greenSize; - traits->blue = blueSize; - traits->depth = depthSize; - traits->stencil = stencilSize; - traits->doubleBuffer = doubleBuffer; traits->windowName = SDL_GetWindowTitle(mWindow); traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS); traits->screenNum = SDL_GetWindowDisplayIndex(mWindow); - traits->vsync = vsync; + // FIXME: Some way to get these settings back from the SDL window? + traits->red = 8; + traits->green = 8; + traits->blue = 8; traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel + traits->depth = 24; + traits->stencil = 8; + traits->vsync = vsync; + traits->doubleBuffer = true; traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow); osg::ref_ptr graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits); From 5fcb09112767905d560f15808bc9e2e6d85083f3 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Wed, 27 Jun 2018 22:22:01 +0200 Subject: [PATCH 09/40] Replace FIXME with a detailed explanation of the issue --- apps/openmw/engine.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 72dfaa0e4..93153da87 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -386,7 +386,11 @@ void OMW::Engine::createWindow(Settings::Manager& settings) traits->windowName = SDL_GetWindowTitle(mWindow); traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS); traits->screenNum = SDL_GetWindowDisplayIndex(mWindow); - // FIXME: Some way to get these settings back from the SDL window? + // We tried to get rid of the hardcoding but failed: https://github.com/OpenMW/openmw/pull/1771 + // Here goes kcat's quote: + // It's ultimately a chicken and egg problem, and the reason why the code is like it was in the first place. + // It needs a context to get the current attributes, but it needs the attributes to set up the context. + // So it just specifies the same values that were given to SDL in the hopes that it's good enough to what the window eventually gets. traits->red = 8; traits->green = 8; traits->blue = 8; From 335e2c5897d73ea866807f427c5b362e5867c03a Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Thu, 28 Jun 2018 13:27:08 +1000 Subject: [PATCH 10/40] add keyData struct + general cleanup --- apps/openmw/mwgui/quickkeysmenu.cpp | 181 +++++++++++++--------------- apps/openmw/mwgui/quickkeysmenu.hpp | 20 +-- 2 files changed, 99 insertions(+), 102 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index e19df2bbb..819decfc1 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -43,27 +43,21 @@ namespace MWGui getWidget(mInstructionLabel, "InstructionLabel"); mMainWidget->setSize(mMainWidget->getWidth(), - mMainWidget->getHeight() + (mInstructionLabel->getTextSize().height - mInstructionLabel->getHeight())); + mMainWidget->getHeight() + + (mInstructionLabel->getTextSize().height - mInstructionLabel->getHeight())); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onOkButtonClicked); center(); + mKey = std::vector(10); for (int i = 0; i < 10; ++i) { - ItemWidget* button; - getWidget(button, "QuickKey" + MyGUI::utility::toString(i+1)); + mKey[i].index = i; + getWidget(mKey[i].button, "QuickKey" + MyGUI::utility::toString(i+1)); + mKey[i].button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); - - mQuickKeyButtons.push_back(button); - - mAssigned.push_back(Type_Unassigned); - - mAssignedId.push_back(std::string("")); - mAssignedName.push_back(std::string("")); - - unassign(button, i); + unassign(&mKey[i]); } } @@ -73,7 +67,7 @@ namespace MWGui for (int i=0; i<10; ++i) { - unassign(mQuickKeyButtons[i], i); + unassign(&mKey[i]); } } @@ -94,10 +88,7 @@ namespace MWGui // Check if quick keys are still valid for (int i=0; i<10; ++i) { - ItemWidget* button = mQuickKeyButtons[i]; - int type = mAssigned[i]; - - switch (type) + switch (mKey[i].type) { case Type_Unassigned: case Type_HandToHand: @@ -106,7 +97,7 @@ namespace MWGui case Type_Item: case Type_MagicItem: { - MWWorld::Ptr item = *button->getUserData(); + MWWorld::Ptr item = *mKey[i].button->getUserData(); // Make sure the item is available and is not broken if (item.getRefData().getCount() < 1 || (item.getClass().hasItemHealth(item) && @@ -116,51 +107,53 @@ namespace MWGui std::string id = item.getCellRef().getRefId(); item = store.findReplacement(id); - button->setUserData(MWWorld::Ptr(item)); + mKey[i].button->setUserData(MWWorld::Ptr(item)); break; } } } } - } - void QuickKeysMenu::unassign(ItemWidget* key, int index) + void QuickKeysMenu::unassign(struct keyData* key) { - mAssignedName[index] = ""; - mAssignedId[index] = ""; + key->button->clearUserStrings(); + key->button->setItem(MWWorld::Ptr()); - key->clearUserStrings(); - key->setItem(MWWorld::Ptr()); - while (key->getChildCount()) // Destroy number label - MyGUI::Gui::getInstance().destroyWidget(key->getChildAt(0)); + while(key->button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(key->button->getChildAt(0)); - if (index == 9) + if (key->index == 9) { - mAssigned[index] = Type_HandToHand; + key->type = Type_HandToHand; - MyGUI::ImageBox* image = key->createWidget("ImageBox", + MyGUI::ImageBox* image = key->button->createWidget("ImageBox", MyGUI::IntCoord(14, 13, 32, 32), MyGUI::Align::Default); + image->setImageTexture("icons\\k\\stealth_handtohand.dds"); image->setNeedMouseFocus(false); } else { - mAssigned[index] = Type_Unassigned; + key->type = Type_Unassigned; + key->id = ""; + key->name = ""; - MyGUI::TextBox* textBox = key->createWidgetReal("SandText", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); - textBox->setTextAlign (MyGUI::Align::Center); - textBox->setCaption (MyGUI::utility::toString(index+1)); - textBox->setNeedMouseFocus (false); + MyGUI::TextBox* textBox = key->button->createWidgetReal("SandText", + MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); + + textBox->setTextAlign(MyGUI::Align::Center); + textBox->setCaption(MyGUI::utility::toString(key->index + 1)); + textBox->setNeedMouseFocus(false); } } void QuickKeysMenu::onQuickKeyButtonClicked(MyGUI::Widget* sender) { int index = -1; - for (int i = 0; i < 10; ++i) + for(int i = 0; i < 10; ++i) { - if (sender == mQuickKeyButtons[i] || sender->getParent () == mQuickKeyButtons[i]) + if(sender == mKey[i].button || sender->getParent() == mKey[i].button) { index = i; break; @@ -170,9 +163,10 @@ namespace MWGui mSelectedIndex = index; // open assign dialog - if (!mAssignDialog) + if(!mAssignDialog) mAssignDialog = new QuickKeysMenuAssign(this); - mAssignDialog->setVisible (true); + + mAssignDialog->setVisible(true); } void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender) @@ -180,7 +174,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_QuickKeysMenu); } - void QuickKeysMenu::onItemButtonClicked(MyGUI::Widget* sender) { if (!mItemSelectionDialog ) @@ -198,42 +191,42 @@ namespace MWGui void QuickKeysMenu::onMagicButtonClicked(MyGUI::Widget* sender) { - if (!mMagicSelectionDialog ) + if(!mMagicSelectionDialog) { mMagicSelectionDialog = new MagicSelectionDialog(this); } mMagicSelectionDialog->setVisible(true); - mAssignDialog->setVisible (false); + mAssignDialog->setVisible(false); } void QuickKeysMenu::onUnassignButtonClicked(MyGUI::Widget* sender) { - unassign(mQuickKeyButtons[mSelectedIndex], mSelectedIndex); - mAssignDialog->setVisible (false); + unassign(&mKey[mSelectedIndex]); + mAssignDialog->setVisible(false); } void QuickKeysMenu::onCancelButtonClicked(MyGUI::Widget* sender) { - mAssignDialog->setVisible (false); + mAssignDialog->setVisible(false); } void QuickKeysMenu::onAssignItem(MWWorld::Ptr item) { - assert (mSelectedIndex >= 0); - ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; - while (button->getChildCount()) // Destroy number label - MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); + assert(mSelectedIndex >= 0); - mAssigned[mSelectedIndex] = Type_Item; - mAssignedId[mSelectedIndex] = item.getCellRef().getRefId(); - mAssignedName[mSelectedIndex] = item.getClass().getName(item); + while(mKey[mSelectedIndex].button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(mKey[mSelectedIndex].button->getChildAt(0)); - button->setItem(item, ItemWidget::Barter); - button->setUserString ("ToolTipType", "ItemPtr"); - button->setUserData(item); + mKey[mSelectedIndex].type = Type_Item; + mKey[mSelectedIndex].id = item.getCellRef().getRefId(); + mKey[mSelectedIndex].name = item.getClass().getName(item); - if (mItemSelectionDialog) + mKey[mSelectedIndex].button->setItem(item, ItemWidget::Barter); + mKey[mSelectedIndex].button->setUserString("ToolTipType", "ItemPtr"); + mKey[mSelectedIndex].button->setUserData(item); + + if(mItemSelectionDialog) mItemSelectionDialog->setVisible(false); } @@ -242,37 +235,37 @@ namespace MWGui mItemSelectionDialog->setVisible(false); } - void QuickKeysMenu::onAssignMagicItem (MWWorld::Ptr item) + void QuickKeysMenu::onAssignMagicItem(MWWorld::Ptr item) { - assert (mSelectedIndex >= 0); - ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; - while (button->getChildCount()) // Destroy number label - MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); + assert(mSelectedIndex >= 0); - mAssigned[mSelectedIndex] = Type_MagicItem; + while(mKey[mSelectedIndex].button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(mKey[mSelectedIndex].button->getChildAt(0)); - button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); - button->setIcon(item); + mKey[mSelectedIndex].type = Type_MagicItem; - button->setUserString ("ToolTipType", "ItemPtr"); - button->setUserData(MWWorld::Ptr(item)); + mKey[mSelectedIndex].button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); + mKey[mSelectedIndex].button->setIcon(item); - if (mMagicSelectionDialog) + mKey[mSelectedIndex].button->setUserString ("ToolTipType", "ItemPtr"); + mKey[mSelectedIndex].button->setUserData(MWWorld::Ptr(item)); + + if(mMagicSelectionDialog) mMagicSelectionDialog->setVisible(false); } - void QuickKeysMenu::onAssignMagic (const std::string& spellId) + void QuickKeysMenu::onAssignMagic(const std::string& spellId) { - assert (mSelectedIndex >= 0); - ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; - while (button->getChildCount()) // Destroy number label + assert(mSelectedIndex >= 0); + ItemWidget* button = mKey[mSelectedIndex].button; + while(mKey[mSelectedIndex].button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); - mAssigned[mSelectedIndex] = Type_Magic; + mKey[mSelectedIndex].type = Type_Magic; - button->setItem(MWWorld::Ptr()); - button->setUserString ("ToolTipType", "Spell"); - button->setUserString ("Spell", spellId); + mKey[mSelectedIndex].button->setItem(MWWorld::Ptr()); + mKey[mSelectedIndex].button->setUserString("ToolTipType", "Spell"); + mKey[mSelectedIndex].button->setUserString("Spell", spellId); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -311,10 +304,9 @@ namespace MWGui void QuickKeysMenu::activateQuickKey(int index) { - assert (index-1 >= 0); - ItemWidget* button = mQuickKeyButtons[index-1]; - - QuickKeyType type = mAssigned[index-1]; + assert(index-1 >= 0); + ItemWidget* button = mKey[index-1].button; + QuickKeyType type = mKey[index-1].type; MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); @@ -351,17 +343,17 @@ namespace MWGui break; } if (it == store.end()) - item = NULL; + item = nullptr; // check the item is available and not broken if (!item || item.getRefData().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { - item = store.findReplacement(mAssignedId[index-1]); + item = store.findReplacement(mKey[index-1].id); if (!item || item.getRefData().getCount() < 1) { MWBase::Environment::get().getWindowManager()->messageBox( - "#{sQuickMenu5} " + mAssignedName[index-1]); + "#{sQuickMenu5} " + mKey[index-1].name); return; } @@ -490,9 +482,9 @@ namespace MWGui for (int i=0; i<10; ++i) { - ItemWidget* button = mQuickKeyButtons[i]; + ItemWidget* button = mKey[i].button; - int type = mAssigned[i]; + int type = mKey[i].type; ESM::QuickKeys::QuickKey key; key.mType = type; @@ -525,7 +517,7 @@ namespace MWGui void QuickKeysMenu::readRecord(ESM::ESMReader &reader, uint32_t type) { - if (type != ESM::REC_KEYS) + if(type != ESM::REC_KEYS) return; ESM::QuickKeys keys; @@ -535,20 +527,19 @@ namespace MWGui MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); int i=0; - for (std::vector::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it) + for(std::vector::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it) { - if (i >= 10) + if(i >= 10) return; mSelectedIndex = i; int keyType = it->mType; std::string id = it->mId; - ItemWidget* button = mQuickKeyButtons[i]; switch (keyType) { case Type_Magic: - if (MWBase::Environment::get().getWorld()->getStore().get().search(id)) + if(MWBase::Environment::get().getWorld()->getStore().get().search(id)) onAssignMagic(id); break; case Type_Item: @@ -557,13 +548,13 @@ namespace MWGui // Find the item by id MWWorld::Ptr item = store.findReplacement(id); - if (item.isEmpty()) - unassign(button, i); + if(item.isEmpty()) + unassign(&mKey[i]); else { - if (keyType == Type_Item) + if(keyType == Type_Item) onAssignItem(item); - else if (keyType == Type_MagicItem) + else if(keyType == Type_MagicItem) onAssignMagicItem(item); } @@ -571,7 +562,7 @@ namespace MWGui } case Type_Unassigned: case Type_HandToHand: - unassign(button, i); + unassign(&mKey[i]); break; } diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index 29506ab58..f11673f5f 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -2,7 +2,6 @@ #define MWGUI_QUICKKEYS_H #include "../mwworld/ptr.hpp" -#include "../mwworld/containerstore.hpp" #include "windowbase.hpp" @@ -56,14 +55,21 @@ namespace MWGui private: + + struct keyData { + int index; + ItemWidget* button; + QuickKeysMenu::QuickKeyType type; + std::string id; + std::string name; + keyData(): index(-1), button(nullptr), type(Type_Unassigned), id(""), name("") {} + }; + + std::vector mKey; + MyGUI::EditBox* mInstructionLabel; MyGUI::Button* mOkButton; - std::vector mQuickKeyButtons; - std::vector mAssigned; - std::vector mAssignedId; - std::vector mAssignedName; - QuickKeysMenuAssign* mAssignDialog; ItemSelectionDialog* mItemSelectionDialog; MagicSelectionDialog* mMagicSelectionDialog; @@ -74,7 +80,7 @@ namespace MWGui void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender); - void unassign(ItemWidget* key, int index); + void unassign(struct keyData* key); }; class QuickKeysMenuAssign : public WindowModal From 80a3f0a0d40a7f39a8be6337119286a3d64d683a Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Thu, 28 Jun 2018 17:02:25 +1000 Subject: [PATCH 11/40] switch mSelectedIndex/mActivatedIndex int to mSelected/mActivated keyData pointers --- apps/openmw/mwgui/quickkeysmenu.cpp | 174 ++++++++++++++-------------- apps/openmw/mwgui/quickkeysmenu.hpp | 5 +- 2 files changed, 90 insertions(+), 89 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 819decfc1..b2bf9df0e 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -33,11 +33,13 @@ namespace MWGui QuickKeysMenu::QuickKeysMenu() : WindowBase("openmw_quickkeys_menu.layout") + , mKey(std::vector(10)) + , mSelected(nullptr) + , mActivated(nullptr) , mAssignDialog(0) , mItemSelectionDialog(0) , mMagicSelectionDialog(0) - , mSelectedIndex(-1) - , mActivatedIndex(-1) + { getWidget(mOkButton, "OKButton"); getWidget(mInstructionLabel, "InstructionLabel"); @@ -49,8 +51,6 @@ namespace MWGui mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onOkButtonClicked); center(); - mKey = std::vector(10); - for (int i = 0; i < 10; ++i) { mKey[i].index = i; @@ -63,7 +63,7 @@ namespace MWGui void QuickKeysMenu::clear() { - mActivatedIndex = -1; + mActivated = nullptr; for (int i=0; i<10; ++i) { @@ -120,7 +120,7 @@ namespace MWGui key->button->clearUserStrings(); key->button->setItem(MWWorld::Ptr()); - while(key->button->getChildCount()) // Destroy number label + while (key->button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(key->button->getChildAt(0)); if (key->index == 9) @@ -151,19 +151,19 @@ namespace MWGui void QuickKeysMenu::onQuickKeyButtonClicked(MyGUI::Widget* sender) { int index = -1; - for(int i = 0; i < 10; ++i) + for (int i = 0; i < 10; ++i) { - if(sender == mKey[i].button || sender->getParent() == mKey[i].button) + if (sender == mKey[i].button || sender->getParent() == mKey[i].button) { index = i; break; } } assert(index != -1); - mSelectedIndex = index; + mSelected = &mKey[index]; // open assign dialog - if(!mAssignDialog) + if (!mAssignDialog) mAssignDialog = new QuickKeysMenuAssign(this); mAssignDialog->setVisible(true); @@ -176,7 +176,7 @@ namespace MWGui void QuickKeysMenu::onItemButtonClicked(MyGUI::Widget* sender) { - if (!mItemSelectionDialog ) + if (!mItemSelectionDialog) { mItemSelectionDialog = new ItemSelectionDialog("#{sQuickMenu6}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItem); @@ -186,12 +186,12 @@ namespace MWGui mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyUsableItems); - mAssignDialog->setVisible (false); + mAssignDialog->setVisible(false); } void QuickKeysMenu::onMagicButtonClicked(MyGUI::Widget* sender) { - if(!mMagicSelectionDialog) + if (!mMagicSelectionDialog) { mMagicSelectionDialog = new MagicSelectionDialog(this); } @@ -202,7 +202,7 @@ namespace MWGui void QuickKeysMenu::onUnassignButtonClicked(MyGUI::Widget* sender) { - unassign(&mKey[mSelectedIndex]); + unassign(mSelected); mAssignDialog->setVisible(false); } @@ -213,20 +213,20 @@ namespace MWGui void QuickKeysMenu::onAssignItem(MWWorld::Ptr item) { - assert(mSelectedIndex >= 0); + assert(mSelected); - while(mKey[mSelectedIndex].button->getChildCount()) // Destroy number label - MyGUI::Gui::getInstance().destroyWidget(mKey[mSelectedIndex].button->getChildAt(0)); + while (mSelected->button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); - mKey[mSelectedIndex].type = Type_Item; - mKey[mSelectedIndex].id = item.getCellRef().getRefId(); - mKey[mSelectedIndex].name = item.getClass().getName(item); + mSelected->type = Type_Item; + mSelected->id = item.getCellRef().getRefId(); + mSelected->name = item.getClass().getName(item); - mKey[mSelectedIndex].button->setItem(item, ItemWidget::Barter); - mKey[mSelectedIndex].button->setUserString("ToolTipType", "ItemPtr"); - mKey[mSelectedIndex].button->setUserData(item); + mSelected->button->setItem(item, ItemWidget::Barter); + mSelected->button->setUserString("ToolTipType", "ItemPtr"); + mSelected->button->setUserData(item); - if(mItemSelectionDialog) + if (mItemSelectionDialog) mItemSelectionDialog->setVisible(false); } @@ -237,38 +237,37 @@ namespace MWGui void QuickKeysMenu::onAssignMagicItem(MWWorld::Ptr item) { - assert(mSelectedIndex >= 0); + assert(mSelected); - while(mKey[mSelectedIndex].button->getChildCount()) // Destroy number label - MyGUI::Gui::getInstance().destroyWidget(mKey[mSelectedIndex].button->getChildAt(0)); + while (mSelected->button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); - mKey[mSelectedIndex].type = Type_MagicItem; + mSelected->type = Type_MagicItem; - mKey[mSelectedIndex].button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); - mKey[mSelectedIndex].button->setIcon(item); + mSelected->button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); + mSelected->button->setIcon(item); - mKey[mSelectedIndex].button->setUserString ("ToolTipType", "ItemPtr"); - mKey[mSelectedIndex].button->setUserData(MWWorld::Ptr(item)); + mSelected->button->setUserString("ToolTipType", "ItemPtr"); + mSelected->button->setUserData(MWWorld::Ptr(item)); - if(mMagicSelectionDialog) + if (mMagicSelectionDialog) mMagicSelectionDialog->setVisible(false); } void QuickKeysMenu::onAssignMagic(const std::string& spellId) { - assert(mSelectedIndex >= 0); - ItemWidget* button = mKey[mSelectedIndex].button; - while(mKey[mSelectedIndex].button->getChildCount()) // Destroy number label - MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); + assert(mSelected); + while (mSelected->button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(mSelected->button->getChildAt(0)); - mKey[mSelectedIndex].type = Type_Magic; + mSelected->type = Type_Magic; + mSelected->id = spellId; - mKey[mSelectedIndex].button->setItem(MWWorld::Ptr()); - mKey[mSelectedIndex].button->setUserString("ToolTipType", "Spell"); - mKey[mSelectedIndex].button->setUserString("Spell", spellId); + mSelected->button->setItem(MWWorld::Ptr()); + mSelected->button->setUserString("ToolTipType", "Spell"); + mSelected->button->setUserString("Spell", spellId); - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); // use the icon of the first effect const ESM::Spell* spell = esmStore.get().find(spellId); @@ -281,14 +280,14 @@ namespace MWGui path.insert(slashPos+1, "b_"); path = MWBase::Environment::get().getWindowManager()->correctIconPath(path); - button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); - button->setIcon(path); + mSelected->button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); + mSelected->button->setIcon(path); if (mMagicSelectionDialog) mMagicSelectionDialog->setVisible(false); } - void QuickKeysMenu::onAssignMagicCancel () + void QuickKeysMenu::onAssignMagicCancel() { mMagicSelectionDialog->setVisible(false); } @@ -296,17 +295,16 @@ namespace MWGui void QuickKeysMenu::updateActivatedQuickKey() { // there is no delayed action, nothing to do. - if (mActivatedIndex < 0) + if (!mActivated) return; - activateQuickKey(mActivatedIndex); + activateQuickKey(mActivated->index); } void QuickKeysMenu::activateQuickKey(int index) { - assert(index-1 >= 0); - ItemWidget* button = mKey[index-1].button; - QuickKeyType type = mKey[index-1].type; + assert(index > 0); + struct keyData *key = &mKey[index-1]; MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); @@ -319,22 +317,23 @@ namespace MWGui || playerStats.getHitRecovery(); bool isReturnNeeded = playerStats.isParalyzed() || playerStats.isDead(); - if (isReturnNeeded && type != Type_Item) + + if (isReturnNeeded && key->type != Type_Item) { return; } - - if (isDelayNeeded && type != Type_Item) + else if(isDelayNeeded && key->type != Type_Item) { - mActivatedIndex = index; + mActivated = key; return; } else - mActivatedIndex = -1; + mActivated = nullptr; + - if (type == Type_Item || type == Type_MagicItem) + if (key->type == Type_Item || key->type == Type_MagicItem) { - MWWorld::Ptr item = *button->getUserData(); + MWWorld::Ptr item = *key->button->getUserData(); MWWorld::ContainerStoreIterator it = store.begin(); for (; it != store.end(); ++it) @@ -347,19 +346,20 @@ namespace MWGui // check the item is available and not broken if (!item || item.getRefData().getCount() < 1 || - (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) + (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { - item = store.findReplacement(mKey[index-1].id); + item = store.findReplacement(key->id); + if (!item || item.getRefData().getCount() < 1) { MWBase::Environment::get().getWindowManager()->messageBox( - "#{sQuickMenu5} " + mKey[index-1].name); + "#{sQuickMenu5} " + key->name); return; } } - if (type == Type_Item) + if (key->type == Type_Item) { bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || @@ -368,12 +368,11 @@ namespace MWGui // delay weapon switching if player is busy if (isDelayNeeded && (isWeapon || isTool)) { - mActivatedIndex = index; + mActivated = key; return; } - // disable weapon switching if player is dead or paralyzed - if (isReturnNeeded && (isWeapon || isTool)) + else if (isReturnNeeded && (isWeapon || isTool)) { return; } @@ -386,7 +385,7 @@ namespace MWGui MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); } } - else if (type == Type_MagicItem) + else if (key->type == Type_MagicItem) { // equip, if it can be equipped if (!item.getClass().getEquipmentSlots(item).first.empty()) @@ -402,25 +401,28 @@ namespace MWGui MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); } } - else if (type == Type_Magic) + else if (key->type == Type_Magic) { - std::string spellId = button->getUserString("Spell"); + std::string spellId = key->id; // Make sure the player still has this spell MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); + if (!spells.hasSpell(spellId)) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - MWBase::Environment::get().getWindowManager()->messageBox ( - "#{sQuickMenu5} " + spell->mName); + const ESM::Spell* spell = + MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + spell->mName); return; } + store.setSelectedEnchantItem(store.end()); - MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); + MWBase::Environment::get().getWindowManager() + ->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Spell); } - else if (type == Type_HandToHand) + else if (key->type == Type_HandToHand) { store.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, player); MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); @@ -517,7 +519,7 @@ namespace MWGui void QuickKeysMenu::readRecord(ESM::ESMReader &reader, uint32_t type) { - if(type != ESM::REC_KEYS) + if (type != ESM::REC_KEYS) return; ESM::QuickKeys keys; @@ -527,34 +529,34 @@ namespace MWGui MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); int i=0; - for(std::vector::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it) + for (std::vector::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it) { - if(i >= 10) + if (i >= 10) return; - mSelectedIndex = i; - int keyType = it->mType; - std::string id = it->mId; + mSelected = &mKey[i]; + mSelected->type = (QuickKeysMenu::QuickKeyType) it->mType; + mSelected->id = it->mId; - switch (keyType) + switch (mSelected->type) { case Type_Magic: - if(MWBase::Environment::get().getWorld()->getStore().get().search(id)) - onAssignMagic(id); + if (MWBase::Environment::get().getWorld()->getStore().get().search(mSelected->id)) + onAssignMagic(mSelected->id); break; case Type_Item: case Type_MagicItem: { // Find the item by id - MWWorld::Ptr item = store.findReplacement(id); + MWWorld::Ptr item = store.findReplacement(mSelected->id); - if(item.isEmpty()) + if (item.isEmpty()) unassign(&mKey[i]); else { - if(keyType == Type_Item) + if (mSelected->type == Type_Item) onAssignItem(item); - else if(keyType == Type_MagicItem) + else if (mSelected->type == Type_MagicItem) onAssignMagicItem(item); } diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index f11673f5f..56394d660 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -66,6 +66,8 @@ namespace MWGui }; std::vector mKey; + struct keyData* mSelected; + struct keyData* mActivated; MyGUI::EditBox* mInstructionLabel; MyGUI::Button* mOkButton; @@ -74,9 +76,6 @@ namespace MWGui ItemSelectionDialog* mItemSelectionDialog; MagicSelectionDialog* mMagicSelectionDialog; - int mSelectedIndex; - int mActivatedIndex; - void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender); From 5455490ad2630d68b25a465abcbae2c1c8161cc5 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 28 Jun 2018 11:12:48 +0400 Subject: [PATCH 12/40] Avoid fall-through in spell selection --- apps/openmw/mwmechanics/spellpriority.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 7aef54007..7bedb1e37 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -328,7 +328,10 @@ namespace MWMechanics if (race->mData.mFlags & ESM::Race::Beast) return 0.f; } - // Intended fall-through + else + return 0.f; + + break; // Creatures can not wear armor case ESM::MagicEffect::BoundCuirass: case ESM::MagicEffect::BoundGloves: From d9a1de0ec7e97bb7dee093ec8c881f85886d5406 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 28 Jun 2018 11:13:32 +0400 Subject: [PATCH 13/40] Do not use deprecated function --- apps/opencs/view/world/enumdelegate.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index e582e3356..4bd40b830 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -110,7 +110,11 @@ void CSVWorld::EnumDelegate::paint (QPainter *painter, const QStyleOptionViewIte int valueIndex = getValueIndex(index); if (valueIndex != -1) { +#if QT_VERSION >= QT_VERSION_CHECK(5,7,0) + QStyleOptionViewItem itemOption(option); +#else QStyleOptionViewItemV4 itemOption(option); +#endif itemOption.text = mValues.at(valueIndex).second; QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter); } From 660193ae1b6f66d3fbef9920fa82d9b4d202cb5f Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 28 Jun 2018 13:59:23 +0200 Subject: [PATCH 14/40] Update before_script.msvc.sh This has working GL Win10 MSVC updates, should be cross-compatible with appveyor. --- CI/before_script.msvc.sh | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 38b304bf9..d6cb7323e 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x MISSINGTOOLS=0 @@ -235,7 +236,7 @@ case $VS_VERSION in TOOLSET="vc140" TOOLSET_REAL="vc141" MSVC_REAL_VER="15" - MSVC_VER="14" + MSVC_VER="14.1" MSVC_YEAR="2015" MSVC_DISPLAY_YEAR="2017" ;; @@ -245,7 +246,7 @@ case $VS_VERSION in TOOLSET="vc140" TOOLSET_REAL="vc140" MSVC_REAL_VER="14" - MSVC_VER="14" + MSVC_VER="14.0" MSVC_YEAR="2015" MSVC_DISPLAY_YEAR="2015" ;; @@ -255,7 +256,7 @@ case $VS_VERSION in TOOLSET="vc120" TOOLSET_REAL="vc120" MSVC_REAL_VER="12" - MSVC_VER="12" + MSVC_VER="12.0" MSVC_YEAR="2013" MSVC_DISPLAY_YEAR="2013" ;; @@ -325,9 +326,9 @@ if [ -z $SKIP_DOWNLOAD ]; then # Boost if [ -z $APPVEYOR ]; then - download "Boost 1.61.0" \ - "https://sourceforge.net/projects/boost/files/boost-binaries/1.61.0/boost_1_61_0-msvc-${MSVC_VER}.0-${BITS}.exe" \ - "boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe" + download "Boost 1.67.0" \ + "https://sourceforge.net/projects/boost/files/boost-binaries/1.67.0/boost_1_67_0-msvc-${MSVC_VER}-${BITS}.exe" \ + "boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" fi # Bullet @@ -365,8 +366,8 @@ if [ -z $SKIP_DOWNLOAD ]; then QT_SUFFIX="" fi - download "Qt 5.7.2" \ - "https://download.qt.io/official_releases/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \ + download "Qt 5.7.0" \ + "https://download.qt.io/archive/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \ "qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" \ "https://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \ "qt-5-install.qs" @@ -403,7 +404,7 @@ echo # Boost if [ -z $APPVEYOR ]; then - printf "Boost 1.61.0... " + printf "Boost 1.67.0... " else if [ $MSVC_VER -eq 12 ]; then printf "Boost 1.58.0 AppVeyor... " @@ -421,11 +422,12 @@ fi printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf Boost - "${DEPS}/boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe" //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent + "${DEPS}/boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${SYSTEMDRIVE}\boost" //VERYSILENT //NORESTART //SUPPRESSMSGBOXES //LOG="boost_install.log" + mv "${SYSTEMDRIVE}\boost" ${BOOST_SDK} fi add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ - -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.0" + -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}" add_cmake_opts -DBoost_COMPILER="-${TOOLSET}" echo Done. From 46575d8de7fa0e5ff5fa57efc4155bea4926dcb3 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 28 Jun 2018 14:01:41 +0200 Subject: [PATCH 15/40] Update before_script.msvc.sh 1.61 -> 1.67 --- CI/before_script.msvc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index d6cb7323e..a35a61228 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -422,7 +422,7 @@ fi printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf Boost - "${DEPS}/boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${SYSTEMDRIVE}\boost" //VERYSILENT //NORESTART //SUPPRESSMSGBOXES //LOG="boost_install.log" + "${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${SYSTEMDRIVE}\boost" //VERYSILENT //NORESTART //SUPPRESSMSGBOXES //LOG="boost_install.log" mv "${SYSTEMDRIVE}\boost" ${BOOST_SDK} fi From 24d5fb09da679e2f62737678ad21668b10fa49a9 Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Thu, 28 Jun 2018 22:55:02 +1000 Subject: [PATCH 16/40] fix crash on simultaneous key presses --- apps/openmw/mwgui/quickkeysmenu.cpp | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index b2bf9df0e..4c5a7bf16 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -303,7 +303,8 @@ namespace MWGui void QuickKeysMenu::activateQuickKey(int index) { - assert(index > 0); + assert(index >= 1 && index <= 9); + struct keyData *key = &mKey[index-1]; MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -318,15 +319,12 @@ namespace MWGui bool isReturnNeeded = playerStats.isParalyzed() || playerStats.isDead(); - if (isReturnNeeded && key->type != Type_Item) - { + if (isReturnNeeded) return; - } - else if(isDelayNeeded && key->type != Type_Item) - { + + else if (isDelayNeeded) mActivated = key; - return; - } + else mActivated = nullptr; @@ -367,12 +365,6 @@ namespace MWGui // delay weapon switching if player is busy if (isDelayNeeded && (isWeapon || isTool)) - { - mActivated = key; - return; - } - // disable weapon switching if player is dead or paralyzed - else if (isReturnNeeded && (isWeapon || isTool)) { return; } From 8be52d228e19b01ca5cc05f9aa245a781838bd4f Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 28 Jun 2018 15:12:26 +0200 Subject: [PATCH 17/40] Update before_script.msvc.sh small fixes --- CI/before_script.msvc.sh | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index a35a61228..5aea6e04c 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -233,8 +233,7 @@ fi case $VS_VERSION in 15|15.0|2017 ) GENERATOR="Visual Studio 15 2017" - TOOLSET="vc140" - TOOLSET_REAL="vc141" + TOOLSET="vc141" MSVC_REAL_VER="15" MSVC_VER="14.1" MSVC_YEAR="2015" @@ -244,7 +243,6 @@ case $VS_VERSION in 14|14.0|2015 ) GENERATOR="Visual Studio 14 2015" TOOLSET="vc140" - TOOLSET_REAL="vc140" MSVC_REAL_VER="14" MSVC_VER="14.0" MSVC_YEAR="2015" @@ -254,7 +252,6 @@ case $VS_VERSION in 12|12.0|2013 ) GENERATOR="Visual Studio 12 2013" TOOLSET="vc120" - TOOLSET_REAL="vc120" MSVC_REAL_VER="12" MSVC_VER="12.0" MSVC_YEAR="2013" @@ -406,7 +403,7 @@ echo if [ -z $APPVEYOR ]; then printf "Boost 1.67.0... " else - if [ $MSVC_VER -eq 12 ]; then + if [ $MSVC_VER -eq 12.0 ]; then printf "Boost 1.58.0 AppVeyor... " else printf "Boost 1.67.0 AppVeyor... " @@ -446,7 +443,7 @@ fi add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}" - add_cmake_opts -DBoost_COMPILER="-${TOOLSET_REAL}" + add_cmake_opts -DBoost_COMPILER="-${TOOLSET}" echo Done. fi From ed71656ea6efcbdaadadb08f4315f457d8544ee9 Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Fri, 29 Jun 2018 01:58:57 +1000 Subject: [PATCH 18/40] fix updateActivatedQuickKey() crash keyboard numbers don't start at zero... --- apps/openmw/mwgui/quickkeysmenu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 4c5a7bf16..2899dfb77 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -53,7 +53,7 @@ namespace MWGui for (int i = 0; i < 10; ++i) { - mKey[i].index = i; + mKey[i].index = i+1; getWidget(mKey[i].button, "QuickKey" + MyGUI::utility::toString(i+1)); mKey[i].button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); @@ -143,7 +143,7 @@ namespace MWGui MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); textBox->setTextAlign(MyGUI::Align::Center); - textBox->setCaption(MyGUI::utility::toString(key->index + 1)); + textBox->setCaption(MyGUI::utility::toString(key->index)); textBox->setNeedMouseFocus(false); } } From 72f6b1a6939cd9a220a5262f34b4b3d7224da59d Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Thu, 28 Jun 2018 20:13:18 -0500 Subject: [PATCH 19/40] Separating "Game" Advanced Settings into "Game Mechanics" and "User Interface" --- CHANGELOG.md | 1 + apps/launcher/advancedpage.cpp | 37 +++--- files/ui/advancedpage.ui | 209 +++++++++++++++++---------------- 3 files changed, 129 insertions(+), 118 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1181b615..89fe8eac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal Bug #4474: No fallback when getVampireHead fails Bug #4475: Scripted animations should not cause movement + Bug #4479: "Game" category on Advanced page is getting too long Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results Feature #4222: 360° screenshots Feature #4256: Implement ToggleBorders (TB) console command diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 2b2d7b448..b0a35f0a5 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -73,18 +73,8 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); - loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); - loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); - loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); - loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); - // Expected values are (0, 1, 2, 3) - int showOwnedIndex = mEngineSettings.getInt("show owned", "Game"); - // Match the index with the option. Will default to 0 if invalid. - if (showOwnedIndex >= 0 && showOwnedIndex <= 3) - showOwnedComboBox->setCurrentIndex(showOwnedIndex); - // Input Settings loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); loadSettingBool(grabCursorCheckBox, "grab cursor", "Input"); @@ -94,6 +84,16 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves")); + // User Interface Settings + loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); + loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); + loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); + loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + int showOwnedIndex = mEngineSettings.getInt("show owned", "Game"); + // Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid. + if (showOwnedIndex >= 0 && showOwnedIndex <= 3) + showOwnedComboBox->setCurrentIndex(showOwnedIndex); + // Other Settings QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper(); if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) @@ -125,16 +125,8 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); - saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); - saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); - saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); - saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); - int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); - if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) - mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); - // Input Settings saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); saveSettingBool(grabCursorCheckBox, "grab cursor", "Input"); @@ -147,6 +139,15 @@ void Launcher::AdvancedPage::saveSettings() mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves); } + // User Interface Settings + saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game"); + saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game"); + saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); + saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); + int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); + if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) + mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); + // Other Settings std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General")) diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 7a01ce41d..0e43654f2 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -22,14 +22,14 @@ 0 0 630 - 746 + 791 - Game + Game Mechanics @@ -62,46 +62,6 @@ - - - - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - - - Show effect duration - - - - - - - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - - - Show enchant chance - - - - - - - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be showed on item tooltip.</p><p>The default value is false.</p></body></html> - - - Show melee info - - - - - - - <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be showed on item tooltip.</p><p>The default value is false.</p></body></html> - - - Show projectile damage - - - @@ -112,64 +72,6 @@ - - - - <html><head/><body><p>Enable visual clues for items owned by NPCs when the crosshair is on the object.</p><p>The default value is Off.</p></body></html> - - - - -1 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Show owned: - - - - - - - 1 - - - - Off - - - - - Tool Tip Only - - - - - Crosshair Only - - - - - Tool Tip and Crosshair - - - - - - - @@ -357,6 +259,113 @@ + + + + User Interface + + + + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + + + Show effect duration + + + + + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + + + Show enchant chance + + + + + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be showed on item tooltip.</p><p>The default value is false.</p></body></html> + + + Show melee info + + + + + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be showed on item tooltip.</p><p>The default value is false.</p></body></html> + + + Show projectile damage + + + + + + + <html><head/><body><p>Enable visual clues for items owned by NPCs when the crosshair is on the object.</p><p>The default value is Off.</p></body></html> + + + + -1 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Show owned: + + + + + + + 1 + + + + Off + + + + + Tool Tip Only + + + + + Crosshair Only + + + + + Tool Tip and Crosshair + + + + + + + + + + From 4c0e47509287756ae2d3d72026bfc60fd35ae7d3 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 29 Jun 2018 10:16:28 +0200 Subject: [PATCH 20/40] Update before_script.msvc.sh Use powershell trick with boost_temp so there is little chance of collision. --- CI/before_script.msvc.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 5aea6e04c..15903c6ba 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -413,14 +413,19 @@ fi if [ -z $APPVEYOR ]; then cd $DEPS_INSTALL + # Boost's installer is still based on ms-dos API that doesn't support larger than 260 char path names + # We work around this by installing to root of the current working drive and then move it to our deps + BOOST_SDK="$(real_pwd)/Boost" + CWD_DRIVE_ROOT=`powershell -command '(get-location).Drive.Root'` # get the current working drive's root if [ -d Boost ] && grep "BOOST_VERSION 106100" Boost/boost/version.hpp > /dev/null; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf Boost - "${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${SYSTEMDRIVE}\boost" //VERYSILENT //NORESTART //SUPPRESSMSGBOXES //LOG="boost_install.log" - mv "${SYSTEMDRIVE}\boost" ${BOOST_SDK} + "${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}Boost_temp" //VERYSILENT //NORESTART //SUPPRESSMSGBOXES //LOG="boost_install.log" + mv "${CWD_DRIVE_ROOT}Boost_temp" ${BOOST_SDK} + fi add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ From a532aef9356e8640ea6a553d2c18482b56979630 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 29 Jun 2018 11:31:37 +0200 Subject: [PATCH 21/40] Update before_script.msvc.sh updating version check and correct indentation, wrap BOOST_SDK in "" to support dirs with spaces. --- CI/before_script.msvc.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 15903c6ba..5ca1de133 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -419,12 +419,12 @@ fi BOOST_SDK="$(real_pwd)/Boost" CWD_DRIVE_ROOT=`powershell -command '(get-location).Drive.Root'` # get the current working drive's root - if [ -d Boost ] && grep "BOOST_VERSION 106100" Boost/boost/version.hpp > /dev/null; then + if [ -d Boost ] && grep "BOOST_VERSION 106700" Boost/boost/version.hpp > /dev/null; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf Boost - "${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}Boost_temp" //VERYSILENT //NORESTART //SUPPRESSMSGBOXES //LOG="boost_install.log" - mv "${CWD_DRIVE_ROOT}Boost_temp" ${BOOST_SDK} + "${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}Boost_temp" //VERYSILENT //NORESTART //SUPPRESSMSGBOXES //LOG="boost_install.log" + mv "${CWD_DRIVE_ROOT}Boost_temp" "${BOOST_SDK}" fi From 8811c7141afc9fa65a1de4f0840a80b02b6f92ba Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 29 Jun 2018 15:14:23 +0200 Subject: [PATCH 22/40] Update before_script.msvc.sh taking nits into account :) --- CI/before_script.msvc.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 5ca1de133..4f471462a 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -x +# set -x # turn-on for debugging MISSINGTOOLS=0 @@ -413,17 +413,18 @@ fi if [ -z $APPVEYOR ]; then cd $DEPS_INSTALL - # Boost's installer is still based on ms-dos API that doesn't support larger than 260 char path names - # We work around this by installing to root of the current working drive and then move it to our deps + # Boost's installer is still based on ms-dos API that doesn't support larger than 260 char path names + # We work around this by installing to root of the current working drive and then move it to our deps BOOST_SDK="$(real_pwd)/Boost" - CWD_DRIVE_ROOT=`powershell -command '(get-location).Drive.Root'` # get the current working drive's root + CWD_DRIVE_ROOT=$(powershell -command '(get-location).Drive.Root') # get the current working drive's root if [ -d Boost ] && grep "BOOST_VERSION 106700" Boost/boost/version.hpp > /dev/null; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf Boost - "${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}Boost_temp" //VERYSILENT //NORESTART //SUPPRESSMSGBOXES //LOG="boost_install.log" + [ -n "$CI" ] CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG="boost_install.log" + "${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}Boost_temp" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS} mv "${CWD_DRIVE_ROOT}Boost_temp" "${BOOST_SDK}" fi From c474709127125e8eeed7f7aaec6f1590f721c465 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 29 Jun 2018 15:26:11 +0200 Subject: [PATCH 23/40] Update before_script.msvc.sh --- CI/before_script.msvc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 4f471462a..f6736c2ee 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -423,7 +423,7 @@ fi printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf Boost - [ -n "$CI" ] CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG="boost_install.log" + [ -n "$CI" ] && CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG='boost_install.log'" "${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}Boost_temp" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS} mv "${CWD_DRIVE_ROOT}Boost_temp" "${BOOST_SDK}" From 2722ca50fb2cddb5a8518205ec08ba2126dace57 Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Fri, 29 Jun 2018 23:32:05 +1000 Subject: [PATCH 24/40] fix QuickKeysMenu crash on reopening window after item drop + pickup [see: !11#note_85086570] --- apps/openmw/mwgui/quickkeysmenu.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 2899dfb77..47bc88c8b 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -99,15 +99,16 @@ namespace MWGui { MWWorld::Ptr item = *mKey[i].button->getUserData(); // Make sure the item is available and is not broken - if (item.getRefData().getCount() < 1 || + if (!item || item.getRefData().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { // Try searching for a compatible replacement - std::string id = item.getCellRef().getRefId(); + item = store.findReplacement(mKey[i].id); + + if (item) + mKey[i].button->setUserData(MWWorld::Ptr(item)); - item = store.findReplacement(id); - mKey[i].button->setUserData(MWWorld::Ptr(item)); break; } } From 596be205c1179fb363f04950d435406e522982ea Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Fri, 29 Jun 2018 23:43:51 +1000 Subject: [PATCH 25/40] cleanup unnecessary struct keywords... --- apps/openmw/mwgui/quickkeysmenu.cpp | 6 +++--- apps/openmw/mwgui/quickkeysmenu.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 47bc88c8b..4bfbd4dac 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -33,7 +33,7 @@ namespace MWGui QuickKeysMenu::QuickKeysMenu() : WindowBase("openmw_quickkeys_menu.layout") - , mKey(std::vector(10)) + , mKey(std::vector(10)) , mSelected(nullptr) , mActivated(nullptr) , mAssignDialog(0) @@ -116,7 +116,7 @@ namespace MWGui } } - void QuickKeysMenu::unassign(struct keyData* key) + void QuickKeysMenu::unassign(keyData* key) { key->button->clearUserStrings(); key->button->setItem(MWWorld::Ptr()); @@ -306,7 +306,7 @@ namespace MWGui { assert(index >= 1 && index <= 9); - struct keyData *key = &mKey[index-1]; + keyData *key = &mKey[index-1]; MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index 56394d660..6d2ab8494 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -65,7 +65,7 @@ namespace MWGui keyData(): index(-1), button(nullptr), type(Type_Unassigned), id(""), name("") {} }; - std::vector mKey; + std::vector mKey; struct keyData* mSelected; struct keyData* mActivated; From bccba24c40dd3887896d931aa907957eda7b0a01 Mon Sep 17 00:00:00 2001 From: Capostrophic <21265616+Capostrophic@users.noreply.github.com> Date: Fri, 29 Jun 2018 19:57:09 +0300 Subject: [PATCH 26/40] Make unarmed creature attacks not affect armor condition (fixes #2455) --- CHANGELOG.md | 1 + apps/openmw/mwclass/npc.cpp | 22 ++++++++++++---------- apps/openmw/mwmechanics/combat.cpp | 16 +++++++++------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89fe8eac4..14d644c83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Bug #1990: Sunrise/sunset not set correct Bug #2222: Fatigue's effect on selling price is backwards Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped + Bug #2455: Creatures attacks degrade armor Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash Bug #2772: Non-existing class or faction freezes the game Bug #2835: Player able to slowly move when overencumbered diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 92e25baee..2cf2185ba 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -773,22 +773,24 @@ namespace MWClass float x = damage / (damage + getArmorRating(ptr)); damage *= std::max(gmst.fCombatArmorMinMult->getFloat(), x); int damageDiff = static_cast(unmitigatedDamage - damage); - if (damage < 1) - damage = 1; + damage = std::max(1.f, damage); + damageDiff = std::max(1, damageDiff); MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot); MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr()); if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name()) { - int armorhealth = armor.getClass().getItemHealth(armor); - armorhealth -= std::min(std::max(1, damageDiff), - armorhealth); - armor.getCellRef().setCharge(armorhealth); - - // Armor broken? unequip it - if (armorhealth == 0) - armor = *inv.unequipItem(armor, ptr); + if (!(object.isEmpty() && !attacker.getClass().isNpc())) // Unarmed creature attacks don't affect armor condition + { + int armorhealth = armor.getClass().getItemHealth(armor); + armorhealth -= std::min(damageDiff, armorhealth); + armor.getCellRef().setCharge(armorhealth); + + // Armor broken? unequip it + if (armorhealth == 0) + armor = *inv.unequipItem(armor, ptr); + } if (ptr == MWMechanics::getPlayer()) skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index ac34c658b..6b45a513b 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -115,14 +115,16 @@ namespace MWMechanics if (Misc::Rng::roll0to99() < x) { - // Reduce shield durability by incoming damage - int shieldhealth = shield->getClass().getItemHealth(*shield); - - shieldhealth -= std::min(shieldhealth, int(damage)); - shield->getCellRef().setCharge(shieldhealth); - if (shieldhealth == 0) - inv.unequipItem(*shield, blocker); + if (!(weapon.isEmpty() && !attacker.getClass().isNpc())) // Unarmed creature attacks don't affect armor condition + { + // Reduce shield durability by incoming damage + int shieldhealth = shield->getClass().getItemHealth(*shield); + shieldhealth -= std::min(shieldhealth, int(damage)); + shield->getCellRef().setCharge(shieldhealth); + if (shieldhealth == 0) + inv.unequipItem(*shield, blocker); + } // Reduce blocker fatigue const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->getFloat(); const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->getFloat(); From 6e9c08083d583707ec25c35f8eace1ba1df7a214 Mon Sep 17 00:00:00 2001 From: Capostrophic <21265616+Capostrophic@users.noreply.github.com> Date: Fri, 29 Jun 2018 20:35:45 +0300 Subject: [PATCH 27/40] Add missing empty attacker checks --- 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 2cf2185ba..0becb18df 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -781,7 +781,7 @@ namespace MWClass MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr()); if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name()) { - if (!(object.isEmpty() && !attacker.getClass().isNpc())) // Unarmed creature attacks don't affect armor condition + if (attacker.isEmpty() || (!attacker.isEmpty() && !(object.isEmpty() && !attacker.getClass().isNpc()))) // Unarmed creature attacks don't affect armor condition { int armorhealth = armor.getClass().getItemHealth(armor); armorhealth -= std::min(damageDiff, armorhealth); From 2bf0d598cfb138dc5d7c234d95ce4a19b2c64901 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 29 Jun 2018 20:01:35 +0200 Subject: [PATCH 28/40] Update before_script.msvc.sh Wrap in quites --- CI/before_script.msvc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index f6736c2ee..ce13846c8 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -417,7 +417,7 @@ fi # We work around this by installing to root of the current working drive and then move it to our deps BOOST_SDK="$(real_pwd)/Boost" - CWD_DRIVE_ROOT=$(powershell -command '(get-location).Drive.Root') # get the current working drive's root + CWD_DRIVE_ROOT="$(powershell -command '(get-location).Drive.Root')" # get the current working drive's root if [ -d Boost ] && grep "BOOST_VERSION 106700" Boost/boost/version.hpp > /dev/null; then printf "Exists. " From 4177fd04ebb49afc8c041b8eb6ac51f68e2a4cd7 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 29 Jun 2018 21:08:42 +0200 Subject: [PATCH 29/40] Update before_script.msvc.sh Does it blend? --- CI/before_script.msvc.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index ce13846c8..7c8f3ef68 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -412,12 +412,13 @@ fi { if [ -z $APPVEYOR ]; then cd $DEPS_INSTALL - + + BOOST_SDK="$(real_pwd)/Boost" + # Boost's installer is still based on ms-dos API that doesn't support larger than 260 char path names # We work around this by installing to root of the current working drive and then move it to our deps - - BOOST_SDK="$(real_pwd)/Boost" - CWD_DRIVE_ROOT="$(powershell -command '(get-location).Drive.Root')" # get the current working drive's root + # get the current working drive's root, we'll install to that temporarily + CWD_DRIVE_ROOT=$(echo "$(powershell -command '(get-location).Drive.Root')" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,") if [ -d Boost ] && grep "BOOST_VERSION 106700" Boost/boost/version.hpp > /dev/null; then printf "Exists. " From 09c9bd34c3e438ad2a47be6be098e5e75b46fb5b Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Sat, 30 Jun 2018 12:43:50 +1000 Subject: [PATCH 30/40] cleanup more unnecessary struct keywords... --- apps/openmw/mwgui/quickkeysmenu.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index 6d2ab8494..431e847cb 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -66,8 +66,8 @@ namespace MWGui }; std::vector mKey; - struct keyData* mSelected; - struct keyData* mActivated; + keyData* mSelected; + keyData* mActivated; MyGUI::EditBox* mInstructionLabel; MyGUI::Button* mOkButton; @@ -79,7 +79,7 @@ namespace MWGui void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender); - void unassign(struct keyData* key); + void unassign(keyData* key); }; class QuickKeysMenuAssign : public WindowModal From af75c1e909e136fd32776c6070241f070c9df5e2 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sat, 30 Jun 2018 08:40:21 +0200 Subject: [PATCH 31/40] Update before_script.msvc.sh reverting back to what works --- CI/before_script.msvc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 7c8f3ef68..22f4cc2fe 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -418,7 +418,7 @@ fi # Boost's installer is still based on ms-dos API that doesn't support larger than 260 char path names # We work around this by installing to root of the current working drive and then move it to our deps # get the current working drive's root, we'll install to that temporarily - CWD_DRIVE_ROOT=$(echo "$(powershell -command '(get-location).Drive.Root')" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,") + CWD_DRIVE_ROOT="$(powershell -command '(get-location).Drive.Root')" if [ -d Boost ] && grep "BOOST_VERSION 106700" Boost/boost/version.hpp > /dev/null; then printf "Exists. " From 362798bd908c41a9140b1490b70fc064c519b0b2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 30 Jun 2018 10:20:12 +0200 Subject: [PATCH 32/40] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14d644c83..55442586b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Bug #4474: No fallback when getVampireHead fails Bug #4475: Scripted animations should not cause movement Bug #4479: "Game" category on Advanced page is getting too long + Bug #4480: Segfalt in QuickKeysMenu when item no longer in inventory Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results Feature #4222: 360° screenshots Feature #4256: Implement ToggleBorders (TB) console command From 3660d55adffef8c051f0b11426e6fbb0e1eba980 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 30 Jun 2018 10:21:10 +0200 Subject: [PATCH 33/40] updated credits file --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 415b87b4e..90d25f0b6 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -63,6 +63,7 @@ Programmers Evgeniy Mineev (sandstranger) Federico Guerra (FedeWar) Fil Krynicki (filkry) + Finbar Crago (finbar-crago) Florian Weber (Florianjw) Gašper Sedej gugus/gus From 5d9035c6b224f7d23af731699bf3214571ed6ebf Mon Sep 17 00:00:00 2001 From: Finbar Crago Date: Sun, 1 Jul 2018 13:46:23 +1000 Subject: [PATCH 34/40] [Fixes #4482] Missing HandToHand on key quick key 0 (introduced in MR !11 for issue #4480) because apparently i can't count to ten... --- apps/openmw/mwgui/quickkeysmenu.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 4bfbd4dac..badf50213 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -124,7 +124,7 @@ namespace MWGui while (key->button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(key->button->getChildAt(0)); - if (key->index == 9) + if (key->index == 10) { key->type = Type_HandToHand; @@ -163,6 +163,10 @@ namespace MWGui assert(index != -1); mSelected = &mKey[index]; + // prevent reallocation of zero key from Type_HandToHand + if(mSelected->index == 10) + return; + // open assign dialog if (!mAssignDialog) mAssignDialog = new QuickKeysMenuAssign(this); @@ -304,7 +308,7 @@ namespace MWGui void QuickKeysMenu::activateQuickKey(int index) { - assert(index >= 1 && index <= 9); + assert(index >= 1 && index <= 10); keyData *key = &mKey[index-1]; From 7cbc4eeb492260a7c9f1aa703234d4cc9e742e02 Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Sun, 1 Jul 2018 19:17:50 -0500 Subject: [PATCH 35/40] Adding missing override keywords Prevents compiler warnings such as this: ``` /Users/Will/CLionProjects/OpenMW/apps/openmw/mwgui/windowbase.hpp:65:22: warning: 'onOpen' overrides a member function but is not marked 'override' [-Winconsistent-missing-override] virtual void onOpen(); ^ /Users/Will/CLionProjects/OpenMW/apps/openmw/mwgui/windowbase.hpp:38:22: note: overridden virtual function is here virtual void onOpen() {} ^ ``` --- apps/openmw/mwgui/messagebox.hpp | 2 +- apps/openmw/mwgui/windowbase.hpp | 8 ++++---- apps/openmw/mwworld/worldimp.hpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index e0bcbe667..156a17e67 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -81,7 +81,7 @@ namespace MWGui MyGUI::Widget* getDefaultKeyFocus() override; - virtual bool exit() { return false; } + virtual bool exit() override { return false; } bool mMarkedToDelete; diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 56901c95a..fde1f2ac9 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -56,15 +56,15 @@ namespace MWGui /* - * "Modal" windows cause the rest of the interface to be unaccessible while they are visible + * "Modal" windows cause the rest of the interface to be inaccessible while they are visible */ class WindowModal : public WindowBase { public: WindowModal(const std::string& parLayout); - virtual void onOpen(); - virtual void onClose(); - virtual bool exit() {return true;} + virtual void onOpen() override; + virtual void onClose() override; + virtual bool exit() override {return true;} }; /// A window that cannot be the target of a drag&drop action. diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 29bc4692c..015a8b31b 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -608,7 +608,7 @@ namespace MWWorld void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) override; - void applyLoopingParticles(const MWWorld::Ptr& ptr); + void applyLoopingParticles(const MWWorld::Ptr& ptr) override; const std::vector& getContentFiles() const override; void breakInvisibility (const MWWorld::Ptr& actor) override; From f4330cf057fa79ce1375401b31311939db316c85 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 2 Jul 2018 11:50:59 +0400 Subject: [PATCH 36/40] Editor: limit FPS in 3D preview windows (feature #3641) --- CHANGELOG.md | 1 + apps/opencs/model/prefs/state.cpp | 3 +++ apps/opencs/view/render/scenewidget.cpp | 13 +++++++++++++ 3 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39af121aa..ed21863ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Bug #4474: No fallback when getVampireHead fails Bug #4475: Scripted animations should not cause movement Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results + Feature #3641: Editor: Limit FPS in 3d preview window Feature #4222: 360° screenshots Feature #4256: Implement ToggleBorders (TB) console command Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index e1236a0e4..a704fb825 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -201,6 +201,9 @@ void CSMPrefs::State::declare() declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); declareCategory ("Rendering"); + declareInt ("framerate-limit", "FPS limit", 60). + setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\"."). + setRange(0, 10000); declareInt ("camera-fov", "Camera FOV", 90).setRange(10, 170); declareBool ("camera-ortho", "Orthographic projection for camera", false); declareInt ("camera-ortho-size", "Orthographic projection size parameter", 100). diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index f24a9de50..0d1780d57 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -151,6 +151,9 @@ CompositeViewer::CompositeViewer() connect( &mTimer, SIGNAL(timeout()), this, SLOT(update()) ); mTimer.start( 10 ); + + int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt(); + setRunMaxFrameRate(frameRateLimit); } CompositeViewer &CompositeViewer::get() @@ -168,6 +171,12 @@ void CompositeViewer::update() mSimulationTime += dt; frame(mSimulationTime); + + double minFrameTime = _runMaxFrameRate > 0.0 ? 1.0 / _runMaxFrameRate : 0.0; + if (dt < minFrameTime) + { + OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-dt)); + } } // --------------------------------------------------- @@ -376,6 +385,10 @@ void SceneWidget::settingChanged (const CSMPrefs::Setting *setting) { mOrbitCamControl->setConstRoll(setting->isTrue()); } + else if (*setting=="Rendering/framerate-limit") + { + CompositeViewer::get().setRunMaxFrameRate(setting->toInt()); + } else if (*setting=="Rendering/camera-fov" || *setting=="Rendering/camera-ortho" || *setting=="Rendering/camera-ortho-size") From e9cc697b60522011b53c9816bb942db6305cc869 Mon Sep 17 00:00:00 2001 From: Doc West Date: Tue, 3 Jul 2018 00:52:23 +0200 Subject: [PATCH 37/40] Sort EnumDelegate values by name --- apps/opencs/view/world/enumdelegate.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 4bd40b830..c303b71ac 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -175,5 +175,16 @@ CSVWorld::CommandDelegate *CSVWorld::EnumDelegateFactory::makeDelegate ( void CSVWorld::EnumDelegateFactory::add (int value, const QString& name) { - mValues.push_back (std::make_pair (value, name)); + auto pair = std::make_pair (value, name); + + for (auto it=mValues.begin(); it!=mValues.end(); ++it) + { + if (it->second > name) + { + mValues.insert(it, pair); + return; + } + } + + mValues.push_back(std::make_pair (value, name)); } From 2a367a0c35aa8ce0072f76e995eeea4af22abf46 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 3 Jul 2018 09:09:05 +0200 Subject: [PATCH 38/40] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55442586b..ef8fe436d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Feature #4256: Implement ToggleBorders (TB) console command Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4345: Add equivalents for the command line commands to Launcher + Feature #4404: Editor: All EnumDelegate fields should have their items sorted alphabetically Feature #4444: Per-group KF-animation files support Feature #4466: Editor: Add option to ignore "Base" records when running verifier From 47d0321366824ec2cefb793c6b9380cb5b738589 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 3 Jul 2018 09:09:48 +0200 Subject: [PATCH 39/40] updated credits file --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 90d25f0b6..f1e6e58ee 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -22,6 +22,7 @@ Programmers alexanderkjall Alexander Nadeau (wareya) Alexander Olofsson (Ace) + Alex S (docwest) Allofich Andrei Kortunov (akortunov) AnyOldName3 From 57e25735939d310933e4df94ea6bce2168ff2990 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 3 Jul 2018 15:59:51 +0200 Subject: [PATCH 40/40] Update before_script.msvc.sh check if temp directory exists, error out and warn user about it. --- CI/before_script.msvc.sh | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 22f4cc2fe..eba278316 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -412,28 +412,30 @@ fi { if [ -z $APPVEYOR ]; then cd $DEPS_INSTALL - + BOOST_SDK="$(real_pwd)/Boost" - + # Boost's installer is still based on ms-dos API that doesn't support larger than 260 char path names # We work around this by installing to root of the current working drive and then move it to our deps # get the current working drive's root, we'll install to that temporarily - CWD_DRIVE_ROOT="$(powershell -command '(get-location).Drive.Root')" + CWD_DRIVE_ROOT="$(powershell -command '(get-location).Drive.Root')Boost_temp" + CWD_DRIVE_ROOT_BASH=$(echo "$CWD_DRIVE_ROOT" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,") + if [ -d CWD_DRIVE_ROOT_BASH ]; then + printf "Cannot continue, ${CWD_DRIVE_ROOT_BASH} aka ${CWD_DRIVE_ROOT} already exists. Please remove before re-running. "; + exit 1; + fi - if [ -d Boost ] && grep "BOOST_VERSION 106700" Boost/boost/version.hpp > /dev/null; then + if [ -d ${BOOST_SDK} ] && grep "BOOST_VERSION 106700" Boost/boost/version.hpp > /dev/null; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf Boost [ -n "$CI" ] && CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG='boost_install.log'" - "${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}Boost_temp" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS} - mv "${CWD_DRIVE_ROOT}Boost_temp" "${BOOST_SDK}" - + "${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS} + mv "${CWD_DRIVE_ROOT_BASH}" "${BOOST_SDK}" fi - add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ -DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}" add_cmake_opts -DBoost_COMPILER="-${TOOLSET}" - echo Done. else # Appveyor unstable has all the boost we need already