Merge branch 'master' of https://gitlab.com/OpenMW/openmw.git into fix/osg-animation-rename-update-order-sucks-this-took-too-long

pull/3236/head
Sam Hellawell 6 months ago
commit df0a7a849b

@ -184,6 +184,8 @@
Bug #8005: F3 stats bars are sorted not according to their place in the timeline Bug #8005: F3 stats bars are sorted not according to their place in the timeline
Bug #8018: Potion effects should never explode and always apply on self Bug #8018: Potion effects should never explode and always apply on self
Bug #8021: Player's scale doesn't reset when starting a new game Bug #8021: Player's scale doesn't reset when starting a new game
Bug #8048: Actors can generate negative collision extents and have no collision
Bug #8064: Lua move360 script doesn't respect the enableZoom/disableZoom Camera interface setting
Feature #1415: Infinite fall failsafe Feature #1415: Infinite fall failsafe
Feature #2566: Handle NAM9 records for manual cell references Feature #2566: Handle NAM9 records for manual cell references
Feature #3537: Shader-based water ripples Feature #3537: Shader-based water ripples
@ -249,6 +251,7 @@
Feature #7971: Make save's Time Played value display hours instead of days Feature #7971: Make save's Time Played value display hours instead of days
Feature #7985: Support dark mode on Windows Feature #7985: Support dark mode on Windows
Feature #8034: (Lua) Containers should have respawning/organic flags Feature #8034: (Lua) Containers should have respawning/organic flags
Feature #8067: Support Game Mode on macOS
Task #5896: Do not use deprecated MyGUI properties Task #5896: Do not use deprecated MyGUI properties
Task #6085: Replace boost::filesystem with std::filesystem Task #6085: Replace boost::filesystem with std::filesystem
Task #6149: Dehardcode Lua API_REVISION Task #6149: Dehardcode Lua API_REVISION

@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 62) set(OPENMW_LUA_API_REVISION 64)
set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_POSTPROCESSING_API_REVISION 1)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")

@ -1364,4 +1364,28 @@ namespace
EXPECT_EQ(*result, expected); EXPECT_EQ(*result, expected);
} }
TEST_F(TestBulletNifLoader, dont_assign_invalid_bounding_box_extents)
{
copy(mTransform, mNiTriShape.mTransform);
mNiTriShape.mTransform.mScale = 10;
mNiTriShape.mParents.push_back(&mNiNode);
mNiTriShape2.mName = "Bounding Box";
mNiTriShape2.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV;
mNiTriShape2.mBounds.mBox.mExtents = osg::Vec3f(-1, -2, -3);
mNiTriShape2.mParents.push_back(&mNiNode);
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape), Nif::NiAVObjectPtr(&mNiTriShape2) };
Nif::NIFFile file("test.nif");
file.mRoots.push_back(&mNiNode);
const auto result = mLoader.load(file);
const bool extentsUnassigned
= std::ranges::all_of(result->mCollisionBox.mExtents._v, [](float extent) { return extent == 0.f; });
EXPECT_EQ(extentsUnassigned, true);
}
} }

@ -319,7 +319,6 @@ bool Launcher::SettingsPage::loadSettings()
// Miscellaneous // Miscellaneous
{ {
// Saves // Saves
loadSettingBool(Settings::saves().mTimeplayed, *timePlayedCheckbox);
loadSettingInt(Settings::saves().mMaxQuicksaves, *maximumQuicksavesComboBox); loadSettingInt(Settings::saves().mMaxQuicksaves, *maximumQuicksavesComboBox);
// Other Settings // Other Settings
@ -512,7 +511,6 @@ void Launcher::SettingsPage::saveSettings()
// Miscellaneous // Miscellaneous
{ {
// Saves Settings // Saves Settings
saveSettingBool(*timePlayedCheckbox, Settings::saves().mTimeplayed);
saveSettingInt(*maximumQuicksavesComboBox, Settings::saves().mMaxQuicksaves); saveSettingInt(*maximumQuicksavesComboBox, Settings::saves().mMaxQuicksaves);
// Other Settings // Other Settings

@ -1430,16 +1430,6 @@
<string>Saves</string> <string>Saves</string>
</property> </property>
<layout class="QVBoxLayout" name="savesGroupVerticalLayout"> <layout class="QVBoxLayout" name="savesGroupVerticalLayout">
<item>
<widget class="QCheckBox" name="timePlayedCheckbox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Add &quot;Time Played&quot; to Saves</string>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="maximumQuicksavesLayout"> <layout class="QHBoxLayout" name="maximumQuicksavesLayout">
<item> <item>

@ -71,7 +71,7 @@ bool CSVWorld::DataDisplayDelegate::eventFilter(QObject* target, QEvent* event)
QColor themeColor = QApplication::palette().text().color(); QColor themeColor = QApplication::palette().text().color();
if (themeColor != mPixmapsColor) if (themeColor != mPixmapsColor)
{ {
mPixmapsColor = themeColor; mPixmapsColor = std::move(themeColor);
buildPixmaps(); buildPixmaps();
} }

@ -212,7 +212,7 @@ void CSVWorld::ScriptSubView::useHint(const std::string& hint)
if (hint.empty()) if (hint.empty())
return; return;
unsigned line = 0, column = 0; int line = 0, column = 0;
char c; char c;
std::istringstream stream(hint.c_str() + 1); std::istringstream stream(hint.c_str() + 1);
switch (hint[0]) switch (hint[0])
@ -222,8 +222,8 @@ void CSVWorld::ScriptSubView::useHint(const std::string& hint)
{ {
QModelIndex index = mModel->getModelIndex(getUniversalId().getId(), mColumn); QModelIndex index = mModel->getModelIndex(getUniversalId().getId(), mColumn);
QString source = mModel->data(index).toString(); QString source = mModel->data(index).toString();
unsigned stringSize = source.length(); int stringSize = static_cast<int>(source.length());
unsigned pos, dummy; int pos, dummy;
if (!(stream >> c >> dummy >> pos)) if (!(stream >> c >> dummy >> pos))
return; return;
@ -234,7 +234,7 @@ void CSVWorld::ScriptSubView::useHint(const std::string& hint)
pos = stringSize; pos = stringSize;
} }
for (unsigned i = 0; i <= pos; ++i) for (int i = 0; i <= pos; ++i)
{ {
if (source[i] == '\n') if (source[i] == '\n')
{ {

@ -245,7 +245,7 @@ bool OMW::Engine::frame(unsigned frameNumber, float frametime)
if (mStateManager->getState() != MWBase::StateManager::State_NoGame) if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
{ {
if (!mWindowManager->containsMode(MWGui::GM_MainMenu)) if (!mWindowManager->containsMode(MWGui::GM_MainMenu) || !paused)
{ {
if (mWorld->getScriptsEnabled()) if (mWorld->getScriptsEnabled())
{ {

@ -435,7 +435,7 @@ namespace MWGui
mCurrentSlot->mProfile.mInGameTime.mMonth) mCurrentSlot->mProfile.mInGameTime.mMonth)
<< " " << hour << " " << (pm ? "#{Calendar:pm}" : "#{Calendar:am}"); << " " << hour << " " << (pm ? "#{Calendar:pm}" : "#{Calendar:am}");
if (Settings::saves().mTimeplayed) if (mCurrentSlot->mProfile.mTimePlayed > 0)
{ {
text << "\n" text << "\n"
<< "#{OMWEngine:TimePlayed}: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed); << "#{OMWEngine:TimePlayed}: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed);

@ -1,22 +1,28 @@
#include "itemdata.hpp" #include "itemdata.hpp"
#include <components/esm3/loadcrea.hpp>
#include <components/esm3/loadench.hpp>
#include "context.hpp" #include "context.hpp"
#include "luamanagerimp.hpp" #include "luamanagerimp.hpp"
#include "objectvariant.hpp" #include "objectvariant.hpp"
#include "../mwbase/environment.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
namespace namespace
{ {
using SelfObject = MWLua::SelfObject; using SelfObject = MWLua::SelfObject;
using Index = const SelfObject::CachedStat::Index&; using Index = const SelfObject::CachedStat::Index&;
constexpr std::array properties = { "condition", /*"enchantmentCharge", "soul", "owner", etc..*/ }; constexpr std::array properties = { "condition", "enchantmentCharge", "soul" };
void invalidPropErr(std::string_view prop, const MWWorld::Ptr& ptr) void valueErr(std::string_view prop, std::string type)
{ {
throw std::runtime_error("'" + std::string(prop) + "'" + " property does not exist for item " throw std::logic_error("'" + std::string(prop) + "'" + " received invalid value type (" + type + ")");
+ std::string(ptr.getClass().getName(ptr)) + "(" + std::string(ptr.getTypeDescription()) + ")");
} }
} }
@ -54,26 +60,56 @@ namespace MWLua
if (it != self->mStatsCache.end()) if (it != self->mStatsCache.end())
return it->second; return it->second;
} }
return sol::make_object(context.mLua->sol(), getValue(context, prop)); return sol::make_object(context.mLua->sol(), getValue(context, prop, mObject.ptr()));
} }
void set(const Context& context, std::string_view prop, const sol::object& value) const void set(const Context& context, std::string_view prop, const sol::object& value) const
{ {
SelfObject* obj = mObject.asSelfObject(); if (mObject.isGObject())
addStatUpdateAction(context.mLuaManager, *obj); setValue({}, prop, mObject.ptr(), value);
obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] = value; else if (mObject.isSelfObject())
{
SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] = value;
}
else
throw std::runtime_error("Only global or self scripts can set the value");
} }
sol::object getValue(const Context& context, std::string_view prop) const static sol::object getValue(const Context& context, std::string_view prop, const MWWorld::Ptr& ptr)
{ {
if (prop == "condition") if (prop == "condition")
{ {
MWWorld::Ptr o = mObject.ptr(); if (ptr.mRef->getType() == ESM::REC_LIGH)
if (o.mRef->getType() == ESM::REC_LIGH) return sol::make_object(context.mLua->sol(), ptr.getClass().getRemainingUsageTime(ptr));
return sol::make_object(context.mLua->sol(), o.getClass().getRemainingUsageTime(o)); else if (ptr.getClass().hasItemHealth(ptr))
else if (o.getClass().hasItemHealth(o)) return sol::make_object(context.mLua->sol(),
return sol::make_object( ptr.getClass().getItemHealth(ptr) + ptr.getCellRef().getChargeIntRemainder());
context.mLua->sol(), o.getClass().getItemHealth(o) + o.getCellRef().getChargeIntRemainder()); }
else if (prop == "enchantmentCharge")
{
const ESM::RefId& enchantmentName = ptr.getClass().getEnchantment(ptr);
if (enchantmentName.empty())
return sol::lua_nil;
float charge = ptr.getCellRef().getEnchantmentCharge();
const auto& store = MWBase::Environment::get().getESMStore();
const auto* enchantment = store->get<ESM::Enchantment>().find(enchantmentName);
if (charge == -1) // return the full charge
return sol::make_object(context.mLua->sol(), MWMechanics::getEnchantmentCharge(*enchantment));
return sol::make_object(context.mLua->sol(), charge);
}
else if (prop == "soul")
{
ESM::RefId soul = ptr.getCellRef().getSoul();
if (soul.empty())
return sol::lua_nil;
return sol::make_object(context.mLua->sol(), soul.serializeText());
} }
return sol::lua_nil; return sol::lua_nil;
@ -83,17 +119,48 @@ namespace MWLua
{ {
if (prop == "condition") if (prop == "condition")
{ {
float cond = LuaUtil::cast<float>(value); if (value.get_type() == sol::type::number)
if (ptr.mRef->getType() == ESM::REC_LIGH) {
ptr.getClass().setRemainingUsageTime(ptr, cond); float cond = LuaUtil::cast<float>(value);
else if (ptr.getClass().hasItemHealth(ptr)) if (ptr.mRef->getType() == ESM::REC_LIGH)
ptr.getClass().setRemainingUsageTime(ptr, cond);
else if (ptr.getClass().hasItemHealth(ptr))
{
// if the value set is less than 0, chargeInt and chargeIntRemainder is set to 0
ptr.getCellRef().setChargeIntRemainder(std::max(0.f, std::modf(cond, &cond)));
ptr.getCellRef().setCharge(std::max(0.f, cond));
}
}
else
valueErr(prop, sol::type_name(value.lua_state(), value.get_type()));
}
else if (prop == "enchantmentCharge")
{
if (value.get_type() == sol::type::lua_nil)
ptr.getCellRef().setEnchantmentCharge(-1);
else if (value.get_type() == sol::type::number)
ptr.getCellRef().setEnchantmentCharge(std::max(0.0f, LuaUtil::cast<float>(value)));
else
valueErr(prop, sol::type_name(value.lua_state(), value.get_type()));
}
else if (prop == "soul")
{
if (value.get_type() == sol::type::lua_nil)
ptr.getCellRef().setSoul(ESM::RefId{});
else if (value.get_type() == sol::type::string)
{ {
// if the value set is less than 0, chargeInt and chargeIntRemainder is set to 0 std::string_view souldId = LuaUtil::cast<std::string_view>(value);
ptr.getCellRef().setChargeIntRemainder(std::max(0.f, std::modf(cond, &cond))); ESM::RefId creature = ESM::RefId::deserializeText(souldId);
ptr.getCellRef().setCharge(std::max(0.f, cond)); const auto& store = *MWBase::Environment::get().getESMStore();
// TODO: Add Support for NPC Souls
if (store.get<ESM::Creature>().search(creature))
ptr.getCellRef().setSoul(creature);
else
throw std::runtime_error("Cannot use non-existent creature as a soul: " + std::string(souldId));
} }
else else
invalidPropErr(prop, ptr); valueErr(prop, sol::type_name(value.lua_state(), value.get_type()));
} }
} }
}; };

@ -1,6 +1,7 @@
#include "localscripts.hpp" #include "localscripts.hpp"
#include <components/esm3/loadcell.hpp> #include <components/esm3/loadcell.hpp>
#include <components/esm3/loadweap.hpp>
#include <components/misc/strings/lower.hpp> #include <components/misc/strings/lower.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -13,6 +14,7 @@
#include "../mwmechanics/aisequence.hpp" #include "../mwmechanics/aisequence.hpp"
#include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/aitravel.hpp"
#include "../mwmechanics/aiwander.hpp" #include "../mwmechanics/aiwander.hpp"
#include "../mwmechanics/attacktype.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/ptr.hpp" #include "../mwworld/ptr.hpp"
@ -63,6 +65,11 @@ namespace MWLua
selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; });
selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; };
selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; };
selfAPI["ATTACK_TYPE"]
= LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs<std::string_view, MWMechanics::AttackType>(
{ { "NoAttack", MWMechanics::AttackType::NoAttack }, { "Any", MWMechanics::AttackType::Any },
{ "Chop", MWMechanics::AttackType::Chop }, { "Slash", MWMechanics::AttackType::Slash },
{ "Thrust", MWMechanics::AttackType::Thrust } }));
using AiPackage = MWMechanics::AiPackage; using AiPackage = MWMechanics::AiPackage;
sol::usertype<AiPackage> aiPackage = context.mLua->sol().new_usertype<AiPackage>("AiPackage"); sol::usertype<AiPackage> aiPackage = context.mLua->sol().new_usertype<AiPackage>("AiPackage");

@ -2,6 +2,7 @@
#include <components/esm3/loadacti.hpp> #include <components/esm3/loadacti.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -51,7 +52,8 @@ namespace MWLua
record["model"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { record["model"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string {
return Misc::ResourceHelpers::correctMeshPath(rec.mModel); return Misc::ResourceHelpers::correctMeshPath(rec.mModel);
}); });
record["mwscript"] = sol::readonly_property( record["mwscript"] = sol::readonly_property([](const ESM::Activator& rec) -> sol::optional<std::string> {
[](const ESM::Activator& rec) -> std::string { return rec.mScript.serializeText(); }); return LuaUtil::serializeRefId(rec.mScript);
});
} }
} }

