1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-15 22:49:48 +00:00

Merge branch 'raciallybound' into 'master'

Expose races to Lua

See merge request OpenMW/openmw!3863
This commit is contained in:
psi29a 2024-03-02 20:14:27 +00:00
commit d168466034
9 changed files with 203 additions and 55 deletions

View File

@ -64,7 +64,7 @@ add_openmw_dir (mwlua
context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings
mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings
postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings
classbindings itemdata inputprocessor animationbindings birthsignbindings
classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc
types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus
types/potion types/ingredient types/misc types/repair types/armor types/light types/static

View File

@ -8,6 +8,7 @@
#include "../mwworld/esmstore.hpp"
#include "birthsignbindings.hpp"
#include "idcollectionbindings.hpp"
#include "luamanagerimp.hpp"
#include "types/types.hpp"
@ -17,10 +18,6 @@ namespace sol
struct is_automagical<ESM::BirthSign> : std::false_type
{
};
template <>
struct is_automagical<MWWorld::Store<ESM::BirthSign>> : std::false_type
{
};
}
namespace MWLua
@ -44,10 +41,7 @@ namespace MWLua
return Misc::ResourceHelpers::correctTexturePath(rec.mTexture, vfs);
});
signT["spells"] = sol::readonly_property([lua](const ESM::BirthSign& rec) -> sol::table {
sol::table res(lua, sol::create);
for (size_t i = 0; i < rec.mPowers.mList.size(); ++i)
res[i + 1] = rec.mPowers.mList[i].serializeText();
return res;
return createReadOnlyRefIdTable(lua, rec.mPowers.mList);
});
return LuaUtil::makeReadOnly(birthSigns);

View File

