1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-12-27 20:13:08 +00:00

Merge branch openmw:master into master

This commit is contained in:
Andy Lanzone 2025-07-12 14:13:10 -07:00
commit 3600a5c716
33 changed files with 253 additions and 166 deletions

View file

@ -242,11 +242,36 @@ void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode)
{
if (mode == Settings::WindowMode::Fullscreen || mode == Settings::WindowMode::WindowedFullscreen)
{
QString customSizeMessage = tr("Custom window size is available only in Windowed mode.");
QString windowBorderMessage = tr("Window border is available only in Windowed mode.");
standardRadioButton->toggle();
customRadioButton->setEnabled(false);
customWidthSpinBox->setEnabled(false);
customHeightSpinBox->setEnabled(false);
windowBorderCheckBox->setEnabled(false);
windowBorderCheckBox->setToolTip(windowBorderMessage);
customWidthSpinBox->setToolTip(customSizeMessage);
customHeightSpinBox->setToolTip(customSizeMessage);
customRadioButton->setToolTip(customSizeMessage);
}
if (mode == Settings::WindowMode::Fullscreen)
{
resolutionComboBox->setEnabled(true);
resolutionComboBox->setToolTip("");
standardRadioButton->setToolTip("");
}
else if (mode == Settings::WindowMode::WindowedFullscreen)
{
QString fullScreenMessage = tr("Windowed Fullscreen mode always uses the native display resolution.");
resolutionComboBox->setEnabled(false);
resolutionComboBox->setToolTip(fullScreenMessage);
standardRadioButton->setToolTip(fullScreenMessage);
// Assume that a first item is a native screen resolution
resolutionComboBox->setCurrentIndex(0);
}
else
{
@ -254,6 +279,13 @@ void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode)
customWidthSpinBox->setEnabled(true);
customHeightSpinBox->setEnabled(true);
windowBorderCheckBox->setEnabled(true);
resolutionComboBox->setEnabled(true);
resolutionComboBox->setToolTip("");
standardRadioButton->setToolTip("");
windowBorderCheckBox->setToolTip("");
customWidthSpinBox->setToolTip("");
customHeightSpinBox->setToolTip("");
customRadioButton->setToolTip("");
}
}

View file

@ -360,16 +360,12 @@ namespace MWClass
{
stats.setAttacked(true);
// No retaliation for totally static creatures (they have no movement or attacks anyway)
if (isMobile(ptr))
{
bool complain = sourceType == MWMechanics::DamageSourceType::Melee;
bool supportFriendlyFire = sourceType != MWMechanics::DamageSourceType::Ranged;
if (supportFriendlyFire && MWMechanics::friendlyHit(attacker, ptr, complain))
setOnPcHitMe = false;
else
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
}
bool complain = sourceType == MWMechanics::DamageSourceType::Melee;
bool supportFriendlyFire = sourceType != MWMechanics::DamageSourceType::Ranged;
if (supportFriendlyFire && MWMechanics::friendlyHit(attacker, ptr, complain))
setOnPcHitMe = false;
else
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
}
// Attacker and target store each other as hitattemptactor if they have no one stored yet

View file

@ -270,14 +270,14 @@ namespace MWClass
std::pair<int, std::string_view> Weapon::canBeEquipped(const MWWorld::ConstPtr& ptr, const MWWorld::Ptr& npc) const
{
if (hasItemHealth(ptr) && getItemHealth(ptr) == 0)
return { 0, "#{sInventoryMessage1}" };
// Do not allow equip weapons from inventory during attack
if (npc.isInCell() && MWBase::Environment::get().getWindowManager()->isGuiMode()
&& MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc))
return { 0, "#{sCantEquipWeapWarning}" };
if (hasItemHealth(ptr) && getItemHealth(ptr) == 0)
return { 0, "#{sInventoryMessage1}" };
std::pair<std::vector<int>, bool> slots_ = getEquipmentSlots(ptr);
if (slots_.first.empty())

View file

