diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index cf9f265730..5fb06881ec 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -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 diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index e569bc1b8f..95b427eaa4 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -21,6 +21,10 @@ namespace sol struct is_automagical> : std::false_type { }; + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -43,12 +47,19 @@ namespace MWLua signT["texture"] = sol::readonly_property([vfs](const ESM::BirthSign& rec) -> std::string { 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; - }); + signT["spells"] + = sol::readonly_property([](const ESM::BirthSign& rec) -> const ESM::SpellList* { return &rec.mPowers; }); + + auto spellListT = lua.new_usertype("ESM3_SpellList"); + spellListT[sol::meta_function::length] = [](const ESM::SpellList& list) { return list.mList.size(); }; + spellListT[sol::meta_function::index] + = [](const ESM::SpellList& list, size_t index) -> sol::optional { + if (index == 0 || index > list.mList.size()) + return sol::nullopt; + return list.mList[index - 1].serializeText(); // Translate from Lua's 1-based indexing. + }; + spellListT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + spellListT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); return LuaUtil::makeReadOnly(birthSigns); } diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp new file mode 100644 index 0000000000..b5ea3c12bf --- /dev/null +++ b/apps/openmw/mwlua/racebindings.cpp @@ -0,0 +1,120 @@ +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.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 : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; + template <> + struct is_automagical : 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(races, context); + + auto raceT = lua.new_usertype("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) -> const ESM::SpellList* { return &rec.mPowers; }); + 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("ESM3_RaceAttributes"); + const auto& store = MWBase::Environment::get().getESMStore()->get(); + attributesT[sol::meta_function::index] + = [&](const RaceAttributes& attributes, std::string_view stringId) -> sol::optional { + 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> { + 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); + } +} diff --git a/apps/openmw/mwlua/racebindings.hpp b/apps/openmw/mwlua/racebindings.hpp new file mode 100644 index 0000000000..43ba9237c5 --- /dev/null +++ b/apps/openmw/mwlua/racebindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_RACEBINDINGS_H +#define MWLUA_RACEBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initRaceRecordBindings(const Context& context); +} + +#endif // MWLUA_RACEBINDINGS_H diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index d7d459bb81..29147a826b 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -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(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) { diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 0c51544f64..d085b355c8 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -958,6 +958,35 @@ -- @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 #map<#string, #number> height A read-only table with male and female fields +-- @field #map<#string, #number> weight A read-only table with male and female fields +-- @field #map<#string, #map<#string, #number>> attributes A read-only table of attribute ID to male and female base values +-- @usage -- Get base strength for men +-- strength = types.NPC.races.records[1].attributes.strength.male + --- -- @type NpcRecord -- @field #string id The record ID of the NPC