Merge branch 'ui' into 'master'

Control GUI from Lua

See merge request OpenMW/openmw!3236
macos_ci_fix
psi29a 1 year ago
commit 86f15fa194

@ -7,6 +7,7 @@
#include <SDL_events.h>
#include "../mwgui/mode.hpp"
#include <components/sdlutil/events.hpp>
namespace MWWorld
@ -52,6 +53,10 @@ namespace MWBase
virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;
virtual void exteriorCreated(MWWorld::CellStore& cell) = 0;
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;
// `arg` is either forwarded from MWGui::pushGuiMode or empty
virtual void uiModeChanged(const MWWorld::Ptr& arg) = 0;
// TODO: notify LuaManager about other events
// virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object,
// const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0;

@ -119,9 +119,9 @@ namespace MWBase
virtual void pushGuiMode(MWGui::GuiMode mode, const MWWorld::Ptr& arg) = 0;
virtual void pushGuiMode(MWGui::GuiMode mode) = 0;
virtual void popGuiMode(bool noSound = false) = 0;
virtual void popGuiMode() = 0;
virtual void removeGuiMode(MWGui::GuiMode mode, bool noSound = false) = 0;
virtual void removeGuiMode(MWGui::GuiMode mode) = 0;
///< can be anywhere in the stack
virtual void goToJail(int days) = 0;
@ -378,6 +378,12 @@ namespace MWBase
/// Same as viewer->getCamera()->getCullMask(), provided for consistency.
virtual uint32_t getCullMask() = 0;
// Used in Lua bindings
virtual const std::vector<MWGui::GuiMode>& getGuiModeStack() const = 0;
virtual void setDisabledByLua(std::string_view windowId, bool disabled) = 0;
virtual std::vector<std::string_view> getAllWindowIds() const = 0;
virtual std::vector<std::string_view> getAllowedWindowIds(MWGui::GuiMode mode) const = 0;
};
}