@ -2,6 +2,7 @@
#include <components/esm3/loadappa.hpp> #include <components/esm3/loadappa.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -39,8 +40,9 @@ namespace MWLua
record["model"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { record["model"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string {
return Misc::ResourceHelpers::correctMeshPath(rec.mModel); return Misc::ResourceHelpers::correctMeshPath(rec.mModel);
}); });
record["mwscript"] = sol::readonly_property( record["mwscript"] = sol::readonly_property([](const ESM::Apparatus& rec) -> sol::optional<std::string> {
[](const ESM::Apparatus& rec) -> std::string { return rec.mScript.serializeText(); }); return LuaUtil::serializeRefId(rec.mScript);
});
record["icon"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string { record["icon"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
}); });

@ -2,6 +2,7 @@
#include <components/esm3/loadarmo.hpp> #include <components/esm3/loadarmo.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -98,10 +99,10 @@ namespace MWLua
record["icon"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string { record["icon"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
}); });
record["enchant"] record["enchant"] = sol::readonly_property(
= sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mEnchant.serializeText(); }); [](const ESM::Armor& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mEnchant); });
record["mwscript"] record["mwscript"] = sol::readonly_property(
= sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mScript.serializeText(); }); [](const ESM::Armor& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["weight"] = sol::readonly_property([](const ESM::Armor& rec) -> float { return rec.mData.mWeight; }); record["weight"] = sol::readonly_property([](const ESM::Armor& rec) -> float { return rec.mData.mWeight; });
record["value"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mValue; }); record["value"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mValue; });
record["type"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mType; }); record["type"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mType; });

@ -6,6 +6,7 @@
#include <components/esm3/loadbook.hpp> #include <components/esm3/loadbook.hpp>
#include <components/esm3/loadskil.hpp> #include <components/esm3/loadskil.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -104,14 +105,14 @@ namespace MWLua
record["name"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mName; }); record["name"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mName; });
record["model"] = sol::readonly_property( record["model"] = sol::readonly_property(
[](const ESM::Book& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); [](const ESM::Book& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); });
record["mwscript"] record["mwscript"] = sol::readonly_property(
= sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mScript.serializeText(); }); [](const ESM::Book& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["icon"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { record["icon"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
}); });
record["text"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mText; }); record["text"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mText; });
record["enchant"] record["enchant"] = sol::readonly_property(
= sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mEnchant.serializeText(); }); [](const ESM::Book& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mEnchant); });
record["isScroll"] = sol::readonly_property([](const ESM::Book& rec) -> bool { return rec.mData.mIsScroll; }); record["isScroll"] = sol::readonly_property([](const ESM::Book& rec) -> bool { return rec.mData.mIsScroll; });
record["value"] = sol::readonly_property([](const ESM::Book& rec) -> int { return rec.mData.mValue; }); record["value"] = sol::readonly_property([](const ESM::Book& rec) -> int { return rec.mData.mValue; });
record["weight"] = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mWeight; }); record["weight"] = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mWeight; });
@ -119,9 +120,7 @@ namespace MWLua
= sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mEnchant * 0.1f; }); = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mEnchant * 0.1f; });
record["skill"] = sol::readonly_property([](const ESM::Book& rec) -> sol::optional<std::string> { record["skill"] = sol::readonly_property([](const ESM::Book& rec) -> sol::optional<std::string> {
ESM::RefId skill = ESM::Skill::indexToRefId(rec.mData.mSkillId); ESM::RefId skill = ESM::Skill::indexToRefId(rec.mData.mSkillId);
if (!skill.empty()) return LuaUtil::serializeRefId(skill);
return skill.serializeText();
return sol::nullopt;
}); });
} }
} }

@ -2,6 +2,7 @@
#include <components/esm3/loadclot.hpp> #include <components/esm3/loadclot.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -93,10 +94,12 @@ namespace MWLua
record["icon"] = sol::readonly_property([vfs](const ESM::Clothing& rec) -> std::string { record["icon"] = sol::readonly_property([vfs](const ESM::Clothing& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
}); });
record["enchant"] = sol::readonly_property( record["enchant"] = sol::readonly_property([](const ESM::Clothing& rec) -> sol::optional<std::string> {
[](const ESM::Clothing& rec) -> std::string { return rec.mEnchant.serializeText(); }); return LuaUtil::serializeRefId(rec.mEnchant);
record["mwscript"] = sol::readonly_property( });
[](const ESM::Clothing& rec) -> std::string { return rec.mScript.serializeText(); }); record["mwscript"] = sol::readonly_property([](const ESM::Clothing& rec) -> sol::optional<std::string> {
return LuaUtil::serializeRefId(rec.mScript);
});
record["weight"] = sol::readonly_property([](const ESM::Clothing& rec) -> float { return rec.mData.mWeight; }); record["weight"] = sol::readonly_property([](const ESM::Clothing& rec) -> float { return rec.mData.mWeight; });
record["value"] = sol::readonly_property([](const ESM::Clothing& rec) -> int { return rec.mData.mValue; }); record["value"] = sol::readonly_property([](const ESM::Clothing& rec) -> int { return rec.mData.mValue; });
record["type"] = sol::readonly_property([](const ESM::Clothing& rec) -> int { return rec.mData.mType; }); record["type"] = sol::readonly_property([](const ESM::Clothing& rec) -> int { return rec.mData.mType; });