@ -614,36 +614,25 @@ namespace MWGui
}
MWWorld::Ptr player = MWMechanics::getPlayer();
auto type = ptr.getType();
bool isWeaponOrArmor = type == ESM::Weapon::sRecordId || type == ESM::Armor::sRecordId;
bool isBroken = ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0;
// early-out for items that need to be equipped, but can't be equipped: we don't want to set OnPcEquip in that
// case
if (!ptr.getClass().getEquipmentSlots(ptr).first.empty())
// In vanilla, broken armor or weapons cannot be equipped
// tools with 0 charges is equippable
if (isBroken && isWeaponOrArmor)
{
if (ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0)
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}");
updateItemView();
return;
}
if (!force)
{
auto canEquip = ptr.getClass().canBeEquipped(ptr, player);
if (canEquip.first == 0)
{
MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second);
updateItemView();
return;
}
}
MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}");
return;
}
bool canEquip = ptr.getClass().canBeEquipped(ptr, mPtr).first != 0;
bool shouldSetOnPcEquip = canEquip || force;
// If the item has a script, set OnPCEquip or PCSkipEquip to 1
if (!script.empty())
if (!script.empty() && shouldSetOnPcEquip)
{
// Ingredients, books and repair hammers must not have OnPCEquip set to 1 here
auto type = ptr.getType();
bool isBook = type == ESM::Book::sRecordId;
if (!isBook && type != ESM::Ingredient::sRecordId && type != ESM::Repair::sRecordId)
ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1);
@ -653,7 +642,7 @@ namespace MWGui
}
std::unique_ptr<MWWorld::Action> action = ptr.getClass().use(ptr, force);
action->execute(player);
action->execute(player, !canEquip);
// Handles partial equipping (final part)
if (mEquippedStackableCount.has_value())
@ -684,6 +673,14 @@ namespace MWGui
{
MWWorld::Ptr ptr = mDragAndDrop->mItem.mBase;
auto [canEquipRes, canEquipMsg] = ptr.getClass().canBeEquipped(ptr, mPtr);
if (canEquipRes == 0) // cannot equip
{
mDragAndDrop->drop(mTradeModel, mItemView); // also plays down sound
MWBase::Environment::get().getWindowManager()->messageBox(canEquipMsg);
return;
}
mDragAndDrop->finish();
if (mDragAndDrop->mSourceModel != mTradeModel)

View file

@ -23,7 +23,6 @@ namespace MWGui
: WindowBase("openmw_jail_screen.layout")
, mDays(1)
, mFadeTimeRemaining(0)
, mTimeAdvancer(0.01f)
{
getWidget(mProgressBar, "ProgressBar");

View file

@ -90,9 +90,8 @@ namespace MWGui
case ESM::QuickKeys::Type::MagicItem:
{
MWWorld::Ptr item = *mKey[index].button->getUserData<MWWorld::Ptr>();
// Make sure the item is available and is not broken
if (item.isEmpty() || item.getCellRef().getCount() < 1
|| (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0))
// Make sure the item is available
if (item.isEmpty() || item.getCellRef().getCount() < 1)
{
// Try searching for a compatible replacement
item = store.findReplacement(mKey[index].id);
@ -259,6 +258,8 @@ namespace MWGui
if (mItemSelectionDialog)
mItemSelectionDialog->setVisible(false);
MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item));
}
void QuickKeysMenu::onAssignItemCancel()
@ -395,23 +396,16 @@ namespace MWGui
if (it == store.end())
item = nullptr;
// check the item is available and not broken
if (item.isEmpty() || item.getCellRef().getCount() < 1
|| (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0))
// check the quickkey item is available
if (item.isEmpty() || item.getCellRef().getCount() < 1)
{
item = store.findReplacement(key->id);
if (item.isEmpty() || item.getCellRef().getCount() < 1)
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + key->name);
return;
}
MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + key->name);
return;
}
if (key->type == ESM::QuickKeys::Type::Item)
{
if (!store.isEquipped(item))
if (!store.isEquipped(item.getCellRef().getRefId()))
MWBase::Environment::get().getWindowManager()->useItem(item);
MWWorld::ConstContainerStoreIterator rightHand
= store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);

View file

