1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-12-02 09:34:31 +00:00

Resolve merge conflicts from !4924 and !4928

luamanagerimp still needs float frameDuration, and reordering a bunch of fields in the scene manager conflicted with changing the type of maxanisotropy.
This commit is contained in:
AnyOldName3 2025-10-04 23:08:20 +01:00
commit 06cb00bb0b
39 changed files with 364 additions and 157 deletions

View file

@ -1,3 +1,7 @@
0.51.0
------
0.50.0 0.50.0
------ ------
@ -71,6 +75,7 @@
Bug #8650: Some plants turn invisible when being called types.Container.inventory(cont):isResolved() Bug #8650: Some plants turn invisible when being called types.Container.inventory(cont):isResolved()
Bug #8680: Dead ancestor ghosts stop being dust when you rest near them Bug #8680: Dead ancestor ghosts stop being dust when you rest near them
Bug #8686: openmw-cs: Crash when smoothing terrain of a not-yet-created cell. Bug #8686: openmw-cs: Crash when smoothing terrain of a not-yet-created cell.
Bug #8710: Absorb Skill breaks on creatures
Feature #2522: Support quick item transfer Feature #2522: Support quick item transfer
Feature #3740: Gamepad GUI Mode Feature #3740: Gamepad GUI Mode
Feature #3769: Allow GetSpellEffects on enchantments Feature #3769: Allow GetSpellEffects on enchantments

View file

@ -80,9 +80,9 @@ endif()
message(STATUS "Configuring OpenMW...") message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 50) set(OPENMW_VERSION_MINOR 51)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 95) set(OPENMW_LUA_API_REVISION 96)
set(OPENMW_POSTPROCESSING_API_REVISION 3) set(OPENMW_POSTPROCESSING_API_REVISION 3)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")

View file