@ -2,6 +2,7 @@
#include <components/esm3/loadcont.hpp> #include <components/esm3/loadcont.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -51,8 +52,9 @@ namespace MWLua
record["model"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { record["model"] = sol::readonly_property([](const ESM::Container& rec) -> std::string {
return Misc::ResourceHelpers::correctMeshPath(rec.mModel); return Misc::ResourceHelpers::correctMeshPath(rec.mModel);
}); });
record["mwscript"] = sol::readonly_property( record["mwscript"] = sol::readonly_property([](const ESM::Container& rec) -> sol::optional<std::string> {
[](const ESM::Container& rec) -> std::string { return rec.mScript.serializeText(); }); return LuaUtil::serializeRefId(rec.mScript);
});
record["weight"] = sol::readonly_property([](const ESM::Container& rec) -> float { return rec.mWeight; }); record["weight"] = sol::readonly_property([](const ESM::Container& rec) -> float { return rec.mWeight; });
record["isOrganic"] = sol::readonly_property( record["isOrganic"] = sol::readonly_property(
[](const ESM::Container& rec) -> bool { return rec.mFlags & ESM::Container::Organic; }); [](const ESM::Container& rec) -> bool { return rec.mFlags & ESM::Container::Organic; });

@ -4,6 +4,7 @@
#include <components/esm3/loadcrea.hpp> #include <components/esm3/loadcrea.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -36,8 +37,9 @@ namespace MWLua
record["name"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mName; }); record["name"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mName; });
record["model"] = sol::readonly_property( record["model"] = sol::readonly_property(
[](const ESM::Creature& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); [](const ESM::Creature& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); });
record["mwscript"] = sol::readonly_property( record["mwscript"] = sol::readonly_property([](const ESM::Creature& rec) -> sol::optional<std::string> {
[](const ESM::Creature& rec) -> std::string { return rec.mScript.serializeText(); }); return LuaUtil::serializeRefId(rec.mScript);
});
record["baseCreature"] = sol::readonly_property( record["baseCreature"] = sol::readonly_property(
[](const ESM::Creature& rec) -> std::string { return rec.mOriginal.serializeText(); }); [](const ESM::Creature& rec) -> std::string { return rec.mOriginal.serializeText(); });
record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; }); record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; });

@ -2,6 +2,7 @@
#include <components/esm3/loaddoor.hpp> #include <components/esm3/loaddoor.hpp>
#include <components/esm4/loaddoor.hpp> #include <components/esm4/loaddoor.hpp>
#include <components/lua/util.hpp>
#include <components/lua/utilpackage.hpp> #include <components/lua/utilpackage.hpp>
#include <components/misc/convert.hpp> #include <components/misc/convert.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
@ -64,8 +65,8 @@ namespace MWLua
record["name"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mName; }); record["name"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mName; });
record["model"] = sol::readonly_property( record["model"] = sol::readonly_property(
[](const ESM::Door& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); [](const ESM::Door& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); });
record["mwscript"] record["mwscript"] = sol::readonly_property(
= sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mScript.serializeText(); }); [](const ESM::Door& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["openSound"] = sol::readonly_property( record["openSound"] = sol::readonly_property(
[](const ESM::Door& rec) -> std::string { return rec.mOpenSound.serializeText(); }); [](const ESM::Door& rec) -> std::string { return rec.mOpenSound.serializeText(); });
record["closeSound"] = sol::readonly_property( record["closeSound"] = sol::readonly_property(

@ -34,8 +34,9 @@ namespace MWLua
record["model"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { record["model"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string {
return Misc::ResourceHelpers::correctMeshPath(rec.mModel); return Misc::ResourceHelpers::correctMeshPath(rec.mModel);
}); });
record["mwscript"] = sol::readonly_property( record["mwscript"] = sol::readonly_property([](const ESM::Ingredient& rec) -> sol::optional<std::string> {
[](const ESM::Ingredient& rec) -> std::string { return rec.mScript.serializeText(); }); return LuaUtil::serializeRefId(rec.mScript);
});
record["icon"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string { record["icon"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
}); });

@ -11,6 +11,7 @@ namespace MWLua
{ {
void addItemBindings(sol::table item, const Context& context) void addItemBindings(sol::table item, const Context& context)
{ {
// Deprecated. Moved to itemData; should be removed later
item["getEnchantmentCharge"] = [](const Object& object) -> sol::optional<float> { item["getEnchantmentCharge"] = [](const Object& object) -> sol::optional<float> {
float charge = object.ptr().getCellRef().getEnchantmentCharge(); float charge = object.ptr().getCellRef().getEnchantmentCharge();
if (charge == -1) if (charge == -1)

@ -2,6 +2,7 @@
#include <components/esm3/loadligh.hpp> #include <components/esm3/loadligh.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -96,8 +97,8 @@ namespace MWLua
}); });
record["sound"] record["sound"]
= sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mSound.serializeText(); }); = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mSound.serializeText(); });
record["mwscript"] record["mwscript"] = sol::readonly_property(
= sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mScript.serializeText(); }); [](const ESM::Light& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["weight"] = sol::readonly_property([](const ESM::Light& rec) -> float { return rec.mData.mWeight; }); record["weight"] = sol::readonly_property([](const ESM::Light& rec) -> float { return rec.mData.mWeight; });
record["value"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mValue; }); record["value"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mValue; });
record["duration"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mTime; }); record["duration"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mTime; });

@ -2,6 +2,7 @@
#include <components/esm3/loadlock.hpp> #include <components/esm3/loadlock.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -31,8 +32,9 @@ namespace MWLua
record["name"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mName; }); record["name"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mName; });
record["model"] = sol::readonly_property( record["model"] = sol::readonly_property(
[](const ESM::Lockpick& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); [](const ESM::Lockpick& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); });
record["mwscript"] = sol::readonly_property( record["mwscript"] = sol::readonly_property([](const ESM::Lockpick& rec) -> sol::optional<std::string> {
[](const ESM::Lockpick& rec) -> std::string { return rec.mScript.serializeText(); }); return LuaUtil::serializeRefId(rec.mScript);
});
record["icon"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { record["icon"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
}); });

@ -3,6 +3,7 @@
#include <components/esm3/loadcrea.hpp> #include <components/esm3/loadcrea.hpp>
#include <components/esm3/loadmisc.hpp> #include <components/esm3/loadmisc.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -55,6 +56,7 @@ namespace MWLua
addRecordFunctionBinding<ESM::Miscellaneous>(miscellaneous, context); addRecordFunctionBinding<ESM::Miscellaneous>(miscellaneous, context);
miscellaneous["createRecordDraft"] = tableToMisc; miscellaneous["createRecordDraft"] = tableToMisc;
// Deprecated. Moved to itemData; should be removed later
miscellaneous["setSoul"] = [](const GObject& object, std::string_view soulId) { miscellaneous["setSoul"] = [](const GObject& object, std::string_view soulId) {
ESM::RefId creature = ESM::RefId::deserializeText(soulId); ESM::RefId creature = ESM::RefId::deserializeText(soulId);
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
@ -69,12 +71,10 @@ namespace MWLua
}; };
miscellaneous["getSoul"] = [](const Object& object) -> sol::optional<std::string> { miscellaneous["getSoul"] = [](const Object& object) -> sol::optional<std::string> {
ESM::RefId soul = object.ptr().getCellRef().getSoul(); ESM::RefId soul = object.ptr().getCellRef().getSoul();
if (soul.empty()) return LuaUtil::serializeRefId(soul);
return sol::nullopt;
else
return soul.serializeText();
}; };
miscellaneous["soul"] = miscellaneous["getSoul"]; // for compatibility; should be removed later miscellaneous["soul"] = miscellaneous["getSoul"]; // for compatibility; should be removed later
sol::usertype<ESM::Miscellaneous> record sol::usertype<ESM::Miscellaneous> record
= context.mLua->sol().new_usertype<ESM::Miscellaneous>("ESM3_Miscellaneous"); = context.mLua->sol().new_usertype<ESM::Miscellaneous>("ESM3_Miscellaneous");
record[sol::meta_function::to_string] record[sol::meta_function::to_string]
@ -85,8 +85,9 @@ namespace MWLua
record["model"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { record["model"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string {
return Misc::ResourceHelpers::correctMeshPath(rec.mModel); return Misc::ResourceHelpers::correctMeshPath(rec.mModel);
}); });
record["mwscript"] = sol::readonly_property( record["mwscript"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> sol::optional<std::string> {
[](const ESM::Miscellaneous& rec) -> std::string { return rec.mScript.serializeText(); }); return LuaUtil::serializeRefId(rec.mScript);
});
record["icon"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string { record["icon"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
}); });

@ -85,8 +85,8 @@ namespace MWLua
= sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mRace.serializeText(); }); = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mRace.serializeText(); });
record["class"] record["class"]
= sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mClass.serializeText(); }); = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mClass.serializeText(); });
record["mwscript"] record["mwscript"] = sol::readonly_property(
= sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mScript.serializeText(); }); [](const ESM::NPC& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["hair"] record["hair"]
= sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair.serializeText(); }); = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair.serializeText(); });
record["baseDisposition"] record["baseDisposition"]

