Merge branch 'mwdialogue-bindings' into 'master'

Add Lua bindings for ESM::Dialogue record stores to openmw.core

Closes #7964

See merge request OpenMW/openmw!4034
i-have-no-land-and-i-must-scream
Zackhasacat 8 months ago
commit 0e1678b3b8

@ -229,6 +229,7 @@ Programmers
thegriglat
Thomas Luppi (Digmaster)
tlmullis
trav
tri4ng1e
Thoronador
Tobias Tribble (zackhasacat)

@ -232,6 +232,7 @@
Feature #7932: Support two-channel normal maps
Feature #7936: Scalable icons in Qt applications
Feature #7953: Allow to change SVG icons colors depending on color scheme
Feature #7964: Add Lua read access to MW Dialogue records
Task #5896: Do not use deprecated MyGUI properties
Task #6085: Replace boost::filesystem with std::filesystem
Task #6149: Dehardcode Lua API_REVISION

@ -81,7 +81,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 61)
set(OPENMW_LUA_API_REVISION 62)
set(OPENMW_POSTPROCESSING_API_REVISION 1)
set(OPENMW_VERSION_COMMITHASH "")

@ -62,7 +62,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua
luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings
mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings
mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings dialoguebindings
postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker magicbindings factionbindings
classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc

@ -19,6 +19,7 @@
#include "../mwworld/esmstore.hpp"
#include "animationbindings.hpp"
#include "dialoguebindings.hpp"
#include "factionbindings.hpp"
#include "luaevents.hpp"
#include "magicbindings.hpp"
@ -98,7 +99,7 @@ namespace MWLua
api["stats"] = initCoreStatsBindings(context);
api["factions"] = initCoreFactionBindings(context);
api["dialogue"] = initCoreDialogueBindings(context);
api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager());
const MWWorld::Store<ESM::GameSetting>* gmstStore
= &MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();