@ -5,7 +5,7 @@ OpenMW is an open-source open-world RPG game engine that supports playing Morrow
OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set.
* Version: 0.50.0 * Version: 0.51.0
* License: GPLv3 (see [LICENSE](https://gitlab.com/OpenMW/openmw/-/raw/master/LICENSE) for more information) * License: GPLv3 (see [LICENSE](https://gitlab.com/OpenMW/openmw/-/raw/master/LICENSE) for more information)
* Website: https://www.openmw.org * Website: https://www.openmw.org
* IRC: #openmw on irc.libera.chat * IRC: #openmw on irc.libera.chat

View file

@ -84,6 +84,7 @@ file(GLOB UNITTEST_SRC_FILES
esmterrain/testgridsampling.cpp esmterrain/testgridsampling.cpp
resource/testobjectcache.cpp resource/testobjectcache.cpp
resource/testresourcesystem.cpp
vfs/testpathutil.cpp vfs/testpathutil.cpp

View file

@ -0,0 +1,32 @@
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/toutf8/toutf8.hpp>
#include <components/vfs/manager.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <thread>
namespace
{
using namespace testing;
TEST(ResourceResourceSystem, scenemanager_getinstance_should_be_thread_safe)
{
const VFS::Manager vfsManager;
const ToUTF8::Utf8Encoder encoder(ToUTF8::WINDOWS_1252);
Resource::ResourceSystem resourceSystem(&vfsManager, 1.0, &encoder.getStatelessEncoder());
Resource::SceneManager* sceneManager = resourceSystem.getSceneManager();
constexpr VFS::Path::NormalizedView noSuchPath("meshes/whatever.nif");
std::vector<std::thread> threads;
for (int i = 0; i < 50; ++i)
{
threads.emplace_back([=]() { sceneManager->getInstance(noSuchPath); });
}
for (std::thread& thread : threads)
thread.join();
}
}

View file

@ -146,6 +146,7 @@ namespace MWClass
{ {
// Do not allow equip tools from inventory during attack // Do not allow equip tools from inventory during attack
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)
&& !MWBase::Environment::get().getMechanicsManager()->isCastingSpell(npc)
&& MWBase::Environment::get().getWindowManager()->isGuiMode()) && MWBase::Environment::get().getWindowManager()->isGuiMode())
return { 0, "#{sCantEquipWeapWarning}" }; return { 0, "#{sCantEquipWeapWarning}" };

View file

@ -144,6 +144,7 @@ namespace MWClass
{ {
// Do not allow equip tools from inventory during attack // Do not allow equip tools from inventory during attack
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)
&& !MWBase::Environment::get().getMechanicsManager()->isCastingSpell(npc)
&& MWBase::Environment::get().getWindowManager()->isGuiMode()) && MWBase::Environment::get().getWindowManager()->isGuiMode())
return { 0, "#{sCantEquipWeapWarning}" }; return { 0, "#{sCantEquipWeapWarning}" };

View file

@ -270,10 +270,25 @@ namespace MWClass
std::pair<int, std::string_view> Weapon::canBeEquipped(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const std::pair<int, std::string_view> Weapon::canBeEquipped(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const
{ {
int type = ptr.get<ESM::Weapon>()->mBase->mData.mType;
// Do not allow equip weapons from inventory during attack // Do not allow equip weapons from inventory during attack
if (npc.isInCell() && MWBase::Environment::get().getWindowManager()->isGuiMode() if (npc.isInCell() && MWBase::Environment::get().getWindowManager()->isGuiMode()
&& MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)) && MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc))
return { 0, "#{sCantEquipWeapWarning}" }; {
int activeWeaponType = ESM::Weapon::None;
MWMechanics::getActiveWeapon(npc, &activeWeaponType);
if (activeWeaponType > ESM::Weapon::None || activeWeaponType == ESM::Weapon::HandToHand)
{
auto* activeWeapon = MWMechanics::getWeaponType(activeWeaponType);
bool isAmmo = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Class::Ammo;
bool activeWeapUsesAmmo = activeWeapon->mWeaponClass == ESM::WeaponType::Class::Ranged;
bool sameAmmoType = activeWeapon->mAmmoType == type;
// special case for ammo equipping
if ((activeWeapUsesAmmo && !sameAmmoType) || !isAmmo)
return { 0, "#{sCantEquipWeapWarning}" };
}
}
if (hasItemHealth(ptr) && getItemHealth(ptr) == 0) if (hasItemHealth(ptr) && getItemHealth(ptr) == 0)
return { 0, "#{sInventoryMessage1}" }; return { 0, "#{sInventoryMessage1}" };
@ -283,7 +298,6 @@ namespace MWClass
if (slots.first.empty()) if (slots.first.empty())
return { 0, {} }; return { 0, {} };
int type = ptr.get<ESM::Weapon>()->mBase->mData.mType;
if (MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded) if (MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded)
{ {
return { 2, {} }; return { 2, {} };

View file

@ -325,10 +325,10 @@ namespace MWGui
// If we unequip weapon during attack, it can lead to unexpected behaviour // If we unequip weapon during attack, it can lead to unexpected behaviour
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPtr)) if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPtr))
{ {
bool isWeapon = item.mBase.getType() == ESM::Weapon::sRecordId;
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ContainerStoreIterator weapIt = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (isWeapon && invStore.isEquipped(item.mBase)) bool weapActive = mPtr.getClass().getCreatureStats(mPtr).getDrawState() == MWMechanics::DrawState::Weapon;
if (weapActive && weapIt != invStore.end() && *weapIt == item.mBase)
{ {
MWBase::Environment::get().getWindowManager()->messageBox("#{sCantEquipWeapWarning}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sCantEquipWeapWarning}");
return; return;
@ -756,6 +756,8 @@ namespace MWGui
void InventoryWindow::onFrame(float dt) void InventoryWindow::onFrame(float dt)
{ {
updateEncumbranceBar();
if (mUpdateNextFrame) if (mUpdateNextFrame)
{ {
if (mTrading) if (mTrading)
@ -766,7 +768,6 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->getTradeWindow()->updateOffer(); MWBase::Environment::get().getWindowManager()->getTradeWindow()->updateOffer();
} }
updateEncumbranceBar();
mDragAndDrop->update(); mDragAndDrop->update();
mItemView->update(); mItemView->update();
notifyContentChanged(); notifyContentChanged();
@ -969,25 +970,25 @@ namespace MWGui
mControllerButtons.mA = "#{OMWEngine:InventorySelect}"; mControllerButtons.mA = "#{OMWEngine:InventorySelect}";
mControllerButtons.mB = "#{Interface:Close}"; mControllerButtons.mB = "#{Interface:Close}";
mControllerButtons.mX.clear(); mControllerButtons.mX.clear();
mControllerButtons.mR2 = "#{sCompanionShare}"; mControllerButtons.mR2 = "#{Interface:Share}";
break; break;
case MWGui::GM_Container: case MWGui::GM_Container:
mControllerButtons.mA = "#{OMWEngine:InventorySelect}"; mControllerButtons.mA = "#{OMWEngine:InventorySelect}";
mControllerButtons.mB = "#{Interface:Close}"; mControllerButtons.mB = "#{Interface:Close}";
mControllerButtons.mX = "#{Interface:TakeAll}"; mControllerButtons.mX = "#{Interface:TakeAll}";
mControllerButtons.mR2 = "#{sContainer}"; mControllerButtons.mR2 = "#{Interface:Container}";
break; break;
case MWGui::GM_Barter: case MWGui::GM_Barter:
mControllerButtons.mA = "#{sSell}"; mControllerButtons.mA = "#{Interface:Sell}";
mControllerButtons.mB = "#{Interface:Cancel}"; mControllerButtons.mB = "#{Interface:Cancel}";
mControllerButtons.mX = "#{Interface:Offer}"; mControllerButtons.mX = "#{Interface:Offer}";
mControllerButtons.mR2 = "#{sBarter}"; mControllerButtons.mR2 = "#{Interface:Barter}";
break; break;
case MWGui::GM_Inventory: case MWGui::GM_Inventory:
default: default:
mControllerButtons.mA = "#{sEquip}"; mControllerButtons.mA = "#{Interface:Equip}";
mControllerButtons.mB = "#{Interface:Back}"; mControllerButtons.mB = "#{Interface:Back}";
mControllerButtons.mX = "#{sDrop}"; mControllerButtons.mX = "#{Interface:Drop}";
mControllerButtons.mR2.clear(); mControllerButtons.mR2.clear();
break; break;
} }

View file

@ -836,9 +836,10 @@ namespace MWGui
if (Settings::gui().mControllerMenus) if (Settings::gui().mControllerMenus)
{ {
mControllerButtons.mB = "#{Interface:Back}"; mControllerButtons.mB = "#{Interface:Back}";
mControllerButtons.mX = global ? "#{sLocal}" : "#{sWorld}"; mControllerButtons.mX = global ? "#{Interface:Local}" : "#{Interface:World}";
mControllerButtons.mY = "#{sCenter}"; mControllerButtons.mY = "#{Interface:Center}";
mControllerButtons.mDpad = Settings::map().mAllowZooming ? "" : "#{sMove}"; if (!Settings::map().mAllowZooming)
mControllerButtons.mDpad = "#{Interface:Move}";
} }
} }
@ -1233,7 +1234,7 @@ namespace MWGui
mLocalMap->setVisible(!global); mLocalMap->setVisible(!global);
mButton->setCaptionWithReplacing(global ? "#{sLocal}" : "#{sWorld}"); mButton->setCaptionWithReplacing(global ? "#{sLocal}" : "#{sWorld}");
mControllerButtons.mX = global ? "#{sLocal}" : "#{sWorld}"; mControllerButtons.mX = global ? "#{Interface:Local}" : "#{Interface:World}";
MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay(); MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
} }
@ -1537,7 +1538,10 @@ namespace MWGui
ControllerButtons* EditNoteDialog::getControllerButtons() ControllerButtons* EditNoteDialog::getControllerButtons()
{ {
mControllerButtons.mX = getDeleteButtonShown() ? "#{sDelete}" : ""; if (getDeleteButtonShown())
mControllerButtons.mX = "#{Interface:Delete}";
else
mControllerButtons.mX.clear();
return &mControllerButtons; return &mControllerButtons;
} }

View file

@ -111,9 +111,9 @@ namespace MWGui
mControllerButtons.mLStick = "#{Interface:Mouse}"; mControllerButtons.mLStick = "#{Interface:Mouse}";
mControllerButtons.mA = "#{Interface:Select}"; mControllerButtons.mA = "#{Interface:Select}";
mControllerButtons.mB = "#{Interface:Back}"; mControllerButtons.mB = "#{Interface:Back}";
mControllerButtons.mY = "#{sSex}"; mControllerButtons.mY = "#{Interface:Sex}";
mControllerButtons.mL1 = "#{sHair}"; mControllerButtons.mL1 = "#{Interface:Hair}";
mControllerButtons.mR1 = "#{sFace}"; mControllerButtons.mR1 = "#{Interface:Face}";
} }
updateRaces(); updateRaces();

View file