@ -80,8 +80,8 @@ namespace MWLua
record["icon"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { record["icon"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
}); });
record["mwscript"] record["mwscript"] = sol::readonly_property(
= sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mScript.serializeText(); }); [](const ESM::Potion& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["weight"] = sol::readonly_property([](const ESM::Potion& rec) -> float { return rec.mData.mWeight; }); record["weight"] = sol::readonly_property([](const ESM::Potion& rec) -> float { return rec.mData.mWeight; });
record["value"] = sol::readonly_property([](const ESM::Potion& rec) -> int { return rec.mData.mValue; }); record["value"] = sol::readonly_property([](const ESM::Potion& rec) -> int { return rec.mData.mValue; });
record["effects"] = sol::readonly_property([context](const ESM::Potion& rec) -> sol::table { record["effects"] = sol::readonly_property([context](const ESM::Potion& rec) -> sol::table {

@ -2,6 +2,7 @@
#include <components/esm3/loadprob.hpp> #include <components/esm3/loadprob.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -31,8 +32,8 @@ namespace MWLua
record["name"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mName; }); record["name"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mName; });
record["model"] = sol::readonly_property( record["model"] = sol::readonly_property(
[](const ESM::Probe& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); [](const ESM::Probe& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); });
record["mwscript"] record["mwscript"] = sol::readonly_property(
= sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mScript.serializeText(); }); [](const ESM::Probe& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["icon"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { record["icon"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
}); });

@ -2,6 +2,7 @@
#include <components/esm3/loadrepa.hpp> #include <components/esm3/loadrepa.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -31,8 +32,8 @@ namespace MWLua
record["name"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mName; }); record["name"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mName; });
record["model"] = sol::readonly_property( record["model"] = sol::readonly_property(
[](const ESM::Repair& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); [](const ESM::Repair& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); });
record["mwscript"] record["mwscript"] = sol::readonly_property(
= sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mScript.serializeText(); }); [](const ESM::Repair& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["icon"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { record["icon"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
}); });

@ -2,6 +2,7 @@
#include <components/esm3/loadweap.hpp> #include <components/esm3/loadweap.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -132,9 +133,9 @@ namespace MWLua
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
}); });
record["enchant"] = sol::readonly_property( record["enchant"] = sol::readonly_property(
[](const ESM::Weapon& rec) -> std::string { return rec.mEnchant.serializeText(); }); [](const ESM::Weapon& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mEnchant); });
record["mwscript"] record["mwscript"] = sol::readonly_property(
= sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mScript.serializeText(); }); [](const ESM::Weapon& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["isMagical"] = sol::readonly_property( record["isMagical"] = sol::readonly_property(
[](const ESM::Weapon& rec) -> bool { return rec.mData.mFlags & ESM::Weapon::Magical; }); [](const ESM::Weapon& rec) -> bool { return rec.mData.mFlags & ESM::Weapon::Magical; });
record["isSilver"] = sol::readonly_property( record["isSilver"] = sol::readonly_property(

@ -47,6 +47,7 @@
#include "aifollow.hpp" #include "aifollow.hpp"
#include "aipursue.hpp" #include "aipursue.hpp"
#include "aiwander.hpp" #include "aiwander.hpp"
#include "attacktype.hpp"
#include "character.hpp" #include "character.hpp"
#include "creaturestats.hpp" #include "creaturestats.hpp"
#include "movement.hpp" #include "movement.hpp"
@ -239,6 +240,23 @@ namespace MWMechanics
namespace namespace
{ {
std::string_view attackTypeName(AttackType attackType)
{
switch (attackType)
{
case AttackType::NoAttack:
case AttackType::Any:
return {};
case AttackType::Chop:
return "chop";
case AttackType::Slash:
return "slash";
case AttackType::Thrust:
return "thrust";
}
throw std::logic_error("Invalid attack type value: " + std::to_string(static_cast<int>(attackType)));
}
float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration,
const osg::Vec3f& halfExtents) const osg::Vec3f& halfExtents)
{ {
@ -363,7 +381,11 @@ namespace MWMechanics
mov.mSpeedFactor = osg::Vec2(controls.mMovement, controls.mSideMovement).length(); mov.mSpeedFactor = osg::Vec2(controls.mMovement, controls.mSideMovement).length();
stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, controls.mRun); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, controls.mRun);
stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, controls.mSneak); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, controls.mSneak);
stats.setAttackingOrSpell((controls.mUse & 1) == 1);
AttackType attackType = static_cast<AttackType>(controls.mUse);
stats.setAttackingOrSpell(attackType != AttackType::NoAttack);
stats.setAttackType(attackTypeName(attackType));
controls.mChanged = false; controls.mChanged = false;
} }
// For the player we don't need to copy these values to Lua because mwinput doesn't change them. // For the player we don't need to copy these values to Lua because mwinput doesn't change them.

@ -455,27 +455,37 @@ namespace MWMechanics
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration,
MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage) MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage)
{ {
switch (storage.mState) // Attempt to fast forward to the next state instead of remaining in an intermediate state for a frame
for (int i = 0; i < 2; ++i)
{ {
case AiWanderStorage::Wander_IdleNow: switch (storage.mState)
onIdleStatePerFrameActions(actor, duration, storage); {
break; case AiWanderStorage::Wander_IdleNow:
{
case AiWanderStorage::Wander_Walking: onIdleStatePerFrameActions(actor, duration, storage);
onWalkingStatePerFrameActions(actor, duration, supportedMovementDirections, storage); if (storage.mState != AiWanderStorage::Wander_ChooseAction)
break; return;
continue;
case AiWanderStorage::Wander_ChooseAction: }
onChooseActionStatePerFrameActions(actor, storage); case AiWanderStorage::Wander_Walking:
break; onWalkingStatePerFrameActions(actor, duration, supportedMovementDirections, storage);
return;
case AiWanderStorage::Wander_MoveNow: case AiWanderStorage::Wander_ChooseAction:
break; // nothing to do {
onChooseActionStatePerFrameActions(actor, storage);
if (storage.mState != AiWanderStorage::Wander_IdleNow)
return;
continue;
}
case AiWanderStorage::Wander_MoveNow:
return; // nothing to do
default: default:
// should never get here // should never get here
assert(false); assert(false);
break; return;
}
} }
} }

@ -0,0 +1,16 @@
#ifndef OPENMW_MWMECHANICS_ATTACKTYPE_H
#define OPENMW_MWMECHANICS_ATTACKTYPE_H
namespace MWMechanics
{
enum class AttackType
{
NoAttack,
Any,
Chop,
Slash,
Thrust
};
}
#endif