@ -0,0 +1,419 @@
#include "dialoguebindings.hpp"
#include "apps/openmw/mwbase/environment.hpp"
#include "apps/openmw/mwworld/esmstore.hpp"
#include "apps/openmw/mwworld/store.hpp"
#include "context.hpp"
#include "object.hpp"
#include <algorithm>
#include <components/esm3/loaddial.hpp>
#include <components/lua/luastate.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/vfs/pathutil.hpp>
namespace
{
template <ESM::Dialogue::Type filter>
class FilteredDialogueStore
{
const MWWorld::Store<ESM::Dialogue>& mDialogueStore;
const ESM::Dialogue* foundDialogueFilteredOut(const ESM::Dialogue* possibleResult) const
{
if (possibleResult && possibleResult->mType == filter)
{
return possibleResult;
}
return nullptr;
}
public:
FilteredDialogueStore()
: mDialogueStore{ MWBase::Environment::get().getESMStore()->get<ESM::Dialogue>() }
{
}
class FilteredDialogueIterator
{
using DecoratedIterator = MWWorld::Store<ESM::Dialogue>::iterator;
DecoratedIterator mIter;
DecoratedIterator mEndIter;
public:
using iterator_category = DecoratedIterator::iterator_category;
using value_type = DecoratedIterator::value_type;
using difference_type = DecoratedIterator::difference_type;
using pointer = DecoratedIterator::pointer;
using reference = DecoratedIterator::reference;
FilteredDialogueIterator(const DecoratedIterator& pointingIterator, const DecoratedIterator& end)
: mIter{ pointingIterator }
, mEndIter{ end }
{
}
FilteredDialogueIterator& operator++()
{
if (mIter == mEndIter)
{
return *this;
}
do
{
++mIter;
} while (mIter != mEndIter && mIter->mType != filter);
return *this;
}
FilteredDialogueIterator operator++(int)
{
FilteredDialogueIterator iter = *this;
++(*this);
return iter;
}
FilteredDialogueIterator& operator+=(difference_type advance)
{
while (advance > 0 && mIter != mEndIter)
{
++(*this);
--advance;
}
return *this;
}
bool operator==(const FilteredDialogueIterator& x) const { return mIter == x.mIter; }
bool operator!=(const FilteredDialogueIterator& x) const { return !(*this == x); }
const value_type& operator*() const { return *mIter; }
const value_type* operator->() const { return &(*mIter); }
};
using iterator = FilteredDialogueIterator;
const ESM::Dialogue* search(const ESM::RefId& id) const
{
return foundDialogueFilteredOut(mDialogueStore.search(id));
}
const ESM::Dialogue* at(size_t index) const
{
auto result = begin();
result += index;
if (result == end())
{
return nullptr;
}
return &(*result);
}
size_t getSize() const
{
return std::count_if(
mDialogueStore.begin(), mDialogueStore.end(), [](const auto& d) { return d.mType == filter; });
}
iterator begin() const
{
iterator result{ mDialogueStore.begin(), mDialogueStore.end() };
while (result != end() && result->mType != filter)
{
++result;
}
return result;
}
iterator end() const { return iterator{ mDialogueStore.end(), mDialogueStore.end() }; }
};
template <ESM::Dialogue::Type filter>
void prepareBindingsForDialogueRecordStores(sol::table& table, const MWLua::Context& context)
{
using StoreT = FilteredDialogueStore<filter>;
sol::state_view& lua = context.mLua->sol();
sol::usertype<StoreT> storeBindingsClass
= lua.new_usertype<StoreT>("ESM3_Dialogue_Type" + std::to_string(filter) + " Store");
storeBindingsClass[sol::meta_function::to_string] = [](const StoreT& store) {
return "{" + std::to_string(store.getSize()) + " ESM3_Dialogue_Type" + std::to_string(filter) + " records}";
};
storeBindingsClass[sol::meta_function::length] = [](const StoreT& store) { return store.getSize(); };
storeBindingsClass[sol::meta_function::index] = sol::overload(
[](const StoreT& store, size_t index) -> const ESM::Dialogue* {
if (index == 0)
{
return nullptr;
}
return store.at(index - 1);
},
[](const StoreT& store, std::string_view id) -> const ESM::Dialogue* {
return store.search(ESM::RefId::deserializeText(id));
});
storeBindingsClass[sol::meta_function::ipairs] = lua["ipairsForArray"].template get<sol::function>();
storeBindingsClass[sol::meta_function::pairs] = lua["ipairsForArray"].template get<sol::function>();
table["records"] = StoreT{};
}
struct DialogueInfos
{
const ESM::Dialogue& parentDialogueRecord;
};
void prepareBindingsForDialogueRecord(sol::state_view& lua)
{
auto recordBindingsClass = lua.new_usertype<ESM::Dialogue>("ESM3_Dialogue");
recordBindingsClass[sol::meta_function::to_string]
= [](const ESM::Dialogue& rec) { return "ESM3_Dialogue[" + rec.mId.toDebugString() + "]"; };
recordBindingsClass["id"]
= sol::readonly_property([](const ESM::Dialogue& rec) { return rec.mId.serializeText(); });
recordBindingsClass["name"]
= sol::readonly_property([](const ESM::Dialogue& rec) -> std::string_view { return rec.mStringId; });
recordBindingsClass["questName"]
= sol::readonly_property([](const ESM::Dialogue& rec) -> sol::optional<std::string_view> {
if (rec.mType != ESM::Dialogue::Type::Journal)
{
return sol::nullopt;
}
for (const auto& mwDialogueInfo : rec.mInfo)
{
if (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Name)
{
return sol::optional<std::string_view>(mwDialogueInfo.mResponse);
}
}
return sol::nullopt;
});
recordBindingsClass["infos"]
= sol::readonly_property([](const ESM::Dialogue& rec) { return DialogueInfos{ rec }; });
}
void prepareBindingsForDialogueRecordInfoList(sol::state_view& lua)
{
auto recordInfosBindingsClass = lua.new_usertype<DialogueInfos>("ESM3_Dialogue_Infos");
recordInfosBindingsClass[sol::meta_function::to_string] = [](const DialogueInfos& store) {
const ESM::Dialogue& dialogueRecord = store.parentDialogueRecord;
return "{" + std::to_string(dialogueRecord.mInfo.size()) + " ESM3_Dialogue["
+ dialogueRecord.mId.toDebugString() + "] info elements}";
};
recordInfosBindingsClass[sol::meta_function::length]
= [](const DialogueInfos& store) { return store.parentDialogueRecord.mInfo.size(); };
recordInfosBindingsClass[sol::meta_function::index]
= [](const DialogueInfos& store, size_t index) -> const ESM::DialInfo* {
const ESM::Dialogue& dialogueRecord = store.parentDialogueRecord;
if (index == 0 || index > dialogueRecord.mInfo.size())
{
return nullptr;
}
ESM::Dialogue::InfoContainer::const_iterator iter{ dialogueRecord.mInfo.cbegin() };
std::advance(iter, index - 1);
return &(*iter);
};
recordInfosBindingsClass[sol::meta_function::ipairs] = lua["ipairsForArray"].template get<sol::function>();
recordInfosBindingsClass[sol::meta_function::pairs] = lua["ipairsForArray"].template get<sol::function>();
}
void prepareBindingsForDialogueRecordInfoListElement(sol::state_view& lua)
{
auto recordInfoBindingsClass = lua.new_usertype<ESM::DialInfo>("ESM3_Dialogue_Info");
recordInfoBindingsClass[sol::meta_function::to_string]
= [](const ESM::DialInfo& rec) { return "ESM3_Dialogue_Info[" + rec.mId.toDebugString() + "]"; };
recordInfoBindingsClass["id"]
= sol::readonly_property([](const ESM::DialInfo& rec) { return rec.mId.serializeText(); });
recordInfoBindingsClass["text"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> std::string_view { return rec.mResponse; });
recordInfoBindingsClass["questStage"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<int> {
if (rec.mData.mType != ESM::Dialogue::Type::Journal)
{
return sol::nullopt;
}
return rec.mData.mJournalIndex;
});
recordInfoBindingsClass["isQuestFinished"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<bool> {
if (rec.mData.mType != ESM::Dialogue::Type::Journal)
{
return sol::nullopt;
}
return (rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Finished);
});
recordInfoBindingsClass["isQuestRestart"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<bool> {
if (rec.mData.mType != ESM::Dialogue::Type::Journal)
{
return sol::nullopt;
}
return (rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Restart);
});
recordInfoBindingsClass["isQuestName"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<bool> {
if (rec.mData.mType != ESM::Dialogue::Type::Journal)
{
return sol::nullopt;
}
return (rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Name);
});
recordInfoBindingsClass["filterActorId"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<std::string> {
if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mActor.empty())
{
return sol::nullopt;
}
return rec.mActor.serializeText();
});
recordInfoBindingsClass["filterActorRace"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<std::string> {
if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mRace.empty())
{
return sol::nullopt;
}
return rec.mRace.serializeText();
});
recordInfoBindingsClass["filterActorClass"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<std::string> {
if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mClass.empty())
{
return sol::nullopt;
}
return rec.mClass.serializeText();
});
recordInfoBindingsClass["filterActorFaction"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<std::string> {
if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mFaction.empty())
{
return sol::nullopt;
}
if (rec.mFactionLess)
{
return sol::optional<std::string>("");
}
return rec.mFaction.serializeText();
});
recordInfoBindingsClass["filterActorFactionRank"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<int> {
if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mData.mRank == -1)
{
return sol::nullopt;
}
return rec.mData.mRank + 1;
});
recordInfoBindingsClass["filterPlayerCell"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<std::string> {
if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mCell.empty())
{
return sol::nullopt;
}
return rec.mCell.serializeText();
});
recordInfoBindingsClass["filterActorDisposition"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<int> {
if (rec.mData.mType == ESM::Dialogue::Type::Journal)
{
return sol::nullopt;
}
return rec.mData.mDisposition;
});
recordInfoBindingsClass["filterActorGender"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<std::string_view> {
if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mData.mGender == -1)
{
return sol::nullopt;
}
return sol::optional<std::string_view>(rec.mData.mGender == 0 ? "male" : "female");
});
recordInfoBindingsClass["filterPlayerFaction"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<std::string> {
if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mPcFaction.empty())
{
return sol::nullopt;
}
return rec.mPcFaction.serializeText();
});
recordInfoBindingsClass["filterPlayerFactionRank"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<int> {
if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mData.mPCrank == -1)
{
return sol::nullopt;
}
return rec.mData.mPCrank + 1;
});
recordInfoBindingsClass["sound"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<std::string> {
if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mSound.empty())
{
return sol::nullopt;
}
return Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(rec.mSound)).value();
});
recordInfoBindingsClass["resultScript"]
= sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional<std::string_view> {
if (rec.mResultScript.empty())
{
return sol::nullopt;
}
return sol::optional<std::string_view>(rec.mResultScript);
});
}
void prepareBindingsForDialogueRecords(sol::state_view& lua)
{
prepareBindingsForDialogueRecord(lua);
prepareBindingsForDialogueRecordInfoList(lua);
prepareBindingsForDialogueRecordInfoListElement(lua);
}
}
namespace sol
{
template <ESM::Dialogue::Type filter>
struct is_automagical<FilteredDialogueStore<filter>> : std::false_type
{
};
template <>
struct is_automagical<ESM::Dialogue> : std::false_type
{
};
template <>
struct is_automagical<DialogueInfos> : std::false_type
{
};
template <>
struct is_automagical<ESM::DialInfo> : std::false_type
{
};
}
namespace MWLua
{
sol::table initCoreDialogueBindings(const Context& context)
{
sol::state_view& lua = context.mLua->sol();
sol::table api(lua, sol::create);
sol::table journalTable(lua, sol::create);
sol::table topicTable(lua, sol::create);
sol::table greetingTable(lua, sol::create);
sol::table persuasionTable(lua, sol::create);
sol::table voiceTable(lua, sol::create);
prepareBindingsForDialogueRecordStores<ESM::Dialogue::Type::Journal>(journalTable, context);
prepareBindingsForDialogueRecordStores<ESM::Dialogue::Type::Topic>(topicTable, context);
prepareBindingsForDialogueRecordStores<ESM::Dialogue::Type::Greeting>(greetingTable, context);
prepareBindingsForDialogueRecordStores<ESM::Dialogue::Type::Persuasion>(persuasionTable, context);
prepareBindingsForDialogueRecordStores<ESM::Dialogue::Type::Voice>(voiceTable, context);
api["journal"] = LuaUtil::makeStrictReadOnly(journalTable);
api["topic"] = LuaUtil::makeStrictReadOnly(topicTable);
api["greeting"] = LuaUtil::makeStrictReadOnly(greetingTable);
api["persuasion"] = LuaUtil::makeStrictReadOnly(persuasionTable);
api["voice"] = LuaUtil::makeStrictReadOnly(voiceTable);
prepareBindingsForDialogueRecords(lua);
return LuaUtil::makeReadOnly(api);
}
}