@ -215,6 +215,12 @@ namespace MWInput
void ControllerManager::axisMoved(int deviceID, const SDL_ControllerAxisEvent& arg) void ControllerManager::axisMoved(int deviceID, const SDL_ControllerAxisEvent& arg)
{ {
if (mBindingsManager->isDetectingBindingState())
{
mBindingsManager->controllerAxisMoved(deviceID, arg);
return;
}
if (!Settings::input().mEnableController || MWBase::Environment::get().getInputManager()->controlsDisabled()) if (!Settings::input().mEnableController || MWBase::Environment::get().getInputManager()->controlsDisabled())
return; return;

View file

@ -42,6 +42,20 @@
namespace MWLua namespace MWLua
{ {
namespace
{
struct BoolScopeGuard
{
bool& mValue;
BoolScopeGuard(bool& value)
: mValue(value)
{
mValue = true;
}
~BoolScopeGuard() { mValue = false; }
};
}
static LuaUtil::LuaStateSettings createLuaStateSettings() static LuaUtil::LuaStateSettings createLuaStateSettings()
{ {
@ -264,31 +278,33 @@ namespace MWLua
// can teleport the player to the starting location before the first frame is rendered. // can teleport the player to the starting location before the first frame is rendered.
mGlobalScripts.newGameStarted(); mGlobalScripts.newGameStarted();
} }
BoolScopeGuard updateGuard(mRunningSynchronizedUpdates);
// We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
mProcessingInputEvents = true;
PlayerScripts* playerScripts PlayerScripts* playerScripts
= mPlayer.isEmpty() ? nullptr : dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts()); = mPlayer.isEmpty() ? nullptr : dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency.
for (const auto& event : mMenuInputEvents)
mMenuScripts.processInputEvent(event);
mMenuInputEvents.clear();
if (playerScripts && !windowManager->containsMode(MWGui::GM_MainMenu))
{ {
for (const auto& event : mInputEvents) BoolScopeGuard processingGuard(mProcessingInputEvents);
playerScripts->processInputEvent(event);
for (const auto& event : mMenuInputEvents)
mMenuScripts.processInputEvent(event);
mMenuInputEvents.clear();
if (playerScripts && !windowManager->containsMode(MWGui::GM_MainMenu))
{
for (const auto& event : mInputEvents)
playerScripts->processInputEvent(event);
}
mInputEvents.clear();
mLuaEvents.callMenuEventHandlers();
float frameDuration = MWBase::Environment::get().getWorld()->getTimeManager()->isPaused()
? 0.f
: MWBase::Environment::get().getFrameDuration();
mInputActions.update(frameDuration);
mMenuScripts.onFrame(frameDuration);
if (playerScripts)
playerScripts->onFrame(frameDuration);
} }
mInputEvents.clear();
mLuaEvents.callMenuEventHandlers();
float frameDuration = MWBase::Environment::get().getWorld()->getTimeManager()->isPaused()
? 0.f
: MWBase::Environment::get().getFrameDuration();
mInputActions.update(frameDuration);
mMenuScripts.onFrame(frameDuration);
if (playerScripts)
playerScripts->onFrame(frameDuration);
mProcessingInputEvents = false;
for (const auto& [message, mode] : mUIMessages) for (const auto& [message, mode] : mUIMessages)
windowManager->messageBox(message, mode); windowManager->messageBox(message, mode);
@ -316,7 +332,7 @@ namespace MWLua
void LuaManager::applyDelayedActions() void LuaManager::applyDelayedActions()
{ {
mApplyingDelayedActions = true; BoolScopeGuard applyingGuard(mApplyingDelayedActions);
for (DelayedAction& action : mActionQueue) for (DelayedAction& action : mActionQueue)
action.apply(); action.apply();
mActionQueue.clear(); mActionQueue.clear();
@ -324,7 +340,6 @@ namespace MWLua
if (mTeleportPlayerAction) if (mTeleportPlayerAction)
mTeleportPlayerAction->apply(); mTeleportPlayerAction->apply();
mTeleportPlayerAction.reset(); mTeleportPlayerAction.reset();
mApplyingDelayedActions = false;
} }
void LuaManager::clear() void LuaManager::clear()

View file

@ -125,7 +125,7 @@ namespace MWLua
// Some changes to the game world can not be done from the scripting thread (because it runs in parallel with // Some changes to the game world can not be done from the scripting thread (because it runs in parallel with
// OSG Cull), so we need to queue it and apply from the main thread. // OSG Cull), so we need to queue it and apply from the main thread.
void addAction(std::function<void()> action, std::string_view name = ""); void addAction(std::function<void()> action, std::string_view name = {});
void addTeleportPlayerAction(std::function<void()> action); void addTeleportPlayerAction(std::function<void()> action);
// Saving // Saving
@ -174,6 +174,8 @@ namespace MWLua
void sendLocalEvent( void sendLocalEvent(
const MWWorld::Ptr& target, const std::string& name, const std::optional<sol::table>& data = std::nullopt); const MWWorld::Ptr& target, const std::string& name, const std::optional<sol::table>& data = std::nullopt);
bool isSynchronizedUpdateRunning() const { return mRunningSynchronizedUpdates; }
private: private:
void initConfiguration(); void initConfiguration();
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr,
@ -187,6 +189,7 @@ namespace MWLua
bool mApplyingDelayedActions = false; bool mApplyingDelayedActions = false;
bool mNewGameStarted = false; bool mNewGameStarted = false;
bool mReloadAllScriptsRequested = false; bool mReloadAllScriptsRequested = false;
bool mRunningSynchronizedUpdates = false;
LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::ScriptsConfiguration mConfiguration;
LuaUtil::LuaState mLua; LuaUtil::LuaState mLua;
LuaUi::ResourceManager mUiResourceManager; LuaUi::ResourceManager mUiResourceManager;

View file

@ -8,6 +8,7 @@
#include "../mwstate/character.hpp" #include "../mwstate/character.hpp"
#include "context.hpp" #include "context.hpp"
#include "luamanagerimp.hpp"
namespace MWLua namespace MWLua
{ {
@ -72,7 +73,9 @@ namespace MWLua
return sol::nullopt; return sol::nullopt;
}; };
api["saveGame"] = [](std::string_view description, sol::optional<std::string_view> slotName) { api["saveGame"] = [context](std::string_view description, sol::optional<std::string_view> slotName) {
if (!context.mLuaManager->isSynchronizedUpdateRunning())
throw std::runtime_error("menu.saveGame can only be used during engine or event handler processing");
MWBase::StateManager* manager = MWBase::Environment::get().getStateManager(); MWBase::StateManager* manager = MWBase::Environment::get().getStateManager();
const MWState::Character* character = manager->getCurrentCharacter(); const MWState::Character* character = manager->getCurrentCharacter();
const MWState::Slot* slot = nullptr; const MWState::Slot* slot = nullptr;

View file

@ -851,7 +851,7 @@ namespace MWMechanics
else if (!godmode) else if (!godmode)
{ {
damageSkill(target, effect, effect.mMagnitude); damageSkill(target, effect, effect.mMagnitude);
if (!caster.isEmpty()) if (!caster.isEmpty() && caster.getClass().isNpc())
fortifySkill(caster, effect, effect.mMagnitude); fortifySkill(caster, effect, effect.mMagnitude);
} }
break; break;
@ -1303,7 +1303,7 @@ namespace MWMechanics
{ {
const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId()); const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId());
restoreSkill(target, effect, effect.mMagnitude); restoreSkill(target, effect, effect.mMagnitude);
if (!caster.isEmpty()) if (!caster.isEmpty() && caster.getClass().isNpc())
fortifySkill(caster, effect, -effect.mMagnitude); fortifySkill(caster, effect, -effect.mMagnitude);
} }
break; break;

View file

@ -659,15 +659,13 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
{ {
const char* release; const char* release;
// Report the last version still capable of reading this save // Report the last version still capable of reading this save
if (e.getFormatVersion() <= ESM::OpenMW0_48SaveGameFormatVersion) if (e.getFormatVersion() < ESM::OpenMW0_49MinSaveGameFormatVersion)
release = "OpenMW 0.48.0"; release = "OpenMW 0.48.0";
else if (e.getFormatVersion() <= ESM::OpenMW0_49SaveGameFormatVersion)
release = "OpenMW 0.49.0";
else else
{ {
// Insert additional else if statements above to cover future releases // Insert additional else if statements above to cover future releases
static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_50SaveGameFormatVersion); static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_49MinSaveGameFormatVersion);
release = "OpenMW 0.50.0"; release = "OpenMW 0.51.0";
} }
auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine"); auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine");
std::string error = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release }); std::string error = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release });

View file

@ -31,9 +31,7 @@ namespace ESM
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 34; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 34;
inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 5; inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 5;
inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_49MinSaveGameFormatVersion = 5;
inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = 34;
inline constexpr FormatVersion OpenMW0_50SaveGameFormatVersion = CurrentSaveGameFormatVersion;
} }
#endif #endif

