diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 380a2d1e9b..220b845882 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -29,6 +29,110 @@ namespace sol }; } +namespace +{ + ESM::NPC tableToNPC(const sol::table& rec) + { + ESM::NPC npc; + + // Start from template if provided + if (rec["template"] != sol::nil) + npc = LuaUtil::cast(rec["template"]); + else + npc.blank(); + + if (npc.mId == ESM::RefId::deserializeText("Player")) + npc.mId = ESM::RefId::deserializeText("blank"); + + // Basic fields + if (rec["name"] != sol::nil) + npc.mName = rec["name"]; + if (rec["model"] != sol::nil) + npc.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + if (rec["mwscript"] != sol::nil) + npc.mScript = ESM::RefId::deserializeText(rec["mwscript"].get()); + if (rec["race"] != sol::nil) + npc.mRace = ESM::RefId::deserializeText(rec["race"].get()); + if (rec["class"] != sol::nil) + npc.mClass = ESM::RefId::deserializeText(rec["class"].get()); + if (rec["head"] != sol::nil) + npc.mHead = ESM::RefId::deserializeText(rec["head"].get()); + if (rec["hair"] != sol::nil) + npc.mHair = ESM::RefId::deserializeText(rec["hair"].get()); + + if (rec["isMale"] != sol::nil) + { + bool male = rec["isMale"]; + if (male) + npc.mFlags &= ~ESM::NPC::Female; + else + npc.mFlags |= ESM::NPC::Female; + } + + if (rec["isEssential"] != sol::nil) + { + bool essential = rec["isEssential"]; + if (essential) + npc.mFlags |= ESM::NPC::Essential; + else + npc.mFlags &= ~ESM::NPC::Essential; + } + + if (rec["isRespawning"] != sol::nil) + { + bool respawn = rec["isRespawning"]; + if (respawn) + npc.mFlags |= ESM::NPC::Respawn; + else + npc.mFlags &= ~ESM::NPC::Respawn; + } + + if (rec["baseDisposition"] != sol::nil) + npc.mNpdt.mDisposition = rec["baseDisposition"].get(); + + if (rec["baseGold"] != sol::nil) + npc.mNpdt.mGold = rec["baseGold"].get(); + + if (rec["bloodType"] != sol::nil) + npc.mBloodType = rec["bloodType"].get(); + + // Services offered + if (rec["servicesOffered"] != sol::nil) + { + const sol::table services = rec["servicesOffered"]; + int flags = 0; + auto setFlag = [&](std::string_view key, int mask) { + if (services[key] != sol::nil && services[key]) + flags |= mask; + }; + + setFlag("Spells", ESM::NPC::Spells); + setFlag("Spellmaking", ESM::NPC::Spellmaking); + setFlag("Enchanting", ESM::NPC::Enchanting); + setFlag("Training", ESM::NPC::Training); + setFlag("Repair", ESM::NPC::Repair); + setFlag("Barter", ESM::NPC::AllItems); + setFlag("Weapon", ESM::NPC::Weapon); + setFlag("Armor", ESM::NPC::Armor); + setFlag("Clothing", ESM::NPC::Clothing); + setFlag("Books", ESM::NPC::Books); + setFlag("Ingredients", ESM::NPC::Ingredients); + setFlag("Picks", ESM::NPC::Picks); + setFlag("Probes", ESM::NPC::Probes); + setFlag("Lights", ESM::NPC::Lights); + setFlag("Apparatus", ESM::NPC::Apparatus); + setFlag("RepairItem", ESM::NPC::RepairItem); + setFlag("Misc", ESM::NPC::Misc); + setFlag("Potions", ESM::NPC::Potions); + setFlag("MagicItems", ESM::NPC::MagicItems); + + npc.mAiData.mServices = flags; + } + + return npc; + } +} + namespace { size_t getValidRanksCount(const ESM::Faction* faction) @@ -152,6 +256,7 @@ namespace MWLua stats.setBaseDisposition(stats.getBaseDisposition() + value); }; + npc["createRecordDraft"] = tableToNPC; npc["getFactionRank"] = [](const Object& actor, std::string_view faction) -> size_t { const MWWorld::Ptr ptr = actor.ptr(); ESM::RefId factionId = parseFactionId(faction); diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp index c02bad3bd3..dca136cd63 100644 --- a/apps/openmw/mwlua/worldbindings.cpp +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -188,6 +189,10 @@ namespace MWLua checkGameInitialized(lua); return MWBase::Environment::get().getESMStore()->insert(potion); }, + [lua = context.mLua](const ESM::NPC& npc) -> const ESM::NPC* { + checkGameInitialized(lua); + return MWBase::Environment::get().getESMStore()->insert(npc); + }, [lua = context.mLua](const ESM::Weapon& weapon) -> const ESM::Weapon* { checkGameInitialized(lua); return MWBase::Environment::get().getESMStore()->insert(weapon); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 7262805f81..369731a019 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -726,8 +726,6 @@ namespace MWWorld switch (type) { case ESM::REC_ALCH: - case ESM::REC_MISC: - case ESM::REC_ACTI: case ESM::REC_ARMO: case ESM::REC_BOOK: case ESM::REC_CLAS: @@ -735,14 +733,16 @@ namespace MWWorld case ESM::REC_ENCH: case ESM::REC_SPEL: case ESM::REC_WEAP: - case ESM::REC_LEVI: - case ESM::REC_LEVC: - case ESM::REC_LIGH: mStoreImp->mRecNameToStore[type]->read(reader); return true; case ESM::REC_NPC_: case ESM::REC_CREA: case ESM::REC_CONT: + case ESM::REC_MISC: + case ESM::REC_ACTI: + case ESM::REC_LEVI: + case ESM::REC_LEVC: + case ESM::REC_LIGH: mStoreImp->mRecNameToStore[type]->read(reader, true); return true; diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 80bcdb056a..0e64e6e627 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -269,13 +269,29 @@ namespace MWWorld list.push_back((*it)->mId); } } + template + inline bool shouldInsert(const IdType& id, const StaticMap& map) + { + auto it = map.find(id); + return it != map.end(); + } + + template + inline bool shouldInsert(const ESM::RefId& id, const StaticMap& map) + { + if (!id.template is()) + { + auto it = map.find(id); + return it != map.end(); + } + return true; + } template T* TypedDynamicStore::insert(const T& item, bool overrideOnly) { if (overrideOnly) { - auto it = mStatic.find(item.mId); - if (it == mStatic.end()) + if (!shouldInsert(item.mId, mStatic)) return nullptr; } std::pair result = mDynamic.insert_or_assign(item.mId, item); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index b2711c895b..5c21de8f56 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -875,6 +875,13 @@ -- @field #Actor baseType @{#Actor} -- @field [parent=#NPC] #NpcStats stats +--- +-- Creates a @{#NpcRecord} without adding it to the world database. +-- Use @{openmw_world#(world).createRecord} to add the record to the world. +-- @function [parent=#NPC] createRecordDraft +-- @param #NpcRecord book A Lua table with the fields of a NpcRecord, with an optional field `template` that accepts a @{#NpcRecord} as a base. +-- @return #NpcRecord A strongly typed NPC record. + --- -- A read-only list of all @{#NpcRecord}s in the world database, may be indexed by recordId. -- Implements [iterables#List](iterables.html#List) of #NpcRecord.