mirror of
https://github.com/OpenMW/openmw.git
synced 2025-10-17 22:16:38 +00:00
Merge branch 'master' of https://gitlab.com/OpenMW/openmw
This commit is contained in:
commit
e4cc3adc98
9 changed files with 186 additions and 15 deletions
|
@ -75,6 +75,7 @@
|
|||
Feature #8580: Sort characters in the save loading menu
|
||||
Feature #8597: Lua: Add more built-in event handlers
|
||||
Feature #8629: Expose path grid data to Lua
|
||||
Feature #8654: Allow lua world.createRecord to create NPC records
|
||||
|
||||
0.49.0
|
||||
------
|
||||
|
|
|
@ -645,6 +645,9 @@ namespace MWLua
|
|||
}
|
||||
inventoryT["isResolved"] = [](const InventoryT& inventory) -> bool {
|
||||
const MWWorld::Ptr& ptr = inventory.mObj.ptr();
|
||||
// Avoid initializing custom data
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
return false;
|
||||
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
|
||||
return store.isResolved();
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "../context.hpp"
|
||||
|
||||
#include "servicesoffered.hpp"
|
||||
namespace MWLua
|
||||
{
|
||||
|
||||
|
@ -25,15 +26,6 @@ namespace MWLua
|
|||
record["servicesOffered"] = sol::readonly_property([context](const T& rec) -> sol::table {
|
||||
sol::state_view lua = context.sol();
|
||||
sol::table providedServices(lua, sol::create);
|
||||
constexpr std::array<std::pair<int, std::string_view>, 19> serviceNames = { { { ESM::NPC::Spells,
|
||||
"Spells" },
|
||||
{ ESM::NPC::Spellmaking, "Spellmaking" }, { ESM::NPC::Enchanting, "Enchanting" },
|
||||
{ ESM::NPC::Training, "Training" }, { ESM::NPC::Repair, "Repair" }, { ESM::NPC::AllItems, "Barter" },
|
||||
{ ESM::NPC::Weapon, "Weapon" }, { ESM::NPC::Armor, "Armor" }, { ESM::NPC::Clothing, "Clothing" },
|
||||
{ ESM::NPC::Books, "Books" }, { ESM::NPC::Ingredients, "Ingredients" }, { ESM::NPC::Picks, "Picks" },
|
||||
{ ESM::NPC::Probes, "Probes" }, { ESM::NPC::Lights, "Lights" }, { ESM::NPC::Apparatus, "Apparatus" },
|
||||
{ ESM::NPC::RepairItem, "RepairItem" }, { ESM::NPC::Misc, "Misc" }, { ESM::NPC::Potions, "Potions" },
|
||||
{ ESM::NPC::MagicItems, "MagicItems" } } };
|
||||
|
||||
int services = rec.mAiData.mServices;
|
||||
if constexpr (std::is_same_v<T, ESM::NPC>)
|
||||
|
@ -42,10 +34,11 @@ namespace MWLua
|
|||
services
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Class>().find(rec.mClass)->mData.mServices;
|
||||
}
|
||||
for (const auto& [flag, name] : serviceNames)
|
||||
for (const auto& [flag, name] : MWLua::ServiceNames)
|
||||
{
|
||||
providedServices[name] = (services & flag) != 0;
|
||||
}
|
||||
|
||||
providedServices["Travel"] = !rec.getTransport().empty();
|
||||
return LuaUtil::makeReadOnly(providedServices);
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "actor.hpp"
|
||||
#include "modelproperty.hpp"
|
||||
#include "servicesoffered.hpp"
|
||||
|
||||
#include <components/esm3/loadfact.hpp>
|
||||
#include <components/esm3/loadnpc.hpp>
|
||||
|
@ -44,6 +45,126 @@ namespace
|
|||
|
||||
return faction->mRanks.size();
|
||||
}
|
||||
ESM::NPC tableToNPC(const sol::table& rec)
|
||||
{
|
||||
ESM::NPC npc;
|
||||
|
||||
// Start from template if provided
|
||||
if (rec["template"] != sol::nil)
|
||||
npc = LuaUtil::cast<ESM::NPC>(rec["template"]);
|
||||
else
|
||||
npc.blank();
|
||||
|
||||
npc.mId = {};
|
||||
|
||||
// Basic fields
|
||||
if (rec["name"] != sol::nil)
|
||||
npc.mName = rec["name"];
|
||||
if (rec["model"] != sol::nil)
|
||||
npc.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get<std::string_view>());
|
||||
if (rec["mwscript"] != sol::nil)
|
||||
npc.mScript = ESM::RefId::deserializeText(rec["mwscript"].get<std::string_view>());
|
||||
if (rec["race"] != sol::nil)
|
||||
npc.mRace = ESM::RefId::deserializeText(rec["race"].get<std::string_view>());
|
||||
if (rec["class"] != sol::nil)
|
||||
npc.mClass = ESM::RefId::deserializeText(rec["class"].get<std::string_view>());
|
||||
if (rec["head"] != sol::nil)
|
||||
npc.mHead = ESM::RefId::deserializeText(rec["head"].get<std::string_view>());
|
||||
if (rec["hair"] != sol::nil)
|
||||
npc.mHair = ESM::RefId::deserializeText(rec["hair"].get<std::string_view>());
|
||||
if (rec["primaryFaction"] != sol::nil)
|
||||
{
|
||||
auto factionStr = rec["primaryFaction"].get<std::string_view>();
|
||||
ESM::RefId factionId = ESM::RefId::deserializeText(factionStr);
|
||||
|
||||
const auto& factionStore = MWBase::Environment::get().getESMStore()->get<ESM::Faction>();
|
||||
if (!factionStore.search(factionId))
|
||||
throw std::runtime_error("Invalid faction '" + std::string(factionStr) + "' in primaryFaction");
|
||||
|
||||
npc.mFaction = factionId;
|
||||
}
|
||||
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["isAutocalc"] != sol::nil)
|
||||
{
|
||||
bool autoCalc = rec["isAutocalc"];
|
||||
if (autoCalc)
|
||||
npc.mFlags |= ESM::NPC::Autocalc;
|
||||
else
|
||||
npc.mFlags &= ~ESM::NPC::Autocalc;
|
||||
}
|
||||
|
||||
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<int>();
|
||||
|
||||
if (rec["baseGold"] != sol::nil)
|
||||
npc.mNpdt.mGold = rec["baseGold"].get<int>();
|
||||
|
||||
if (rec["bloodType"] != sol::nil)
|
||||
npc.mBloodType = rec["bloodType"].get<int>();
|
||||
|
||||
if (rec["primaryFactionRank"] != sol::nil)
|
||||
{
|
||||
if (!npc.mFaction.empty())
|
||||
{
|
||||
const ESM::Faction* faction
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Faction>().find(npc.mFaction);
|
||||
|
||||
int luaValue = rec["primaryFactionRank"];
|
||||
int rank = LuaUtil::fromLuaIndex(luaValue);
|
||||
|
||||
int maxRank = static_cast<int>(getValidRanksCount(faction));
|
||||
|
||||
if (rank < 0 || rank >= maxRank)
|
||||
throw std::runtime_error("primaryFactionRank: Requested rank " + std::to_string(rank)
|
||||
+ " is out of bounds for faction " + npc.mFaction.toDebugString());
|
||||
|
||||
npc.mNpdt.mRank = rank;
|
||||
}
|
||||
}
|
||||
|
||||
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<bool>())
|
||||
flags |= mask;
|
||||
}
|
||||
|
||||
npc.mAiData.mServices = flags;
|
||||
}
|
||||
|
||||
return npc;
|
||||
}
|
||||
|
||||
ESM::RefId parseFactionId(std::string_view faction)
|
||||
{
|
||||
|
@ -95,9 +216,18 @@ namespace MWLua
|
|||
= 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["primaryFactionRank"] = sol::readonly_property([](const ESM::NPC& rec, sol::this_state s) -> int {
|
||||
if (rec.mFaction.empty())
|
||||
return 0;
|
||||
return LuaUtil::toLuaIndex(rec.mNpdt.mRank);
|
||||
});
|
||||
addModelProperty(record);
|
||||
record["isEssential"]
|
||||
= sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.mFlags & ESM::NPC::Essential; });
|
||||
record["isAutocalc"]
|
||||
= sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.mFlags & ESM::NPC::Autocalc; });
|
||||
record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); });
|
||||
record["isRespawning"]
|
||||
= sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.mFlags & ESM::NPC::Respawn; });
|
||||
|
@ -152,6 +282,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);
|
||||
|
|
21
apps/openmw/mwlua/types/servicesoffered.hpp
Normal file
21
apps/openmw/mwlua/types/servicesoffered.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#ifndef MWLUA_SERVICESOFFERED_HPP
|
||||
#define MWLUA_SERVICESOFFERED_HPP
|
||||
|
||||
#include <array>
|
||||
#include <components/esm3/loadnpc.hpp>
|
||||
#include <string_view>
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
|
||||
inline constexpr std::array<std::pair<int, std::string_view>, 19> ServiceNames
|
||||
= { { { ESM::NPC::Spells, "Spells" }, { ESM::NPC::Spellmaking, "Spellmaking" },
|
||||
{ ESM::NPC::Enchanting, "Enchanting" }, { ESM::NPC::Training, "Training" }, { ESM::NPC::Repair, "Repair" },
|
||||
{ ESM::NPC::AllItems, "Barter" }, { ESM::NPC::Weapon, "Weapon" }, { ESM::NPC::Armor, "Armor" },
|
||||
{ ESM::NPC::Clothing, "Clothing" }, { ESM::NPC::Books, "Books" }, { ESM::NPC::Ingredients, "Ingredients" },
|
||||
{ ESM::NPC::Picks, "Picks" }, { ESM::NPC::Probes, "Probes" }, { ESM::NPC::Lights, "Lights" },
|
||||
{ ESM::NPC::Apparatus, "Apparatus" }, { ESM::NPC::RepairItem, "RepairItem" }, { ESM::NPC::Misc, "Misc" },
|
||||
{ ESM::NPC::Potions, "Potions" }, { ESM::NPC::MagicItems, "MagicItems" } } };
|
||||
}
|
||||
|
||||
#endif
|
|
@ -7,6 +7,7 @@
|
|||
#include <components/esm3/loadclot.hpp>
|
||||
#include <components/esm3/loadligh.hpp>
|
||||
#include <components/esm3/loadmisc.hpp>
|
||||
#include <components/esm3/loadnpc.hpp>
|
||||
#include <components/esm3/loadweap.hpp>
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/misc/finitenumbers.hpp>
|
||||
|
@ -188,6 +189,14 @@ namespace MWLua
|
|||
checkGameInitialized(lua);
|
||||
return MWBase::Environment::get().getESMStore()->insert(potion);
|
||||
},
|
||||
[lua = context.mLua](const ESM::NPC& npc) -> const ESM::NPC* {
|
||||
checkGameInitialized(lua);
|
||||
if (npc.mId.empty())
|
||||
return MWBase::Environment::get().getESMStore()->insert(npc);
|
||||
ESM::NPC copy = npc;
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -269,9 +269,12 @@ namespace MWWorld
|
|||
list.push_back((*it)->mId);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T, class Id>
|
||||
T* TypedDynamicStore<T, Id>::insert(const T& item, bool overrideOnly)
|
||||
{
|
||||
if constexpr (std::is_same_v<decltype(item.mId), ESM::RefId>)
|
||||
overrideOnly = overrideOnly && !item.mId.template is<ESM::GeneratedRefId>();
|
||||
if (overrideOnly)
|
||||
{
|
||||
auto it = mStatic.find(item.mId);
|
||||
|
|
|
@ -861,6 +861,9 @@
|
|||
-- @field #boolean canWalk whether the creature can walk
|
||||
-- @field #boolean canUseWeapons whether the creature can use weapons and shields
|
||||
-- @field #boolean isBiped whether the creature is a biped
|
||||
-- @field #boolean isAutocalc If true, the actors stats will be automatically calculated based on level and class.
|
||||
-- @field #string primaryFaction Faction ID of the NPCs default faction. Nil if no faction
|
||||
-- @field #number primaryFactionRank Faction rank of the NPCs default faction. Nil if no faction
|
||||
-- @field #boolean isEssential whether the creature is essential
|
||||
-- @field #boolean isRespawning whether the creature respawns after death
|
||||
-- @field #number bloodType integer representing the blood type of the Creature. Used to generate the correct blood vfx.
|
||||
|
@ -875,6 +878,13 @@
|
|||
-- @field #Actor baseType @{#Actor}
|
||||
-- @field [parent=#NPC] #NpcStats stats
|
||||
|
||||
---
|
||||
-- Creates an @{#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 npc A Lua table with the fields of an NpcRecord, with an optional field `template` that accepts an @{#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.
|
||||
|
|
Loading…
Reference in a new issue