View file

@ -47,7 +47,7 @@ namespace LuaUtil
} }
} }
void ScriptsContainer::printError(int scriptId, std::string_view msg, const std::exception& e) void ScriptsContainer::printError(int scriptId, std::string_view msg, const std::exception& e) const
{ {
Log(Debug::Error) << mNamePrefix << "[" << scriptPath(scriptId) << "] " << msg << ": " << e.what(); Log(Debug::Error) << mNamePrefix << "[" << scriptPath(scriptId) << "] " << msg << ": " << e.what();
} }
@ -408,7 +408,7 @@ namespace LuaUtil
void ScriptsContainer::save(ESM::LuaScripts& data) void ScriptsContainer::save(ESM::LuaScripts& data)
{ {
if (UnloadedData* unloadedData = std::get_if<UnloadedData>(&mData)) if (const UnloadedData* unloadedData = std::get_if<UnloadedData>(&mData))
{ {
data.mScripts = unloadedData->mScripts; data.mScripts = unloadedData->mScripts;
return; return;

View file

@ -271,7 +271,7 @@ namespace LuaUtil
// Returns script by id (throws an exception if doesn't exist) // Returns script by id (throws an exception if doesn't exist)
Script& getScript(int scriptId); Script& getScript(int scriptId);
void printError(int scriptId, std::string_view msg, const std::exception& e); void printError(int scriptId, std::string_view msg, const std::exception& e) const;
const VFS::Path::Normalized& scriptPath(int scriptId) const const VFS::Path::Normalized& scriptPath(int scriptId) const
{ {

View file

@ -444,15 +444,6 @@ namespace Resource
Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay) Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay)
: ResourceManager(vfs, expiryDelay) : ResourceManager(vfs, expiryDelay)
, mShaderManager(new Shader::ShaderManager) , mShaderManager(new Shader::ShaderManager)
, mForceShaders(false)
, mClampLighting(true)
, mAutoUseNormalMaps(false)
, mAutoUseSpecularMaps(false)
, mApplyLightingToEnvMaps(false)
, mLightingMethod(SceneUtil::LightingMethod::FFP)
, mConvertAlphaTestToAlphaToCoverage(false)
, mAdjustCoverageForAlphaTest(false)
, mSupportsNormalsRT(false)
, mSharedStateManager(new SharedStateManager) , mSharedStateManager(new SharedStateManager)
, mImageManager(imageManager) , mImageManager(imageManager)
, mNifFileManager(nifFileManager) , mNifFileManager(nifFileManager)
@ -460,8 +451,8 @@ namespace Resource
, mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR)
, mMagFilter(osg::Texture::LINEAR) , mMagFilter(osg::Texture::LINEAR)
, mMaxAnisotropy(1.f) , mMaxAnisotropy(1.f)
, mUnRefImageDataAfterApply(false)
, mParticleSystemMask(~0u) , mParticleSystemMask(~0u)
, mLightingMethod(SceneUtil::LightingMethod::FFP)
{ {
} }
@ -986,8 +977,7 @@ namespace Resource
osg::ref_ptr<osg::Node> SceneManager::cloneErrorMarker() osg::ref_ptr<osg::Node> SceneManager::cloneErrorMarker()
{ {
if (!mErrorMarker) std::call_once(mErrorMarkerFlag, [this] { mErrorMarker = loadErrorMarker(); });
mErrorMarker = loadErrorMarker();
return static_cast<osg::Node*>(mErrorMarker->clone(osg::CopyOp::DEEP_COPY_ALL)); return static_cast<osg::Node*>(mErrorMarker->clone(osg::CopyOp::DEEP_COPY_ALL));
} }

View file

@ -237,42 +237,43 @@ namespace Resource
osg::ref_ptr<osg::Node> loadErrorMarker(); osg::ref_ptr<osg::Node> loadErrorMarker();
osg::ref_ptr<osg::Node> cloneErrorMarker(); osg::ref_ptr<osg::Node> cloneErrorMarker();
mutable std::mutex mSharedStateMutex;
std::unique_ptr<Shader::ShaderManager> mShaderManager; std::unique_ptr<Shader::ShaderManager> mShaderManager;
bool mForceShaders;
bool mClampLighting;
bool mAutoUseNormalMaps;
std::string mNormalMapPattern; std::string mNormalMapPattern;
std::string mNormalHeightMapPattern; std::string mNormalHeightMapPattern;
bool mAutoUseSpecularMaps;
std::string mSpecularMapPattern; std::string mSpecularMapPattern;
bool mApplyLightingToEnvMaps;
SceneUtil::LightingMethod mLightingMethod;
SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods;
bool mConvertAlphaTestToAlphaToCoverage;
bool mAdjustCoverageForAlphaTest;
bool mSupportsNormalsRT;
std::array<osg::ref_ptr<osg::Texture>, 2> mOpaqueDepthTex; std::array<osg::ref_ptr<osg::Texture>, 2> mOpaqueDepthTex;
bool mWeatherParticleOcclusion = false;
osg::ref_ptr<Resource::SharedStateManager> mSharedStateManager; osg::ref_ptr<Resource::SharedStateManager> mSharedStateManager;
mutable std::mutex mSharedStateMutex;
Resource::ImageManager* mImageManager; Resource::ImageManager* mImageManager;
Resource::NifFileManager* mNifFileManager; Resource::NifFileManager* mNifFileManager;
Resource::BgsmFileManager* mBgsmFileManager; Resource::BgsmFileManager* mBgsmFileManager;
osg::ref_ptr<osgUtil::IncrementalCompileOperation> mIncrementalCompileOperation;
mutable osg::ref_ptr<osg::Node> mErrorMarker;
mutable std::once_flag mErrorMarkerFlag;
osg::Texture::FilterMode mMinFilter; osg::Texture::FilterMode mMinFilter;
osg::Texture::FilterMode mMagFilter; osg::Texture::FilterMode mMagFilter;
float mMaxAnisotropy; float mMaxAnisotropy;
bool mUnRefImageDataAfterApply;
osg::ref_ptr<osgUtil::IncrementalCompileOperation> mIncrementalCompileOperation;
unsigned int mParticleSystemMask; unsigned int mParticleSystemMask;
mutable osg::ref_ptr<osg::Node> mErrorMarker; SceneUtil::LightingMethod mLightingMethod;
SceneUtil::LightManager::SupportedMethods mSupportedLightingMethods;
bool mForceShaders = false;
bool mClampLighting = true;
bool mAutoUseNormalMaps = false;
bool mAutoUseSpecularMaps = false;
bool mApplyLightingToEnvMaps = false;
bool mConvertAlphaTestToAlphaToCoverage = false;
bool mAdjustCoverageForAlphaTest = false;
bool mSupportsNormalsRT = false;
bool mWeatherParticleOcclusion = false;
bool mUnRefImageDataAfterApply = false;
SceneManager(const SceneManager&); SceneManager(const SceneManager&) = delete;
void operator=(const SceneManager&); void operator=(const SceneManager&) = delete;
}; };
} }

View file