@ -580,7 +580,7 @@ namespace MWDialogue
void DialogueManager::applyBarterDispositionChange(int delta)
{
if (mActor.getClass().isNpc())
if (!mActor.isEmpty() && mActor.getClass().isNpc())
{
updateOriginalDisposition();
mCurrentDisposition += delta;

@ -30,6 +30,8 @@ namespace MWGui
void onResChange(int, int) override { center(); }
std::string_view getWindowIdForLua() const override { return "Alchemy"; }
private:
static const float sCountChangeInitialPause; // in seconds
static const float sCountChangeInterval; // in seconds

@ -83,6 +83,8 @@ namespace MWGui
void BookWindow::setPtr(const MWWorld::Ptr& book)
{
if (book.isEmpty() || book.getType() != ESM::REC_BOOK)
throw std::runtime_error("Invalid argument in BookWindow::setPtr");
mBook = book;
MWWorld::Ptr player = MWMechanics::getPlayer();

@ -19,6 +19,8 @@ namespace MWGui
void onResChange(int, int) override { center(); }
std::string_view getWindowIdForLua() const override { return "Book"; }
protected:
void onNextPageButtonClicked(MyGUI::Widget* sender);
void onPrevPageButtonClicked(MyGUI::Widget* sender);

@ -121,6 +121,8 @@ namespace MWGui
void CompanionWindow::setPtr(const MWWorld::Ptr& npc)
{
if (npc.isEmpty() || npc.getType() != ESM::REC_NPC_)
throw std::runtime_error("Invalid argument in CompanionWindow::setPtr");
mPtr = npc;
updateEncumbranceBar();
auto model = std::make_unique<CompanionItemModel>(npc);

@ -30,6 +30,8 @@ namespace MWGui
void onFrame(float dt) override;
void clear() override { resetReference(); }
std::string_view getWindowIdForLua() const override { return "Companion"; }
private:
ItemView* mItemView;
SortFilterItemModel* mSortModel;

@ -124,6 +124,8 @@ namespace MWGui
void ContainerWindow::setPtr(const MWWorld::Ptr& container)
{
if (container.isEmpty() || (container.getType() != ESM::REC_CONT && !container.getClass().isActor()))
throw std::runtime_error("Invalid argument in ContainerWindow::setPtr");
bool lootAnyway = mTreatNextOpenAsLoot;
mTreatNextOpenAsLoot = false;
mPtr = container;
@ -191,6 +193,8 @@ namespace MWGui
void ContainerWindow::onTakeAllButtonClicked(MyGUI::Widget* _sender)
{
if (!mModel)
return;
if (mDragAndDrop != nullptr && mDragAndDrop->mIsOnDragAndDrop)
return;

@ -38,6 +38,8 @@ namespace MWGui
void treatNextOpenAsLoot() { mTreatNextOpenAsLoot = true; }
std::string_view getWindowIdForLua() const override { return "Container"; }
private:
DragAndDrop* mDragAndDrop;

@ -446,7 +446,7 @@ namespace MWGui
void DialogueWindow::setPtr(const MWWorld::Ptr& actor)
{
if (!actor.getClass().isActor())
if (actor.isEmpty() || !actor.getClass().isActor())
{
Log(Debug::Warning) << "Warning: can not talk with non-actor object.";
return;

@ -165,6 +165,8 @@ namespace MWGui
void onClose() override;
std::string_view getWindowIdForLua() const override { return "Dialogue"; }
protected:
void updateTopicsPane();
bool isCompanion(const MWWorld::Ptr& actor);

@ -139,6 +139,9 @@ namespace MWGui
void EnchantingDialog::setPtr(const MWWorld::Ptr& ptr)
{
if (ptr.isEmpty() || (ptr.getType() != ESM::REC_MISC && !ptr.getClass().isActor()))
throw std::runtime_error("Invalid argument in EnchantingDialog::setPtr");
mName->setCaption({});
if (ptr.getClass().isActor())

@ -33,6 +33,8 @@ namespace MWGui
void resetReference() override;
std::string_view getWindowIdForLua() const override { return "EnchantingDialog"; }
protected:
void onReferenceUnavailable() override;
void notifyEffectsChanged() override;

@ -65,6 +65,8 @@ namespace MWGui
/// Cycle to previous/next weapon
void cycle(bool next);
std::string_view getWindowIdForLua() const override { return "Inventory"; }
protected:
void onTitleDoubleClicked() override;

@ -16,6 +16,8 @@ namespace MWGui
bool exit() override { return false; }
std::string_view getWindowIdForLua() const override { return "JailScreen"; }
private:
int mDays;

@ -226,10 +226,6 @@ namespace
void onOpen() override
{
if (!MWBase::Environment::get().getWindowManager()->getJournalAllowed())
{
MWBase::Environment::get().getWindowManager()->popGuiMode();
}
mModel->load();
setBookMode();

@ -29,6 +29,8 @@ namespace MWGui
/// show/hide the journal window
void setVisible(bool newValue) override = 0;
std::string_view getWindowIdForLua() const override { return "Journal"; }
};
}

@ -15,6 +15,8 @@ namespace MWGui
void onOpen() override;
std::string_view getWindowIdForLua() const override { return "LevelUpDialog"; }
private:
struct Widgets
{

@ -268,6 +268,8 @@ namespace MWGui
void asyncPrepareSaveMap();
std::string_view getWindowIdForLua() const override { return "Map"; }
private:
void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);

@ -32,6 +32,8 @@ namespace MWGui
void MerchantRepair::setPtr(const MWWorld::Ptr& actor)
{
if (actor.isEmpty() || !actor.getClass().isActor())
throw std::runtime_error("Invalid argument in MerchantRepair::setPtr");
mActor = actor;
while (mList->getChildCount())

@ -16,6 +16,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& actor) override;
std::string_view getWindowIdForLua() const override { return "MerchantRepair"; }
private:
MyGUI::ScrollView* mList;
MyGUI::Button* mOkButton;

@ -44,6 +44,8 @@ namespace MWGui
void readRecord(ESM::ESMReader& reader, uint32_t type);
void clear() override;
std::string_view getWindowIdForLua() const override { return "QuickKeys"; }
private:
struct keyData
{

@ -56,6 +56,9 @@ namespace MWGui
void Recharge::setPtr(const MWWorld::Ptr& item)
{
if (item.isEmpty() || !item.getClass().isItem(item))
throw std::runtime_error("Invalid argument in Recharge::setPtr");
mGemIcon->setItem(item);
mGemIcon->setUserString("ToolTipType", "ItemPtr");
mGemIcon->setUserData(MWWorld::Ptr(item));

@ -26,6 +26,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& gem) override;
std::string_view getWindowIdForLua() const override { return "Recharge"; }
protected:
ItemChargeView* mBox;

@ -56,6 +56,9 @@ namespace MWGui
void Repair::setPtr(const MWWorld::Ptr& item)
{
if (item.isEmpty() || !item.getClass().isItem(item))
throw std::runtime_error("Invalid argument in Repair::setPtr");
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Item Repair Up"));
mRepair.setTool(item);

@ -23,6 +23,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& item) override;
std::string_view getWindowIdForLua() const override { return "Repair"; }
protected:
ItemChargeView* mRepairBox;

@ -42,6 +42,8 @@ namespace MWGui
void ScrollWindow::setPtr(const MWWorld::Ptr& scroll)
{
if (scroll.isEmpty() || scroll.getType() != ESM::REC_BOOK)
throw std::runtime_error("Invalid argument in ScrollWindow::setPtr");
mScroll = scroll;
MWWorld::Ptr player = MWMechanics::getPlayer();
@ -105,6 +107,6 @@ namespace MWGui
MWWorld::ActionTake take(mScroll);
take.execute(MWMechanics::getPlayer());
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll, true);
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll);
}
}

@ -22,6 +22,8 @@ namespace MWGui
void onResChange(int, int) override { center(); }
std::string_view getWindowIdForLua() const override { return "Scroll"; }
protected:
void onCloseButtonClicked(MyGUI::Widget* _sender);
void onTakeButtonClicked(MyGUI::Widget* _sender);

@ -88,6 +88,9 @@ namespace MWGui
void SpellBuyingWindow::setPtr(const MWWorld::Ptr& actor, int startOffset)
{
if (actor.isEmpty() || !actor.getClass().isActor())
throw std::runtime_error("Invalid argument in SpellBuyingWindow::setPtr");
center();
mPtr = actor;
clearSpells();

@ -30,6 +30,8 @@ namespace MWGui
void onResChange(int, int) override { center(); }
std::string_view getWindowIdForLua() const override { return "SpellBuying"; }
protected:
MyGUI::Button* mCancelButton;
MyGUI::TextBox* mPlayerGold;

@ -364,6 +364,9 @@ namespace MWGui
void SpellCreationDialog::setPtr(const MWWorld::Ptr& actor)
{
if (actor.isEmpty() || !actor.getClass().isActor())
throw std::runtime_error("Invalid argument in SpellCreationDialog::setPtr");
mPtr = actor;
mNameEdit->setCaption({});

@ -158,6 +158,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& actor) override;
std::string_view getWindowIdForLua() const override { return "SpellCreationDialog"; }
protected:
void onReferenceUnavailable() override;

@ -23,6 +23,8 @@ namespace MWGui
/// Cycle to next/previous spell
void cycle(bool next);
std::string_view getWindowIdForLua() const override { return "Magic"; }
protected:
MyGUI::Widget* mEffectBox;

@ -45,6 +45,8 @@ namespace MWGui
void onOpen() override { onWindowResize(mMainWidget->castType<MyGUI::Window>()); }
std::string_view getWindowIdForLua() const override { return "Stats"; }
private:
void addSkills(const std::vector<ESM::RefId>& skills, const std::string& titleId,
const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2);

@ -102,6 +102,8 @@ namespace MWGui
void TradeWindow::setPtr(const MWWorld::Ptr& actor)
{
if (actor.isEmpty() || !actor.getClass().isActor())
throw std::runtime_error("Invalid argument in TradeWindow::setPtr");
mPtr = actor;
mCurrentBalance = 0;

@ -47,6 +47,8 @@ namespace MWGui
typedef MyGUI::delegates::MultiDelegate<> EventHandle_TradeDone;
EventHandle_TradeDone eventTradeDone;
std::string_view getWindowIdForLua() const override { return "Trade"; }
private:
ItemView* mItemView;
SortFilterItemModel* mSortModel;

@ -53,6 +53,8 @@ namespace MWGui
void TrainingWindow::setPtr(const MWWorld::Ptr& actor)
{
if (actor.isEmpty() || !actor.getClass().isActor())
throw std::runtime_error("Invalid argument in TrainingWindow::setPtr");
mPtr = actor;
MWWorld::Ptr player = MWMechanics::getPlayer();

@ -31,6 +31,8 @@ namespace MWGui
void clear() override { resetReference(); }
std::string_view getWindowIdForLua() const override { return "Training"; }
protected:
void onReferenceUnavailable() override;

@ -112,6 +112,9 @@ namespace MWGui
void TravelWindow::setPtr(const MWWorld::Ptr& actor)
{
if (actor.isEmpty() || !actor.getClass().isActor())
throw std::runtime_error("Invalid argument in TravelWindow::setPtr");
center();
mPtr = actor;
clearDestinations();

@ -19,6 +19,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& actor) override;
std::string_view getWindowIdForLua() const override { return "Travel"; }
protected:
MyGUI::Button* mCancelButton;
MyGUI::TextBox* mPlayerGold;

@ -43,6 +43,8 @@ namespace MWGui
WindowBase* getProgressBar() { return &mProgressBar; }
std::string_view getWindowIdForLua() const override { return "WaitDialog"; }
protected:
MyGUI::TextBox* mDateTimeText;
MyGUI::TextBox* mRestText;

@ -48,6 +48,7 @@ void WindowBase::onDoubleClick(MyGUI::Widget* _sender)
void WindowBase::setVisible(bool visible)
{
visible = visible && !mDisabledByLua;
bool wasVisible = mMainWidget->getVisible();
mMainWidget->setVisible(visible);

@ -49,11 +49,16 @@ namespace MWGui
virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) {}
virtual std::string_view getWindowIdForLua() const { return ""; }
void setDisabledByLua(bool disabled) { mDisabledByLua = disabled; }
protected:
virtual void onTitleDoubleClicked();
private:
void onDoubleClick(MyGUI::Widget* _sender);
bool mDisabledByLua = false;
};
/*

@ -360,8 +360,6 @@ namespace MWGui
bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds");
auto journal = JournalWindow::create(JournalViewModel::create(), questList, mEncoding);
mGuiModeStates[GM_Journal] = GuiModeState(journal.get());
mGuiModeStates[GM_Journal].mCloseSound = ESM::RefId::stringRefId("book close");
mGuiModeStates[GM_Journal].mOpenSound = ESM::RefId::stringRefId("book open");
mWindows.push_back(std::move(journal));
mMessageBoxManager = std::make_unique<MessageBoxManager>(
@ -398,15 +396,11 @@ namespace MWGui
mScrollWindow = scrollWindow.get();
mWindows.push_back(std::move(scrollWindow));
mGuiModeStates[GM_Scroll] = GuiModeState(mScrollWindow);
mGuiModeStates[GM_Scroll].mOpenSound = ESM::RefId::stringRefId("scroll");
mGuiModeStates[GM_Scroll].mCloseSound = ESM::RefId::stringRefId("scroll");
auto bookWindow = std::make_unique<BookWindow>();
mBookWindow = bookWindow.get();
mWindows.push_back(std::move(bookWindow));
mGuiModeStates[GM_Book] = GuiModeState(mBookWindow);
mGuiModeStates[GM_Book].mOpenSound = ESM::RefId::stringRefId("book open");
mGuiModeStates[GM_Book].mCloseSound = ESM::RefId::stringRefId("book close");
auto countDialog = std::make_unique<CountDialog>();
mCountDialog = countDialog.get();
@ -525,6 +519,13 @@ namespace MWGui
mStatsWatcher->addListener(mHud);
mStatsWatcher->addListener(mStatsWindow);
mStatsWatcher->addListener(mCharGen.get());
for (auto& window : mWindows)
{
std::string_view id = window->getWindowIdForLua();
if (!id.empty())
mLuaIdToWindow.emplace(id, window.get());
}
}
void WindowManager::setNewGame(bool newgame)
@ -1267,16 +1268,25 @@ namespace MWGui
mGuiModes.push_back(mode);
mGuiModeStates[mode].update(true);
playSound(mGuiModeStates[mode].mOpenSound);
}
if (force)
mContainerWindow->treatNextOpenAsLoot();
for (WindowBase* window : mGuiModeStates[mode].mWindows)
window->setPtr(arg);
try
{
for (WindowBase* window : mGuiModeStates[mode].mWindows)
window->setPtr(arg);
}
catch (...)
{
popGuiMode();
throw;
}
mKeyboardNavigation->restoreFocus(mode);
updateVisible();
MWBase::Environment::get().getLuaManager()->uiModeChanged(arg);
}
void WindowManager::setCullMask(uint32_t mask)
@ -1294,7 +1304,7 @@ namespace MWGui
return mViewer->getCamera()->getCullMask();
}
void WindowManager::popGuiMode(bool noSound)
void WindowManager::popGuiMode()
{
if (mDragAndDrop && mDragAndDrop->mIsOnDragAndDrop)
{
@ -1307,8 +1317,7 @@ namespace MWGui
mKeyboardNavigation->saveFocus(mode);
mGuiModes.pop_back();
mGuiModeStates[mode].update(false);
if (!noSound)
playSound(mGuiModeStates[mode].mCloseSound);
MWBase::Environment::get().getLuaManager()->uiModeChanged(MWWorld::Ptr());
}
if (!mGuiModes.empty())
@ -1325,11 +1334,11 @@ namespace MWGui
mConsole->onOpen();
}
void WindowManager::removeGuiMode(GuiMode mode, bool noSound)
void WindowManager::removeGuiMode(GuiMode mode)
{
if (!mGuiModes.empty() && mGuiModes.back() == mode)
{
popGuiMode(noSound);
popGuiMode();
return;
}
@ -1343,6 +1352,7 @@ namespace MWGui
}
updateVisible();
MWBase::Environment::get().getLuaManager()->uiModeChanged(MWWorld::Ptr());
}
void WindowManager::goToJail(int days)
@ -1748,7 +1758,10 @@ namespace MWGui
mPlayerBounty = -1;
for (const auto& window : mWindows)
{
window->clear();
window->setDisabledByLua(false);
}
if (mLocalMapRender)
mLocalMapRender->clear();
@ -2334,4 +2347,45 @@ namespace MWGui
{
mMap->asyncPrepareSaveMap();
}
void WindowManager::setDisabledByLua(std::string_view windowId, bool disabled)
{
mLuaIdToWindow.at(windowId)->setDisabledByLua(disabled);
updateVisible();
}
std::vector<std::string_view> WindowManager::getAllWindowIds() const
{
std::vector<std::string_view> res;
for (const auto& [id, _] : mLuaIdToWindow)
res.push_back(id);
return res;
}
std::vector<std::string_view> WindowManager::getAllowedWindowIds(GuiMode mode) const
{
std::vector<std::string_view> res;
if (mode == GM_Inventory)
{
if (mAllowed & GW_Map)
res.push_back(mMap->getWindowIdForLua());
if (mAllowed & GW_Inventory)
res.push_back(mInventoryWindow->getWindowIdForLua());
if (mAllowed & GW_Magic)
res.push_back(mSpellWindow->getWindowIdForLua());
if (mAllowed & GW_Stats)
res.push_back(mStatsWindow->getWindowIdForLua());
}
else
{
auto it = mGuiModeStates.find(mode);
if (it != mGuiModeStates.end())
{
for (const auto* w : it->second.mWindows)
if (!w->getWindowIdForLua().empty())
res.push_back(w->getWindowIdForLua());
}
}
return res;
}
}

@ -149,8 +149,8 @@ namespace MWGui
void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg) override;
void pushGuiMode(GuiMode mode) override;
void popGuiMode(bool noSound = false) override;
void removeGuiMode(GuiMode mode, bool noSound = false) override; ///< can be anywhere in the stack
void popGuiMode() override;
void removeGuiMode(GuiMode mode) override; ///< can be anywhere in the stack
void goToJail(int days) override;
@ -387,6 +387,12 @@ namespace MWGui
void asyncPrepareSaveMap() override;
// Used in Lua bindings
const std::vector<GuiMode>& getGuiModeStack() const override { return mGuiModes; }
void setDisabledByLua(std::string_view windowId, bool disabled) override;
std::vector<std::string_view> getAllWindowIds() const override;
std::vector<std::string_view> getAllowedWindowIds(GuiMode mode) const override;
private:
unsigned int mOldUpdateMask;
unsigned int mOldCullMask;
@ -451,6 +457,9 @@ namespace MWGui
std::vector<std::unique_ptr<WindowBase>> mWindows;
// Mapping windowId -> Window; used by Lua bindings.
std::map<std::string_view, WindowBase*> mLuaIdToWindow;
Translation::Storage& mTranslationDataStorage;
std::unique_ptr<CharacterCreation> mCharGen;
@ -479,9 +488,6 @@ namespace MWGui
void update(bool visible);
std::vector<WindowBase*> mWindows;
ESM::RefId mCloseSound;
ESM::RefId mOpenSound;
};
// Defines the windows that should be shown in a particular GUI mode.
std::map<GuiMode, GuiModeState> mGuiModeStates;

@ -71,9 +71,6 @@ namespace MWInput
case A_Screenshot:
screenshot();
break;
case A_Inventory:
toggleInventory();
break;
case A_Console:
toggleConsole();
break;
@ -87,9 +84,6 @@ namespace MWInput
case A_MoveBackward:
handleGuiArrowKey(action);
break;
case A_Journal:
toggleJournal();
break;
case A_Rest:
rest();
break;
@ -123,9 +117,6 @@ namespace MWInput
case A_QuickKey10:
quickKey(10);
break;
case A_QuickKeysMenu:
showQuickKeysMenu();
break;
case A_ToggleHUD:
windowManager->toggleHud();
break;
@ -157,6 +148,11 @@ namespace MWInput
if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Inventory))
MWBase::Environment::get().getWindowManager()->cycleWeapon(true);
break;
case A_Inventory:
case A_Journal:
case A_QuickKeysMenu:
// Handled in Lua
break;
}
}
@ -248,30 +244,6 @@ namespace MWInput
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Rest); // Open rest GUI
}
void ActionManager::toggleInventory()
{
if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols"))
return;
if (MyGUI::InputManager::getInstance().isModalAny())
return;
if (MWBase::Environment::get().getWindowManager()->isConsoleMode())
return;
// Toggle between game mode and inventory mode
if (!MWBase::Environment::get().getWindowManager()->isGuiMode())
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Inventory);
else
{
MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode();
if (mode == MWGui::GM_Inventory || mode == MWGui::GM_Container)
MWBase::Environment::get().getWindowManager()->popGuiMode();
}
// .. but don't touch any other mode, except container.
}
void ActionManager::toggleConsole()
{
if (MyGUI::InputManager::getInstance().isModalAny())
@ -280,25 +252,6 @@ namespace MWInput
MWBase::Environment::get().getWindowManager()->toggleConsole();
}
void ActionManager::toggleJournal()
{
if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols"))
return;
if (MyGUI::InputManager::getInstance().isModalAny())
return;
MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
if (windowManager->getMode() != MWGui::GM_Journal && windowManager->getMode() != MWGui::GM_MainMenu
&& windowManager->getMode() != MWGui::GM_Settings && windowManager->getJournalAllowed())
{
windowManager->pushGuiMode(MWGui::GM_Journal);
}
else if (windowManager->containsMode(MWGui::GM_Journal))
{
windowManager->removeGuiMode(MWGui::GM_Journal);
}
}
void ActionManager::quickKey(int index)
{
if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")
@ -315,23 +268,6 @@ namespace MWInput
MWBase::Environment::get().getWindowManager()->activateQuickKey(index);
}
void ActionManager::showQuickKeysMenu()
{
if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_QuickKeysMenu)
{
MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode();
return;
}
if (MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) != -1)
return;
if (!checkAllowedToUseItems())
return;
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_QuickKeysMenu);
}
void ActionManager::activate()
{
if (MWBase::Environment::get().getWindowManager()->isGuiMode())

@ -28,17 +28,14 @@ namespace MWInput
bool checkAllowedToUseItems() const;
void toggleMainMenu();
void toggleInventory();
void toggleConsole();
void screenshot();
void toggleJournal();
void activate();
void rest();
void quickLoad();
void quickSave();
void quickKey(int index);
void showQuickKeysMenu();
void resetIdleTime();
float getIdleTime() const { return mTimeIdle; }

@ -128,7 +128,7 @@ namespace MWLua
{
auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = 43;
api["API_REVISION"] = 44;
api["quit"] = [lua]() {
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
MWBase::Environment::get().getStateManager()->requestQuit();

@ -2,6 +2,7 @@
#include <filesystem>
#include <MyGUI_InputManager.h>
#include <osg/Stats>
#include "sol/state_view.hpp"
@ -311,6 +312,15 @@ namespace MWLua
mGlobalScriptsStarted = true;
}
void LuaManager::uiModeChanged(const MWWorld::Ptr& arg)
{
if (mPlayer.isEmpty())
return;
PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
if (playerScripts)
playerScripts->uiModeChanged(arg);
}
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
{
mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.
@ -343,6 +353,15 @@ namespace MWLua
}
}
void LuaManager::inputEvent(const InputEvent& event)
{
if (!MyGUI::InputManager::getInstance().isModalAny()
&& !MWBase::Environment::get().getWindowManager()->isConsoleMode())
{
mInputEvents.push_back(event);
}
}
MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const
{
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();

@ -68,7 +68,7 @@ namespace MWLua
void gameLoaded() override;
void objectAddedToScene(const MWWorld::Ptr& ptr) override;
void objectRemovedFromScene(const MWWorld::Ptr& ptr) override;
void inputEvent(const InputEvent& event) override { mInputEvents.push_back(event); }
void inputEvent(const InputEvent& event) override;
void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) override
{
mEngineEvents.addToQueue(EngineEvents::OnConsume{ getId(actor), getId(consumable) });
@ -83,6 +83,7 @@ namespace MWLua
}
void objectTeleported(const MWWorld::Ptr& ptr) override;
void questUpdated(const ESM::RefId& questId, int stage) override;
void uiModeChanged(const MWWorld::Ptr& arg) override;
MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override;

@ -20,7 +20,7 @@ namespace MWLua
{
registerEngineHandlers({ &mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers,
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mOnFrameHandlers,
&mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved, &mQuestUpdate });
&mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved, &mQuestUpdate, &mUiModeChanged });
}
void processInputEvent(const MWBase::LuaManager::InputEvent& event)
@ -65,6 +65,15 @@ namespace MWLua
return !mConsoleCommandHandlers.mList.empty();
}
// `arg` is either forwarded from MWGui::pushGuiMode or empty
void uiModeChanged(const MWWorld::Ptr& arg)
{
if (arg.isEmpty())
callEngineHandlers(mUiModeChanged);
else
callEngineHandlers(mUiModeChanged, LObject(arg));
}
private:
EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" };
EngineHandlerList mKeyPressHandlers{ "onKeyPress" };
@ -77,6 +86,7 @@ namespace MWLua
EngineHandlerList mTouchpadReleased{ "onTouchRelease" };
EngineHandlerList mTouchpadMoved{ "onTouchMove" };
EngineHandlerList mQuestUpdate{ "onQuestUpdate" };
EngineHandlerList mUiModeChanged{ "_onUiModeChanged" };
};
}

@ -5,6 +5,7 @@
#include <apps/openmw/mwbase/world.hpp>
#include <apps/openmw/mwmechanics/npcstats.hpp>
#include <apps/openmw/mwworld/class.hpp>
#include <apps/openmw/mwworld/globals.hpp>
namespace MWLua
{
@ -126,6 +127,9 @@ namespace MWLua
const MWWorld::Class& cls = o.ptr().getClass();
return cls.getNpcStats(o.ptr()).getBounty();
};
player["isCharGenFinished"] = [](const Object&) -> bool {
return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1;
};
addPlayerQuestBindings(player, context);
}
}

@ -45,10 +45,55 @@ namespace MWLua
{
return i + 1;
}
const std::unordered_map<MWGui::GuiMode, std::string_view> modeToName{
{ MWGui::GM_Settings, "SettingsMenu" },
{ MWGui::GM_Inventory, "Interface" },
{ MWGui::GM_Container, "Container" },
{ MWGui::GM_Companion, "Companion" },
{ MWGui::GM_MainMenu, "MainMenu" },
{ MWGui::GM_Journal, "Journal" },
{ MWGui::GM_Scroll, "Scroll" },
{ MWGui::GM_Book, "Book" },
{ MWGui::GM_Alchemy, "Alchemy" },
{ MWGui::GM_Repair, "Repair" },
{ MWGui::GM_Dialogue, "Dialogue" },
{ MWGui::GM_Barter, "Barter" },
{ MWGui::GM_Rest, "Rest" },
{ MWGui::GM_SpellBuying, "SpellBuying" },
{ MWGui::GM_Travel, "Travel" },
{ MWGui::GM_SpellCreation, "SpellCreation" },
{ MWGui::GM_Enchanting, "Enchanting" },
{ MWGui::GM_Recharge, "Recharge" },
{ MWGui::GM_Training, "Training" },
{ MWGui::GM_MerchantRepair, "MerchantRepair" },
{ MWGui::GM_Levelup, "LevelUp" },
{ MWGui::GM_Name, "ChargenName" },
{ MWGui::GM_Race, "ChargenRace" },
{ MWGui::GM_Birth, "ChargenBirth" },
{ MWGui::GM_Class, "ChargenClass" },
{ MWGui::GM_ClassGenerate, "ChargenClassGenerate" },
{ MWGui::GM_ClassPick, "ChargenClassPick" },
{ MWGui::GM_ClassCreate, "ChargenClassCreate" },
{ MWGui::GM_Review, "ChargenClassReview" },
{ MWGui::GM_Loading, "Loading" },
{ MWGui::GM_LoadingWallpaper, "LoadingWallpaper" },
{ MWGui::GM_Jail, "Jail" },
{ MWGui::GM_QuickKeysMenu, "QuickKeysMenu" },
};
const auto nameToMode = [] {
std::unordered_map<std::string_view, MWGui::GuiMode> res;
for (const auto& [mode, name] : modeToName)
res[name] = mode;
return res;
}();
}
sol::table initUserInterfacePackage(const Context& context)
{
MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
auto element = context.mLua->sol().new_usertype<LuaUi::Element>("Element");
element["layout"] = sol::property([](LuaUi::Element& element) { return element.mLayout; },
[](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; });
@ -78,19 +123,18 @@ namespace MWLua
= [luaManager = context.mLuaManager](const std::string& message, const Misc::Color& color) {
luaManager->addInGameConsoleMessage(message + "\n", color);
};
api["setConsoleMode"] = [luaManager = context.mLuaManager](std::string_view mode) {
luaManager->addAction(
[mode = std::string(mode)] { MWBase::Environment::get().getWindowManager()->setConsoleMode(mode); });
api["setConsoleMode"] = [luaManager = context.mLuaManager, windowManager](std::string_view mode) {
luaManager->addAction([mode = std::string(mode), windowManager] { windowManager->setConsoleMode(mode); });
};
api["setConsoleSelectedObject"] = [luaManager = context.mLuaManager](const sol::object& obj) {
const auto wm = MWBase::Environment::get().getWindowManager();
api["setConsoleSelectedObject"] = [luaManager = context.mLuaManager, windowManager](const sol::object& obj) {
if (obj == sol::nil)
luaManager->addAction([wm] { wm->setConsoleSelectedObject(MWWorld::Ptr()); });
luaManager->addAction([windowManager] { windowManager->setConsoleSelectedObject(MWWorld::Ptr()); });
else
{
if (!obj.is<LObject>())
throw std::runtime_error("Game object expected");
luaManager->addAction([wm, obj = obj.as<LObject>()] { wm->setConsoleSelectedObject(obj.ptr()); });
luaManager->addAction(
[windowManager, obj = obj.as<LObject>()] { windowManager->setConsoleSelectedObject(obj.ptr()); });
}
};
api["content"] = LuaUi::loadContentConstructor(context.mLua);
@ -189,6 +233,65 @@ namespace MWLua
Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video"));
};
api["_getAllUiModes"] = [](sol::this_state lua) {
sol::table res(lua, sol::create);
for (const auto& [_, name] : modeToName)
res[name] = name;
return res;
};
api["_getUiModeStack"] = [windowManager](sol::this_state lua) {
sol::table res(lua, sol::create);
int i = 1;
for (MWGui::GuiMode m : windowManager->getGuiModeStack())
res[i++] = modeToName.at(m);
return res;
};
api["_setUiModeStack"]
= [windowManager, luaManager = context.mLuaManager](sol::table modes, sol::optional<LObject> arg) {
std::vector<MWGui::GuiMode> newStack(modes.size());
for (unsigned i = 0; i < newStack.size(); ++i)
newStack[i] = nameToMode.at(LuaUtil::cast<std::string_view>(modes[i + 1]));
luaManager->addAction(
[windowManager, newStack, arg]() {
MWWorld::Ptr ptr;
if (arg.has_value())
ptr = arg->ptr();
const std::vector<MWGui::GuiMode>& stack = windowManager->getGuiModeStack();
unsigned common = 0;
while (common < std::min(stack.size(), newStack.size()) && stack[common] == newStack[common])
common++;
// TODO: Maybe disallow opening/closing special modes (main menu, settings, loading screen)
// from player scripts. Add new Lua context "menu" that can do it.
for (unsigned i = stack.size() - common; i > 0; i--)
windowManager->popGuiMode();
if (common == newStack.size() && !newStack.empty() && arg.has_value())
windowManager->pushGuiMode(newStack.back(), ptr);
for (unsigned i = common; i < newStack.size(); ++i)
windowManager->pushGuiMode(newStack[i], ptr);
},
"Set UI modes");
};
api["_getAllWindowIds"] = [windowManager](sol::this_state lua) {
sol::table res(lua, sol::create);
for (std::string_view name : windowManager->getAllWindowIds())
res[name] = name;
return res;
};
api["_getAllowedWindows"] = [windowManager](sol::this_state lua, std::string_view mode) {
sol::table res(lua, sol::create);
for (std::string_view name : windowManager->getAllowedWindowIds(nameToMode.at(mode)))
res[name] = name;
return res;
};
api["_setWindowDisabled"]
= [windowManager, luaManager = context.mLuaManager](std::string_view window, bool disabled) {
luaManager->addAction([=]() { windowManager->setDisabledByLua(window, disabled); });
};
// TODO
// api["_showHUD"] = [](bool) {};
// api["_showMouseCursor"] = [](bool) {};
return LuaUtil::makeReadOnly(api);
}
}

@ -204,8 +204,7 @@ CUSTOM, PLAYER: useInterface.lua
{
testing::internal::CaptureStdout();
scripts.receiveEvent("SomeEvent", X1);
EXPECT_EQ(internal::GetCapturedStdout(),
"Test has received event 'SomeEvent', but there are no handlers for this event\n");
EXPECT_EQ(internal::GetCapturedStdout(), "");
}
{
testing::internal::CaptureStdout();

@ -246,6 +246,9 @@ namespace LuaUtil
local nextFn, t, firstKey = ipairs(getmetatable(v).t)
return function(_, k) return nextFn(t, k) end, v, firstKey
end
function lenForReadOnly(v)
return #getmetatable(v).t
end
local function nextForArray(array, index)
index = (index or 0) + 1
if index <= #array then
@ -300,6 +303,7 @@ namespace LuaUtil
meta["__index"] = table;
meta["__pairs"] = lua["pairsForReadOnly"];
meta["__ipairs"] = lua["ipairsForReadOnly"];
meta["__len"] = lua["lenForReadOnly"];
lua_newuserdata(luaState, 0);
sol::stack::push(luaState, meta);

@ -297,11 +297,7 @@ namespace LuaUtil
{
auto it = mEventHandlers.find(eventName);
if (it == mEventHandlers.end())
{
Log(Debug::Warning) << mNamePrefix << " has received event '" << eventName
<< "', but there are no handlers for this event";
return;
}
sol::object data;
try
{

@ -6,5 +6,6 @@ paths=(
scripts/omw/camera/camera.lua
scripts/omw/mwui/init.lua
scripts/omw/settings/player.lua
scripts/omw/ui.lua
)
printf '%s\n' "${paths[@]}"

@ -34,6 +34,7 @@ Lua API reference
interface_controls
interface_mwui
interface_settings
interface_ui
iterables
@ -90,3 +91,7 @@ Interfaces of built-in scripts
* - :ref:`MWUI <Interface MWUI>`
- by player scripts
- Morrowind-style UI templates.
* - :ref:`UI <Interface UI>`
- by player scripts
- | High-level UI modes interface. Allows to override parts
| of the interface.

@ -1,6 +1,9 @@
Built-in events
===============
Actor events
------------
Any script can send to any actor (except player, for player will be ignored) events ``StartAIPackage`` and ``RemoveAIPackages``.
The effect is equivalent to calling ``interfaces.AI.startPackage`` or ``interfaces.AI.removePackages`` in a local script on this actor.
@ -11,3 +14,16 @@ Examples:
actor:sendEvent('StartAIPackage', {type='Combat', target=self.object})
actor:sendEvent('RemoveAIPackages', 'Pursue')
UI events
---------
Every time UI mode is changed built-in scripts send to player the event ``UiModeChanged`` with arguments ``oldMode, ``newMode`` (same as ``I.UI.getMode()``)
and ``arg`` (for example in the mode ``Book`` the argument is the book the player is reading).
.. code-block:: Lua
eventHandlers = {
UiModeChanged = function(data)
print('UiModeChanged from', data.oldMode , 'to', data.newMode, '('..tostring(data.arg)..')')
end
}

@ -0,0 +1,6 @@
Interface UI
============
.. raw:: html
:file: generated_html/scripts_omw_ui.html

@ -481,6 +481,10 @@ The order in which the scripts are started is important. So if one mod should ov
* - :ref:`MWUI <Interface MWUI>`
- by player scripts
- Morrowind-style UI templates.
* - :ref:`UI <Interface UI>`
- by player scripts
- | High-level UI modes interface. Allows to override parts
| of the interface.
Event system
============

@ -89,6 +89,7 @@ set(BUILTIN_DATA_FILES
scripts/omw/mwui/textEdit.lua
scripts/omw/mwui/space.lua
scripts/omw/mwui/init.lua
scripts/omw/ui.lua
shaders/adjustments.omwfx
shaders/bloomlinear.omwfx

@ -13,6 +13,9 @@ PLAYER: scripts/omw/playercontrols.lua
PLAYER: scripts/omw/camera/camera.lua
NPC,CREATURE: scripts/omw/ai.lua
# User interface
PLAYER: scripts/omw/ui.lua
# Lua console
PLAYER: scripts/omw/console/player.lua
GLOBAL: scripts/omw/console/global.lua

@ -47,6 +47,7 @@ local startAttack = false
local autoMove = false
local movementControlsOverridden = false
local combatControlsOverridden = false
local uiControlsOverridden = false
local function processMovement()
local controllerMovement = -input.getAxisValue(input.CONTROLLER_AXIS.MoveForwardBackward)
@ -123,8 +124,48 @@ local function onFrame(dt)
startAttack = false
end
local function checkNotWerewolf()
if Player.isWerewolf(self) then
ui.showMessage(core.getGMST('sWerewolfRefusal'))
return false
else
return true
end
end
local function isJournalAllowed()
-- During chargen journal is not allowed until magic window is allowed
return I.UI.getWindowsForMode(I.UI.MODE.Interface)[I.UI.WINDOW.Magic]
end
local function onInputAction(action)
if core.isWorldPaused() or not input.getControlSwitch(input.CONTROL_SWITCH.Controls) then
if not input.getControlSwitch(input.CONTROL_SWITCH.Controls) then
return
end
if not uiControlsOverridden then
if action == input.ACTION.Inventory then
if I.UI.getMode() == nil then
I.UI.setMode(I.UI.MODE.Interface)
elseif I.UI.getMode() == I.UI.MODE.Interface or I.UI.getMode() == I.UI.MODE.Container then
I.UI.removeMode(I.UI.getMode())
end
elseif action == input.ACTION.Journal then
if I.UI.getMode() == I.UI.MODE.Journal then
I.UI.removeMode(I.UI.MODE.Journal)
elseif isJournalAllowed() then
I.UI.addMode(I.UI.MODE.Journal)
end
elseif action == input.ACTION.QuickKeysMenu then
if I.UI.getMode() == I.UI.MODE.QuickKeysMenu then
I.UI.removeMode(I.UI.MODE.QuickKeysMenu)
elseif checkNotWerewolf() and Player.isCharGenFinished(self) then
I.UI.addMode(I.UI.MODE.QuickKeysMenu)
end
end
end
if core.isWorldPaused() then
return
end
@ -144,9 +185,7 @@ local function onInputAction(action)
if Actor.stance(self) == Actor.STANCE.Spell then
Actor.setStance(self, Actor.STANCE.Nothing)
elseif input.getControlSwitch(input.CONTROL_SWITCH.Magic) then
if Player.isWerewolf(self) then
ui.showMessage(core.getGMST('sWerewolfRefusal'))
else
if checkNotWerewolf() then
Actor.setStance(self, Actor.STANCE.Spell)
end
end
@ -171,19 +210,24 @@ return {
interface = {
--- Interface version
-- @field [parent=#Controls] #number version
version = 0,
version = 1,
--- When set to true then the movement controls including jump and sneak are not processed and can be handled by another script.
-- If movement should be dissallowed completely, consider to use `input.setControlSwitch` instead.
-- If movement should be disallowed completely, consider to use `input.setControlSwitch` instead.
-- @function [parent=#Controls] overrideMovementControls
-- @param #boolean value
overrideMovementControls = function(v) movementControlsOverridden = v end,
--- When set to true then the controls "attack", "toggle spell", "toggle weapon" are not processed and can be handled by another script.
-- If combat should be dissallowed completely, consider to use `input.setControlSwitch` instead.
-- If combat should be disallowed completely, consider to use `input.setControlSwitch` instead.
-- @function [parent=#Controls] overrideCombatControls
-- @param #boolean value
overrideCombatControls = function(v) combatControlsOverridden = v end,
--- When set to true then the controls "open inventory", "open journal" and so on are not processed and can be handled by another script.
-- @function [parent=#Controls] overrideUiControls
-- @param #boolean value
overrideUiControls = function(v) uiControlsOverridden = v end,
}
}

@ -0,0 +1,220 @@
local ui = require('openmw.ui')
local util = require('openmw.util')
local self = require('openmw.self')
local ambient = require('openmw.ambient')
local MODE = ui._getAllUiModes()
local WINDOW = ui._getAllWindowIds()
local replacedWindows = {}
local hiddenWindows = {}
local modeStack = {}
local function registerWindow(window, showFn, hideFn)
if not WINDOW[window] then
error('At the moment it is only possible to override existing windows. Window "'..
tostring(window)..'" not found.')
end
ui._setWindowDisabled(window, true)
if replacedWindows[window] then
replacedWindows[window].hideFn()
end
replacedWindows[window] = {showFn = showFn, hideFn = hideFn, visible = false}
hiddenWindows[window] = nil
end
local function updateHidden(mode, options)
local toHide = {}
if options and options.windows then
for _, w in pairs(ui._getAllowedWindows(mode)) do
toHide[w] = true
end
for _, w in pairs(options.windows) do
toHide[w] = nil
end
end
for w, _ in pairs(hiddenWindows) do
if toHide[w] then
toHide[w] = nil
else
hiddenWindows[w] = nil
if not replacedWindows[w] then
ui._setWindowDisabled(w, false)
end
end
end
for w, _ in pairs(toHide) do
hiddenWindows[w] = true
if not replacedWindows[w] then
ui._setWindowDisabled(w, true)
end
end
end
local function setMode(mode, options)
local function impl()
updateHidden(mode, options)
ui._setUiModeStack({mode}, options and options.target)
end
if mode then
if not pcall(impl) then
error('Invalid mode: ' .. tostring(mode))
end
else
ui._setUiModeStack({})
end
end
local function addMode(mode, options)
local function impl()
updateHidden(mode, options)
ui._setUiModeStack(modeStack, options and options.target)
end
modeStack[#modeStack + 1] = mode
if not pcall(impl) then
modeStack[#modeStack] = nil
error('Invalid mode: ' .. tostring(mode))
end
end
local function removeMode(mode)
local sizeBefore = #modeStack
local j = 1
for i = 1, sizeBefore do
if modeStack[i] ~= mode then
modeStack[j] = modeStack[i]
j = j + 1
end
end
for i = j, sizeBefore do modeStack[i] = nil end
if sizeBefore > #modeStack then
ui._setUiModeStack(modeStack)
end
end
local oldMode = nil
local function onUiModeChanged(arg)
local newStack = ui._getUiModeStack()
for i = 1, math.max(#modeStack, #newStack) do
modeStack[i] = newStack[i]
end
for w, state in pairs(replacedWindows) do
if state.visible then
state.hideFn()
state.visible = false
end
end
local mode = newStack[#newStack]
if mode then
for _, w in pairs(ui._getAllowedWindows(mode)) do
local state = replacedWindows[w]
if state and not hiddenWindows[w] then
state.showFn(arg)
state.visible = true
end
end
end
self:sendEvent('UiModeChanged', {oldMode = oldMode, newMode = mode, arg = arg})
oldMode = mode
end
local function onUiModeChangedEvent(data)
if data.oldMode == data.newMode then
return
end
-- Sounds are processed in the event handler rather than in engine handler
-- in order to allow them to be overridden in mods.
if data.newMode == MODE.Journal or data.newMode == MODE.Book then
ambient.playSound('book open', {scale = false})
elseif data.oldMode == MODE.Journal or data.oldMode == MODE.Book then
if not ambient.isSoundPlaying('item book up') then
ambient.playSound('book close', {scale = false})
end
elseif data.newMode == MODE.Scroll or data.oldMode == MODE.Scroll then
if not ambient.isSoundPlaying('item book up') then
ambient.playSound('scroll', {scale = false})
end
end
end
return {
interfaceName = 'UI',
---
-- @module UI
-- @usage require('openmw.interfaces').UI
interface = {
--- Interface version
-- @field [parent=#UI] #number version
version = 0,
--- All available UI modes.
-- Use `view(I.UI.MODE)` in `luap` console mode to see the list.
-- @field [parent=#UI] #table MODE
MODE = util.makeStrictReadOnly(MODE),
--- All windows.
-- Use `view(I.UI.WINDOW)` in `luap` console mode to see the list.
-- @field [parent=#UI] #table WINDOW
WINDOW = util.makeStrictReadOnly(WINDOW),
--- Register new implementation for the window with given name; overrides previous implementation.
-- Adding new windows is not supported yet. At the moment it is only possible to override built-in windows.
-- @function [parent=#UI] registerWindow
-- @param #string windowName
-- @param #function showFn Callback that will be called when the window should become visible
-- @param #function hideFn Callback that will be called when the window should be hidden
registerWindow = registerWindow,
--- Returns windows that can be shown in given mode.
-- @function [parent=#UI] getWindowsForMode
-- @param #string mode
-- @return #table
getWindowsForMode = ui._getAllowedWindows,
--- Stack of currently active modes
-- @field [parent=#UI] modes
modes = util.makeReadOnly(modeStack),
--- Get current mode (nil if all windows are closed), equivalent to `I.UI.modes[#I.UI.modes]`
-- @function [parent=#UI] getMode
-- @return #string
getMode = function() return modeStack[#modeStack] end,
--- Drop all active modes and set mode.
-- @function [parent=#UI] setMode
-- @param #string mode (optional) New mode
-- @param #table options (optional) Table with keys 'windows' and/or 'target' (see example).
-- @usage I.UI.setMode() -- drop all modes
-- @usage I.UI.setMode('Interface') -- drop all modes and open interface
-- @usage -- Drop all modes, open interface, but show only the map window.
-- I.UI.setMode('Interface', {windows = {'Map'}})
setMode = setMode,
--- Add mode to stack without dropping other active modes.
-- @function [parent=#UI] addMode
-- @param #string mode New mode
-- @param #table options (optional) Table with keys 'windows' and/or 'target' (see example).
-- @usage I.UI.addMode('Journal') -- open journal without dropping active modes.
-- @usage -- Open barter with an NPC
-- I.UI.addMode('Barter', {target = actor})
addMode = addMode,
--- Remove the specified mode from active modes.
-- @function [parent=#UI] removeMode
-- @param #string mode Mode to drop
removeMode = removeMode,
-- TODO
-- registerHudElement = function(name, showFn, hideFn) end,
-- showHud = function(bool) end,
-- isHudVisible = function() end,
-- showHudElement = function(name, bool) end,
-- hudElements, -- map from element name to its visibility
},
engineHandlers = {
_onUiModeChanged = onUiModeChanged,
},
eventHandlers = {
UiModeChanged = onUiModeChangedEvent,
},
}

@ -17,6 +17,9 @@
---
-- @field [parent=#interfaces] scripts.omw.settings.player#scripts.omw.settings.player Settings
---
-- @field [parent=#interfaces] scripts.omw.ui#scripts.omw.ui UI
---
-- @function [parent=#interfaces] __index
-- @param #interfaces self

@ -751,6 +751,12 @@
-- @param openmw.core#GameObject player
-- @return #number
---
-- Whether the character generation for this player is finished.
-- @function [parent=#Player] isCharGenFinished
-- @param openmw.core#GameObject player
-- @return #boolean
---
-- Returns a list containing quests @{#PlayerQuest} for the specified player, indexed by quest ID.
-- @function [parent=#Player] quests

Loading…
Cancel
Save