diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 2c8eb8d155..1a49f2f53c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -63,7 +63,7 @@ add_openmw_dir (mwlua context globalscripts localscripts playerscripts luabindings objectbindings cellbindings camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings types/types types/door types/actor types/container types/weapon types/npc types/creature types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing - worker + worker magicbindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 65f5737e85..45d9069d27 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -2,7 +2,10 @@ #include +#include +#include #include +#include #include #include @@ -19,6 +22,8 @@ #include "luamanagerimp.hpp" #include "worldview.hpp" +#include "magicbindings.hpp" + namespace MWLua { @@ -53,7 +58,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 35; + api["API_REVISION"] = 36; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); MWBase::Environment::get().getStateManager()->requestQuit(); @@ -63,6 +68,7 @@ namespace MWLua { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); }; addTimeBindings(api, context, false); + api["magic"] = initCoreMagicBindings(context); api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmst = &MWBase::Environment::get().getWorld()->getStore().get(); @@ -75,6 +81,18 @@ namespace MWLua else return sol::make_object(lua->sol(), value.getFloat()); }; + + sol::table skill(context.mLua->sol(), sol::create); + api["SKILL"] = LuaUtil::makeStrictReadOnly(skill); + for (int id = 0; id < ESM::Skill::Length; ++id) + skill[ESM::Skill::sSkillNames[id]] = Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[id]); + + sol::table attribute(context.mLua->sol(), sol::create); + api["ATTRIBUTE"] = LuaUtil::makeStrictReadOnly(attribute); + for (int id = 0; id < ESM::Attribute::Length; ++id) + attribute[ESM::Attribute::sAttributeNames[id]] + = Misc::StringUtils::lowerCase(ESM::Attribute::sAttributeNames[id]); + return LuaUtil::makeReadOnly(api); } @@ -97,7 +115,7 @@ namespace MWLua const MWWorld::Ptr& ptr = mref.getPtr(); ptr.getRefData().disable(); MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, *cell, count.value_or(1)); - return GObject(getId(newPtr)); + return GObject(newPtr); }; // Creates a new record in the world database. diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp new file mode 100644 index 0000000000..f99e571c1f --- /dev/null +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -0,0 +1,266 @@ +#include "magicbindings.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwmechanics/activespells.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" +#include "../mwworld/action.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "localscripts.hpp" +#include "luamanagerimp.hpp" +#include "object.hpp" +#include "objectvariant.hpp" +#include "worldview.hpp" + +namespace MWLua +{ + sol::table initCoreMagicBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::table magicApi(lua, sol::create); + + // Constants + magicApi["RANGE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Self", ESM::RT_Self }, + { "Touch", ESM::RT_Touch }, + { "Target", ESM::RT_Target }, + })); + magicApi["SCHOOL"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Alteration", 0 }, + { "Conjuration", 1 }, + { "Destruction", 2 }, + { "Illusion", 3 }, + { "Mysticism", 4 }, + { "Restoration", 5 }, + })); + magicApi["SPELL_TYPE"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Spell", ESM::Spell::ST_Spell }, + { "Ability", ESM::Spell::ST_Ability }, + { "Blight", ESM::Spell::ST_Blight }, + { "Disease", ESM::Spell::ST_Disease }, + { "Curse", ESM::Spell::ST_Curse }, + { "Power", ESM::Spell::ST_Power }, + })); + + // Spell store + using SpellStore = MWWorld::Store; + const SpellStore* spellStore = &MWBase::Environment::get().getWorld()->getStore().get(); + sol::usertype spellStoreT = lua.new_usertype("ESM3_SpellStore"); + spellStoreT[sol::meta_function::to_string] + = [](const SpellStore& store) { return "ESM3_SpellStore{" + std::to_string(store.getSize()) + " spells}"; }; + spellStoreT[sol::meta_function::length] = [](const SpellStore& store) { return store.getSize(); }; + spellStoreT[sol::meta_function::index] = sol::overload( + [](const SpellStore& store, size_t index) -> const ESM::Spell* { return store.at(index - 1); }, + [](const SpellStore& store, std::string_view spellId) -> const ESM::Spell* { + return store.find(ESM::RefId::deserializeText(spellId)); + }); + spellStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + spellStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + + magicApi["spells"] = spellStore; + + // MagicEffect store + using MagicEffectStore = MWWorld::Store; + const MagicEffectStore* magicEffectStore + = &MWBase::Environment::get().getWorld()->getStore().get(); + auto magicEffectStoreT = lua.new_usertype("ESM3_MagicEffectStore"); + magicEffectStoreT[sol::meta_function::to_string] = [](const MagicEffectStore& store) { + return "ESM3_MagicEffectStore{" + std::to_string(store.getSize()) + " effects}"; + }; + magicEffectStoreT[sol::meta_function::index] + = [](const MagicEffectStore& store, int id) -> const ESM::MagicEffect* { return store.find(id); }; + auto magicEffectsIter = [magicEffectStore](sol::this_state lua, const sol::object& /*store*/, + sol::optional id) -> std::tuple { + MagicEffectStore::iterator iter; + if (id.has_value()) + { + iter = magicEffectStore->findIter(*id); + if (iter != magicEffectStore->end()) + iter++; + } + else + iter = magicEffectStore->begin(); + if (iter != magicEffectStore->end()) + return std::make_tuple(sol::make_object(lua, iter->first), sol::make_object(lua, &iter->second)); + else + return std::make_tuple(sol::nil, sol::nil); + }; + magicEffectStoreT[sol::meta_function::pairs] + = [iter = sol::make_object(lua, magicEffectsIter)] { return iter; }; + + magicApi["effects"] = magicEffectStore; + + // Spell record + auto spellT = lua.new_usertype("ESM3_Spell"); + spellT[sol::meta_function::to_string] + = [](const ESM::Spell& rec) -> std::string { return "ESM3_Spell[" + rec.mId.toDebugString() + "]"; }; + spellT["id"] = sol::readonly_property([](const ESM::Spell& rec) { return rec.mId.serializeText(); }); + spellT["name"] = sol::readonly_property([](const ESM::Spell& rec) -> std::string_view { return rec.mName; }); + spellT["type"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mType; }); + spellT["cost"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mCost; }); + spellT["effects"] = sol::readonly_property([&lua](const ESM::Spell& rec) -> sol::table { + sol::table res(lua, sol::create); + for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) + res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) + return res; + }); + + // Effect params + auto effectParamsT = lua.new_usertype("ESM3_EffectParams"); + effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::ENAMstruct& params) { + const ESM::MagicEffect* const rec = magicEffectStore->find(params.mEffectID); + return "ESM3_EffectParams[" + ESM::MagicEffect::effectIdToString(rec->mIndex) + "]"; + }; + effectParamsT["effect"] + = sol::readonly_property([magicEffectStore](const ESM::ENAMstruct& params) -> const ESM::MagicEffect* { + return magicEffectStore->find(params.mEffectID); + }); + effectParamsT["affectedSkill"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional { + if (params.mSkill >= 0 && params.mSkill < ESM::Skill::Length) + return Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[params.mSkill]); + else + return sol::nullopt; + }); + effectParamsT["affectedAttribute"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional { + if (params.mAttribute >= 0 && params.mAttribute < ESM::Attribute::Length) + return Misc::StringUtils::lowerCase(ESM::Attribute::sAttributeNames[params.mAttribute]); + else + return sol::nullopt; + }); + effectParamsT["range"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mRange; }); + effectParamsT["area"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mArea; }); + effectParamsT["magnitudeMin"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMin; }); + effectParamsT["magnitudeMax"] + = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMax; }); + + // MagicEffect record + auto magicEffectT = context.mLua->sol().new_usertype("ESM3_MagicEffect"); + + magicEffectT[sol::meta_function::to_string] = [](const ESM::MagicEffect& rec) { + return "ESM3_MagicEffect[" + ESM::MagicEffect::effectIdToString(rec.mIndex) + "]"; + }; + magicEffectT["id"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> int { return rec.mIndex; }); + magicEffectT["name"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string_view { + return MWBase::Environment::get() + .getWorld() + ->getStore() + .get() + .find(ESM::MagicEffect::effectIdToString(rec.mIndex)) + ->mValue.getString(); + }); + magicEffectT["school"] + = sol::readonly_property([](const ESM::MagicEffect& rec) -> int { return rec.mData.mSchool; }); + magicEffectT["baseCost"] + = sol::readonly_property([](const ESM::MagicEffect& rec) -> float { return rec.mData.mBaseCost; }); + magicEffectT["color"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> Misc::Color { + return Misc::Color(rec.mData.mRed / 255.f, rec.mData.mGreen / 255.f, rec.mData.mBlue / 255.f, 1.f); + }); + magicEffectT["harmful"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::Harmful; }); + + // TODO: Should we expose it? What happens if a spell has several effects with different projectileSpeed? + // magicEffectT["projectileSpeed"] + // = sol::readonly_property([](const ESM::MagicEffect& rec) -> float { return rec.mData.mSpeed; }); + + return LuaUtil::makeReadOnly(magicApi); + } + + // class returned via 'types.Actor.spells(obj)' in Lua + struct ActorSpells + { + const ObjectVariant mActor; + }; + + void addActorMagicBindings(sol::table& actor, const Context& context) + { + const MWWorld::Store* spellStore + = &MWBase::Environment::get().getWorld()->getStore().get(); + + // types.Actor.spells(o) + actor["spells"] = [](const sol::object actor) { return ActorSpells{ ObjectVariant(actor) }; }; + auto spellsT = context.mLua->sol().new_usertype("ActorSpells"); + spellsT[sol::meta_function::to_string] + = [](const ActorSpells& spells) { return "ActorSpells[" + spells.mActor.object().toString(); }; + + // #(types.Actor.spells(o)) + spellsT[sol::meta_function::length] = [](const ActorSpells& spells) -> size_t { + const MWWorld::Ptr& ptr = spells.mActor.ptr(); + return ptr.getClass().getCreatureStats(ptr).getSpells().count(); + }; + + // types.Actor.spells(o)[i] + spellsT[sol::meta_function::index] = sol::overload( + [](const ActorSpells& spells, size_t index) -> const ESM::Spell* { + const MWWorld::Ptr& ptr = spells.mActor.ptr(); + return ptr.getClass().getCreatureStats(ptr).getSpells().at(index - 1); + }, + [spellStore](const ActorSpells& spells, std::string_view spellId) -> sol::optional { + const MWWorld::Ptr& ptr = spells.mActor.ptr(); + const ESM::Spell* spell = spellStore->find(ESM::RefId::deserializeText(spellId)); + if (ptr.getClass().getCreatureStats(ptr).getSpells().hasSpell(spell)) + return spell; + else + return sol::nullopt; + }); + + // pairs(types.Actor.spells(o)) + spellsT[sol::meta_function::pairs] = context.mLua->sol()["ipairsForArray"].template get(); + + // ipairs(types.Actor.spells(o)) + spellsT[sol::meta_function::ipairs] = context.mLua->sol()["ipairsForArray"].template get(); + + auto toSpellId = [](const sol::object& spellOrId) -> ESM::RefId { + if (spellOrId.is()) + return spellOrId.as()->mId; + else + return ESM::RefId::deserializeText(spellOrId.as()); + }; + + // types.Actor.spells(o):add(id) + spellsT["add"] = [context, toSpellId](const ActorSpells& spells, const sol::object& spellOrId) { + if (spells.mActor.isLObject()) + throw std::runtime_error("Local scripts can modify only spells of the actor they are attached to."); + context.mLuaManager->addAction([obj = spells.mActor.object(), id = toSpellId(spellOrId)]() { + const MWWorld::Ptr& ptr = obj.ptr(); + ptr.getClass().getCreatureStats(ptr).getSpells().add(id); + }); + }; + + // types.Actor.spells(o):remove(id) + spellsT["remove"] = [context, toSpellId](const ActorSpells& spells, const sol::object& spellOrId) { + if (spells.mActor.isLObject()) + throw std::runtime_error("Local scripts can modify only spells of the actor they are attached to."); + context.mLuaManager->addAction([obj = spells.mActor.object(), id = toSpellId(spellOrId)]() { + const MWWorld::Ptr& ptr = obj.ptr(); + ptr.getClass().getCreatureStats(ptr).getSpells().remove(id); + }); + }; + + // types.Actor.spells(o):clear() + spellsT["clear"] = [context](const ActorSpells& spells) { + if (spells.mActor.isLObject()) + throw std::runtime_error("Local scripts can modify only spells of the actor they are attached to."); + context.mLuaManager->addAction([obj = spells.mActor.object()]() { + const MWWorld::Ptr& ptr = obj.ptr(); + ptr.getClass().getCreatureStats(ptr).getSpells().clear(); + }); + }; + } +} diff --git a/apps/openmw/mwlua/magicbindings.hpp b/apps/openmw/mwlua/magicbindings.hpp new file mode 100644 index 0000000000..047bd2e3d9 --- /dev/null +++ b/apps/openmw/mwlua/magicbindings.hpp @@ -0,0 +1,14 @@ +#ifndef MWLUA_MAGICBINDINGS_H +#define MWLUA_MAGICBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initCoreMagicBindings(const Context& context); + void addActorMagicBindings(sol::table& actor, const Context& context); +} + +#endif // MWLUA_MAGICBINDINGS_H diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index 9f4075582a..b3fbfcdae2 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -29,9 +29,6 @@ namespace MWLua { public: using SafePtr::SafePtr; - virtual sol::object getObject(lua_State* lua, ObjectId id) const = 0; // returns LObject or GObject - virtual sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const = 0; // returns LCell or GCell - const MWWorld::Ptr& ptr() const { const MWWorld::Ptr& res = ptrOrNull(); @@ -49,11 +46,6 @@ namespace MWLua class LObject : public Object { using Object::Object; - sol::object getObject(lua_State* lua, ObjectId id) const final { return sol::make_object(lua, id); } - sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const final - { - return sol::make_object(lua, LCell{ store }); - } }; // Used only in global scripts @@ -64,11 +56,6 @@ namespace MWLua class GObject : public Object { using Object::Object; - sol::object getObject(lua_State* lua, ObjectId id) const final { return sol::make_object(lua, id); } - sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const final - { - return sol::make_object(lua, GCell{ store }); - } }; using ObjectIdList = std::shared_ptr>; diff --git a/apps/openmw/mwlua/objectvariant.hpp b/apps/openmw/mwlua/objectvariant.hpp index 8be0a390ea..389839da49 100644 --- a/apps/openmw/mwlua/objectvariant.hpp +++ b/apps/openmw/mwlua/objectvariant.hpp @@ -46,6 +46,8 @@ namespace MWLua mVariant); } + Object object() const { return Object(ptr()); } + private: std::variant mVariant; }; diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 15e29544ee..1672786648 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include "../localscripts.hpp" #include "../luamanagerimp.hpp" +#include "../magicbindings.hpp" #include "../stats.hpp" namespace MWLua @@ -218,9 +220,9 @@ namespace MWLua actor["inventory"] = sol::overload([](const LObject& o) { return Inventory{ o }; }, [](const GObject& o) { return Inventory{ o }; }); - auto getAllEquipment = [context](const Object& o) { + auto getAllEquipment = [](sol::this_state lua, const Object& o) { const MWWorld::Ptr& ptr = o.ptr(); - sol::table equipment(context.mLua->sol(), sol::create); + sol::table equipment(lua, sol::create); if (!ptr.getClass().hasInventoryStore(ptr)) return equipment; @@ -231,13 +233,15 @@ namespace MWLua if (it == store.end()) continue; MWBase::Environment::get().getWorldModel()->registerPtr(*it); - equipment[slot] = o.getObject(context.mLua->sol(), getId(*it)); + if (dynamic_cast(&o)) + equipment[slot] = sol::make_object(lua, GObject(*it)); + else + equipment[slot] = sol::make_object(lua, LObject(*it)); } return equipment; }; - auto getEquipmentFromSlot = [context](const Object& o, int slot) -> sol::object { + auto getEquipmentFromSlot = [](sol::this_state lua, const Object& o, int slot) -> sol::object { const MWWorld::Ptr& ptr = o.ptr(); - sol::table equipment(context.mLua->sol(), sol::create); if (!ptr.getClass().hasInventoryStore(ptr)) return sol::nil; MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); @@ -245,7 +249,10 @@ namespace MWLua if (it == store.end()) return sol::nil; MWBase::Environment::get().getWorldModel()->registerPtr(*it); - return o.getObject(context.mLua->sol(), getId(*it)); + if (dynamic_cast(&o)) + return sol::make_object(lua, GObject(*it)); + else + return sol::make_object(lua, LObject(*it)); }; actor["equipment"] = sol::overload(getAllEquipment, getEquipmentFromSlot); actor["hasEquipped"] = [](const Object& o, const Object& item) { @@ -274,16 +281,17 @@ namespace MWLua context.mLuaManager->addAction( std::make_unique(context.mLua, obj.id(), std::move(eqp))); }; - actor["getPathfindingAgentBounds"] = [context](const LObject& o) { + actor["getPathfindingAgentBounds"] = [](sol::this_state lua, const LObject& o) { const DetourNavigator::AgentBounds agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(o.ptr()); - sol::table result = context.mLua->newTable(); + sol::table result(lua, sol::create); result["shapeType"] = agentBounds.mShapeType; result["halfExtents"] = agentBounds.mHalfExtents; return result; }; addActorStatsBindings(actor, context); + addActorMagicBindings(actor, context); } } diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp index 651ba8fd4f..4306fa2ade 100644 --- a/apps/openmw/mwlua/types/book.cpp +++ b/apps/openmw/mwlua/types/book.cpp @@ -22,6 +22,8 @@ namespace MWLua { void addBookBindings(sol::table book, const Context& context) { + // types.book.SKILL is deprecated (core.SKILL should be used instead) + // TODO: Remove book.SKILL after branching 0.49 sol::table skill(context.mLua->sol(), sol::create); book["SKILL"] = LuaUtil::makeStrictReadOnly(skill); for (int id = ESM::Skill::Block; id < ESM::Skill::Length; ++id) diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp index c823222885..e537f7c377 100644 --- a/apps/openmw/mwlua/types/door.cpp +++ b/apps/openmw/mwlua/types/door.cpp @@ -48,7 +48,10 @@ namespace MWLua if (!cellRef.getTeleport()) return sol::nil; MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getCell(cellRef.getDestCell()); - return o.getCell(lua, &cell); + if (dynamic_cast(&o)) + return sol::make_object(lua, GCell{ &cell }); + else + return sol::make_object(lua, LCell{ &cell }); }; auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); @@ -84,7 +87,10 @@ namespace MWLua if (!cellRef.getTeleport()) return sol::nil; MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getCell(cellRef.getDestCell()); - return o.getCell(lua, &cell); + if (dynamic_cast(&o)) + return sol::make_object(lua, GCell{ &cell }); + else + return sol::make_object(lua, LCell{ &cell }); }; auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index e268b50f0d..efa22d8316 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -95,6 +95,10 @@ namespace MWMechanics bool hasBlightDisease() const; + /// Iteration methods for lua + size_t count() const { return mSpells.size(); } + const ESM::Spell* at(size_t index) const { return mSpells.at(index); } + void readState(const ESM::SpellState& state, CreatureStats* creatureStats); void writeState(ESM::SpellState& state) const; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 8f7a69ebe3..4a82ea9e43 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -94,6 +94,7 @@ namespace MWWorld iterator begin() const; iterator end() const; + iterator findIter(int index) const { return mStatic.find(index); } void load(ESM::ESMReader& esm); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index b6dc078390..5e2055215f 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -292,4 +292,141 @@ -- @usage for _, item in ipairs(inventory:findAll('common_shirt_01')) do ... end +--- Possible @{#ATTRIBUTE} values +-- @field [parent=#core] #ATTRIBUTE ATTRIBUTE + +--- `core.ATTRIBUTE` +-- @type ATTRIBUTE +-- @field #string Strength "strength" +-- @field #string Intelligence "intelligence" +-- @field #string Willpower "willpower" +-- @field #string Agility "agility" +-- @field #string Speed "speed" +-- @field #string Endurance "endurance" +-- @field #string Personality "personality" +-- @field #string Luck "luck" + + +--- Possible @{#SKILL} values +-- @field [parent=#core] #SKILL SKILL + +--- `core.SKILL` +-- @type SKILL +-- @field #string Acrobatics "acrobatics" +-- @field #string Alchemy "alchemy" +-- @field #string Alteration "alteration" +-- @field #string Armorer "armorer" +-- @field #string Athletics "athletics" +-- @field #string Axe "axe" +-- @field #string Block "block" +-- @field #string BluntWeapon "bluntweapon" +-- @field #string Conjuration "conjuration" +-- @field #string Destruction "destruction" +-- @field #string Enchant "enchant" +-- @field #string HandToHand "handtohand" +-- @field #string HeavyArmor "heavyarmor" +-- @field #string Illusion "illusion" +-- @field #string LightArmor "lightarmor" +-- @field #string LongBlade "longblade" +-- @field #string Marksman "marksman" +-- @field #string MediumArmor "mediumarmor" +-- @field #string Mercantile "mercantile" +-- @field #string Mysticism "mysticism" +-- @field #string Restoration "restoration" +-- @field #string Security "security" +-- @field #string ShortBlade "shortblade" +-- @field #string Sneak "sneak" +-- @field #string Spear "spear" +-- @field #string Speechcraft "speechcraft" +-- @field #string Unarmored "unarmored" + + +--- @{#Magic}: spells and spell effects +-- @field [parent=#core] #Magic magic + + +--- Possible @{#SpellRange} values +-- @field [parent=#Magic] #SpellRange RANGE + +--- `core.magic.RANGE` +-- @type SpellRange +-- @field #number Self Applied on self +-- @field #number Touch On touch +-- @field #number Target Ranged spell + + +--- Possible @{#MagicSchool} values +-- @field [parent=#Magic] #MagicSchool SCHOOL + +--- `core.magic.SCHOOL` +-- @type MagicSchool +-- @field #number Alteration Alteration +-- @field #number Conjuration Conjuration +-- @field #number Destruction Destruction +-- @field #number Illusion Illusion +-- @field #number Mysticism Mysticism +-- @field #number Restoration Restoration + + +--- Possible @{#SpellType} values +-- @field [parent=#Magic] #SpellType SPELL_TYPE + +--- `core.magic.SPELL_TYPE` +-- @type SpellType +-- @field #number Spell Normal spell, must be cast and costs mana +-- @field #number Ability Inert ability, always in effect +-- @field #number Blight Blight disease +-- @field #number Disease Common disease +-- @field #number Curse Curse +-- @field #number Power Power, can be used once a day + + +--- List of all @{#Spell}s. +-- @field [parent=#Magic] #list<#Spell> spells +-- @usage local spell = core.magic.spells['thunder fist'] -- get by id +-- @usage local spell = core.magic.spells[1] -- get by index +-- @usage -- Print all powers +-- for _, spell in pairs(core.magic.spells) do +-- if spell.types == core.magic.SPELL_TYPE.Power then +-- print(spell.name) +-- end +-- end + +--- Map from effectId to @{#SpellEffect} +-- @field [parent=#Magic] #map<#number, #MagicEffect> effects +-- @usage -- Print all harmful effects +-- for _, effect in pairs(core.magic.effects) do +-- if effect.harmful then +-- print(effect.name) +-- end +-- end + +--- +-- @type Spell +-- @field #string id Spell id +-- @field #string name Spell name +-- @field #number type @{#SpellType} +-- @field #number cost +-- @field #list<#MagicEffectWithParams> effects The effects (@{#MagicEffectWithParams}) of the spell + +--- +-- @type MagicEffect +-- @field #number id +-- @field #string name +-- @field #number school @{#MagicSchool} +-- @field #number baseCost +-- @field openmw.util#Color color +-- @field #boolean harmful + +--- +-- @type MagicEffectWithParams +-- @field #MagicEffect effect @{#MagicEffect} +-- @field #any affectedSkill @{#SKILL} or nil +-- @field #any affectedAttribute @{#ATTRIBUTE} or nil +-- @field #number range +-- @field #number area +-- @field #number magnitudeMin +-- @field #number magnitudeMax + + return nil diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index bd1787fb83..2ac02c1456 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -149,6 +149,50 @@ -- local Actor = require('openmw.types').Actor -- Actor.setEquipment(self, {}) -- unequip all +--- +-- Return the spells (@{ActorSpells}) of the given actor. +-- @function [parent=#Actor] spells +-- @param openmw.core#GameObject actor +-- @return #ActorSpells + +--- List of spells with additional functions add/remove/clear (modification are allowed only in global scripts or on self). +-- @type ActorSpells +-- @usage -- print available spells +-- local mySpells = types.Actor.spells(self) +-- for _, spell in pairs(mySpells) do print(spell.id) end +-- @usage -- print available spells (equivalent) +-- local mySpells = types.Actor.spells(self) +-- for i = 1, #mySpells do print(mySpells[i].id) end +-- @usage -- add ALL spells that exist in the world +-- local mySpells = types.Actor.spells(self) +-- for _, spell in pairs(core.magic.spells) do +-- if spell.type == core.magic.SPELL_TYPE.Spell then +-- mySpells:add(spell) +-- end +-- end +-- @usage -- add specific spell +-- types.Actor.spells(self):add('thunder fist') +-- @usage -- check specific spell +-- local mySpells = types.Actor.spells(self) +-- if mySpells['thunder fist'] then print('I have thunder fist') end + +--- +-- Add spell (only in global scripts or on self). +-- @function [parent=#ActorSpells] add +-- @param self +-- @param #any spellOrId @{openmw.core#Spell} or string spell id + +--- +-- Remove spell (only in global scripts or on self). +-- @function [parent=#ActorSpells] remove +-- @param self +-- @param #any spellOrId @{openmw.core#Spell} or string spell id + +--- +-- Remove all spells (only in global scripts or on self). +-- @function [parent=#ActorSpells] clear +-- @param self + --- -- @type LevelStat -- @field #number current The actor's current level. @@ -631,7 +675,7 @@ -- @field #string speechcraft "speechcraft" -- @field #string unarmored "unarmored" ---- @{#BookSKILL} +--- DEPRECATED, use @{openmw.core#SKILL} -- @field [parent=#Book] #BookSKILL SKILL --- @@ -651,7 +695,7 @@ -- @field #string text The text content of the book -- @field #number weight -- @field #number value --- @field #string skill The skill that this book teaches. See @{#Book.SKILL} +-- @field #string skill The skill that this book teaches. See @{openmw.core#SKILL} -- @field #boolean isScroll -- @field #number enchantCapacity