@ -0,0 +1,12 @@
#ifndef MWLUA_DIALOGUEBINDINGS_H
#define MWLUA_DIALOGUEBINDINGS_H
#include <sol/forward.hpp>
namespace MWLua
{
struct Context;
sol::table initCoreDialogueBindings(const Context&);
}
#endif // MWLUA_DIALOGUEBINDINGS_H

@ -925,6 +925,193 @@
-- @field #string failureSound VFS path to the failure sound
-- @field #string hitSound VFS path to the hit sound
--- @{#Dialogue}: Dialogue
-- @field [parent=#core] #Dialogue dialogue
---
-- @{#DialogueRecords} functions for journal (quest) read-only records.
-- @field [parent=#Dialogue] journal
-- @usage --print the name of the record, which is a capitalized version of its id
-- print(core.dialogue.journal.records["ms_fargothring"].name) -- MS_FargothRing
-- @usage --print ids of all journal records
-- for _, journalRecord in pairs(core.dialogue.journal.records) do
-- print(journalRecord.id)
-- end
-- @usage --print quest names for all quests the player has inside a player script
-- for _, quest in pairs(types.Player.quests(self)) do
-- print(quest.id, core.dialogue.journal.records[quest.id].questName)
-- end
---
-- @{#DialogueRecords} functions for topic read-only records.
-- @field [parent=#Dialogue] topic
-- @usage --print ids of all topic records
-- for _, topicRecord in pairs(core.dialogue.topic.records) do
-- print(topicRecord.id)
-- end
-- @usage --print all NPC lines for "vivec"
-- for idx, topicInfo in pairs(core.dialogue.topic.records["vivec"].infos) do
-- print(idx, topicInfo.text)
-- end
---
-- @{#DialogueRecords} functions for voice read-only records.
-- @field [parent=#Dialogue] voice
-- @usage --print ids of all voice records
-- for _, voiceRecord in pairs(core.dialogue.voice.records) do
-- print(voiceRecord.id)
-- end
-- @usage --print all NPC lines & sounds for "flee"
-- for idx, voiceInfo in pairs(core.dialogue.voice.records["flee"].infos) do
-- print(idx, voiceInfo.text, voiceInfo.sound)
-- end
---
-- @{#DialogueRecords} functions for greeting read-only records.
-- @field [parent=#Dialogue] greeting
-- @usage --print ids of all greeting records
-- for _, greetingRecord in pairs(core.dialogue.greeting.records) do
-- print(greetingRecord.id)
-- end
-- @usage --print all NPC lines for "greeting 0"
-- for idx, greetingInfo in pairs(core.dialogue.greeting.records["greeting 0"].infos) do
-- print(idx, greetingInfo.text)
-- end
---
-- @{#DialogueRecords} functions for persuasion read-only records.
-- @field [parent=#Dialogue] persuasion
-- @usage --print ids of all persuasion records
-- for _, persuasionRecord in pairs(core.dialogue.persuasion.records) do
-- print(persuasionRecord.id)
-- end
-- @usage --print all NPC lines for "admire success"
-- for idx, persuasionInfo in pairs(core.dialogue.persuasion.records["admire success"].infos) do
-- print(idx, persuasionInfo.text)
-- end
---
-- A read-only list of all @{#DialogueRecord}s in the world database, may be indexed by recordId, which doesn't have to be lowercase.
-- Implements [iterables#List](iterables.html#list-iterable) of #DialogueRecord.
-- @field [parent=#DialogueRecords] #list<#DialogueRecord> records
-- @usage local record = core.dialogue.journal.records['ms_fargothring']
-- @usage local record = core.dialogue.journal.records['MS_FargothRing']
-- @usage local record = core.dialogue.journal.records[1]
-- @usage local record = core.dialogue.topic.records[1]
-- @usage local record = core.dialogue.topic.records['background']
-- @usage local record = core.dialogue.greeting.records[1]
-- @usage local record = core.dialogue.greeting.records['greeting 0']
-- @usage local record = core.dialogue.persuasion.records[1]
-- @usage local record = core.dialogue.persuasion.records['admire success']
-- @usage local record = core.dialogue.voice.records[1]
-- @usage local record = core.dialogue.voice.records["flee"]
---
-- Depending on which store this read-only dialogue record is from, it may either be a journal, topic, greeting, persuasion or voice.
-- @type DialogueRecord
-- @field #string id Record identifier
-- @field #string name Same as id, but with upper cases preserved.
-- @field #string questName Non-nil only for journal records with available value. Holds the quest name for this journal entry. Same info may be available under `infos[1].text` as well, but this variable is made for convenience.
-- @field #list<#DialogueRecordInfo> infos A read-only list containing all @{#DialogueRecordInfo}s for this record, in order.
-- @usage local journalId = core.dialogue.journal.records['A2_4_MiloGone'].id -- "a2_4_milogone"
-- @usage local journalName = core.dialogue.journal.records['A2_4_MiloGone'].name -- "A2_4_MiloGone"
-- @usage local questName = core.dialogue.journal.records['A2_4_MiloGone'].questName -- "Mehra Milo and the Lost Prophecies"
---
-- Holds the read-only data for one of many info entries inside a dialogue record. Depending on the type of the dialogue record (journal, topic, greeting, persuasion or voice), it could be, for example, a single journal entry or a NPC dialogue line.
-- @type DialogueRecordInfo
-- @field #string id Identifier for this info entry. Is unique only within the @{#DialogueRecord} it belongs to.
-- @field #string text Text associated with this info entry.
-- @usage --Variable `aa` below is "Congratulations, %PCName. You are now %PCName the %NextPCRank." in vanilla MW:
-- local aa = core.dialogue.topic.records['advancement'].infos[100].text
-- @usage --Variable `bb` below is "sound/vo/a/f/fle_af003.mp3" in vanilla MW:
-- local bb = core.dialogue.voice.records['flee'].infos[149].sound
---
-- Quest stage (same as in @{openmw_types#PlayerQuest.stage}) this info entry is associated with.
-- Non-nil only for journal records.
-- @field [parent=#DialogueRecordInfo] #number questStage
---
-- True if this info entry has the "Finished" flag checked.
-- Non-nil only for journal records.
-- @field [parent=#DialogueRecordInfo] #boolean isQuestFinished
---
-- True if this info entry has the "Restart" flag checked.
-- Non-nil only for journal records.
-- @field [parent=#DialogueRecordInfo] #boolean isQuestRestart
---
-- True if this info entry has the "Quest Name" flag checked.
-- Non-nil only for journal records.
-- If true, then the @{#DialogueRecord}, to which this info entry belongs, should have this info entry's @{#DialogueRecordInfo.text} value available in its @{#DialogueRecord.questName}.
-- @field [parent=#DialogueRecordInfo] #boolean isQuestName
---
-- Faction of which the speaker must be a member for this info entry to appear.
-- Always nil for journal records. Otherwise the nil value represents no conditions, i.e. no filtering applied using these criteria.
-- Can return an empty string - this means that the actor must not be a member of any faction for this filtering to apply.
-- @field [parent=#DialogueRecordInfo] #string filterActorFaction
---
-- Speaker ID allowing for this info entry to appear.
-- Always nil for journal records. Otherwise the nil value represents no conditions, i.e. no filtering applied using these criteria.
-- @field [parent=#DialogueRecordInfo] #string filterActorId
---
-- Speaker race allowing for this info entry to appear.
-- Always nil for journal records. Otherwise the nil value represents no conditions, i.e. no filtering applied using these criteria.
-- @field [parent=#DialogueRecordInfo] #string filterActorRace
---
-- Speaker class allowing for this info entry to appear.
-- Always nil for journal records. Otherwise the nil value represents no conditions, i.e. no filtering applied using these criteria.
-- @field [parent=#DialogueRecordInfo] #string filterActorClass
---
-- Minimum speaker's rank in their faction allowing for this info entry to appear.
-- Always nil for journal records. Otherwise the nil value represents no conditions, i.e. no filtering applied using these criteria.
-- Rank index starts from 1, matching the value in @{openmw_types#NPC.getFactionRank}
-- @field [parent=#DialogueRecordInfo] #number filterActorFactionRank
---
-- Cell name prefix of location where the player must be for this info entry to appear.
-- Always nil for journal records. Otherwise the nil value represents no conditions, i.e. no filtering applied using these criteria.
-- "Prefix" means that the cell's name starting with this value shall pass the filtering. For example: `filterPlayerCell` being "Seyda Neen" does apply to the cell "Seyda Neen, Fargoth's House".
-- @field [parent=#DialogueRecordInfo] #string filterPlayerCell
---
-- Minimum speaker disposition allowing for this info entry to appear.
-- Always nil for journal records. Otherwise is a nonnegative number, with the zero value representing no conditions, i.e. no filtering applied using these criteria.
-- @field [parent=#DialogueRecordInfo] #number filterActorDisposition
---
-- Speaker gender allowing for this info entry to appear: "male" or "female".
-- Always nil for journal records. Otherwise the nil value represents no conditions, i.e. no filtering applied using these criteria.
-- @field [parent=#DialogueRecordInfo] #string filterActorGender
---
-- Faction of which the player must be a member for this info entry to appear.
-- Always nil for journal records. Otherwise the nil value represents no conditions, i.e. no filtering applied using these criteria.
-- @field [parent=#DialogueRecordInfo] #string filterPlayerFaction
---
-- Minimum player's rank in their faction allowing for this info entry to appear.
-- Always nil for journal records. Otherwise the nil value represents no conditions, i.e. no filtering applied using these criteria.
-- Rank index starts from 1, matching the value in @{openmw_types#NPC.getFactionRank}
-- @field [parent=#DialogueRecordInfo] #number filterPlayerFactionRank
---
-- Sound file path for this info entry.
-- Always nil for journal records or if there is no sound set.
-- @field [parent=#DialogueRecordInfo] #string sound
---
-- MWScript (full script text) executed when this info is chosen.
-- Always nil for journal records or if there is no value set.
-- @field [parent=#DialogueRecordInfo] #string resultScript
--- @{#Factions}: Factions
-- @field [parent=#core] #Factions factions

Loading…
Cancel
Save