@ -70,6 +70,31 @@ Screenshots
.. note:: .. note::
Flatpak sets ``$XDG_DATA_HOME`` to ``$HOME/.var/app/$FLATPAK_ID/data``, so screenshots will be at ``$HOME/.var/app/org.openmw.OpenMW/data/openmw/screenshots`` if you use the Flatpak. Flatpak sets ``$XDG_DATA_HOME`` to ``$HOME/.var/app/$FLATPAK_ID/data``, so screenshots will be at ``$HOME/.var/app/org.openmw.OpenMW/data/openmw/screenshots`` if you use the Flatpak.
Override directory (data-local)
-------------------------------
This is the directory in which OpenMW-CS saves generated content files.
Additionally, this is always the last-loaded data directory in OpenMW, overriding any which came before it.
This can be useful, for instance, for placing automatically-generated plugins created by external tools or to be very certain that particular assets are always overridden regardless of load order.
You may define your own, custom ``data-local`` directory by using it as a key in ``openmw.cfg``, e.g. ``data-local=C:/Games/OpenMW/data``.
+--------------+----------------------------------------------------------------------------------------------------+
| OS | Location |
+==============+====================================================================================================+
| Linux | ``$XDG_DATA_HOME/openmw/data`` or ``$HOME/.local/share/openmw/data`` |
+--------------+----------------------------------------------------------------------------------------------------+
| Mac | ``$HOME/Library/Application\ Support/openmw/data`` |
+--------------+---------------+------------------------------------------------------------------------------------+
| Windows | File Explorer | ``Documents\My Games\OpenMW\data`` |
| +---------------+------------------------------------------------------------------------------------+
| | PowerShell | ``Join-Path ([environment]::GetFolderPath("mydocuments")) "My Games\OpenMW\data"`` |
| +---------------+------------------------------------------------------------------------------------+
| | Example | ``C:\Users\Username\Documents\My Games\OpenMW\data`` |
+--------------+---------------+------------------------------------------------------------------------------------+
.. note::
Flatpak sets ``$XDG_DATA_HOME`` to ``$HOME/.var/app/$FLATPAK_ID/data``, so data-local will be set to ``$HOME/.var/app/org.openmw.OpenMW/data/openmw/data`` if you use the Flatpak.
Custom configuration directories Custom configuration directories
================================ ================================

View file

@ -1,17 +1,26 @@
Ask: "sAsk" Ask: "sAsk"
Back: "sBack" Back: "sBack"
Barter: "sBarter"
Buy: "sBuy" Buy: "sBuy"
Cancel: "sCancel" Cancel: "sCancel"
Center: "sCenter" # This has a trailing space in Russian and French games
Close: "sClose" Close: "sClose"
Container: "sContainer" # This has a trailing space in the Russian game
Create: "sCreate" Create: "sCreate"
Delete: "sDelete"
DisposeOfCorpse: "sDisposeofCorpse" DisposeOfCorpse: "sDisposeofCorpse"
Done: "sDone" Done: "sDone"
Drop: "sDrop" # This has a trailing space in the Russian game
Equip: "sEquip" # This has a trailing space in the Russian game
Face: "sFace"
Goodbye: "sGoodbye" Goodbye: "sGoodbye"
Hair: "sHair"
Info: "sInfo" Info: "sInfo"
Inventory: "sInventory" Inventory: "sInventory"
Item: "sItem" Item: "sItem"
Local: "sLocal"
MagicEffects: "sMagicEffects" MagicEffects: "sMagicEffects"
# NB: sMouse exists but it is not localized in the Russian game and should not be used to translate Mouse # Mouse/Move: sMouse and sMove exist but they are not localised in the Russian game and should not be used
Next: "sNext" Next: "sNext"
No: "sNo" No: "sNo"
None: "sNone" None: "sNone"
@ -25,6 +34,9 @@ Rest: "sRest"
ScrollDown: "sScrolldown" ScrollDown: "sScrolldown"
ScrollUp: "sScrollup" ScrollUp: "sScrollup"
Select: "sSelect" Select: "sSelect"
Sell: "sSell"
Sex: "sSex"
Share: "sCompanionShare"
Soul: "sSoulGem" Soul: "sSoulGem"
Take: "sTake" Take: "sTake"
TakeAll: "sTakeAll" TakeAll: "sTakeAll"
@ -32,4 +44,5 @@ Topics: "sTopics"
Travel: "sTravel" Travel: "sTravel"
UntilHealed: "sUntilHealed" UntilHealed: "sUntilHealed"
Wait: "sWait" Wait: "sWait"
World: "sWorld"
Yes: "sYes" Yes: "sYes"

View file

@ -76,6 +76,7 @@ set(BUILTIN_DATA_FILES
l10n/OMWCamera/fr.yaml l10n/OMWCamera/fr.yaml
l10n/OMWCamera/pl.yaml l10n/OMWCamera/pl.yaml
l10n/OMWCombat/de.yaml
l10n/OMWCombat/en.yaml l10n/OMWCombat/en.yaml
l10n/OMWCombat/fr.yaml l10n/OMWCombat/fr.yaml
l10n/OMWCombat/ru.yaml l10n/OMWCombat/ru.yaml

View file

@ -1,12 +1,17 @@
Ask: "Fragen" Ask: "Fragen"
Back: "Zurück" Back: "Zurück"
Barter: "Handeln"
Buy: "Kaufen" Buy: "Kaufen"
Cancel: "Abbruch" Cancel: "Abbruch"
Center: "Zentrieren"
Close: "Schließen" Close: "Schließen"
Container: "Behälter"
Copy: "Kopieren" Copy: "Kopieren"
Create: "Herstellen" Create: "Herstellen"
Delete: "Entfernen"
DisposeOfCorpse: "Leiche beseitigen" DisposeOfCorpse: "Leiche beseitigen"
Done: "Fertig" Done: "Fertig"
Drop: "Ablegen"
DurationDay: "{days} d " DurationDay: "{days} d "
DurationHour: "{hours} h " DurationHour: "{hours} h "
DurationMinute: "{minutes} min " DurationMinute: "{minutes} min "
@ -26,12 +31,17 @@ DurationYear: |-
one{{years} Jahr } one{{years} Jahr }
other{{years} Jahre } other{{years} Jahre }
} }
Equip: "Verwenden"
Face: "Gesicht"
Goodbye: "Lebt wohl!" Goodbye: "Lebt wohl!"
Hair: "Haar"
Info: "Info" Info: "Info"
Inventory: "Inventar" Inventory: "Inventar"
Item: "Gegenstand" Item: "Gegenstand"
Local: "Lokal"
MagicEffects: "Magischer Effekt" MagicEffects: "Magischer Effekt"
Mouse: "Maus" Mouse: "Maus"
Move: "Bewegen"
Next: "Weiter" Next: "Weiter"
No: "Nein" No: "Nein"
# This one is a bit tricky since it can be translated to # This one is a bit tricky since it can be translated to
@ -51,6 +61,9 @@ Rest: "Rasten"
ScrollDown: "Nach unten scrollen" ScrollDown: "Nach unten scrollen"
ScrollUp: "Nach oben scrollen" ScrollUp: "Nach oben scrollen"
Select: "Auswählen" Select: "Auswählen"
Sell: "Verkaufen"
Sex: "Geschlecht"
Share: "Teilen"
Soul: "Seele" Soul: "Seele"
Take: "Nehmen" Take: "Nehmen"
TakeAll: "Alles nehmen" TakeAll: "Alles nehmen"
@ -58,4 +71,5 @@ Topics: "Themen"
Travel: "Reisen" Travel: "Reisen"
UntilHealed: "Bis geheilt" UntilHealed: "Bis geheilt"
Wait: "Warten" Wait: "Warten"
World: "Welt"
Yes: "Ja" Yes: "Ja"