@ -6,6 +6,7 @@
#include "../mwworld/esmstore.hpp"
#include "classbindings.hpp"
#include "idcollectionbindings.hpp"
#include "luamanagerimp.hpp"
#include "stats.hpp"
#include "types/types.hpp"
@ -16,10 +17,6 @@ namespace sol
struct is_automagical<ESM::Class> : std::false_type
{
};
template <>
struct is_automagical<MWWorld::Store<ESM::Class>> : std::false_type
{
};
}
namespace MWLua
@ -40,34 +37,15 @@ namespace MWLua
= sol::readonly_property([](const ESM::Class& rec) -> std::string_view { return rec.mDescription; });
classT["attributes"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table {
sol::table res(lua, sol::create);
auto attribute = rec.mData.mAttribute;
for (size_t i = 0; i < attribute.size(); ++i)
{
ESM::RefId attributeId = ESM::Attribute::indexToRefId(attribute[i]);
res[i + 1] = attributeId.serializeText();
}
return res;
return createReadOnlyRefIdTable(lua, rec.mData.mAttribute, ESM::Attribute::indexToRefId);
});
classT["majorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table {
sol::table res(lua, sol::create);
auto skills = rec.mData.mSkills;
for (size_t i = 0; i < skills.size(); ++i)
{
ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][1]);
res[i + 1] = skillId.serializeText();
}
return res;
return createReadOnlyRefIdTable(
lua, rec.mData.mSkills, [](const auto& pair) { return ESM::Skill::indexToRefId(pair[1]); });
});
classT["minorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table {
sol::table res(lua, sol::create);
auto skills = rec.mData.mSkills;
for (size_t i = 0; i < skills.size(); ++i)
{
ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][0]);
res[i + 1] = skillId.serializeText();
}
return res;
return createReadOnlyRefIdTable(
lua, rec.mData.mSkills, [](const auto& pair) { return ESM::Skill::indexToRefId(pair[0]); });
});
classT["specialization"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view {

View File

@ -9,6 +9,7 @@
#include "../mwmechanics/npcstats.hpp"
#include "idcollectionbindings.hpp"
#include "luamanagerimp.hpp"
namespace
@ -96,26 +97,10 @@ namespace MWLua
return res;
});
factionT["attributes"] = sol::readonly_property([&lua](const ESM::Faction& rec) {
sol::table res(lua, sol::create);
for (auto attributeIndex : rec.mData.mAttribute)
{
ESM::RefId id = ESM::Attribute::indexToRefId(attributeIndex);
if (!id.empty())
res.add(id.serializeText());
}
return res;
return createReadOnlyRefIdTable(lua, rec.mData.mAttribute, ESM::Attribute::indexToRefId);
});
factionT["skills"] = sol::readonly_property([&lua](const ESM::Faction& rec) {
sol::table res(lua, sol::create);
for (auto skillIndex : rec.mData.mSkills)
{
ESM::RefId id = ESM::Skill::indexToRefId(skillIndex);
if (!id.empty())
res.add(id.serializeText());
}
return res;
return createReadOnlyRefIdTable(lua, rec.mData.mSkills, ESM::Skill::indexToRefId);
});
auto rankT = lua.new_usertype<FactionRank>("ESM3_FactionRank");
rankT[sol::meta_function::to_string] = [](const FactionRank& rec) -> std::string {

View File

@ -0,0 +1,25 @@
#ifndef MWLUA_IDCOLLECTIONBINDINGS_H
#define MWLUA_IDCOLLECTIONBINDINGS_H
#include <functional>
#include <components/esm/refid.hpp>
#include <components/lua/luastate.hpp>
namespace MWLua
{
template <class C, class P = std::identity>
sol::table createReadOnlyRefIdTable(const sol::state_view& lua, const C& container, P projection = {})
{
sol::table res(lua, sol::create);
for (const auto& element : container)
{
ESM::RefId id = projection(element);
if (!id.empty())
res.add(id.serializeText());
}
return LuaUtil::makeReadOnly(res);
}
}
#endif

View File

@ -0,0 +1,117 @@
#include <components/esm/attr.hpp>
#include <components/esm3/loadrace.hpp>
#include <components/lua/luastate.hpp>
#include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "idcollectionbindings.hpp"
#include "luamanagerimp.hpp"
#include "racebindings.hpp"
#include "types/types.hpp"
namespace
{
struct RaceAttributes
{
const ESM::Race& mRace;
const sol::state_view mLua;
sol::table getAttribute(ESM::RefId id) const
{
sol::table res(mLua, sol::create);
res["male"] = mRace.mData.getAttribute(id, true);
res["female"] = mRace.mData.getAttribute(id, false);
return LuaUtil::makeReadOnly(res);
}
};
}
namespace sol
{
template <>
struct is_automagical<ESM::Race> : std::false_type
{
};
template <>
struct is_automagical<RaceAttributes> : std::false_type
{
};
}
namespace MWLua
{
sol::table initRaceRecordBindings(const Context& context)
{
sol::state_view& lua = context.mLua->sol();
sol::table races(context.mLua->sol(), sol::create);
addRecordFunctionBinding<ESM::Race>(races, context);
auto raceT = lua.new_usertype<ESM::Race>("ESM3_Race");
raceT[sol::meta_function::to_string]
= [](const ESM::Race& rec) -> std::string { return "ESM3_Race[" + rec.mId.toDebugString() + "]"; };
raceT["id"] = sol::readonly_property([](const ESM::Race& rec) { return rec.mId.serializeText(); });
raceT["name"] = sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mName; });
raceT["description"]
= sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mDescription; });
raceT["spells"] = sol::readonly_property(
[lua](const ESM::Race& rec) -> sol::table { return createReadOnlyRefIdTable(lua, rec.mPowers.mList); });
raceT["skills"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table {
sol::table res(lua, sol::create);
for (const auto& skillBonus : rec.mData.mBonus)
{
ESM::RefId skill = ESM::Skill::indexToRefId(skillBonus.mSkill);
if (!skill.empty())
res[skill.serializeText()] = skillBonus.mBonus;
}
return res;
});
raceT["isPlayable"] = sol::readonly_property(
[](const ESM::Race& rec) -> bool { return rec.mData.mFlags & ESM::Race::Playable; });
raceT["isBeast"]
= sol::readonly_property([](const ESM::Race& rec) -> bool { return rec.mData.mFlags & ESM::Race::Beast; });
raceT["height"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table {
sol::table res(lua, sol::create);
res["male"] = rec.mData.mMaleHeight;
res["female"] = rec.mData.mFemaleHeight;
return LuaUtil::makeReadOnly(res);
});
raceT["weight"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table {
sol::table res(lua, sol::create);
res["male"] = rec.mData.mMaleWeight;
res["female"] = rec.mData.mFemaleWeight;
return LuaUtil::makeReadOnly(res);
});
raceT["attributes"] = sol::readonly_property([lua](const ESM::Race& rec) -> RaceAttributes {
return { rec, lua };
});
auto attributesT = lua.new_usertype<RaceAttributes>("ESM3_RaceAttributes");
const auto& store = MWBase::Environment::get().getESMStore()->get<ESM::Attribute>();
attributesT[sol::meta_function::index]
= [&](const RaceAttributes& attributes, std::string_view stringId) -> sol::optional<sol::table> {
ESM::RefId id = ESM::RefId::deserializeText(stringId);
if (!store.search(id))
return sol::nullopt;
return attributes.getAttribute(id);
};
attributesT[sol::meta_function::pairs] = [&](sol::this_state ts, RaceAttributes& attributes) {
auto iterator = store.begin();
return sol::as_function(
[iterator, attributes,
&store]() mutable -> std::pair<sol::optional<std::string>, sol::optional<sol::table>> {
if (iterator != store.end())
{
ESM::RefId id = iterator->mId;
++iterator;
return { id.serializeText(), attributes.getAttribute(id) };
}
return { sol::nullopt, sol::nullopt };
});
};
return LuaUtil::makeReadOnly(races);
}
}