@ -279,7 +279,7 @@ namespace MWGui
&& !base.get<ESM::Book>()->mBase->mData.mIsScroll)
return false;
if ((mFilter & Filter_OnlyUsableItems) && base.getClass().getScript(base).empty())
if ((mFilter & Filter_OnlyUsableItems))
{
std::unique_ptr<MWWorld::Action> actionOnUse = base.getClass().use(base);
if (!actionOnUse || actionOnUse->isNullAction())

View file

@ -1,14 +1,19 @@
#include "timeadvancer.hpp"
namespace
{
// Time per hour tick
constexpr float kProgressStepDelay = 1.0f / 60.0f;
}
namespace MWGui
{
TimeAdvancer::TimeAdvancer(float delay)
TimeAdvancer::TimeAdvancer()
: mRunning(false)
, mCurHour(0)
, mHours(1)
, mInterruptAt(-1)
, mDelay(delay)
, mRemainingTime(delay)
, mRemainingTime(kProgressStepDelay)
{
}
@ -17,7 +22,7 @@ namespace MWGui
mHours = hours;
mCurHour = 0;
mInterruptAt = interruptAt;
mRemainingTime = mDelay;
mRemainingTime = kProgressStepDelay;
mRunning = true;
}
@ -43,7 +48,7 @@ namespace MWGui
while (mRemainingTime <= 0)
{
mRemainingTime += mDelay;
mRemainingTime += kProgressStepDelay;
++mCurHour;
if (mCurHour <= mHours)

View file

@ -8,7 +8,7 @@ namespace MWGui
class TimeAdvancer
{
public:
TimeAdvancer(float delay);
TimeAdvancer();
void run(int hours, int interruptAt = -1);
void stop();
@ -32,7 +32,6 @@ namespace MWGui
int mHours;
int mInterruptAt;
float mDelay;
float mRemainingTime;
};
}

View file

@ -27,7 +27,6 @@ namespace MWGui
TrainingWindow::TrainingWindow()
: WindowBase("openmw_trainingwindow.layout")
, mTimeAdvancer(0.05f)
{
getWidget(mTrainingOptions, "TrainingOptions");
getWidget(mCancelButton, "CancelButton");

View file

@ -52,7 +52,6 @@ namespace MWGui
WaitDialog::WaitDialog()
: WindowBase("openmw_wait_dialog.layout")
, mTimeAdvancer(0.05f)
, mSleeping(false)
, mHours(1)
, mManualHours(1)

View file

@ -273,13 +273,14 @@ namespace MWMechanics
}
bool updateSpellWindow = false;
bool playNonLooping = false;
if (ptr.getClass().hasInventoryStore(ptr)
&& !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished()))
{
auto& store = ptr.getClass().getInventoryStore(ptr);
if (store.getInvListener() != nullptr)
{
bool playNonLooping = !store.isFirstEquip();
playNonLooping = !store.isFirstEquip();
const auto world = MWBase::Environment::get().getWorld();
for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++)
{
@ -307,9 +308,6 @@ namespace MWMechanics
applyPurges(ptr);
ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr });
params.setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
for (const auto& effect : params.mEffects)
MWMechanics::playEffects(
ptr, *world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId), playNonLooping);
updateSpellWindow = true;
}
}
@ -327,7 +325,7 @@ namespace MWMechanics
std::optional<ActiveSpellParams> reflected;
for (auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
{
auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration, playNonLooping);
if (result.mType == MagicApplicationResult::Type::REFLECTED)
{
if (!reflected)

View file

@ -605,10 +605,6 @@ namespace MWMechanics
void Actors::engageCombat(
const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, SidingCache& cachedAllies, bool againstPlayer) const
{
// No combat for totally static creatures
if (!actor1.getClass().isMobile(actor1))
return;
CreatureStats& creatureStats1 = actor1.getClass().getCreatureStats(actor1);
if (creatureStats1.isDead() || creatureStats1.getAiSequence().isInCombat(actor2))
return;

View file

@ -104,10 +104,10 @@ namespace MWMechanics
bool AiCombat::execute(
const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
{
// get or create temporary storage
// Get or create temporary storage
AiCombatStorage& storage = state.get<AiCombatStorage>();
// General description
// No combat for dead creatures
if (actor.getClass().getCreatureStats(actor).isDead())
return true;
@ -124,6 +124,13 @@ namespace MWMechanics
if (actor == target) // This should never happen.
return true;
// No actions for totally static creatures
if (!actor.getClass().isMobile(actor))
{
storage.mFleeState = AiCombatStorage::FleeState_Idle;
return false;
}
if (!storage.isFleeing())
{
if (storage.mCurrentAction.get()) // need to wait to init action with its attack range

View file

@ -914,7 +914,7 @@ namespace MWMechanics
}
MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt)
ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt, bool playNonLooping)
{
const auto world = MWBase::Environment::get().getWorld();
bool invalid = false;
@ -1032,9 +1032,12 @@ namespace MWMechanics
oldMagnitude = effect.mMagnitude;
else
{
if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_Equipment)
&& !spellParams.hasFlag(ESM::ActiveSpells::Flag_Lua))
playEffects(target, *magicEffect, spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary));
bool isTemporary = spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary);
bool isEquipment = spellParams.hasFlag(ESM::ActiveSpells::Flag_Equipment);
if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_Lua))
playEffects(target, *magicEffect, (isTemporary || (isEquipment && playNonLooping)));
if (effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc()
&& target.getType() == ESM::Creature::sRecordId
&& target.get<ESM::Creature>()->mBase->mData.mSoul == 0 && caster == getPlayer())

