From ae42260b91f038d70d3559c1fa5079c1aa5cd9ef Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Thu, 21 Aug 2025 16:47:56 -0700 Subject: [PATCH] Add record creation for container, creature --- apps/openmw/mwlua/types/container.cpp | 48 ++++++++++ apps/openmw/mwlua/types/creature.cpp | 133 ++++++++++++++++++++++++++ apps/openmw/mwlua/worldbindings.cpp | 18 ++++ files/lua_api/openmw/types.lua | 24 +++++ files/lua_api/openmw/world.lua | 5 +- 5 files changed, 227 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/container.cpp b/apps/openmw/mwlua/types/container.cpp index 9d3821ca48..5b60fab16e 100644 --- a/apps/openmw/mwlua/types/container.cpp +++ b/apps/openmw/mwlua/types/container.cpp @@ -18,6 +18,53 @@ namespace sol }; } +namespace +{ + ESM::Container tableToContainer(const sol::table& rec) + { + ESM::Container cont; + + // Start from template if provided + if (rec["template"] != sol::nil) + cont = LuaUtil::cast(rec["template"]); + else + cont.blank(); + + cont.mId = {}; + + // Basic fields + if (rec["name"] != sol::nil) + cont.mName = rec["name"]; + if (rec["model"] != sol::nil) + cont.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + if (rec["mwscript"] != sol::nil) + cont.mScript = ESM::RefId::deserializeText(rec["mwscript"].get()); + if (rec["weight"] != sol::nil) + cont.mWeight = rec["weight"].get(); + + // Flags + if (rec["isOrganic"] != sol::nil) + { + bool isOrganic = rec["isOrganic"]; + if (isOrganic) + cont.mFlags |= ESM::Container::Organic; + else + cont.mFlags &= ~ESM::Container::Organic; + } + + if (rec["isRespawning"] != sol::nil) + { + bool isRespawning = rec["isRespawning"]; + if (isRespawning) + cont.mFlags |= ESM::Container::Respawn; + else + cont.mFlags &= ~ESM::Container::Respawn; + } + + return cont; + } +} + namespace MWLua { @@ -35,6 +82,7 @@ namespace MWLua const MWWorld::Ptr& ptr = containerPtr(obj); return ptr.getClass().getEncumbrance(ptr); }; + container["createRecordDraft"] = tableToContainer; container["encumbrance"] = container["getEncumbrance"]; // for compatibility; should be removed later container["getCapacity"] = [](const Object& obj) -> float { const MWWorld::Ptr& ptr = containerPtr(obj); diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index a9a1be9eee..b5ef157c16 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -10,6 +10,138 @@ #include #include +namespace +{ + ESM::Creature tableToCreature(const sol::table& rec) + { + ESM::Creature crea; + + // Start from template if provided + if (rec["template"] != sol::nil) + crea = LuaUtil::cast(rec["template"]); + else + crea.blank(); + + crea.mId = {}; + + // Basic fields + if (rec["name"] != sol::nil) + crea.mName = rec["name"]; + if (rec["model"] != sol::nil) + crea.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + if (rec["mwscript"] != sol::nil) + crea.mScript = ESM::RefId::deserializeText(rec["mwscript"].get()); + if (rec["baseCreature"] != sol::nil) + crea.mOriginal = ESM::RefId::deserializeText(rec["baseCreature"].get()); + + if (rec["soulValue"] != sol::nil) + crea.mData.mSoul = rec["soulValue"].get(); + if (rec["type"] != sol::nil) + crea.mData.mType + = rec["type"].get(); + if (rec["baseGold"] != sol::nil) + crea.mData.mGold = rec["baseGold"].get(); + if (rec["combatSkill"] != sol::nil) + crea.mData.mCombat = rec["combatSkill"].get(); + if (rec["magicSkill"] != sol::nil) + crea.mData.mMagic = rec["magicSkill"].get(); + if (rec["stealthSkill"] != sol::nil) + crea.mData.mStealth = rec["stealthSkill"].get(); + + if (rec["attack"] != sol::nil) + { + const sol::table atk = rec["attack"]; + for (int i = 0; i < 3; ++i) + { + sol::object v = atk[i + 1]; + if (v != sol::nil) + crea.mData.mAttack[i] = v.as(); + } + } + + if (rec["canFly"] != sol::nil) + { + bool canFly = rec["canFly"]; + if (canFly) + crea.mFlags |= ESM::Creature::Flies; + else + crea.mFlags &= ~ESM::Creature::Flies; + } + + if (rec["canSwim"] != sol::nil) + { + bool canSwim = rec["canSwim"]; + if (canSwim) + crea.mFlags |= ESM::Creature::Swims; + else + crea.mFlags &= ~ESM::Creature::Swims; + } + + if (rec["canUseWeapons"] != sol::nil) + { + bool canUseWeapons = rec["canUseWeapons"]; + if (canUseWeapons) + crea.mFlags |= ESM::Creature::Weapon; + else + crea.mFlags &= ~ESM::Creature::Weapon; + } + + if (rec["canWalk"] != sol::nil) + { + bool canWalk = rec["canWalk"]; + if (canWalk) + crea.mFlags |= ESM::Creature::Walks; + else + crea.mFlags &= ~ESM::Creature::Walks; + } + + if (rec["isBiped"] != sol::nil) + { + bool isBiped = rec["isBiped"]; + if (isBiped) + crea.mFlags |= ESM::Creature::Bipedal; + else + crea.mFlags &= ~ESM::Creature::Bipedal; + } + + if (rec["isEssential"] != sol::nil) + { + bool isEssential = rec["isEssential"]; + if (isEssential) + crea.mFlags |= ESM::Creature::Essential; + else + crea.mFlags &= ~ESM::Creature::Essential; + } + + if (rec["isRespawning"] != sol::nil) + { + bool isRespawning = rec["isRespawning"]; + if (isRespawning) + crea.mFlags |= ESM::Creature::Respawn; + else + crea.mFlags &= ~ESM::Creature::Respawn; + } + + if (rec["bloodType"] != sol::nil) + crea.mBloodType = rec["bloodType"].get(); + + if (rec["servicesOffered"] != sol::nil) + { + const sol::table services = rec["servicesOffered"]; + int flags = 0; + for (const auto& [mask, key] : MWLua::ServiceNames) + { + sol::object value = services[key]; + if (value != sol::nil && value.as()) + flags |= mask; + } + crea.mAiData.mServices = flags; + } + + return crea; + } +} + namespace sol { template <> @@ -30,6 +162,7 @@ namespace MWLua { "Undead", ESM::Creature::Undead }, { "Humanoid", ESM::Creature::Humanoid }, })); + creature["createRecordDraft"] = tableToCreature; addRecordFunctionBinding(creature, context); diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp index 5539d8d77c..d4aeddb7ef 100644 --- a/apps/openmw/mwlua/worldbindings.cpp +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -197,6 +199,22 @@ namespace MWLua copy.mId = {}; return MWBase::Environment::get().getESMStore()->insert(copy); }, + [lua = context.mLua](const ESM::Creature& crea) -> const ESM::Creature* { + checkGameInitialized(lua); + if (crea.mId.empty()) + return MWBase::Environment::get().getESMStore()->insert(crea); + ESM::Creature copy = crea; + copy.mId = {}; + return MWBase::Environment::get().getESMStore()->insert(copy); + }, + [lua = context.mLua](const ESM::Container& cont) -> const ESM::Container* { + checkGameInitialized(lua); + if (cont.mId.empty()) + return MWBase::Environment::get().getESMStore()->insert(cont); + ESM::Container copy = cont; + copy.mId = {}; + return MWBase::Environment::get().getESMStore()->insert(copy); + }, [lua = context.mLua](const ESM::Weapon& weapon) -> const ESM::Weapon* { checkGameInitialized(lua); return MWBase::Environment::get().getESMStore()->insert(weapon); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index add678a37f..218c818099 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -806,6 +806,18 @@ -- @extends #Actor -- @field #Actor baseType @{#Actor} +--- +-- Creates a @{#CreatureRecord} without adding it to the world database. +-- Use @{openmw_world#(world).createRecord} to add the record to the world. +-- @function [parent=#Creature] createRecordDraft +-- @param #CreatureRecord creature A Lua table with the fields of a CreatureRecord, with an additional field `template` that accepts a @{#CreatureRecord} as a base. +-- @return #CreatureRecord A strongly typed Creature record. +-- @usage local creatureTemplate = types.Creature.records['mudcrab'] +-- local creatureTable = {name = "Epic Mudcrab", template = creatureTemplate, soulValue = 500, isEssential = true} +-- local recordDraft = types.Creature.createRecordDraft(creatureTable) +-- local newRecord = world.createRecord(recordDraft) +-- world.createObject(newRecord.id):teleport(playerCell, playerPosition) + --- -- A read-only list of all @{#CreatureRecord}s in the world database, may be indexed by recordId. -- Implements [iterables#List](iterables.html#List) of #CreatureRecord. @@ -2184,6 +2196,18 @@ -- @param openmw.core#GameObject object -- @return openmw.core#Inventory +--- +-- Creates a @{#ContainerRecord} without adding it to the world database. +-- Use @{openmw_world#(world).createRecord} to add the record to the world. +-- @function [parent=#Container] createRecordDraft +-- @param #ContainerRecord container A Lua table with the fields of a ContainerRecord, with an additional field `template` that accepts a @{#ContainerRecord} as a base. +-- @return #ContainerRecord A strongly typed Container record. +-- @usage local chestTemplate = types.Container.records['chest_small_01'] +-- local containerTable = {name = "Respawning Treasure Chest", template = chestTemplate, isRespawning = true, weight = 150.0} +-- local recordDraft = types.Container.createRecordDraft(containerTable) +-- local newRecord = world.createRecord(recordDraft) +-- world.createObject(newRecord.id):moveInto(playerCell) + --- -- Container content (same as `Container.content`, added for consistency with `Actor.inventory`). -- @function [parent=#Container] inventory diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 22126ce8f4..bb75268d45 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -177,7 +177,10 @@ -- * @{openmw.types#ClothingRecord}, -- * @{openmw.types#WeaponRecord}, -- * @{openmw.types#ActivatorRecord}, --- * @{openmw.types#LightRecord} +-- * @{openmw.types#LightRecord}, +-- * @{openmw.types#NpcRecord}, +-- * @{openmw.types#ContainerRecord}, +-- * @{openmw.types#CreatureRecord} -- @function [parent=#world] createRecord -- @param #any record A record to be registered in the database. Must be one of the supported types. The id field is not used, one will be generated for you. -- @return #any A new record added to the database. The type is the same as the input's.