@ -1674,7 +1674,12 @@ namespace MWMechanics
} }
} }
else if (aiInactive) else if (aiInactive)
mAttackType = getRandomAttackType(); {
mAttackType = getDesiredAttackType();
if (mAttackType == "")
mAttackType = getRandomAttackType();
}
// else if (mPtr != getPlayer()) use mAttackType set by AiCombat // else if (mPtr != getPlayer()) use mAttackType set by AiCombat
startKey = mAttackType + ' ' + startKey; startKey = mAttackType + ' ' + startKey;
stopKey = mAttackType + " max attack"; stopKey = mAttackType + " max attack";
@ -3003,6 +3008,11 @@ namespace MWMechanics
return mPtr.getClass().getCreatureStats(mPtr).getAttackingOrSpell(); return mPtr.getClass().getCreatureStats(mPtr).getAttackingOrSpell();
} }
std::string_view CharacterController::getDesiredAttackType() const
{
return mPtr.getClass().getCreatureStats(mPtr).getAttackType();
}
void CharacterController::setActive(int active) const void CharacterController::setActive(int active) const
{ {
mAnimation->setActive(active); mAnimation->setActive(active);

@ -247,6 +247,8 @@ namespace MWMechanics
bool getAttackingOrSpell() const; bool getAttackingOrSpell() const;
void setAttackingOrSpell(bool attackingOrSpell) const; void setAttackingOrSpell(bool attackingOrSpell) const;
std::string_view getDesiredAttackType() const;
void prepareHit(); void prepareHit();
public: public:

@ -97,6 +97,7 @@ namespace MWMechanics
protected: protected:
int mLevel; int mLevel;
bool mAttackingOrSpell; bool mAttackingOrSpell;
std::string mAttackType;
public: public:
CreatureStats(); CreatureStats();
@ -130,6 +131,7 @@ namespace MWMechanics
const MagicEffects& getMagicEffects() const; const MagicEffects& getMagicEffects() const;
bool getAttackingOrSpell() const { return mAttackingOrSpell; } bool getAttackingOrSpell() const { return mAttackingOrSpell; }
std::string_view getAttackType() const { return mAttackType; }
int getLevel() const; int getLevel() const;
@ -156,6 +158,8 @@ namespace MWMechanics
void setAttackingOrSpell(bool attackingOrSpell) { mAttackingOrSpell = attackingOrSpell; } void setAttackingOrSpell(bool attackingOrSpell) { mAttackingOrSpell = attackingOrSpell; }
void setAttackType(std::string_view attackType) { mAttackType = attackType; }
void setLevel(int level); void setLevel(int level);
void setAiSetting(AiSetting index, Stat<int> value); void setAiSetting(AiSetting index, Stat<int> value);

@ -2,6 +2,8 @@
#include <osg/FrameBufferObject> #include <osg/FrameBufferObject>
#include "postprocessor.hpp"
namespace MWRender namespace MWRender
{ {
void DistortionCallback::drawImplementation( void DistortionCallback::drawImplementation(
@ -10,6 +12,11 @@ namespace MWRender
osg::State* state = renderInfo.getState(); osg::State* state = renderInfo.getState();
size_t frameId = state->getFrameStamp()->getFrameNumber() % 2; size_t frameId = state->getFrameStamp()->getFrameNumber() % 2;
PostProcessor* postProcessor = dynamic_cast<PostProcessor*>(renderInfo.getCurrentCamera()->getUserData());
if (!postProcessor || bin->getStage()->getFrameBufferObject() != postProcessor->getPrimaryFbo(frameId))
return;
mFBO[frameId]->apply(*state); mFBO[frameId]->apply(*state);
const osg::Texture* tex const osg::Texture* tex

@ -630,6 +630,12 @@ namespace MWWorld
} }
void Store<ESM::Cell>::clearDynamic() void Store<ESM::Cell>::clearDynamic()
{ {
for (const auto& [_, cell] : mDynamicExt)
mCells.erase(cell->mId);
mDynamicExt.clear();
for (const auto& [_, cell] : mDynamicInt)
mCells.erase(cell->mId);
mDynamicInt.clear();
setUp(); setUp();
} }

@ -473,8 +473,8 @@ namespace MWWorld
mStore.write(writer, progress); // dynamic Store must be written (and read) before Cells, so that mStore.write(writer, progress); // dynamic Store must be written (and read) before Cells, so that
// references to custom made records will be recognized // references to custom made records will be recognized
mWorldModel.write(writer, progress); // the player's cell needs to be loaded before the player
mPlayer->write(writer, progress); mPlayer->write(writer, progress);
mWorldModel.write(writer, progress);
mGlobalVariables.write(writer, progress); mGlobalVariables.write(writer, progress);
mWeatherManager->write(writer, progress); mWeatherManager->write(writer, progress);
mProjectileManager->write(writer, progress); mProjectileManager->write(writer, progress);

@ -101,6 +101,24 @@ namespace MWWorld
return Cell(*cell); return Cell(*cell);
return std::nullopt; return std::nullopt;
} }
CellStore* getOrCreateExterior(const ESM::ExteriorCellLocation& location,
std::map<ESM::ExteriorCellLocation, MWWorld::CellStore*>& exteriors, ESMStore& store,
ESM::ReadersCache& readers, std::unordered_map<ESM::RefId, CellStore>& cells, bool triggerEvent)
{
if (const auto it = exteriors.find(location); it != exteriors.end())
{
assert(it->second != nullptr);
return it->second;
}
auto [cell, created] = createExteriorCell(location, store);
const ESM::RefId id = cell.getId();
CellStore* const cellStore = &emplaceCellStore(id, std::move(cell), store, readers, cells);
exteriors.emplace(location, cellStore);
if (created && triggerEvent)
MWBase::Environment::get().getLuaManager()->exteriorCreated(*cellStore);
return cellStore;
}
} }
} }
@ -178,23 +196,7 @@ namespace MWWorld
{ {
CellStore& WorldModel::getExterior(ESM::ExteriorCellLocation location, bool forceLoad) const CellStore& WorldModel::getExterior(ESM::ExteriorCellLocation location, bool forceLoad) const
{ {
const auto it = mExteriors.find(location); CellStore* cellStore = getOrCreateExterior(location, mExteriors, mStore, mReaders, mCells, true);
CellStore* cellStore = nullptr;
if (it == mExteriors.end())
{
auto [cell, created] = createExteriorCell(location, mStore);
const ESM::RefId id = cell.getId();
cellStore = &emplaceCellStore(id, std::move(cell), mStore, mReaders, mCells);
mExteriors.emplace(location, cellStore);
if (created)
MWBase::Environment::get().getLuaManager()->exteriorCreated(*cellStore);
}
else
{
assert(it->second != nullptr);
cellStore = it->second;
}
if (forceLoad && cellStore->getState() != CellStore::State_Loaded) if (forceLoad && cellStore->getState() != CellStore::State_Loaded)
cellStore->load(); cellStore->load();
@ -447,17 +449,26 @@ void MWWorld::WorldModel::write(ESM::ESMWriter& writer, Loading::Listener& progr
} }
} }
struct GetCellStoreCallback : public MWWorld::CellStore::GetCellStoreCallback struct MWWorld::WorldModel::GetCellStoreCallback : public CellStore::GetCellStoreCallback
{ {
public: public:
GetCellStoreCallback(MWWorld::WorldModel& worldModel) GetCellStoreCallback(WorldModel& worldModel)
: mWorldModel(worldModel) : mWorldModel(worldModel)
{ {
} }
MWWorld::WorldModel& mWorldModel; WorldModel& mWorldModel;
MWWorld::CellStore* getCellStore(const ESM::RefId& cellId) override { return mWorldModel.findCell(cellId); } CellStore* getCellStore(const ESM::RefId& cellId) override
{
if (const auto* exteriorId = cellId.getIf<ESM::ESM3ExteriorCellRefId>())
{
ESM::ExteriorCellLocation location(exteriorId->getX(), exteriorId->getY(), ESM::Cell::sDefaultWorldspaceId);
return getOrCreateExterior(
location, mWorldModel.mExteriors, mWorldModel.mStore, mWorldModel.mReaders, mWorldModel.mCells, false);
}
return mWorldModel.findCell(cellId);
}
}; };
bool MWWorld::WorldModel::readRecord(ESM::ESMReader& reader, uint32_t type) bool MWWorld::WorldModel::readRecord(ESM::ESMReader& reader, uint32_t type)
@ -467,7 +478,10 @@ bool MWWorld::WorldModel::readRecord(ESM::ESMReader& reader, uint32_t type)
ESM::CellState state; ESM::CellState state;
state.mId = reader.getCellId(); state.mId = reader.getCellId();
CellStore* const cellStore = findCell(state.mId); GetCellStoreCallback callback(*this);
CellStore* const cellStore = callback.getCellStore(state.mId);
if (cellStore == nullptr) if (cellStore == nullptr)
{ {
Log(Debug::Warning) << "Dropping state for cell " << state.mId << " (cell no longer exists)"; Log(Debug::Warning) << "Dropping state for cell " << state.mId << " (cell no longer exists)";
@ -484,8 +498,6 @@ bool MWWorld::WorldModel::readRecord(ESM::ESMReader& reader, uint32_t type)
if (cellStore->getState() != CellStore::State_Loaded) if (cellStore->getState() != CellStore::State_Loaded)
cellStore->load(); cellStore->load();
GetCellStoreCallback callback(*this);
cellStore->readReferences(reader, &callback); cellStore->readReferences(reader, &callback);
return true; return true;

@ -104,6 +104,8 @@ namespace MWWorld
bool readRecord(ESM::ESMReader& reader, uint32_t type); bool readRecord(ESM::ESMReader& reader, uint32_t type);
private: private:
struct GetCellStoreCallback;
PtrRegistry mPtrRegistry; // defined before mCells because during destruction it should be the last PtrRegistry mPtrRegistry; // defined before mCells because during destruction it should be the last
MWWorld::ESMStore& mStore; MWWorld::ESMStore& mStore;

@ -1,5 +1,7 @@
#include "fileparser.hpp" #include "fileparser.hpp"
#include <components/misc/strings/algorithm.hpp>
#include "scanner.hpp" #include "scanner.hpp"
#include "tokenloc.hpp" #include "tokenloc.hpp"
@ -12,11 +14,6 @@ namespace Compiler
{ {
} }
std::string FileParser::getName() const
{
return mName;
}
Interpreter::Program FileParser::getProgram() const Interpreter::Program FileParser::getProgram() const
{ {
return mScriptParser.getProgram(); return mScriptParser.getProgram();
@ -39,7 +36,7 @@ namespace Compiler
if (mState == EndNameState) if (mState == EndNameState)
{ {
// optional repeated name after end statement // optional repeated name after end statement
if (mName != name) if (!Misc::StringUtils::ciEqual(mName, name))
reportWarning("Names for script " + mName + " do not match", loc); reportWarning("Names for script " + mName + " do not match", loc);
mState = EndCompleteState; mState = EndCompleteState;
@ -79,7 +76,7 @@ namespace Compiler
if (mState == EndNameState) if (mState == EndNameState)
{ {
// optional repeated name after end statement // optional repeated name after end statement
if (mName != loc.mLiteral) if (!Misc::StringUtils::ciEqual(mName, loc.mLiteral))
reportWarning("Names for script " + mName + " do not match", loc); reportWarning("Names for script " + mName + " do not match", loc);
mState = EndCompleteState; mState = EndCompleteState;

@ -28,9 +28,6 @@ namespace Compiler
public: public:
FileParser(ErrorHandler& errorHandler, Context& context); FileParser(ErrorHandler& errorHandler, Context& context);
std::string getName() const;
///< Return script name.
Interpreter::Program getProgram() const; Interpreter::Program getProgram() const;
const Locals& getLocals() const; const Locals& getLocals() const;

@ -2,6 +2,11 @@
#define COMPONENTS_LUA_UTIL_H #define COMPONENTS_LUA_UTIL_H
#include <cstdint> #include <cstdint>
#include <string>
#include <sol/sol.hpp>
#include <components/esm/refid.hpp>
namespace LuaUtil namespace LuaUtil
{ {
@ -15,6 +20,13 @@ namespace LuaUtil
{ {
return i + 1; return i + 1;
} }
inline sol::optional<std::string> serializeRefId(ESM::RefId id)
{
if (id.empty())
return sol::nullopt;
return id.serializeText();
}
} }
#endif #endif

@ -241,7 +241,7 @@ namespace LuaUtil
return std::make_tuple(angles.x(), angles.z()); return std::make_tuple(angles.x(), angles.z());
}; };
transMType["getAnglesZYX"] = [](const TransformM& m) { transMType["getAnglesZYX"] = [](const TransformM& m) {
osg::Vec3f angles = Misc::toEulerAnglesXZ(m.mM); osg::Vec3f angles = Misc::toEulerAnglesZYX(m.mM);
return std::make_tuple(angles.z(), angles.y(), angles.x()); return std::make_tuple(angles.z(), angles.y(), angles.x());
}; };
@ -277,7 +277,7 @@ namespace LuaUtil
return std::make_tuple(angles.x(), angles.z()); return std::make_tuple(angles.x(), angles.z());
}; };
transQType["getAnglesZYX"] = [](const TransformQ& q) { transQType["getAnglesZYX"] = [](const TransformQ& q) {
osg::Vec3f angles = Misc::toEulerAnglesXZ(q.mQ); osg::Vec3f angles = Misc::toEulerAnglesZYX(q.mQ);
return std::make_tuple(angles.z(), angles.y(), angles.x()); return std::make_tuple(angles.z(), angles.y(), angles.x());
}; };

@ -36,7 +36,12 @@ namespace Misc
return QIcon(); return QIcon();
QFile iconFile(fileName); QFile iconFile(fileName);
iconFile.open(QIODevice::ReadOnly); if (!iconFile.open(QIODevice::ReadOnly))
{
qDebug() << "Failed to open icon file:" << fileName;
return QIcon();
}
auto content = iconFile.readAll(); auto content = iconFile.readAll();
if (!content.startsWith("<?xml")) if (!content.startsWith("<?xml"))
return QIcon(fileName); return QIcon(fileName);

@ -85,7 +85,8 @@ namespace NifBullet
{ {
if (Misc::StringUtils::ciEqual(node.mName, "Bounding Box")) if (Misc::StringUtils::ciEqual(node.mName, "Bounding Box"))
{ {
if (node.mBounds.mType == Nif::BoundingVolume::Type::BOX_BV) if (node.mBounds.mType == Nif::BoundingVolume::Type::BOX_BV
&& std::ranges::all_of(node.mBounds.mBox.mExtents._v, [](float extent) { return extent > 0.f; }))
{ {
mShape->mCollisionBox.mExtents = node.mBounds.mBox.mExtents; mShape->mCollisionBox.mExtents = node.mBounds.mBox.mExtents;
mShape->mCollisionBox.mCenter = node.mBounds.mBox.mCenter; mShape->mCollisionBox.mCenter = node.mBounds.mBox.mCenter;

@ -43,7 +43,12 @@ namespace Platform
setStyle("windows"); setStyle("windows");
QFile file(getStyleSheetPath()); QFile file(getStyleSheetPath());
file.open(QIODevice::ReadOnly); if (!file.open(QIODevice::ReadOnly))
{
qDebug() << "Failed to open style sheet file:" << getStyleSheetPath();
return;
}
setStyleSheet(file.readAll()); setStyleSheet(file.readAll());
} }
} }
@ -60,7 +65,12 @@ namespace Platform
setStyle("windows"); setStyle("windows");
QFile file(getStyleSheetPath()); QFile file(getStyleSheetPath());
file.open(QIODevice::ReadOnly); if (!file.open(QIODevice::ReadOnly))
{
qDebug() << "Failed to open style sheet file:" << getStyleSheetPath();
return;
}
setStyleSheet(file.readAll()); setStyleSheet(file.readAll());
} }
else else

@ -37,7 +37,8 @@ namespace SceneUtil
osg::StateSet* stateset = node.getOrCreateStateSet(); osg::StateSet* stateset = node.getOrCreateStateSet();
stateset->setRenderBinDetails(14, "Distortion", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); stateset->setNestRenderBins(false);
stateset->setRenderBinDetails(14, "Distortion", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS);
stateset->addUniform(new osg::Uniform("distortionStrength", distortionStrength)); stateset->addUniform(new osg::Uniform("distortionStrength", distortionStrength));
stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);

@ -20,7 +20,6 @@ namespace Settings
SettingValue<std::string> mCharacter{ mIndex, "Saves", "character" }; SettingValue<std::string> mCharacter{ mIndex, "Saves", "character" };
SettingValue<bool> mAutosave{ mIndex, "Saves", "autosave" }; SettingValue<bool> mAutosave{ mIndex, "Saves", "autosave" };
SettingValue<bool> mTimeplayed{ mIndex, "Saves", "timeplayed" };
SettingValue<int> mMaxQuicksaves{ mIndex, "Saves", "max quicksaves", makeMaxSanitizerInt(1) }; SettingValue<int> mMaxQuicksaves{ mIndex, "Saves", "max quicksaves", makeMaxSanitizerInt(1) };
}; };
} }

