1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-12-05 19:04:34 +00:00

Lua: Return nil instead of empty strings for optional RefId fields

Implement sol_lua_push for ESM::RefId to automatically convert empty
RefIds to nil in Lua. This fixes cell.region and cell.worldSpaceId
returning empty strings, and applies the same pattern consistently
across all Lua bindings.

Removes LuaUtil::serializeRefId as it's no longer needed.

Fixes #8718
This commit is contained in:
Andrzej Głuszak 2025-10-16 02:05:26 +02:00 committed by Alexei Kotov
parent b516770a04
commit c4b28a39c3
22 changed files with 62 additions and 90 deletions

View file

@ -92,12 +92,11 @@ namespace MWLua
const auto& storage = MWBase::Environment::get().getWindowManager()->getTranslationDataStorage();
return storage.translateCellName(c.mStore->getCell()->getNameId());
});
cellT["id"]
= sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getId().serializeText(); });
cellT["region"] = sol::readonly_property(
[](const CellT& c) -> std::string { return c.mStore->getCell()->getRegion().serializeText(); });
cellT["worldSpaceId"] = sol::readonly_property(
[](const CellT& c) -> std::string { return c.mStore->getCell()->getWorldSpace().serializeText(); });
cellT["id"] = sol::readonly_property([](const CellT& c) -> ESM::RefId { return c.mStore->getCell()->getId(); });
cellT["region"]
= sol::readonly_property([](const CellT& c) -> ESM::RefId { return c.mStore->getCell()->getRegion(); });
cellT["worldSpaceId"]
= sol::readonly_property([](const CellT& c) -> ESM::RefId { return c.mStore->getCell()->getWorldSpace(); });
cellT["gridX"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridX(); });
cellT["gridY"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->getGridY(); });
cellT["hasWater"] = sol::readonly_property([](const CellT& c) { return c.mStore->getCell()->hasWater(); });

View file

@ -46,7 +46,7 @@ namespace MWLua
regionT["mapColor"] = sol::readonly_property(
[](const ESM::Region& rec) -> Misc::Color { return Misc::Color::fromRGB(rec.mMapColor); });
regionT["sleepList"]
= sol::readonly_property([](const ESM::Region& rec) { return LuaUtil::serializeRefId(rec.mSleepList); });
= sol::readonly_property([](const ESM::Region& rec) -> ESM::RefId { return rec.mSleepList; });
regionT["weatherProbabilities"] = sol::readonly_property([lua = lua.lua_state()](const ESM::Region& rec) {
sol::table res(lua, sol::create);

View file

@ -52,8 +52,7 @@ namespace MWLua
= sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mId.serializeText(); });
record["name"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mName; });
addModelProperty(record);
record["mwscript"] = sol::readonly_property([](const ESM::Activator& rec) -> sol::optional<std::string> {
return LuaUtil::serializeRefId(rec.mScript);
});
record["mwscript"]
= sol::readonly_property([](const ESM::Activator& rec) -> ESM::RefId { return rec.mScript; });
}
}

View file

@ -42,9 +42,8 @@ namespace MWLua
= sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mId.serializeText(); });
record["name"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mName; });
addModelProperty(record);
record["mwscript"] = sol::readonly_property([](const ESM::Apparatus& rec) -> sol::optional<std::string> {
return LuaUtil::serializeRefId(rec.mScript);
});
record["mwscript"]
= sol::readonly_property([](const ESM::Apparatus& rec) -> ESM::RefId { return rec.mScript; });
record["icon"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});

View file