View file

@ -28,7 +28,8 @@ namespace MWMechanics
// Applies a tick of a single effect. Returns true if the effect should be removed immediately
MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt);
ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt,
bool playNonLoopingEffect = true);
// Undoes permanent effects created by ESM::MagicEffect::AppliedOnce
void onMagicEffectRemoved(

View file

@ -280,15 +280,6 @@ namespace MWRender
{
pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
if (pass.mRenderTexture->getNumMipmapLevels() > 0)
{
state.setActiveTextureUnit(0);
state.applyTextureAttribute(0,
pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0)
.getTexture());
ext->glGenerateMipmap(GL_TEXTURE_2D);
}
lastApplied = pass.mRenderTarget->getHandle(state.getContextID());
}
else if (pass.mResolve && index == filtered.back())
@ -325,6 +316,15 @@ namespace MWRender
drawGeometry(renderInfo);
if (pass.mRenderTarget && pass.mRenderTexture->getNumMipmapLevels() > 0)
{
state.setActiveTextureUnit(0);
state.applyTextureAttribute(0,
pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0)
.getTexture());
ext->glGenerateMipmap(GL_TEXTURE_2D);
}
state.popStateSet();
state.apply();
}

View file

@ -1146,9 +1146,18 @@ namespace MWScript
{
void printLocalVars(Interpreter::Runtime& runtime, const MWWorld::Ptr& ptr)
{
std::stringstream str;
std::ostringstream str;
const ESM::RefId& script = ptr.getClass().getScript(ptr);
auto printVariables = [&str](const auto& names, const auto& values, std::string_view type) {
size_t size = std::min(names.size(), values.size());
for (size_t i = 0; i < size; ++i)
{
str << "\n " << names[i] << " = " << values[i] << " (" << type << ")";
}
};
if (script.empty())
str << ptr.getCellRef().getRefId() << " does not have a script.";
else
@ -1159,27 +1168,9 @@ namespace MWScript
const Compiler::Locals& complocals
= MWBase::Environment::get().getScriptManager()->getLocals(script);
const std::vector<std::string>* names = &complocals.get('s');
for (size_t i = 0; i < names->size(); ++i)
{
if (i >= locals.mShorts.size())
break;
str << std::endl << " " << (*names)[i] << " = " << locals.mShorts[i] << " (short)";
}
names = &complocals.get('l');
for (size_t i = 0; i < names->size(); ++i)
{
if (i >= locals.mLongs.size())
break;
str << std::endl << " " << (*names)[i] << " = " << locals.mLongs[i] << " (long)";
}
names = &complocals.get('f');
for (size_t i = 0; i < names->size(); ++i)
{
if (i >= locals.mFloats.size())
break;
str << std::endl << " " << (*names)[i] << " = " << locals.mFloats[i] << " (float)";
}
printVariables(complocals.get('s'), locals.mShorts, "short");
printVariables(complocals.get('l'), locals.mLongs, "long");
printVariables(complocals.get('f'), locals.mFloats, "float");
}
runtime.getContext().report(str.str());
@ -1187,50 +1178,43 @@ namespace MWScript
void printGlobalVars(Interpreter::Runtime& runtime)
{
std::stringstream str;
str << "Global variables:";
std::ostringstream str;
str << "Global Variables:";
MWBase::World* world = MWBase::Environment::get().getWorld();
std::vector<std::string> names = runtime.getContext().getGlobals();
auto& context = runtime.getContext();
std::vector<std::string> names = context.getGlobals();
// sort for user convenience
std::sort(names.begin(), names.end());
for (size_t i = 0; i < names.size(); ++i)
{
char type = world->getGlobalVariableType(names[i]);
str << std::endl << " " << names[i] << " = ";
std::sort(names.begin(), names.end(), ::Misc::StringUtils::ciLess);
auto printVariable = [&str, &context](const std::string& name, char type) {
str << "\n " << name << " = ";
switch (type)
{
case 's':
str << runtime.getContext().getGlobalShort(names[i]) << " (short)";
str << context.getGlobalShort(name) << " (short)";
break;
case 'l':
str << runtime.getContext().getGlobalLong(names[i]) << " (long)";
str << context.getGlobalLong(name) << " (long)";
break;
case 'f':
str << runtime.getContext().getGlobalFloat(names[i]) << " (float)";
str << context.getGlobalFloat(name) << " (float)";
break;
default:
str << "<unknown type>";
}
}
};
runtime.getContext().report(str.str());
for (const auto& name : names)
printVariable(name, world->getGlobalVariableType(name));
context.report(str.str());
}
void printGlobalScriptsVars(Interpreter::Runtime& runtime)
{
std::stringstream str;
str << std::endl << "Global Scripts:";
std::ostringstream str;
str << "\nGlobal Scripts:";
const auto& scripts = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getScripts();
@ -1238,12 +1222,11 @@ namespace MWScript
std::map<ESM::RefId, std::shared_ptr<GlobalScriptDesc>> globalScripts(scripts.begin(), scripts.end());
auto printVariables
= [&str](const ESM::RefId& scptName, const auto& names, const auto& values, std::string_view type) {
= [&str](std::string_view scptName, const auto& names, const auto& values, std::string_view type) {
size_t size = std::min(names.size(), values.size());
for (size_t i = 0; i < size; ++i)
{
str << std::endl
<< " " << scptName << "->" << names[i] << " = " << values[i] << " (" << type << ")";
str << "\n " << scptName << "->" << names[i] << " = " << values[i] << " (" << type << ")";
}
};
@ -1253,18 +1236,20 @@ namespace MWScript
if (!script->mRunning)
continue;
std::string_view scptName = refId.getRefIdString();
const Compiler::Locals& complocals
= MWBase::Environment::get().getScriptManager()->getLocals(refId);
const Locals& locals
= MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocals(refId);
if (locals.isEmpty())
str << std::endl << " No variables in script " << refId;
str << "\n No variables in script " << scptName;
else
{
printVariables(refId, complocals.get('s'), locals.mShorts, "short");
printVariables(refId, complocals.get('l'), locals.mLongs, "long");
printVariables(refId, complocals.get('f'), locals.mFloats, "float");
printVariables(scptName, complocals.get('s'), locals.mShorts, "short");
printVariables(scptName, complocals.get('l'), locals.mLongs, "long");
printVariables(scptName, complocals.get('f'), locals.mFloats, "float");
}
}

View file

@ -21,12 +21,11 @@ namespace MWWorld
MWWorld::Ptr object = getTarget();
MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor);
if (object.getClass().hasItemHealth(object) && object.getCellRef().getCharge() == 0)
if (actor != MWMechanics::getPlayer())
{
if (actor == MWMechanics::getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}");
return;
// player logic is handled in InventoryWindow::useItem
if (object.getClass().hasItemHealth(object) && object.getCellRef().getCharge() == 0)
return;
}
if (!mForce)

View file

@ -732,6 +732,16 @@ bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr& item)
return false;
}
bool MWWorld::InventoryStore::isEquipped(const ESM::RefId& id)
{
for (int i = 0; i < MWWorld::InventoryStore::Slots; ++i)
{
if (getSlot(i) != end() && getSlot(i)->getCellRef().getRefId() == id)
return true;
}
return false;
}
bool MWWorld::InventoryStore::isFirstEquip()
{
bool first = mFirstAutoEquip;

View file

@ -117,6 +117,7 @@ namespace MWWorld
///< \warning \a iterator can not be an end()-iterator, use unequip function instead
bool isEquipped(const MWWorld::ConstPtr& item);
bool isEquipped(const ESM::RefId& id);
///< Utility function, returns true if the given item is equipped in any slot
void setSelectedEnchantItem(const ContainerStoreIterator& iterator);

View file

@ -76,15 +76,15 @@ namespace ESM
break;
case fourCC("QSTN"):
mQuestStatus = QS_Name;
esm.skipRecord();
esm.skipHSub();
break;
case fourCC("QSTF"):
mQuestStatus = QS_Finished;
esm.skipRecord();
esm.skipHSub();
break;
case fourCC("QSTR"):
mQuestStatus = QS_Restart;
esm.skipRecord();
esm.skipHSub();
break;
case SREC_DELE:
esm.skipHSub();

View file

@ -20,6 +20,10 @@
white-space: normal;
}
html:has(#luadoc) {
scroll-padding-top: 4.0rem;
}
#content #luadoc code a:not(.toc-backref) {
font-weight: 600;
}