@ -1,5 +1,5 @@
parse_cmake parse_cmake
sphinx==1.8.5 sphinx==7.1.2
docutils==0.17.1 docutils==0.18.1
jinja2==3.1.4 jinja2==3.1.4
sphinx_rtd_theme sphinx_rtd_theme==1.3.0

@ -55,7 +55,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = u'OpenMW' project = u'OpenMW'
copyright = u'2023, OpenMW Team' copyright = u'2024, OpenMW Team'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for

@ -21,19 +21,6 @@ This setting determines whether the game will be automatically saved when the ch
This setting can be toggled in game with the Auto-Save when Rest button in the Prefs panel of the Options menu. This setting can be toggled in game with the Auto-Save when Rest button in the Prefs panel of the Options menu.
timeplayed
----------
:Type: boolean
:Range: True/False
:Default: False
This setting determines whether the amount of the time the player has spent playing will be displayed
for each saved game in the Load menu. Currently, the counter includes time spent in menus, including the pause menu,
but does not include time spent with the game window minimized.
This setting can only be configured by editing the settings configuration file.
max quicksaves max quicksaves
-------------- --------------

@ -1,5 +1,6 @@
local self = require('openmw.self') local self = require('openmw.self')
local interfaces = require('openmw.interfaces') local interfaces = require('openmw.interfaces')
local types = require('openmw.types')
local util = require('openmw.util') local util = require('openmw.util')
local function startPackage(args) local function startPackage(args)
@ -10,6 +11,7 @@ local function startPackage(args)
self:_startAiCombat(args.target, cancelOther) self:_startAiCombat(args.target, cancelOther)
elseif args.type == 'Pursue' then elseif args.type == 'Pursue' then
if not args.target then error("target required") end if not args.target then error("target required") end
if not types.Player.objectIsInstance(args.target) then error("target must be a player") end
self:_startAiPursue(args.target, cancelOther) self:_startAiPursue(args.target, cancelOther)
elseif args.type == 'Follow' then elseif args.type == 'Follow' then
if not args.target then error("target required") end if not args.target then error("target required") end

@ -35,7 +35,8 @@ local function processZoom3rdPerson()
not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or
not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or
input.getBooleanActionValue('TogglePOV') or input.getBooleanActionValue('TogglePOV') or
not I.Camera.isModeControlEnabled() not I.Camera.isModeControlEnabled() or
not I.Camera.isZoomEnabled()
then then
return return
end end

@ -448,7 +448,6 @@ local function resetPlayerGroups()
if not menuGroups[groupKey] then if not menuGroups[groupKey] then
if groupElements[pageKey][groupKey] then if groupElements[pageKey][groupKey] then
groupElements[pageKey][groupKey]:destroy() groupElements[pageKey][groupKey]:destroy()
print(string.format('destroyed group element %s %s', pageKey, groupKey))
groupElements[pageKey][groupKey] = nil groupElements[pageKey][groupKey] = nil
end end
page[groupKey] = nil page[groupKey] = nil

@ -920,10 +920,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>Saves</source> <source>Saves</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>JPG</source> <source>JPG</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@ -1415,10 +1411,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>Can Zoom on Maps</source> <source>Can Zoom on Maps</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Add &quot;Time Played&quot; to Saves</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Notify on Saved Screenshot</source> <source>Notify on Saved Screenshot</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>

@ -1363,14 +1363,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>Saves</source> <source>Saves</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation></translation>
</message>
<message>
<source>Add &quot;Time Played&quot; to Saves</source>
<translation></translation>
</message>
<message> <message>
<source>Maximum Quicksaves</source> <source>Maximum Quicksaves</source>
<translation></translation> <translation></translation>

@ -920,10 +920,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>Saves</source> <source>Saves</source>
<translation>Sauvegardes</translation> <translation>Sauvegardes</translation>
</message> </message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;body&gt;&lt;p&gt;Cette option affiche le temps de jeu de chaque sauvegarde dans leur menu de sélection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message> <message>
<source>JPG</source> <source>JPG</source>
<translation>JPG</translation> <translation>JPG</translation>
@ -1418,10 +1414,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>Can Zoom on Maps</source> <source>Can Zoom on Maps</source>
<translation>Permettre le zoom sur la carte</translation> <translation>Permettre le zoom sur la carte</translation>
</message> </message>
<message>
<source>Add &quot;Time Played&quot; to Saves</source>
<translation>Ajoute le temps de jeu aux sauvegardes</translation>
</message>
<message> <message>
<source>Notify on Saved Screenshot</source> <source>Notify on Saved Screenshot</source>
<translation>Notifier l&apos;enregistrement des captures d&apos;écran</translation> <translation>Notifier l&apos;enregistrement des captures d&apos;écran</translation>

@ -1140,14 +1140,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov
<source>Saves</source> <source>Saves</source>
<translation>Сохранения</translation> <translation>Сохранения</translation>
</message> </message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Эта настройка определяет, будет ли отображаться время с начала новой игры для выбранного сохранения в меню загрузки.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Add &quot;Time Played&quot; to Saves</source>
<translation>Выводить &quot;Время в игре&quot; в сохранениях</translation>
</message>
<message> <message>
<source>JPG</source> <source>JPG</source>
<translation>JPG</translation> <translation>JPG</translation>

@ -933,10 +933,6 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin
<source>Saves</source> <source>Saves</source>
<translation>Sparfiler</translation> <translation>Sparfiler</translation>
</message> </message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Denna inställning avgör huruvida mängden tid spelaren har spenderat i spelet kommer visas för varje sparat spel i Ladda spel-menyn.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message> <message>
<source>JPG</source> <source>JPG</source>
<translation>JPG</translation> <translation>JPG</translation>
@ -1434,10 +1430,6 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin
<source>Can Zoom on Maps</source> <source>Can Zoom on Maps</source>
<translation>Kan zooma på kartor</translation> <translation>Kan zooma på kartor</translation>
</message> </message>
<message>
<source>Add &quot;Time Played&quot; to Saves</source>
<translation>Lägg till spelad tid i sparfiler</translation>
</message>
<message> <message>
<source>Notify on Saved Screenshot</source> <source>Notify on Saved Screenshot</source>
<translation>Ge notis vid sparad skärmdump</translation> <translation>Ge notis vid sparad skärmdump</translation>