View file

@ -1,12 +1,17 @@
Ask: "Ask" Ask: "Ask"
Back: "Back" Back: "Back"
Barter: "Barter"
Buy: "Buy" Buy: "Buy"
Cancel: "Cancel" Cancel: "Cancel"
Center: "Center"
Close: "Close" Close: "Close"
Container: "Container"
Copy: "Copy" Copy: "Copy"
Create: "Create" Create: "Create"
Delete: "Delete"
DisposeOfCorpse: "Dispose of Corpse" DisposeOfCorpse: "Dispose of Corpse"
Done: "Done" Done: "Done"
Drop: "Drop"
DurationDay: "{days} d " DurationDay: "{days} d "
DurationHour: "{hours} h " DurationHour: "{hours} h "
DurationMinute: "{minutes} min " DurationMinute: "{minutes} min "
@ -21,12 +26,17 @@ DurationYear: |-
one{{years} yr } one{{years} yr }
other{{years} yrs } other{{years} yrs }
} }
Equip: "Equip"
Face: "Face"
Goodbye: "Goodbye" Goodbye: "Goodbye"
Hair: "Hair"
Info: "Info" Info: "Info"
Inventory: "Inventory" Inventory: "Inventory"
Item: "Item" Item: "Item"
Local: "Local"
MagicEffects: "Magic Effects" MagicEffects: "Magic Effects"
Mouse: "Mouse" Mouse: "Mouse"
Move: "Move"
Next: "Next" Next: "Next"
No: "No" No: "No"
None: "None" None: "None"
@ -42,6 +52,9 @@ Rest: "Rest"
ScrollDown: "Scroll Down" ScrollDown: "Scroll Down"
ScrollUp: "Scroll Up" ScrollUp: "Scroll Up"
Select: "Select" Select: "Select"
Sell: "Sell"
Sex: "Sex"
Share: "Share"
Soul: "Soul" Soul: "Soul"
Take: "Take" Take: "Take"
TakeAll: "Take All" TakeAll: "Take All"
@ -49,4 +62,5 @@ Topics: "Topics"
Travel: "Travel" Travel: "Travel"
UntilHealed: "Until Healed" UntilHealed: "Until Healed"
Wait: "Wait" Wait: "Wait"
World: "World"
Yes: "Yes" Yes: "Yes"

View file

@ -1,12 +1,17 @@
Ask: "Demander" Ask: "Demander"
Back: "En arrière" Back: "En arrière"
Barter: "Marchander"
Buy: "Acheter" Buy: "Acheter"
Cancel: "Annuler" Cancel: "Annuler"
Center: "Centrer"
Close: "Fermer" Close: "Fermer"
Container: "Contenant"
Copy: "Copier" Copy: "Copier"
Create: "Créer" Create: "Créer"
DisposeOfCorpse: "Supprimer cadavre" DisposeOfCorpse: "Supprimer cadavre"
Delete: "Effacer"
Done: "Fait" Done: "Fait"
Drop: "Lâcher"
DurationDay: |- DurationDay: |-
{days, plural, {days, plural,
one{{days} jour } one{{days} jour }
@ -21,12 +26,17 @@ DurationYear: |-
one{{years} an } one{{years} an }
other{{years} ans } other{{years} ans }
} }
Equip: "S'équiper"
Face: "Face"
Goodbye: "Au revoir" Goodbye: "Au revoir"
Hair: "Cheveux"
Info: "Info" Info: "Info"
Inventory: "Inventaire" Inventory: "Inventaire"
Item: "Objet" Item: "Objet"
Local: "Local"
MagicEffects: "Effets magiques" MagicEffects: "Effets magiques"
Mouse: "Souris" Mouse: "Souris"
Move: "Déplacement"
Next: "Suivant" Next: "Suivant"
No: "Non" No: "Non"
None: "Aucun" None: "Aucun"
@ -42,6 +52,9 @@ Rest: "Repos"
ScrollDown: "Défilement bas" ScrollDown: "Défilement bas"
ScrollUp: "Défilement haut" ScrollUp: "Défilement haut"
Select: "Sélectionner" Select: "Sélectionner"
Sell: "Vendre"
Sex: "Sexe"
Share: "Répartir"
Soul: "Ame" Soul: "Ame"
Take: "Prendre" Take: "Prendre"
TakeAll: "Tout prendre" TakeAll: "Tout prendre"
@ -49,4 +62,5 @@ Topics: "Sujets"
Travel: "Voyager" Travel: "Voyager"
UntilHealed: "Récup. totale" UntilHealed: "Récup. totale"
Wait: "Attendre" Wait: "Attendre"
World: "Monde"
Yes: "Oui" Yes: "Oui"

View file

@ -1,12 +1,17 @@
Ask: "Zapytaj" Ask: "Zapytaj"
Back: "Wstecz" Back: "Wstecz"
Barter: "Handel"
Buy: "Kup" Buy: "Kup"
Cancel: "Anuluj" Cancel: "Anuluj"
Center: "Centruj"
Close: "Zamknij" Close: "Zamknij"
Container: "Pojemnik"
Copy: "Kopiuj" Copy: "Kopiuj"
Create: "Utwórz" Create: "Utwórz"
Delete: "Usuń"
DisposeOfCorpse: "Usuń zwłoki" DisposeOfCorpse: "Usuń zwłoki"
Done: "Koniec" Done: "Koniec"
Drop: "Upuść"
DurationDay: "{days} d. " DurationDay: "{days} d. "
DurationHour: "{hours} godz. " DurationHour: "{hours} godz. "
DurationMinute: "{minutes} min " DurationMinute: "{minutes} min "
@ -19,12 +24,17 @@ DurationYear: |-
few{{years} lata } few{{years} lata }
many{{years} lat } many{{years} lat }
} }
Equip: "Załóż"
Face: "Twarz"
Goodbye: "Do widzenia" Goodbye: "Do widzenia"
Hair: "Włosy"
Info: "Info" Info: "Info"
Inventory: "Ekwipunek" Inventory: "Ekwipunek"
Item: "Przedmiot" Item: "Przedmiot"
Local: "Okolica"
MagicEffects: "Magiczne efekty" MagicEffects: "Magiczne efekty"
Mouse: "Mysz" Mouse: "Mysz"
Move: "Przenieś"
Next: "Nast." Next: "Nast."
No: "Nie" No: "Nie"
None: "Brak" None: "Brak"
@ -40,6 +50,9 @@ Rest: "Odpocznij"
ScrollDown: "Przewiń w dół" ScrollDown: "Przewiń w dół"
ScrollUp: "Przewiń w górę" ScrollUp: "Przewiń w górę"
Select: "Wybierz" Select: "Wybierz"
Sell: "Sprzedaj"
Sex: "Płeć"
Share: "Podział"
Soul: "Dusza" Soul: "Dusza"
Take: "Weź" Take: "Weź"
TakeAll: "Weź wszystko" TakeAll: "Weź wszystko"
@ -47,4 +60,5 @@ Topics: "Tematy"
Travel: "Podróż" Travel: "Podróż"
UntilHealed: "Do wyzdr." UntilHealed: "Do wyzdr."
Wait: "Czekaj" Wait: "Czekaj"
World: "Świat"
Yes: "Tak" Yes: "Tak"