@ -102,10 +102,8 @@ namespace MWLua
record["icon"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});
record["enchant"] = sol::readonly_property(
[](const ESM::Armor& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mEnchant); });
record["mwscript"] = sol::readonly_property(
[](const ESM::Armor& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["enchant"] = sol::readonly_property([](const ESM::Armor& rec) -> ESM::RefId { return rec.mEnchant; });
record["mwscript"] = sol::readonly_property([](const ESM::Armor& rec) -> ESM::RefId { return rec.mScript; });
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["type"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mType; });

View file

@ -106,21 +106,18 @@ namespace MWLua
= sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mId.serializeText(); });
record["name"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mName; });
addModelProperty(record);
record["mwscript"] = sol::readonly_property(
[](const ESM::Book& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["mwscript"] = sol::readonly_property([](const ESM::Book& rec) -> ESM::RefId { return rec.mScript; });
record["icon"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});
record["text"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mText; });
record["enchant"] = sol::readonly_property(
[](const ESM::Book& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mEnchant); });
record["enchant"] = sol::readonly_property([](const ESM::Book& rec) -> ESM::RefId { return rec.mEnchant; });
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["weight"] = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mWeight; });
record["enchantCapacity"]
= 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> {
return LuaUtil::serializeRefId(ESM::Skill::indexToRefId(rec.mData.mSkillId));
});
record["skill"] = sol::readonly_property(
[](const ESM::Book& rec) -> ESM::RefId { return ESM::Skill::indexToRefId(rec.mData.mSkillId); });
}
}

View file

@ -97,12 +97,8 @@ namespace MWLua
record["icon"] = sol::readonly_property([vfs](const ESM::Clothing& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});
record["enchant"] = sol::readonly_property([](const ESM::Clothing& rec) -> sol::optional<std::string> {
return LuaUtil::serializeRefId(rec.mEnchant);
});
record["mwscript"] = sol::readonly_property([](const ESM::Clothing& rec) -> sol::optional<std::string> {
return LuaUtil::serializeRefId(rec.mScript);
});
record["enchant"] = sol::readonly_property([](const ESM::Clothing& rec) -> ESM::RefId { return rec.mEnchant; });
record["mwscript"] = sol::readonly_property([](const ESM::Clothing& rec) -> ESM::RefId { return rec.mScript; });
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["type"] = sol::readonly_property([](const ESM::Clothing& rec) -> int { return rec.mData.mType; });

View file

@ -52,9 +52,8 @@ namespace MWLua
= sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mId.serializeText(); });
record["name"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mName; });
addModelProperty(record);
record["mwscript"] = sol::readonly_property([](const ESM::Container& rec) -> sol::optional<std::string> {
return LuaUtil::serializeRefId(rec.mScript);
});
record["mwscript"]
= sol::readonly_property([](const ESM::Container& rec) -> ESM::RefId { return rec.mScript; });
record["weight"] = sol::readonly_property([](const ESM::Container& rec) -> float { return rec.mWeight; });
record["isOrganic"] = sol::readonly_property(
[](const ESM::Container& rec) -> bool { return rec.mFlags & ESM::Container::Organic; });

View file

@ -40,9 +40,7 @@ namespace MWLua
= sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mId.serializeText(); });
record["name"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mName; });
addModelProperty(record);
record["mwscript"] = sol::readonly_property([](const ESM::Creature& rec) -> sol::optional<std::string> {
return LuaUtil::serializeRefId(rec.mScript);
});
record["mwscript"] = sol::readonly_property([](const ESM::Creature& rec) -> ESM::RefId { return rec.mScript; });
record["baseCreature"] = sol::readonly_property(
[](const ESM::Creature& rec) -> std::string { return rec.mOriginal.serializeText(); });
record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; });

View file

@ -30,7 +30,6 @@ namespace sol
namespace MWLua
{
static const MWWorld::Ptr& doorPtr(const Object& o)
{
return verifyType(ESM::REC_DOOR, o.ptr());
@ -110,8 +109,7 @@ namespace MWLua
= sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mId.serializeText(); });
record["name"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mName; });
addModelProperty(record);
record["mwscript"] = sol::readonly_property(
[](const ESM::Door& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["mwscript"] = sol::readonly_property([](const ESM::Door& rec) -> ESM::RefId { return rec.mScript; });
record["openSound"] = sol::readonly_property(
[](const ESM::Door& rec) -> std::string { return rec.mOpenSound.serializeText(); });
record["closeSound"] = sol::readonly_property(

View file

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

View file

@ -105,8 +105,7 @@ namespace MWLua
});
record["sound"]
= sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mSound.serializeText(); });
record["mwscript"] = sol::readonly_property(
[](const ESM::Light& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["mwscript"] = sol::readonly_property([](const ESM::Light& rec) -> ESM::RefId { return rec.mScript; });
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["duration"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mTime; });

View file

@ -33,9 +33,7 @@ namespace MWLua
= sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mId.serializeText(); });
record["name"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mName; });
addModelProperty(record);
record["mwscript"] = sol::readonly_property([](const ESM::Lockpick& rec) -> sol::optional<std::string> {
return LuaUtil::serializeRefId(rec.mScript);
});
record["mwscript"] = sol::readonly_property([](const ESM::Lockpick& rec) -> ESM::RefId { return rec.mScript; });
record["icon"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});