View file

@ -36,8 +36,8 @@
--muted: 223 27% 14%;
--card: 220 14% 9%;
--link: #ffffff;
--link-hover: #ffffff;
--link: #95b1dd;
--link-hover: #dde2eb;
}
.contents ul li a.reference:hover, .sd-dropdown .toctree-wrapper ul li a.reference, details.sd-dropdown .sd-summary-title {
@ -143,6 +143,10 @@ tbody tr:hover {
margin-bottom: 1.5rem;
}
#content table p {
margin-bottom: 0;
}
h5 {
font-size: 1.15rem;
font-weight: 600;

View file

@ -208,7 +208,7 @@ WaterShaderTextureQuality: "Качество текстуры воды"
WindowBorder: "Рамка окна"
WindowMode: "Режим окна"
WindowModeFullscreen: "Полный экран"
WindowModeHint: "Подсказка: режим Оконный без полей\nвсегда использует родное разрешение экрана."
WindowModeHint: "Подсказка: режим \"Оконный без полей\"\сегда использует родное разрешение экрана."
WindowModeWindowed: "Оконный"
WindowModeWindowedFullscreen: "Оконный без полей"
WobblyShores: "Колеблющиеся берега"

View file

@ -1,36 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<MyGUI type="Layout">
<Widget type="Window" skin="MW_Dialog" position="0 0 370 230" layer="Modal" align="Center" name="_Main">
<Widget type="Window" skin="MW_Dialog" position="0 0 380 224" layer="Modal" align="Center" name="_Main">
<Widget type="TextBox" skin="SandText" position="8 8 354 18">
<Widget type="TextBox" skin="SandText" position="8 7 356 18">
<Property key="Caption" value="#{sQuickMenuTitle}"/>
<Property key="TextAlign" value="Center"/>
</Widget>
<Widget type="EditBox" skin="SandText" position="8 26 354 18" name="InstructionLabel" align="Left Top VStretch">
<Widget type="EditBox" skin="SandText" position="23 26 354 18" name="InstructionLabel" align="Left Top VStretch">
<Property key="Caption" value="#{sQuickMenuInstruc}"/>
<Property key="MultiLine" value="true"/>
<Property key="WordWrap" value="true"/>
<Property key="Static" value="true"/>
<Property key="TextAlign" value="Center"/>
</Widget>
<Widget type="Widget" skin="" position="15 55 332 128" align="Left Bottom HCenter">
<Widget type="Widget" skin="" position="20 49 332 128" align="Left Bottom HCenter">
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="0 0 60 60" name="QuickKey1"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="68 0 60 60" name="QuickKey2"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="136 0 60 60" name="QuickKey3"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="204 0 60 60" name="QuickKey4"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="272 0 60 60" name="QuickKey5"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="0 67 60 60" name="QuickKey6"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="68 67 60 60" name="QuickKey7"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="136 67 60 60" name="QuickKey8"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="204 67 60 60" name="QuickKey9"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="272 67 60 60" name="QuickKey10"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="0 68 60 60" name="QuickKey6"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="68 68 60 60" name="QuickKey7"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="136 68 60 60" name="QuickKey8"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="204 68 60 60" name="QuickKey9"/>
<Widget type="ItemWidget" skin="MW_ItemIconBox" position="272 68 60 60" name="QuickKey10"/>
</Widget>
<Widget type="AutoSizedButton" skin="MW_Button" name="OKButton" position="315 190 32 24" align="Right Bottom">
<Widget type="AutoSizedButton" skin="MW_Button" name="OKButton" position="330 185 32 24" align="Right Bottom">
<Property key="ExpandDirection" value="Left"/>
<Property key="Caption" value="#{Interface:OK}"/>
</Widget>

View file

@ -425,6 +425,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>&lt;br&gt;&lt;b&gt;SDL_GetDisplayMode failed:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Custom window size is available only in Windowed mode.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Window border is available only in Windowed mode.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Windowed Fullscreen mode always uses the native display resolution.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Launcher::ImportPage</name>

View file

@ -425,6 +425,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>&lt;br&gt;&lt;b&gt;SDL_GetDisplayMode failed:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
<translation></translation>
</message>
<message>
<source>Custom window size is available only in Windowed mode.</source>
<translation></translation>
</message>
<message>
<source>Window border is available only in Windowed mode.</source>
<translation></translation>
</message>
<message>
<source>Windowed Fullscreen mode always uses the native display resolution.</source>
<translation></translation>
</message>
</context>
<context>
<name>Launcher::ImportPage</name>

View file

@ -425,6 +425,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>&lt;br&gt;&lt;b&gt;SDL_GetDisplayMode failed:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
<translation>&lt;br&gt;&lt;b&gt;SDL_GetDisplayMode failed:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
</message>
<message>
<source>Custom window size is available only in Windowed mode.</source>
<translation>La taille personalisée de fenêtre n&apos;est disponible qu&apos;en mode fenêtré.</translation>
</message>
<message>
<source>Window border is available only in Windowed mode.</source>
<translation>Les bordures de fenêtres ne sont disponibles qu&apos;en mode fenêtré.</translation>
</message>
<message>
<source>Windowed Fullscreen mode always uses the native display resolution.</source>
<translation>Le mode &quot;Fenêtré plein écran&quot; utilise toujours la résolution native de l&apos;écran.</translation>
</message>
</context>
<context>
<name>Launcher::ImportPage</name>

View file

@ -427,6 +427,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>&lt;br&gt;&lt;b&gt;SDL_GetDisplayMode failed:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
<translation>&lt;br&gt;&lt;b&gt;Вызов SDL_GetDisplayMode завершился с ошибкой:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
</message>
<message>
<source>Custom window size is available only in Windowed mode.</source>
<translation>Особый размер окна доступен только в оконном режиме.</translation>
</message>
<message>
<source>Window border is available only in Windowed mode.</source>
<translation>Рамка окна доступна только в оконном режиме.</translation>
</message>
<message>
<source>Windowed Fullscreen mode always uses the native display resolution.</source>
<translation>Режим &quot;Оконный без полей&quot; всегда использует родное разрешение экрана.</translation>
</message>
</context>
<context>
<name>Launcher::ImportPage</name>

View file

@ -428,6 +428,18 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin
<source>&lt;br&gt;&lt;b&gt;SDL_GetDisplayMode failed:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
<translation>&lt;br&gt;&lt;b&gt;SDL_GetDisplayMode misslyckades:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
</message>
<message>
<source>Custom window size is available only in Windowed mode.</source>
<translation>Anpassad fönsterstorlek finns endast tillgänglig i fönsterläge.</translation>
</message>
<message>
<source>Window border is available only in Windowed mode.</source>
<translation>Fönsterram finns endast tillgänglig i fönsterläge</translation>
</message>
<message>
<source>Windowed Fullscreen mode always uses the native display resolution.</source>
<translation>Helskärm i fönsterläge använder alltid skärmens nativa upplösning.</translation>
</message>
</context>
<context>
<name>Launcher::ImportPage</name>

View file

@ -49,7 +49,7 @@ void main()
vec2 adjustedUV = (gl_TextureMatrix[0] * vec4(uv, 0.0, 1.0)).xy;
#if @parallax
adjustedUV += getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), texture2D(normalMap, adjustedUV).a, 1.f);
adjustedUV += getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), texture2D(normalMap, adjustedUV).a, -1.0f);
#endif
vec4 diffuseTex = texture2D(diffuseMap, adjustedUV);
gl_FragData[0] = vec4(diffuseTex.xyz, 1.0);

View file

@ -46,8 +46,8 @@ void main(void)
normalToViewMatrix = gl_NormalMatrix;
#if @normalMap
mat3 tbnMatrix = generateTangentSpace(vec4(1.0, 0.0, 0.0, 1.0), passNormal);
tbnMatrix[0] = normalize(cross(tbnMatrix[2], tbnMatrix[1])); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal
mat3 tbnMatrix = generateTangentSpace(vec4(1.0, 0.0, 0.0, -1.0), passNormal);
tbnMatrix[0] = -normalize(cross(tbnMatrix[2], tbnMatrix[1])); // our original tangent was not at a 90 degree angle to the normal, so we need to rederive it
normalToViewMatrix *= tbnMatrix;
#endif