View file

@ -1,12 +1,17 @@
Ask: "Спросить" Ask: "Спросить"
Back: "Назад" Back: "Назад"
Barter: "Торговать"
Buy: "Купить" Buy: "Купить"
Cancel: "Отмена" Cancel: "Отмена"
Center: "Центр"
Close: "Закрыть" Close: "Закрыть"
Container: "Контейнер"
Copy: "Скопировать" Copy: "Скопировать"
Create: "Создать" Create: "Создать"
Delete: "Удалить"
DisposeOfCorpse: "Убрать тело" DisposeOfCorpse: "Убрать тело"
Done: "Готово" Done: "Готово"
Drop: "Бросить"
DurationDay: "{days} д " DurationDay: "{days} д "
DurationHour: "{hours} ч " DurationHour: "{hours} ч "
DurationMinute: "{minutes} мин " DurationMinute: "{minutes} мин "
@ -18,12 +23,17 @@ DurationYear: |-
few{{years} г } few{{years} г }
other{{years} л } other{{years} л }
} }
Equip: "Надеть"
Face: "Лицо"
Goodbye: "Прощание" Goodbye: "Прощание"
Hair: "Прическа"
Info: "Инфо" Info: "Инфо"
Inventory: "Инвентарь" Inventory: "Инвентарь"
Item: "Предмет" Item: "Предмет"
Local: "Местность"
MagicEffects: "Маг. эффекты" MagicEffects: "Маг. эффекты"
Mouse: "Мышь" Mouse: "Мышь"
Move: "Переместить"
Next: "След" Next: "След"
No: "Нет" No: "Нет"
None: "Нет" None: "Нет"
@ -39,6 +49,9 @@ Rest: "Отдых"
ScrollDown: "Прокрутить вниз" ScrollDown: "Прокрутить вниз"
ScrollUp: "Прокрутить вверх" ScrollUp: "Прокрутить вверх"
Select: "Выбрать" Select: "Выбрать"
Sell: "Продать"
Sex: "Пол"
Share: "Доля"
Soul: "Душа" Soul: "Душа"
Take: "Взять" Take: "Взять"
TakeAll: "Взять все" TakeAll: "Взять все"
@ -46,4 +59,5 @@ Topics: "Темы"
Travel: "Путешествие" Travel: "Путешествие"
UntilHealed: "Выздороветь" UntilHealed: "Выздороветь"
Wait: "Ждать" Wait: "Ждать"
World: "Мир"
Yes: "Да" Yes: "Да"

View file

@ -1,12 +1,17 @@
Ask: "Fråga" Ask: "Fråga"
Back: "Bakåt" Back: "Bakåt"
Barter: "Handla"
Buy: "Köp" Buy: "Köp"
Cancel: "Avbryt" Cancel: "Avbryt"
Center: "Centrera"
Close: "Stäng" Close: "Stäng"
Container: "Behållare"
Copy: "Kopiera" Copy: "Kopiera"
Create: "Skapa" Create: "Skapa"
Delete: "Radera"
DisposeOfCorpse: "Undanröj liket" DisposeOfCorpse: "Undanröj liket"
Done: "Klar" Done: "Klar"
Drop: "Släpp"
DurationDay: "{days} d " DurationDay: "{days} d "
DurationHour: "{hours} tim " DurationHour: "{hours} tim "
DurationMinute: "{minutes} min " DurationMinute: "{minutes} min "
@ -21,12 +26,17 @@ DurationYear: |-
one{{years} år } one{{years} år }
other{{years} år } other{{years} år }
} }
Equip: "Utrusta"
Face: "Ansikte"
Goodbye: "Adjö" Goodbye: "Adjö"
Hair: "Hår"
Info: "Info" Info: "Info"
Item: "Föremål" Item: "Föremål"
Inventory: "Inventariet" Inventory: "Inventariet"
Local: "Lokal"
MagicEffects: "Magiska effekter" MagicEffects: "Magiska effekter"
Mouse: "Mus" Mouse: "Mus"
Move: "Flytta"
Next: "Nästa" Next: "Nästa"
No: "Nej" No: "Nej"
None: "Inget" None: "Inget"
@ -42,6 +52,9 @@ Rest: "Vila"
ScrollDown: "Scrolla ner" ScrollDown: "Scrolla ner"
ScrollUp: "Scrolla upp" ScrollUp: "Scrolla upp"
Select: "Välj" Select: "Välj"
Sell: "Sälj"
Sex: "Kön"
Share: "Dela"
Soul: "Själ" Soul: "Själ"
Take: "Ta" Take: "Ta"
TakeAll: "Ta allt" TakeAll: "Ta allt"
@ -49,4 +62,5 @@ Topics: "Ämnen"
Travel: "Res" Travel: "Res"
UntilHealed: "Tills återställd" UntilHealed: "Tills återställd"
Wait: "Vänta" Wait: "Vänta"
World: "Värld"
Yes: "Ja" Yes: "Ja"

View file

@ -0,0 +1,16 @@
Combat: "OpenMW: Kampf"
combatSettingsPageDescription: "OpenMW-Kampfeinstellungen"
combatSettings: "Kampf"
unarmedCreatureAttacksDamageArmor: "Angriffe unbewaffneter Kreaturen beschädigen Rüstung"
unarmedCreatureAttacksDamageArmorDescription: |
Auch Angriffe unbewaffneter Kreaturen beschädigen Rüstung.
redistributeShieldHitsWhenNotWearingShield: "Schildtreffer bei fehlendem Schild umverteilen"
redistributeShieldHitsWhenNotWearingShieldDescription: |
Entspricht "Shield hit location fix" aus dem Morrowind Code Patch. Wenn kein Schild getragen wird, werden Treffer auf den SchildSlot auf den linken Schulterpanzer oder den Kürass umverteilt.
spawnBloodEffectsOnPlayer: "Bluteffekte für Spieler anzeigen"
spawnBloodEffectsOnPlayerDescription: |
Wenn aktiviert, werden beim Spieler bei Treffern im Kampf Bluteffekte angezeigt, genau wie bei anderen Charakteren.

View file