View file

@ -71,10 +71,8 @@ namespace MWLua
object.ptr().getCellRef().setSoul(creature);
};
miscellaneous["getSoul"] = [](const Object& object) -> sol::optional<std::string> {
ESM::RefId soul = object.ptr().getCellRef().getSoul();
return LuaUtil::serializeRefId(soul);
};
miscellaneous["getSoul"]
= [](const Object& object) -> ESM::RefId { return object.ptr().getCellRef().getSoul(); };
miscellaneous["soul"] = miscellaneous["getSoul"]; // for compatibility; should be removed later
sol::usertype<ESM::Miscellaneous> record = context.sol().new_usertype<ESM::Miscellaneous>("ESM3_Miscellaneous");
@ -84,9 +82,8 @@ namespace MWLua
[](const ESM::Miscellaneous& rec) -> std::string { return rec.mId.serializeText(); });
record["name"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { return rec.mName; });
addModelProperty(record);
record["mwscript"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> sol::optional<std::string> {
return LuaUtil::serializeRefId(rec.mScript);
});
record["mwscript"]
= sol::readonly_property([](const ESM::Miscellaneous& rec) -> ESM::RefId { return rec.mScript; });
record["icon"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});

View file

@ -208,16 +208,15 @@ namespace MWLua
= sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mRace.serializeText(); });
record["class"]
= sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mClass.serializeText(); });
record["mwscript"] = sol::readonly_property(
[](const ESM::NPC& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["mwscript"] = sol::readonly_property([](const ESM::NPC& rec) -> ESM::RefId { return rec.mScript; });
record["hair"]
= sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair.serializeText(); });
record["baseDisposition"]
= sol::readonly_property([](const ESM::NPC& rec) -> int { return (int)rec.mNpdt.mDisposition; });
record["head"]
= sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.serializeText(); });
record["primaryFaction"] = sol::readonly_property(
[](const ESM::NPC& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mFaction); });
record["primaryFaction"]
= sol::readonly_property([](const ESM::NPC& rec) -> ESM::RefId { return rec.mFaction; });
record["primaryFactionRank"] = sol::readonly_property([](const ESM::NPC& rec, sol::this_state s) -> int {
if (rec.mFaction.empty())
return 0;

View file

@ -82,8 +82,7 @@ namespace MWLua
record["icon"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});
record["mwscript"] = sol::readonly_property(
[](const ESM::Potion& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["mwscript"] = sol::readonly_property([](const ESM::Potion& rec) -> ESM::RefId { return rec.mScript; });
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["effects"] = sol::readonly_property([lua = lua.lua_state()](const ESM::Potion& rec) -> sol::table {

View file

@ -33,8 +33,7 @@ namespace MWLua
= sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mId.serializeText(); });
record["name"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mName; });
addModelProperty(record);
record["mwscript"] = sol::readonly_property(
[](const ESM::Probe& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["mwscript"] = sol::readonly_property([](const ESM::Probe& rec) -> ESM::RefId { return rec.mScript; });
record["icon"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});

View file

@ -33,8 +33,7 @@ namespace MWLua
= sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mId.serializeText(); });
record["name"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mName; });
addModelProperty(record);
record["mwscript"] = sol::readonly_property(
[](const ESM::Repair& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["mwscript"] = sol::readonly_property([](const ESM::Repair& rec) -> ESM::RefId { return rec.mScript; });
record["icon"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});

View file

@ -135,10 +135,8 @@ namespace MWLua
record["icon"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string {
return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs);
});
record["enchant"] = sol::readonly_property(
[](const ESM::Weapon& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mEnchant); });
record["mwscript"] = sol::readonly_property(
[](const ESM::Weapon& rec) -> sol::optional<std::string> { return LuaUtil::serializeRefId(rec.mScript); });
record["enchant"] = sol::readonly_property([](const ESM::Weapon& rec) -> ESM::RefId { return rec.mEnchant; });
record["mwscript"] = sol::readonly_property([](const ESM::Weapon& rec) -> ESM::RefId { return rec.mScript; });
record["isMagical"] = sol::readonly_property(
[](const ESM::Weapon& rec) -> bool { return rec.mData.mFlags & ESM::Weapon::Magical; });
record["isSilver"] = sol::readonly_property(

View file

@ -199,7 +199,7 @@ namespace MWLua
weatherT["rainMinHeight"] = sol::property([](const MWWorld::Weather& w) { return w.mRainMinHeight; },
[](MWWorld::Weather& w, const FiniteFloat rainMinHeight) { w.mRainMinHeight = rainMinHeight; });
weatherT["rainLoopSoundID"]
= sol::property([](const MWWorld::Weather& w) { return LuaUtil::serializeRefId(w.mRainLoopSoundID); },
= sol::property([](const MWWorld::Weather& w) -> ESM::RefId { return w.mRainLoopSoundID; },
[](MWWorld::Weather& w, sol::optional<std::string_view> rainLoopSoundID) {
w.mRainLoopSoundID = ESM::RefId::deserializeText(rainLoopSoundID.value_or(""));
});
@ -209,7 +209,7 @@ namespace MWLua
w.mSunDiscSunsetColor = sunDiscSunsetColor.toVec();
});
weatherT["ambientLoopSoundID"]
= sol::property([](const MWWorld::Weather& w) { return LuaUtil::serializeRefId(w.mAmbientLoopSoundID); },
= sol::property([](const MWWorld::Weather& w) -> ESM::RefId { return w.mAmbientLoopSoundID; },
[](MWWorld::Weather& w, sol::optional<std::string_view> ambientLoopSoundId) {
w.mAmbientLoopSoundID = ESM::RefId::deserializeText(ambientLoopSoundId.value_or(""));
});

View file

@ -20,13 +20,15 @@ namespace LuaUtil
{
return i + 1;
}
}
inline sol::optional<std::string> serializeRefId(ESM::RefId id)
{
// ADL-based customization point for sol2 to automatically convert ESM::RefId
// Empty RefIds are converted to nil, non-empty ones are serialized to strings
inline int sol_lua_push(sol::types<ESM::RefId>, lua_State* L, const ESM::RefId& id)
{
if (id.empty())
return sol::nullopt;
return id.serializeText();
}
return sol::stack::push(L, sol::lua_nil);
return sol::stack::push(L, id.serializeText());
}
#endif

View file

@ -299,12 +299,12 @@
-- @field #string name Name of the cell (can be empty string).
-- @field #string displayName Human-readable cell name (takes into account *.cel file localizations). Can be empty string.
-- @field #string id Unique record ID of the cell, based on cell name for interiors and the worldspace for exteriors, or the formID of the cell for ESM4 cells.
-- @field #string region Region of the cell.
-- @field #string region Region of the cell (can be nil).
-- @field #boolean isExterior Whether the cell is an exterior cell. "Exterior" means grid of cells where the player can seamless walk from one cell to another without teleports. QuasiExterior (interior with sky) is not an exterior.
-- @field #boolean isQuasiExterior (DEPRECATED, use `hasTag("QuasiExterior")`) Whether the cell is a quasi exterior (like interior but with the sky and the weather).
-- @field #number gridX Index of the cell by X (only for exteriors).
-- @field #number gridY Index of the cell by Y (only for exteriors).
-- @field #string worldSpaceId Id of the world space.
-- @field #string worldSpaceId Id of the world space (can be nil).
-- @field #boolean hasWater True if the cell contains water.
-- @field #number waterLevel The water level of the cell. (nil if cell has no water).
-- @field #boolean hasSky True if in this cell sky should be rendered.