View File

@ -0,0 +1,13 @@
#ifndef MWLUA_RACEBINDINGS_H
#define MWLUA_RACEBINDINGS_H
#include <sol/forward.hpp>
#include "context.hpp"
namespace MWLua
{
sol::table initRaceRecordBindings(const Context& context);
}
#endif // MWLUA_RACEBINDINGS_H

View File

@ -15,6 +15,7 @@
#include "../classbindings.hpp"
#include "../localscripts.hpp"
#include "../racebindings.hpp"
#include "../stats.hpp"
namespace sol
@ -86,6 +87,7 @@ namespace MWLua
addActorServicesBindings<ESM::NPC>(record, context);
npc["classes"] = initClassRecordBindings(context);
npc["races"] = initRaceRecordBindings(context);
// This function is game-specific, in future we should replace it with something more universal.
npc["isWerewolf"] = [](const Object& o) {

View File

@ -958,6 +958,40 @@
-- @param #any objectOrRecordId
-- @return #NpcRecord
--- @{#Races}: Race data
-- @field [parent=#NPC] #Races races
---
-- A read-only list of all @{#RaceRecord}s in the world database.
-- @field [parent=#Races] #list<#RaceRecord> records
---
-- Returns a read-only @{#RaceRecord}
-- @function [parent=#Races] record
-- @param #string recordId
-- @return #RaceRecord
---
-- Race data record
-- @type RaceRecord
-- @field #string id Race id
-- @field #string name Race name
-- @field #string description Race description
-- @field #map<#string, #number> skills A map of bonus skill points by skill ID
-- @field #list<#string> spells A read-only list containing the ids of all spells inherent to the race
-- @field #bool isPlayable True if the player can pick this race in character generation
-- @field #bool isBeast True if this race is a beast race
-- @field #GenderedNumber height Height values
-- @field #GenderedNumber weight Weight values
-- @field #map<#string, #GenderedNumber> attributes A read-only table of attribute ID to base value
-- @usage -- Get base strength for men
-- strength = types.NPC.races.records[1].attributes.strength.male
---
-- @type GenderedNumber
-- @field #number male Male value
-- @field #number female Female value
---
-- @type NpcRecord
-- @field #string id The record ID of the NPC