@ -110,5 +110,38 @@ function aux_util.mapFilterSort(array, scoreFn)
return sortedValues, sortedScores return sortedValues, sortedScores
end end
---
-- Iterates over an array of event handlers, calling each in turn until one returns false.
-- @function [parent=#util] callEventHandlers
-- @param #table handlers An optional array of handlers to invoke
-- @param #any ... Arguments to pass to each event handler
-- @return boolean True if no further handlers should be called
function aux_util.callEventHandlers(handlers, ...)
if handlers then
for i = #handlers, 1, -1 do
if handlers[i](...) == false then
return true
end
end
end
return false
end
---
-- Iterates over an array of event handler arrays, passing each to `aux_util.callEventHandlers` until the event is handled.
-- @function [parent=#util] callMultipleEventHandlers
-- @param #table handlers An array of event handler arrays
-- @param #any ... Arguments to pass to each event handler
-- @return boolean True if no further handlers should be called
function aux_util.callMultipleEventHandlers(handlers, ...)
for i = 1, #handlers do
local stop = aux_util.callEventHandlers(handlers[i], ...)
if stop then
return true
end
end
return false
end
return aux_util return aux_util

View file

@ -2,6 +2,7 @@ local async = require('openmw.async')
local core = require('openmw.core') local core = require('openmw.core')
local types = require('openmw.types') local types = require('openmw.types')
local world = require('openmw.world') local world = require('openmw.world')
local auxUtil = require('openmw_aux.util')
local EnableObject = async:registerTimerCallback('EnableObject', function(obj) obj.enabled = true end) local EnableObject = async:registerTimerCallback('EnableObject', function(obj) obj.enabled = true end)
@ -38,21 +39,9 @@ local function onActivate(obj, actor)
if obj.parentContainer then if obj.parentContainer then
return return
end end
local handlers = handlersPerObject[obj.id] local handled = auxUtil.callMultipleEventHandlers({ handlersPerObject[obj.id], handlersPerType[obj.type] }, obj, actor)
if handlers then if handled then
for i = #handlers, 1, -1 do return
if handlers[i](obj, actor) == false then
return -- skip other handlers
end
end
end
handlers = handlersPerType[obj.type]
if handlers then
for i = #handlers, 1, -1 do
if handlers[i](obj, actor) == false then
return -- skip other handlers
end
end
end end
types.Actor.activeEffects(actor):remove('invisibility') types.Actor.activeEffects(actor):remove('invisibility')
world._runStandardActivationAction(obj, actor) world._runStandardActivationAction(obj, actor)

View file

@ -6,6 +6,7 @@ local self = require('openmw.self')
local storage = require('openmw.storage') local storage = require('openmw.storage')
local types = require('openmw.types') local types = require('openmw.types')
local util = require('openmw.util') local util = require('openmw.util')
local auxUtil = require('openmw_aux.util')
local Actor = types.Actor local Actor = types.Actor
local Weapon = types.Weapon local Weapon = types.Weapon
local Player = types.Player local Player = types.Player
@ -270,10 +271,8 @@ local function spawnBloodEffect(position)
end end
local function onHit(data) local function onHit(data)
for i = #onHitHandlers, 1, -1 do if auxUtil.callEventHandlers(onHitHandlers, data) then
if onHitHandlers[i](data) == false then return
return -- skip other handlers
end
end end
if data.successful and not godMode() then if data.successful and not godMode() then
I.Combat.applyArmor(data) I.Combat.applyArmor(data)

View file

@ -1,13 +1,10 @@
local anim = require('openmw.animation') local anim = require('openmw.animation')
local self = require('openmw.self') local self = require('openmw.self')
local auxUtil = require('openmw_aux.util')
local playBlendedHandlers = {} local playBlendedHandlers = {}
local function onPlayBlendedAnimation(groupname, options) local function onPlayBlendedAnimation(groupname, options)
for i = #playBlendedHandlers, 1, -1 do auxUtil.callEventHandlers(playBlendedHandlers, groupname, options)
if playBlendedHandlers[i](groupname, options) == false then
return
end
end
end end
local function playBlendedAnimation(groupname, options) local function playBlendedAnimation(groupname, options)
@ -20,22 +17,7 @@ end
local textKeyHandlers = {} local textKeyHandlers = {}
local function onAnimationTextKey(groupname, key) local function onAnimationTextKey(groupname, key)
local handlers = textKeyHandlers[groupname] auxUtil.callMultipleEventHandlers({ textKeyHandlers[groupname], textKeyHandlers[''] }, groupname, key)
if handlers then
for i = #handlers, 1, -1 do
if handlers[i](groupname, key) == false then
return
end
end
end
handlers = textKeyHandlers['']
if handlers then
for i = #handlers, 1, -1 do
if handlers[i](groupname, key) == false then
return
end
end
end
end end
local initialized = false local initialized = false

View file

@ -2,6 +2,7 @@ local self = require('openmw.self')
local I = require('openmw.interfaces') local I = require('openmw.interfaces')
local types = require('openmw.types') local types = require('openmw.types')
local core = require('openmw.core') local core = require('openmw.core')
local auxUtil = require('openmw_aux.util')
local NPC = require('openmw.types').NPC local NPC = require('openmw.types').NPC
local Skill = core.stats.Skill local Skill = core.stats.Skill
@ -104,11 +105,7 @@ local function skillUsed(skillid, options)
end end
end end
for i = #skillUsedHandlers, 1, -1 do auxUtil.callEventHandlers(skillUsedHandlers, skillid, options)
if skillUsedHandlers[i](skillid, options) == false then
return
end
end
end end
local function skillLevelUp(skillid, source) local function skillLevelUp(skillid, source)
@ -144,11 +141,7 @@ local function skillLevelUp(skillid, source)
options.levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization') options.levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization')
end end
for i = #skillLevelUpHandlers, 1, -1 do auxUtil.callEventHandlers(skillLevelUpHandlers, skillid, source, options)
if skillLevelUpHandlers[i](skillid, source, options) == false then
return
end
end
end end
return { return {

View file

@ -1,26 +1,15 @@
local types = require('openmw.types') local types = require('openmw.types')
local world = require('openmw.world') local world = require('openmw.world')
local auxUtil = require('openmw_aux.util')
local handlersPerObject = {} local handlersPerObject = {}
local handlersPerType = {} local handlersPerType = {}
local function useItem(obj, actor, force) local function useItem(obj, actor, force)
local options = { force = force or false } local options = { force = force or false }
local handlers = handlersPerObject[obj.id] local handled = auxUtil.callMultipleEventHandlers({ handlersPerObject[obj.id], handlersPerType[obj.type] }, obj, actor, options)
if handlers then if handled then
for i = #handlers, 1, -1 do return
if handlers[i](obj, actor, options) == false then
return -- skip other handlers
end
end
end
handlers = handlersPerType[obj.type]
if handlers then
for i = #handlers, 1, -1 do
if handlers[i](obj, actor, options) == false then
return -- skip other handlers
end
end
end end
world._runStandardUseAction(obj, actor, options.force) world._runStandardUseAction(obj, actor, options.force)
end end

View file

@ -1224,6 +1224,16 @@
-- @usage -- Start a new quest, add it to the player's quest list but don't add any journal entries -- @usage -- Start a new quest, add it to the player's quest list but don't add any journal entries
-- types.Player.quests(player)["ms_fargothring"].stage = 0 -- types.Player.quests(player)["ms_fargothring"].stage = 0
---
-- Adds a topic to the list of ones known by the player, so that it can be used in dialogue with actors who can talk about that topic.
-- @function [parent=#PLAYER] addTopic
-- @param openmw.core#GameObject player
-- @param string topicId
-- @usage -- Add topic to the list of known ones, in a player script
-- self.type.addTopic(self, "Some Work")
-- @usage -- Give all players in the current world a specific topic, in a global script
-- for _, player in ipairs(world.players) do player.type.addTopic(player, "Some Unrelated Work") end
--- ---
-- Returns @{#PlayerJournal}, which contains the read-only access to journal text data accumulated by the player. -- Returns @{#PlayerJournal}, which contains the read-only access to journal text data accumulated by the player.
-- Not the same as @{openmw_core#Dialogue.journal} which holds raw game records: with placeholders for dynamic variables and no player-specific info. -- Not the same as @{openmw_core#Dialogue.journal} which holds raw game records: with placeholders for dynamic variables and no player-specific info.