@ -349,6 +349,7 @@
--- ---
-- @type ActiveSpellEffect -- @type ActiveSpellEffect
-- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from.
-- @field #string affectedSkill Optional skill ID -- @field #string affectedSkill Optional skill ID
-- @field #string affectedAttribute Optional attribute ID -- @field #string affectedAttribute Optional attribute ID
-- @field #string id Magic effect id -- @field #string id Magic effect id
@ -729,7 +730,6 @@
-- @field #number magnitude current magnitude of the effect. Will be set to 0 when effect is removed or expires. -- @field #number magnitude current magnitude of the effect. Will be set to 0 when effect is removed or expires.
-- @field #number magnitudeBase -- @field #number magnitudeBase
-- @field #number magnitudeModifier -- @field #number magnitudeModifier
-- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from.
--- @{#Sound}: Sounds and Speech --- @{#Sound}: Sounds and Speech
-- @field [parent=#core] #Sound sound -- @field [parent=#core] #Sound sound

@ -37,7 +37,15 @@
-- @field [parent=#ActorControls] #boolean run true - run, false - walk -- @field [parent=#ActorControls] #boolean run true - run, false - walk
-- @field [parent=#ActorControls] #boolean sneak If true - sneak -- @field [parent=#ActorControls] #boolean sneak If true - sneak
-- @field [parent=#ActorControls] #boolean jump If true - initiate a jump -- @field [parent=#ActorControls] #boolean jump If true - initiate a jump
-- @field [parent=#ActorControls] #number use if 1 - activates the readied weapon/spell. For weapons, keeping at 1 will charge the attack until set to 0. -- @field [parent=#ActorControls] #ATTACK_TYPE use Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#ATTACK_TYPE.NoAttack}. If an @{#ATTACK_TYPE} not appropriate for a currently equipped weapon provided - an appropriate @{#ATTACK_TYPE} will be used instead.
---
-- @type ATTACK_TYPE
-- @field #number NoAttack
-- @field #number Any
-- @field #number Chop
-- @field #number Swing
-- @field #number Thrust
--- ---
-- Enables or disables standard AI (enabled by default). -- Enables or disables standard AI (enabled by default).

@ -750,7 +750,7 @@
-- @return #boolean -- @return #boolean
--- ---
-- Get this item's current enchantment charge. -- (DEPRECATED, use itemData(item).enchantmentCharge) Get this item's current enchantment charge.
-- @function [parent=#Item] getEnchantmentCharge -- @function [parent=#Item] getEnchantmentCharge
-- @param openmw.core#GameObject item -- @param openmw.core#GameObject item
-- @return #number The charge remaining. `nil` if the enchantment has never been used, implying the charge is full. Unenchanted items will always return a value of `nil`. -- @return #number The charge remaining. `nil` if the enchantment has never been used, implying the charge is full. Unenchanted items will always return a value of `nil`.
@ -763,7 +763,7 @@
-- @return #boolean -- @return #boolean
--- ---
-- Set this item's enchantment charge. -- (DEPRECATED, use itemData(item).enchantmentCharge) Set this item's enchantment charge.
-- @function [parent=#Item] setEnchantmentCharge -- @function [parent=#Item] setEnchantmentCharge
-- @param openmw.core#GameObject item -- @param openmw.core#GameObject item
-- @param #number charge Can be `nil` to reset the unused state / full -- @param #number charge Can be `nil` to reset the unused state / full
@ -777,14 +777,16 @@
-- @return #boolean -- @return #boolean
--- ---
-- Set of properties that differentiates one item from another of the same record type. -- Set of properties that differentiates one item from another of the same record type; can be used by any script, but only global and self scripts can change values.
-- @function [parent=#Item] itemData -- @function [parent=#Item] itemData
-- @param openmw.core#GameObject item -- @param openmw.core#GameObject item
-- @return #ItemData -- @return #ItemData
--- ---
-- @type ItemData -- @type ItemData
-- @field #number condition The item's current condition. Time remaining for lights. Uses left for lockpicks and probes. Current health for weapons and armor. -- @field #number condition The item's current condition. Time remaining for lights. Uses left for repairs, lockpicks and probes. Current health for weapons and armor.
-- @field #number enchantmentCharge The item's current enchantment charge. Unenchanted items will always return a value of `nil`. Setting this to `nil` will reset the charge of the item.
-- @field #string soul The recordId of the item's current soul. Items without soul will always return a value of `nil`. Setting this to `nil` will remove the soul from the item.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- @{#Creature} functions -- @{#Creature} functions
@ -835,7 +837,7 @@
-- @field #string name -- @field #string name
-- @field #string baseCreature Record id of a base creature, which was modified to create this one -- @field #string baseCreature Record id of a base creature, which was modified to create this one
-- @field #string model VFS path to the creature's model -- @field #string model VFS path to the creature's model
-- @field #string mwscript -- @field #string mwscript MWScript on this creature (can be nil)
-- @field #number soulValue The soul value of the creature record -- @field #number soulValue The soul value of the creature record
-- @field #number type The @{#Creature.TYPE} of the creature -- @field #number type The @{#Creature.TYPE} of the creature
-- @field #number baseGold The base barter gold of the creature -- @field #number baseGold The base barter gold of the creature
@ -1115,7 +1117,7 @@
-- @field #string race -- @field #string race
-- @field #string class Name of the NPC's class (e. g. Acrobat) -- @field #string class Name of the NPC's class (e. g. Acrobat)
-- @field #string model Path to the model associated with this NPC, used for animations. -- @field #string model Path to the model associated with this NPC, used for animations.
-- @field #string mwscript MWScript that is attached to this NPC -- @field #string mwscript MWScript on this NPC (can be nil)
-- @field #string hair Path to the hair body part model -- @field #string hair Path to the hair body part model
-- @field #string head Path to the head body part model -- @field #string head Path to the head body part model
-- @field #number baseGold The base barter gold of the NPC -- @field #number baseGold The base barter gold of the NPC
@ -1325,9 +1327,9 @@
-- @field #string id Record id -- @field #string id Record id
-- @field #string name Human-readable name -- @field #string name Human-readable name
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this armor (can be empty) -- @field #string mwscript MWScript on this armor (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #string enchant The enchantment ID of this armor (can be empty) -- @field #string enchant The enchantment ID of this armor (can be nil)
-- @field #number weight -- @field #number weight
-- @field #number value -- @field #number value
-- @field #number type See @{#Armor.TYPE} -- @field #number type See @{#Armor.TYPE}
@ -1414,9 +1416,9 @@
-- @field #string id The record ID of the book -- @field #string id The record ID of the book
-- @field #string name Name of the book -- @field #string name Name of the book
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this book (can be empty) -- @field #string mwscript MWScript on this book (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #string enchant The enchantment ID of this book (can be empty) -- @field #string enchant The enchantment ID of this book (can be nil)
-- @field #string text The text content of the book -- @field #string text The text content of the book
-- @field #number weight -- @field #number weight
-- @field #number value -- @field #number value
@ -1492,9 +1494,9 @@
-- @field #string id Record id -- @field #string id Record id
-- @field #string name Name of the clothing -- @field #string name Name of the clothing
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this clothing (can be empty) -- @field #string mwscript MWScript on this clothing (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #string enchant The enchantment ID of this clothing (can be empty) -- @field #string enchant The enchantment ID of this clothing (can be nil)
-- @field #number weight -- @field #number weight
-- @field #number value -- @field #number value
-- @field #number type See @{#Clothing.TYPE} -- @field #number type See @{#Clothing.TYPE}
@ -1535,7 +1537,7 @@
-- @field #string id Record id -- @field #string id Record id
-- @field #string name Human-readable name -- @field #string name Human-readable name
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this potion (can be empty) -- @field #string mwscript MWScript on this potion (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #number weight -- @field #number weight
-- @field #number value -- @field #number value
@ -1641,7 +1643,7 @@
-- @field #string id Record id -- @field #string id Record id
-- @field #string name Human-readable name -- @field #string name Human-readable name
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this light (can be empty) -- @field #string mwscript MWScript on this light (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #string sound VFS path to the sound -- @field #string sound VFS path to the sound
-- @field #number weight -- @field #number weight
@ -1689,7 +1691,7 @@
-- @return #MiscellaneousRecord -- @return #MiscellaneousRecord
--- ---
-- Returns the read-only soul of a miscellaneous item -- (DEPRECATED, use itemData(item).soul) Returns the read-only soul of a miscellaneous item
-- @function [parent=#Miscellaneous] getSoul -- @function [parent=#Miscellaneous] getSoul
-- @param openmw.core#GameObject object -- @param openmw.core#GameObject object
-- @return #string -- @return #string
@ -1702,7 +1704,7 @@
-- @return #MiscellaneousRecord A strongly typed Miscellaneous record. -- @return #MiscellaneousRecord A strongly typed Miscellaneous record.
--- ---
-- Sets the soul of a miscellaneous item, intended for soul gem objects; Must be used in a global script. -- (DEPRECATED, use itemData(item).soul) Sets the soul of a miscellaneous item, intended for soul gem objects; Must be used in a global script.
-- @function [parent=#Miscellaneous] setSoul -- @function [parent=#Miscellaneous] setSoul
-- @param openmw.core#GameObject object -- @param openmw.core#GameObject object
-- @param #string soulId Record ID for the soul of the creature to use -- @param #string soulId Record ID for the soul of the creature to use
@ -1712,7 +1714,7 @@
-- @field #string id The record ID of the miscellaneous item -- @field #string id The record ID of the miscellaneous item
-- @field #string name The name of the miscellaneous item -- @field #string name The name of the miscellaneous item
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this miscellaneous item (can be empty) -- @field #string mwscript MWScript on this miscellaneous item (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #number weight -- @field #number weight
-- @field #number value -- @field #number value
@ -1757,7 +1759,7 @@
-- @field #string id Record id -- @field #string id Record id
-- @field #string name Human-readable name -- @field #string name Human-readable name
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this potion (can be empty) -- @field #string mwscript MWScript on this potion (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #number weight -- @field #number weight
-- @field #number value -- @field #number value
@ -1817,9 +1819,9 @@
-- @field #string id Record id -- @field #string id Record id
-- @field #string name Human-readable name -- @field #string name Human-readable name
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this weapon (can be empty) -- @field #string mwscript MWScript on this weapon (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #string enchant -- @field #string enchant The enchantment ID of this weapon (can be nil)
-- @field #boolean isMagical -- @field #boolean isMagical
-- @field #boolean isSilver -- @field #boolean isSilver
-- @field #number weight -- @field #number weight
@ -1886,7 +1888,7 @@
-- @field #string id The record ID of the apparatus -- @field #string id The record ID of the apparatus
-- @field #string name The name of the apparatus -- @field #string name The name of the apparatus
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this apparatus (can be empty) -- @field #string mwscript MWScript on this apparatus (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #number type The type of apparatus. See @{#Apparatus.TYPE} -- @field #number type The type of apparatus. See @{#Apparatus.TYPE}
-- @field #number weight -- @field #number weight
@ -1925,7 +1927,7 @@
-- @field #string id The record ID of the lockpick -- @field #string id The record ID of the lockpick
-- @field #string name The name of the lockpick -- @field #string name The name of the lockpick
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this lockpick (can be empty) -- @field #string mwscript MWScript on this lockpick (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #number maxCondition The maximum number of uses of this lockpick -- @field #number maxCondition The maximum number of uses of this lockpick
-- @field #number weight -- @field #number weight
@ -1964,7 +1966,7 @@
-- @field #string id The record ID of the probe -- @field #string id The record ID of the probe
-- @field #string name The name of the probe -- @field #string name The name of the probe
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this probe (can be empty) -- @field #string mwscript MWScript on this probe (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #number maxCondition The maximum number of uses of this probe -- @field #number maxCondition The maximum number of uses of this probe
-- @field #number weight -- @field #number weight
@ -2003,7 +2005,7 @@
-- @field #string id The record ID of the repair tool -- @field #string id The record ID of the repair tool
-- @field #string name The name of the repair tool -- @field #string name The name of the repair tool
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this repair tool (can be empty) -- @field #string mwscript MWScript on this repair tool (can be nil)
-- @field #string icon VFS path to the icon -- @field #string icon VFS path to the icon
-- @field #number maxCondition The maximum number of uses of this repair tool -- @field #number maxCondition The maximum number of uses of this repair tool
-- @field #number weight -- @field #number weight
@ -2040,7 +2042,7 @@
-- @field #string id Record id -- @field #string id Record id
-- @field #string name Human-readable name -- @field #string name Human-readable name
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this activator (can be empty) -- @field #string mwscript MWScript on this activator (can be nil)
--- ---
-- Creates a @{#ActivatorRecord} without adding it to the world database. -- Creates a @{#ActivatorRecord} without adding it to the world database.
@ -2107,7 +2109,7 @@
-- @field #string id Record id -- @field #string id Record id
-- @field #string name Human-readable name -- @field #string name Human-readable name
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this container (can be empty) -- @field #string mwscript MWScript on this container (can be nil)
-- @field #number weight capacity of this container -- @field #number weight capacity of this container
-- @field #boolean isOrganic Whether items can be placed in the container -- @field #boolean isOrganic Whether items can be placed in the container
-- @field #boolean isRespawning Whether the container respawns its contents -- @field #boolean isRespawning Whether the container respawns its contents
@ -2169,10 +2171,9 @@
-- @field #string id Record id -- @field #string id Record id
-- @field #string name Human-readable name -- @field #string name Human-readable name
-- @field #string model VFS path to the model -- @field #string model VFS path to the model
-- @field #string mwscript MWScript on this door (can be empty) -- @field #string mwscript MWScript on this door (can be nil)
-- @field #string openSound VFS path to the sound of opening -- @field #string openSound The sound id for door opening
-- @field #string closeSound VFS path to the sound of closing -- @field #string closeSound The sound id for door closing
--- Functions for @{#Static} objects --- Functions for @{#Static} objects

@ -8,8 +8,8 @@
<string>English</string> <string>English</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>openmw-launcher</string> <string>openmw-launcher</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>org.openmw.openmw</string> <string>org.openmw.openmw</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleLongVersionString</key> <key>CFBundleLongVersionString</key>
@ -20,14 +20,16 @@
<string>APPL</string> <string>APPL</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>${OPENMW_VERSION}</string> <string>${OPENMW_VERSION}</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>${OPENMW_VERSION}</string> <string>${OPENMW_VERSION}</string>
<key>CSResourcesFileMapped</key> <key>CSResourcesFileMapped</key>
<true/> <true/>
<key>LSRequiresCarbon</key> <key>LSRequiresCarbon</key>
<true/> <true/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.role-playing-games</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string></string> <string></string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>

@ -566,9 +566,6 @@ character =
# Automatically save the game whenever the player rests. # Automatically save the game whenever the player rests.
autosave = true autosave = true
# Display the time played on each save file in the load menu.
timeplayed = false
# The maximum number of quick (or auto) save slots to have. # The maximum number of quick (or auto) save slots to have.
# If all slots are used, the oldest save is reused # If all slots are used, the oldest save is reused
max quicksaves = 1 max quicksaves = 1

@ -13,18 +13,88 @@ types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Magic, false)
types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.VanityMode, false) types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.VanityMode, false)
types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.ViewMode, false) types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.ViewMode, false)
testing.registerLocalTest('playerRotation', local function rotate(object, targetPitch, targetYaw)
function() local endTime = core.getSimulationTime() + 1
local endTime = core.getSimulationTime() + 1 while core.getSimulationTime() < endTime do
while core.getSimulationTime() < endTime do object.controls.jump = false
self.controls.jump = false object.controls.run = true
self.controls.run = true object.controls.movement = 0
self.controls.movement = 0 object.controls.sideMovement = 0
self.controls.sideMovement = 0 if targetPitch ~= nil then
self.controls.yawChange = util.normalizeAngle(math.rad(90) - self.rotation:getYaw()) * 0.5 object.controls.pitchChange = util.normalizeAngle(targetPitch - object.rotation:getPitch()) * 0.5
coroutine.yield() end
if targetYaw ~= nil then
object.controls.yawChange = util.normalizeAngle(targetYaw - object.rotation:getYaw()) * 0.5
end end
testing.expectEqualWithDelta(self.rotation:getYaw(), math.rad(90), 0.05, 'Incorrect rotation') coroutine.yield()
end
end
local function rotateByYaw(object, target)
rotate(object, nil, target)
end
local function rotateByPitch(object, target)
rotate(object, target, nil)
end
testing.registerLocalTest('playerYawRotation',
function()
local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ()
local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX()
local targetYaw = math.rad(90)
rotateByYaw(self, targetYaw)
testing.expectEqualWithDelta(self.rotation:getYaw(), targetYaw, 0.05, 'Incorrect yaw rotation')
local alpha1, gamma1 = self.rotation:getAnglesXZ()
testing.expectEqualWithDelta(alpha1, initialAlphaXZ, 0.05, 'Alpha rotation in XZ convention should not change')
testing.expectEqualWithDelta(gamma1, targetYaw, 0.05, 'Incorrect gamma rotation in XZ convention')
local alpha2, beta2, gamma2 = self.rotation:getAnglesZYX()
testing.expectEqualWithDelta(alpha2, targetYaw, 0.05, 'Incorrect alpha rotation in ZYX convention')
testing.expectEqualWithDelta(beta2, initialBetaZYX, 0.05, 'Beta rotation in ZYX convention should not change')
testing.expectEqualWithDelta(gamma2, initialGammaZYX, 0.05, 'Gamma rotation in ZYX convention should not change')
end)
testing.registerLocalTest('playerPitchRotation',
function()
local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ()
local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX()
local targetPitch = math.rad(90)
rotateByPitch(self, targetPitch)
testing.expectEqualWithDelta(self.rotation:getPitch(), targetPitch, 0.05, 'Incorrect pitch rotation')
local alpha1, gamma1 = self.rotation:getAnglesXZ()
testing.expectEqualWithDelta(alpha1, targetPitch, 0.05, 'Incorrect alpha rotation in XZ convention')
testing.expectEqualWithDelta(gamma1, initialGammaXZ, 0.05, 'Gamma rotation in XZ convention should not change')
local alpha2, beta2, gamma2 = self.rotation:getAnglesZYX()
testing.expectEqualWithDelta(alpha2, initialAlphaZYX, 0.05, 'Alpha rotation in ZYX convention should not change')
testing.expectEqualWithDelta(beta2, initialBetaZYX, 0.05, 'Beta rotation in ZYX convention should not change')
testing.expectEqualWithDelta(gamma2, targetPitch, 0.05, 'Incorrect gamma rotation in ZYX convention')
end)
testing.registerLocalTest('playerPitchAndYawRotation',
function()
local targetPitch = math.rad(-30)
local targetYaw = math.rad(-60)
rotate(self, targetPitch, targetYaw)
testing.expectEqualWithDelta(self.rotation:getPitch(), targetPitch, 0.05, 'Incorrect pitch rotation')
testing.expectEqualWithDelta(self.rotation:getYaw(), targetYaw, 0.05, 'Incorrect yaw rotation')
local alpha1, gamma1 = self.rotation:getAnglesXZ()
testing.expectEqualWithDelta(alpha1, targetPitch, 0.05, 'Incorrect alpha rotation in XZ convention')
testing.expectEqualWithDelta(gamma1, targetYaw, 0.05, 'Incorrect gamma rotation in XZ convention')
local alpha2, beta2, gamma2 = self.rotation:getAnglesZYX()
testing.expectEqualWithDelta(alpha2, math.rad(-56), 0.05, 'Incorrect alpha rotation in ZYX convention')
testing.expectEqualWithDelta(beta2, math.rad(-25), 0.05, 'Incorrect beta rotation in ZYX convention')
testing.expectEqualWithDelta(gamma2, math.rad(-16), 0.05, 'Incorrect gamma rotation in ZYX convention')
end) end)
testing.registerLocalTest('playerForwardRunning', testing.registerLocalTest('playerForwardRunning',

@ -44,7 +44,17 @@ local function testTeleport()
testing.expectEqualWithDelta(player.position.x, 100, 1, 'incorrect position after teleporting') testing.expectEqualWithDelta(player.position.x, 100, 1, 'incorrect position after teleporting')
testing.expectEqualWithDelta(player.position.y, 50, 1, 'incorrect position after teleporting') testing.expectEqualWithDelta(player.position.y, 50, 1, 'incorrect position after teleporting')
testing.expectEqualWithDelta(player.position.z, 500, 1, 'incorrect position after teleporting') testing.expectEqualWithDelta(player.position.z, 500, 1, 'incorrect position after teleporting')
testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(90), 0.05, 'incorrect rotation after teleporting') testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(90), 0.05, 'incorrect yaw rotation after teleporting')
testing.expectEqualWithDelta(player.rotation:getPitch(), math.rad(0), 0.05, 'incorrect pitch rotation after teleporting')
local rotationX1, rotationZ1 = player.rotation:getAnglesXZ()
testing.expectEqualWithDelta(rotationX1, math.rad(0), 0.05, 'incorrect x rotation from getAnglesXZ after teleporting')
testing.expectEqualWithDelta(rotationZ1, math.rad(90), 0.05, 'incorrect z rotation from getAnglesXZ after teleporting')
local rotationZ2, rotationY2, rotationX2 = player.rotation:getAnglesZYX()
testing.expectEqualWithDelta(rotationZ2, math.rad(90), 0.05, 'incorrect z rotation from getAnglesZYX after teleporting')
testing.expectEqualWithDelta(rotationY2, math.rad(0), 0.05, 'incorrect y rotation from getAnglesZYX after teleporting')
testing.expectEqualWithDelta(rotationX2, math.rad(0), 0.05, 'incorrect x rotation from getAnglesZYX after teleporting')
player:teleport('', player.position, {rotation=util.transform.rotateZ(math.rad(-90)), onGround=true}) player:teleport('', player.position, {rotation=util.transform.rotateZ(math.rad(-90)), onGround=true})
coroutine.yield() coroutine.yield()
@ -193,9 +203,17 @@ end
tests = { tests = {
{'timers', testTimers}, {'timers', testTimers},
{'playerRotation', function() {'rotating player with controls.yawChange should change rotation', function()
initPlayer()
testing.runLocalTest(player, 'playerYawRotation')
end},
{'rotating player with controls.pitchChange should change rotation', function()
initPlayer()
testing.runLocalTest(player, 'playerPitchRotation')
end},
{'rotating player with controls.pitchChange and controls.yawChange should change rotation', function()
initPlayer() initPlayer()
testing.runLocalTest(player, 'playerRotation') testing.runLocalTest(player, 'playerPitchAndYawRotation')
end}, end},
{'playerForwardRunning', function() {'playerForwardRunning', function()
initPlayer() initPlayer()

Loading…
Cancel
Save