mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-10 12:39:53 +00:00
Merge branch 'active-spells-rework' into 'master'
Lua: active spell params and active spell effects See merge request OpenMW/openmw!3179
This commit is contained in:
commit
abde92e207
@ -1,9 +1,15 @@
|
||||
#include "magicbindings.hpp"
|
||||
|
||||
#include <components/esm3/activespells.hpp>
|
||||
#include <components/esm3/loadalch.hpp>
|
||||
#include <components/esm3/loadarmo.hpp>
|
||||
#include <components/esm3/loadbook.hpp>
|
||||
#include <components/esm3/loadclot.hpp>
|
||||
#include <components/esm3/loadench.hpp>
|
||||
#include <components/esm3/loadingr.hpp>
|
||||
#include <components/esm3/loadmgef.hpp>
|
||||
#include <components/esm3/loadspel.hpp>
|
||||
#include <components/esm3/loadweap.hpp>
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/misc/color.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
@ -111,6 +117,12 @@ namespace MWLua
|
||||
MWMechanics::EffectKey key;
|
||||
MWMechanics::EffectParam param;
|
||||
};
|
||||
struct ActiveSpell
|
||||
{
|
||||
ObjectVariant mActor;
|
||||
MWMechanics::ActiveSpells::ActiveSpellParams mParams;
|
||||
};
|
||||
|
||||
// class returned via 'types.Actor.spells(obj)' in Lua
|
||||
using ActorSpells = ActorStore<MWMechanics::Spells>;
|
||||
// class returned via 'types.Actor.activeEffects(obj)' in Lua
|
||||
@ -141,6 +153,10 @@ namespace sol
|
||||
struct is_automagical<MWLua::ActorStore<T>> : std::false_type
|
||||
{
|
||||
};
|
||||
template <>
|
||||
struct is_automagical<MWLua::ActiveSpell> : std::false_type
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
namespace MWLua
|
||||
@ -152,6 +168,27 @@ namespace MWLua
|
||||
else
|
||||
return ESM::RefId::deserializeText(LuaUtil::cast<std::string_view>(spellOrId));
|
||||
}
|
||||
static ESM::RefId toRecordId(const sol::object& recordOrId)
|
||||
{
|
||||
if (recordOrId.is<ESM::Spell>())
|
||||
return recordOrId.as<const ESM::Spell*>()->mId;
|
||||
else if (recordOrId.is<ESM::Potion>())
|
||||
return recordOrId.as<const ESM::Potion*>()->mId;
|
||||
else if (recordOrId.is<ESM::Ingredient>())
|
||||
return recordOrId.as<const ESM::Ingredient*>()->mId;
|
||||
else if (recordOrId.is<ESM::Enchantment>())
|
||||
return recordOrId.as<const ESM::Enchantment*>()->mId;
|
||||
else if (recordOrId.is<ESM::Armor>())
|
||||
return recordOrId.as<const ESM::Armor*>()->mId;
|
||||
else if (recordOrId.is<ESM::Book>())
|
||||
return recordOrId.as<const ESM::Book*>()->mId;
|
||||
else if (recordOrId.is<ESM::Clothing>())
|
||||
return recordOrId.as<const ESM::Clothing*>()->mId;
|
||||
else if (recordOrId.is<ESM::Weapon>())
|
||||
return recordOrId.as<const ESM::Weapon*>()->mId;
|
||||
else
|
||||
return ESM::RefId::deserializeText(LuaUtil::cast<std::string_view>(recordOrId));
|
||||
}
|
||||
|
||||
sol::table initCoreMagicBindings(const Context& context)
|
||||
{
|
||||
@ -372,6 +409,122 @@ namespace MWLua
|
||||
// magicEffectT["projectileSpeed"]
|
||||
// = sol::readonly_property([](const ESM::MagicEffect& rec) -> float { return rec.mData.mSpeed; });
|
||||
|
||||
auto activeSpellEffectT = context.mLua->sol().new_usertype<ESM::ActiveEffect>("ActiveSpellEffect");
|
||||
activeSpellEffectT[sol::meta_function::to_string] = [](const ESM::ActiveEffect& effect) {
|
||||
return "ActiveSpellEffect[" + ESM::MagicEffect::indexToGmstString(effect.mEffectId) + "]";
|
||||
};
|
||||
activeSpellEffectT["id"] = sol::readonly_property([](const ESM::ActiveEffect& effect) -> std::string {
|
||||
auto name = ESM::MagicEffect::indexToName(effect.mEffectId);
|
||||
return Misc::StringUtils::lowerCase(name);
|
||||
});
|
||||
activeSpellEffectT["name"] = sol::readonly_property([](const ESM::ActiveEffect& effect) -> std::string {
|
||||
return MWMechanics::EffectKey(effect.mEffectId, effect.mArg).toString();
|
||||
});
|
||||
activeSpellEffectT["affectedSkill"]
|
||||
= sol::readonly_property([magicEffectStore](const ESM::ActiveEffect& effect) -> sol::optional<std::string> {
|
||||
auto* rec = magicEffectStore->find(effect.mEffectId);
|
||||
if ((rec->mData.mFlags & ESM::MagicEffect::TargetSkill) && effect.mArg >= 0
|
||||
&& effect.mArg < ESM::Skill::Length)
|
||||
return ESM::Skill::indexToRefId(effect.mArg).serializeText();
|
||||
else
|
||||
return sol::nullopt;
|
||||
});
|
||||
activeSpellEffectT["affectedAttribute"]
|
||||
= sol::readonly_property([magicEffectStore](const ESM::ActiveEffect& effect) -> sol::optional<std::string> {
|
||||
auto* rec = magicEffectStore->find(effect.mEffectId);
|
||||
if ((rec->mData.mFlags & ESM::MagicEffect::TargetAttribute) && effect.mArg >= 0
|
||||
&& effect.mArg < ESM::Attribute::Length)
|
||||
return Misc::StringUtils::lowerCase(ESM::Attribute::sAttributeNames[effect.mArg]);
|
||||
else
|
||||
return sol::nullopt;
|
||||
});
|
||||
activeSpellEffectT["magnitudeThisFrame"]
|
||||
= sol::readonly_property([magicEffectStore](const ESM::ActiveEffect& effect) -> sol::optional<float> {
|
||||
auto* rec = magicEffectStore->find(effect.mEffectId);
|
||||
if (rec->mData.mFlags & ESM::MagicEffect::Flags::NoMagnitude)
|
||||
return sol::nullopt;
|
||||
return effect.mMagnitude;
|
||||
});
|
||||
activeSpellEffectT["minMagnitude"]
|
||||
= sol::readonly_property([magicEffectStore](const ESM::ActiveEffect& effect) -> sol::optional<float> {
|
||||
auto* rec = magicEffectStore->find(effect.mEffectId);
|
||||
if (rec->mData.mFlags & ESM::MagicEffect::Flags::NoMagnitude)
|
||||
return sol::nullopt;
|
||||
return effect.mMinMagnitude;
|
||||
});
|
||||
activeSpellEffectT["maxMagnitude"]
|
||||
= sol::readonly_property([magicEffectStore](const ESM::ActiveEffect& effect) -> sol::optional<float> {
|
||||
auto* rec = magicEffectStore->find(effect.mEffectId);
|
||||
if (rec->mData.mFlags & ESM::MagicEffect::Flags::NoMagnitude)
|
||||
return sol::nullopt;
|
||||
return effect.mMaxMagnitude;
|
||||
});
|
||||
activeSpellEffectT["durationLeft"]
|
||||
= sol::readonly_property([magicEffectStore](const ESM::ActiveEffect& effect) -> sol::optional<float> {
|
||||
// Permanent/constant effects, abilities, etc. will have a negative duration
|
||||
if (effect.mDuration < 0)
|
||||
return sol::nullopt;
|
||||
auto* rec = magicEffectStore->find(effect.mEffectId);
|
||||
if (rec->mData.mFlags & ESM::MagicEffect::Flags::NoDuration)
|
||||
return sol::nullopt;
|
||||
return effect.mTimeLeft;
|
||||
});
|
||||
activeSpellEffectT["duration"]
|
||||
= sol::readonly_property([magicEffectStore](const ESM::ActiveEffect& effect) -> sol::optional<float> {
|
||||
// Permanent/constant effects, abilities, etc. will have a negative duration
|
||||
if (effect.mDuration < 0)
|
||||
return sol::nullopt;
|
||||
auto* rec = magicEffectStore->find(effect.mEffectId);
|
||||
if (rec->mData.mFlags & ESM::MagicEffect::Flags::NoDuration)
|
||||
return sol::nullopt;
|
||||
return effect.mDuration;
|
||||
});
|
||||
|
||||
auto activeSpellT = context.mLua->sol().new_usertype<ActiveSpell>("ActiveSpellParams");
|
||||
activeSpellT[sol::meta_function::to_string] = [](const ActiveSpell& activeSpell) {
|
||||
return "ActiveSpellParams[" + activeSpell.mParams.getId().serializeText() + "]";
|
||||
};
|
||||
activeSpellT["name"] = sol::readonly_property(
|
||||
[](const ActiveSpell& activeSpell) -> std::string_view { return activeSpell.mParams.getDisplayName(); });
|
||||
activeSpellT["id"] = sol::readonly_property(
|
||||
[](const ActiveSpell& activeSpell) -> std::string { return activeSpell.mParams.getId().serializeText(); });
|
||||
activeSpellT["item"] = sol::readonly_property([&lua](const ActiveSpell& activeSpell) -> sol::object {
|
||||
auto item = activeSpell.mParams.getItem();
|
||||
if (!item.isSet())
|
||||
return sol::nil;
|
||||
auto itemPtr = MWBase::Environment::get().getWorldModel()->getPtr(item);
|
||||
if (itemPtr.isEmpty())
|
||||
return sol::nil;
|
||||
if (activeSpell.mActor.isGObject())
|
||||
return sol::make_object(lua, GObject(itemPtr));
|
||||
else
|
||||
return sol::make_object(lua, LObject(itemPtr));
|
||||
});
|
||||
activeSpellT["caster"] = sol::readonly_property([&lua](const ActiveSpell& activeSpell) -> sol::object {
|
||||
auto caster
|
||||
= MWBase::Environment::get().getWorld()->searchPtrViaActorId(activeSpell.mParams.getCasterActorId());
|
||||
if (caster.isEmpty())
|
||||
return sol::nil;
|
||||
else
|
||||
{
|
||||
if (activeSpell.mActor.isGObject())
|
||||
return sol::make_object(lua, GObject(getId(caster)));
|
||||
else
|
||||
return sol::make_object(lua, LObject(getId(caster)));
|
||||
}
|
||||
});
|
||||
activeSpellT["effects"] = sol::readonly_property([&lua](const ActiveSpell& activeSpell) -> sol::table {
|
||||
sol::table res(lua, sol::create);
|
||||
size_t tableIndex = 0;
|
||||
for (const ESM::ActiveEffect& effect : activeSpell.mParams.getEffects())
|
||||
{
|
||||
if (!(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
|
||||
continue;
|
||||
res[++tableIndex] = effect; // ESM::ActiveEffect (effect params)
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
auto activeEffectT = context.mLua->sol().new_usertype<ActiveEffect>("ActiveEffect");
|
||||
|
||||
activeEffectT[sol::meta_function::to_string] = [](const ActiveEffect& effect) {
|
||||
@ -547,18 +700,16 @@ namespace MWLua
|
||||
};
|
||||
|
||||
// pairs(types.Actor.activeSpells(o))
|
||||
// Note that the indexes are fake, and only for consistency with other lua pairs interfaces. You can't use them
|
||||
// for anything.
|
||||
activeSpellsT["__pairs"] = [](sol::this_state ts, ActorActiveSpells& self) {
|
||||
sol::state_view lua(ts);
|
||||
self.reset();
|
||||
return sol::as_function([lua, &self]() mutable -> std::pair<sol::object, sol::object> {
|
||||
return sol::as_function([lua, self]() mutable -> std::pair<sol::object, sol::object> {
|
||||
if (!self.isEnd())
|
||||
{
|
||||
auto result = sol::make_object(lua, self.mIterator->getId());
|
||||
auto index = sol::make_object(lua, self.mIndex + 1);
|
||||
auto id = sol::make_object(lua, self.mIterator->getId().serializeText());
|
||||
auto params = sol::make_object(lua, ActiveSpell{ self.mActor, *self.mIterator });
|
||||
self.advance();
|
||||
return { index, result };
|
||||
return { params, params };
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -568,10 +719,13 @@ namespace MWLua
|
||||
};
|
||||
|
||||
// types.Actor.activeSpells(o):isSpellActive(id)
|
||||
activeSpellsT["isSpellActive"] = [](const ActorActiveSpells& spells, const sol::object& spellOrId) -> bool {
|
||||
auto id = toSpellId(spellOrId);
|
||||
if (auto* store = spells.getStore())
|
||||
return store->isSpellActive(id);
|
||||
activeSpellsT["isSpellActive"]
|
||||
= [](const ActorActiveSpells& activeSpells, const sol::object& recordOrId) -> bool {
|
||||
if (auto* store = activeSpells.getStore())
|
||||
{
|
||||
auto id = toRecordId(recordOrId);
|
||||
return store->isSpellActive(id) || store->isEnchantmentActive(id);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
@ -593,7 +747,7 @@ namespace MWLua
|
||||
activeEffectsT["__pairs"] = [](sol::this_state ts, ActorActiveEffects& self) {
|
||||
sol::state_view lua(ts);
|
||||
self.reset();
|
||||
return sol::as_function([lua, &self]() mutable -> std::pair<sol::object, sol::object> {
|
||||
return sol::as_function([lua, self]() mutable -> std::pair<sol::object, sol::object> {
|
||||
if (!self.isEnd())
|
||||
{
|
||||
ActiveEffect effect = ActiveEffect{ self.mIterator->first, self.mIterator->second };
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/manualref.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -423,6 +424,30 @@ namespace MWMechanics
|
||||
!= mSpells.end();
|
||||
}
|
||||
|
||||
bool ActiveSpells::isEnchantmentActive(const ESM::RefId& id) const
|
||||
{
|
||||
const auto& store = MWBase::Environment::get().getESMStore();
|
||||
if (store->get<ESM::Enchantment>().search(id) == nullptr)
|
||||
return false;
|
||||
|
||||
// Enchantment id is not stored directly. Instead the enchanted item is stored.
|
||||
return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) {
|
||||
switch (store->find(spell.mId))
|
||||
{
|
||||
case ESM::REC_ARMO:
|
||||
return store->get<ESM::Armor>().find(spell.mId)->mEnchant == id;
|
||||
case ESM::REC_BOOK:
|
||||
return store->get<ESM::Book>().find(spell.mId)->mEnchant == id;
|
||||
case ESM::REC_CLOT:
|
||||
return store->get<ESM::Clothing>().find(spell.mId)->mEnchant == id;
|
||||
case ESM::REC_WEAP:
|
||||
return store->get<ESM::Weapon>().find(spell.mId)->mEnchant == id;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}) != mSpells.end();
|
||||
}
|
||||
|
||||
void ActiveSpells::addSpell(const ActiveSpellParams& params)
|
||||
{
|
||||
mQueue.emplace_back(params);
|
||||
|
@ -72,6 +72,8 @@ namespace MWMechanics
|
||||
|
||||
const std::string& getDisplayName() const { return mDisplayName; }
|
||||
|
||||
ESM::RefNum getItem() const { return mItem; }
|
||||
|
||||
// Increments worsenings count and sets the next timestamp
|
||||
void worsen();
|
||||
|
||||
@ -143,8 +145,12 @@ namespace MWMechanics
|
||||
/// Remove all spells
|
||||
void clear(const MWWorld::Ptr& ptr);
|
||||
|
||||
/// True if a spell associated with this id is active
|
||||
/// \note For enchantments, this is the id of the enchanted item, not the enchantment itself
|
||||
bool isSpellActive(const ESM::RefId& id) const;
|
||||
///< case insensitive
|
||||
|
||||
/// True if the enchantment is active
|
||||
bool isEnchantmentActive(const ESM::RefId& id) const;
|
||||
|
||||
void skipWorsenings(double hours);
|
||||
|
||||
|
@ -316,6 +316,26 @@
|
||||
-- local all = cell:getAll()
|
||||
-- local weapons = cell:getAll(types.Weapon)
|
||||
|
||||
---
|
||||
-- @type ActiveSpell
|
||||
-- @field #string name The spell or item display name
|
||||
-- @field #string id Record id of the spell or item used to cast the spell
|
||||
-- @field openmw.core#GameObject item The enchanted item used to cast the spell, or nil if the spell was not cast from an enchanted item. Note that if the spell was cast for a single-use enchantment such as a scroll, this will be nil.
|
||||
-- @field openmw.core#GameObject caster The caster object, or nil if the spell has no defined caster
|
||||
-- @field #list<#ActiveSpellEffect> effects The active effects (@{#ActiveSpellEffect}) of this spell.
|
||||
|
||||
---
|
||||
-- @type ActiveSpellEffect
|
||||
-- @field #string affectedSkill @{#SKILL} or nil
|
||||
-- @field #string affectedAttribute @{#ATTRIBUTE} or nil
|
||||
-- @field #string id Magic effect id
|
||||
-- @field #string name Localized name of the effect
|
||||
-- @field #number magnitudeThisFrame The magnitude of the effect in the current frame. This will be a new random number between minMagnitude and maxMagnitude every frame. Or nil if the effect has no magnitude.
|
||||
-- @field #number minMagnitude The minimum magnitude of this effect, or nil if the effect has no magnitude.
|
||||
-- @field #number maxMagnitude The maximum magnitude of this effect, or nil if the effect has no magnitude.
|
||||
-- @field #number duration Total duration in seconds of this spell effect, should not be confused with remaining duration. Or nil if the effect is not temporary.
|
||||
-- @field #number durationLeft Remaining duration in seconds of this spell effect, or nil if the effect is not temporary.
|
||||
|
||||
|
||||
--- Possible @{#EnchantmentType} values
|
||||
-- @field [parent=#Magic] #EnchantmentType ENCHANTMENT_TYPE
|
||||
@ -699,8 +719,8 @@
|
||||
---
|
||||
-- @type MagicEffectWithParams
|
||||
-- @field #MagicEffect effect @{#MagicEffect}
|
||||
-- @field #any affectedSkill @{#SKILL} or nil
|
||||
-- @field #any affectedAttribute @{#ATTRIBUTE} or nil
|
||||
-- @field #string affectedSkill @{#SKILL} or nil
|
||||
-- @field #string affectedAttribute @{#ATTRIBUTE} or nil
|
||||
-- @field #number range
|
||||
-- @field #number area
|
||||
-- @field #number magnitudeMin
|
||||
@ -709,8 +729,8 @@
|
||||
|
||||
---
|
||||
-- @type ActiveEffect
|
||||
-- @field #any affectedSkill @{#SKILL} or nil
|
||||
-- @field #any affectedAttribute @{#ATTRIBUTE} or nil
|
||||
-- @field #string affectedSkill @{#SKILL} or nil
|
||||
-- @field #string affectedAttribute @{#ATTRIBUTE} or nil
|
||||
-- @field #string id Effect id string
|
||||
-- @field #string name Localized name of the effect
|
||||
-- @field #number magnitude
|
||||
|
@ -239,7 +239,7 @@
|
||||
-- @param openmw.core#GameObject actor
|
||||
-- @return #ActorActiveSpells
|
||||
|
||||
--- Read-only list of spells currently affecting the actor.
|
||||
--- Read-only list of spells currently affecting the actor. Can be iterated over for a list of @{openmw.core#ActiveSpell}
|
||||
-- @type ActorActiveSpells
|
||||
-- @usage -- print active spells
|
||||
-- for _, spell in pairs(Actor.activeSpells(self)) do
|
||||
@ -251,12 +251,33 @@
|
||||
-- else
|
||||
-- print('Player does not have bound longbow')
|
||||
-- end
|
||||
-- @usage -- Print all information about active spells
|
||||
-- for id, params in pairs(Actor.activeSpells(self)) do
|
||||
-- print('active spell '..tostring(id)..':')
|
||||
-- print(' name: '..tostring(params.name))
|
||||
-- print(' id: '..tostring(params.id))
|
||||
-- print(' item: '..tostring(params.item))
|
||||
-- print(' caster: '..tostring(params.caster))
|
||||
-- print(' effects: '..tostring(params.effects))
|
||||
-- for _, effect in pairs(params.effects) do
|
||||
-- print(' -> effects['..tostring(effect)..']:')
|
||||
-- print(' id: '..tostring(effect.id))
|
||||
-- print(' name: '..tostring(effect.name))
|
||||
-- print(' affectedSkill: '..tostring(effect.affectedSkill))
|
||||
-- print(' affectedAttribute: '..tostring(effect.affectedAttribute))
|
||||
-- print(' magnitudeThisFrame: '..tostring(effect.magnitudeThisFrame))
|
||||
-- print(' minMagnitude: '..tostring(effect.minMagnitude))
|
||||
-- print(' maxMagnitude: '..tostring(effect.maxMagnitude))
|
||||
-- print(' duration: '..tostring(effect.duration))
|
||||
-- print(' durationLeft: '..tostring(effect.durationLeft))
|
||||
-- end
|
||||
-- end
|
||||
|
||||
---
|
||||
-- Get whether a specific spell is active on the actor.
|
||||
-- @function [parent=#ActorActiveSpells] isSpellActive
|
||||
-- @param self
|
||||
-- @param #any spellOrId @{openmw.core#Spell} or string spell id
|
||||
-- @param #any recordOrId record or string record ID of the active spell's source. valid records are @{openmw.core#Spell}, @{openmw.core#Enchantment}, #IngredientRecord, or #PotionRecord
|
||||
-- @return true if spell is active, false otherwise
|
||||
|
||||
---
|
||||
|
Loading…
x
Reference in New Issue
Block a user