1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-06 09:39:49 +00:00
OpenMW/apps/openmw/mwlua/dialoguebindings.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

433 lines
18 KiB
C++
Raw Normal View History

2024-04-15 22:01:41 +02:00
#include "dialoguebindings.hpp"
2024-04-20 14:28:20 +02:00
#include "apps/openmw/mwbase/environment.hpp"
#include "apps/openmw/mwworld/esmstore.hpp"
2024-04-15 22:10:18 +02:00
#include "apps/openmw/mwworld/store.hpp"
2024-04-15 22:01:41 +02:00
#include "context.hpp"
2024-04-20 14:28:20 +02:00
#include "object.hpp"
2024-04-20 15:47:34 +02:00
#include <algorithm>
2024-04-15 22:01:41 +02:00
#include <components/esm3/loaddial.hpp>
#include <components/lua/luastate.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/vfs/pathutil.hpp>
namespace
{
2024-04-20 14:28:20 +02:00
template <ESM::Dialogue::Type filter>
class FilteredDialogueStore
2024-04-15 22:01:41 +02:00
{
2024-04-20 14:28:20 +02:00
const MWWorld::Store<ESM::Dialogue>& mDialogueStore;
2024-04-15 22:01:41 +02:00
2024-04-20 14:28:20 +02:00
const ESM::Dialogue* foundDialogueFilteredOut(const ESM::Dialogue* possibleResult) const
{
2024-04-27 13:44:08 +02:00
if (possibleResult && possibleResult->mType == filter)
2024-04-20 14:28:20 +02:00
{
return possibleResult;
}
return nullptr;
}
2024-04-20 15:47:34 +02:00
2024-04-20 14:28:20 +02:00
public:
FilteredDialogueStore()
2024-04-20 15:47:34 +02:00
: mDialogueStore{ MWBase::Environment::get().getESMStore()->get<ESM::Dialogue>() }
{
}
2024-04-15 22:01:41 +02:00
2024-04-20 14:28:20 +02:00
class FilteredDialogueIterator
2024-04-15 22:01:41 +02:00
{
2024-04-20 14:28:20 +02:00
using DecoratedIterator = MWWorld::Store<ESM::Dialogue>::iterator;
DecoratedIterator mIter;
2024-04-20 20:23:37 +02:00
DecoratedIterator mEndIter;
2024-04-20 15:47:34 +02:00
2024-04-20 14:28:20 +02:00
public:
2024-04-20 19:53:56 +02:00
using iterator_category = DecoratedIterator::iterator_category;
using value_type = DecoratedIterator::value_type;
2024-04-20 17:44:36 +02:00
using difference_type = DecoratedIterator::difference_type;
2024-04-20 19:53:56 +02:00
using pointer = DecoratedIterator::pointer;
using reference = DecoratedIterator::reference;
2024-04-20 17:44:36 +02:00
2024-04-20 20:23:37 +02:00
FilteredDialogueIterator(const DecoratedIterator& pointingIterator, const DecoratedIterator& end)
: mIter{ pointingIterator }
, mEndIter{ end }
2024-04-20 15:47:34 +02:00
{
}
2024-04-20 14:28:20 +02:00
FilteredDialogueIterator& operator++()
2024-04-15 22:01:41 +02:00
{
2024-04-20 14:28:20 +02:00
do
{
2024-04-20 20:23:37 +02:00
++mIter;
2024-04-27 13:44:08 +02:00
} while (mIter->mType != filter && mIter != mEndIter);
2024-04-20 14:28:20 +02:00
return *this;
2024-04-15 22:01:41 +02:00
}
2024-04-20 14:28:20 +02:00
FilteredDialogueIterator operator++(int)
2024-04-15 22:01:41 +02:00
{
2024-04-20 14:28:20 +02:00
FilteredDialogueIterator iter = *this;
do
{
2024-04-20 20:23:37 +02:00
++mIter;
2024-04-27 13:44:08 +02:00
} while (mIter->mType != filter && mIter != mEndIter);
2024-04-20 14:28:20 +02:00
return iter;
2024-04-15 22:01:41 +02:00
}
2024-04-20 14:28:20 +02:00
2024-04-20 19:53:56 +02:00
FilteredDialogueIterator& operator+=(difference_type advance)
{
2024-04-27 13:44:08 +02:00
while (advance > 0 && mIter != mEndIter)
2024-04-20 19:53:56 +02:00
{
2024-04-20 20:23:37 +02:00
++mIter;
if (mIter->mType == filter)
2024-04-20 19:53:56 +02:00
{
--advance;
}
}
return *this;
}
2024-04-20 14:28:20 +02:00
bool operator==(const FilteredDialogueIterator& x) const { return mIter == x.mIter; }
2024-04-27 13:44:08 +02:00
bool operator!=(const FilteredDialogueIterator& x) const { return !(*this == x); }
2024-04-20 14:28:20 +02:00
2024-04-20 19:53:56 +02:00
const value_type& operator*() const { return *mIter; }
2024-04-20 14:28:20 +02:00
2024-04-20 19:53:56 +02:00
const value_type* operator->() const { return &(*mIter); }
2024-04-20 14:28:20 +02:00
};
2024-04-20 19:53:56 +02:00
2024-04-20 14:28:20 +02:00
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
{
if (index >= getSize())
2024-04-15 22:01:41 +02:00
{
2024-04-20 14:28:20 +02:00
return nullptr;
2024-04-15 22:01:41 +02:00
}
2024-04-20 14:28:20 +02:00
auto result = begin();
2024-04-20 20:23:37 +02:00
result += index;
2024-04-20 14:28:20 +02:00
return &(*result);
}
size_t getSize() const
{
2024-04-20 15:47:34 +02:00
return std::count_if(
2024-04-20 17:44:36 +02:00
mDialogueStore.begin(), mDialogueStore.end(), [](const auto& d) { return d.mType == filter; });
2024-04-20 14:28:20 +02:00
}
iterator begin() const
{
2024-04-20 20:23:37 +02:00
iterator result{ mDialogueStore.begin(), mDialogueStore.end() };
2024-04-27 13:44:08 +02:00
while (result != end() && result->mType != filter)
2024-04-15 22:01:41 +02:00
{
2024-04-20 20:23:37 +02:00
++result;
2024-04-15 22:01:41 +02:00
}
2024-04-20 14:28:20 +02:00
return result;
}
2024-04-20 20:23:37 +02:00
iterator end() const { return iterator{ mDialogueStore.end(), mDialogueStore.end() }; }
2024-04-20 14:28:20 +02:00
};
template <ESM::Dialogue::Type filter>
void prepareBindingsForDialogueRecordStores(sol::table& table, const MWLua::Context& context)
{
using StoreT = FilteredDialogueStore<filter>;
2024-04-15 22:01:41 +02:00
2024-04-20 14:28:20 +02:00
sol::state_view& lua = context.mLua->sol();
2024-04-20 15:47:34 +02:00
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) {
2024-04-20 14:28:20 +02:00
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(
2024-04-20 15:47:34 +02:00
[](const StoreT& store, size_t index) -> const ESM::Dialogue* {
2024-04-27 13:44:08 +02:00
if (index == 0 || index > store.getSize())
2024-04-20 14:28:20 +02:00
{
return nullptr;
}
return store.at(index - 1);
},
2024-04-20 15:47:34 +02:00
[](const StoreT& store, std::string_view id) -> const ESM::Dialogue* {
2024-04-20 14:28:20 +02:00
return store.search(ESM::RefId::deserializeText(Misc::StringUtils::lowerCase(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{};
2024-04-15 22:01:41 +02:00
}
2024-04-20 14:28:20 +02:00
struct DialogueInfos
{
ESM::RefId mDialogueRecordId;
2024-04-15 22:01:41 +02:00
2024-04-20 14:28:20 +02:00
const ESM::Dialogue* getDialogueRecord() const
{
2024-04-20 15:47:34 +02:00
const auto& dialogueStore{ MWBase::Environment::get().getESMStore()->get<ESM::Dialogue>() };
2024-04-20 14:28:20 +02:00
return dialogueStore.search(mDialogueRecordId);
}
};
void prepareBindingsForDialogueRecord(sol::state_view& lua)
2024-04-15 22:01:41 +02:00
{
2024-04-20 14:28:20 +02:00
auto recordBindingsClass = lua.new_usertype<ESM::Dialogue>("ESM3_Dialogue");
2024-04-20 15:47:34 +02:00
recordBindingsClass[sol::meta_function::to_string] = [lua](const ESM::Dialogue& rec) -> sol::object {
2024-04-20 14:28:20 +02:00
return sol::make_object(lua, "ESM3_Dialogue[" + rec.mId.toDebugString() + "]");
};
2024-04-20 15:47:34 +02:00
recordBindingsClass["id"] = sol::readonly_property(
[lua](const ESM::Dialogue& rec) { return sol::make_object(lua, rec.mId.serializeText()); });
recordBindingsClass["name"]
= sol::readonly_property([lua](const ESM::Dialogue& rec) { return sol::make_object(lua, rec.mStringId); });
recordBindingsClass["questName"] = sol::readonly_property([lua](const ESM::Dialogue& rec) -> sol::object {
2024-04-20 14:28:20 +02:00
if (rec.mType != ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
for (const auto& mwDialogueInfo : rec.mInfo)
{
if (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Name)
{
return sol::make_object(lua, mwDialogueInfo.mResponse);
}
}
return sol::nil;
});
2024-04-20 15:47:34 +02:00
recordBindingsClass["infos"]
= sol::readonly_property([lua](const ESM::Dialogue& rec) { return DialogueInfos{ rec.mId }; });
2024-04-20 14:28:20 +02:00
}
2024-04-15 22:01:41 +02:00
2024-04-20 14:28:20 +02:00
void prepareBindingsForDialogueRecordInfoList(sol::state_view& lua)
{
auto recordInfosBindingsClass = lua.new_usertype<DialogueInfos>("ESM3_Dialogue_Infos");
2024-04-20 15:47:34 +02:00
recordInfosBindingsClass[sol::meta_function::to_string] = [lua](const DialogueInfos& store) -> sol::object {
2024-04-20 14:28:20 +02:00
if (const ESM::Dialogue* dialogueRecord = store.getDialogueRecord())
{
2024-04-20 15:47:34 +02:00
return sol::make_object(lua,
"{" + std::to_string(dialogueRecord->mInfo.size()) + " ESM3_Dialogue["
+ dialogueRecord->mId.toDebugString() + "] info elements}");
2024-04-20 14:28:20 +02:00
}
return sol::nil;
};
2024-04-20 15:47:34 +02:00
recordInfosBindingsClass[sol::meta_function::length] = [](const DialogueInfos& store) {
2024-04-20 14:28:20 +02:00
const ESM::Dialogue* dialogueRecord = store.getDialogueRecord();
return dialogueRecord ? dialogueRecord->mInfo.size() : 0;
};
2024-04-20 15:47:34 +02:00
recordInfosBindingsClass[sol::meta_function::index]
= [](const DialogueInfos& store, size_t index) -> const ESM::DialInfo* {
2024-04-20 14:28:20 +02:00
const ESM::Dialogue* dialogueRecord = store.getDialogueRecord();
2024-04-27 13:44:08 +02:00
if (!dialogueRecord || index == 0 || index > dialogueRecord->mInfo.size())
2024-04-20 14:28:20 +02:00
{
return nullptr;
}
2024-04-20 15:47:34 +02:00
ESM::Dialogue::InfoContainer::const_iterator iter{ dialogueRecord->mInfo.cbegin() };
2024-04-20 14:28:20 +02:00
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>();
}
2024-04-15 22:01:41 +02:00
2024-04-20 14:28:20 +02:00
void prepareBindingsForDialogueRecordInfoListElement(sol::state_view& lua)
{
auto recordInfoBindingsClass = lua.new_usertype<ESM::DialInfo>("ESM3_Dialogue_Info");
2024-04-20 15:47:34 +02:00
recordInfoBindingsClass[sol::meta_function::to_string] = [lua](const ESM::DialInfo& rec) {
2024-04-20 14:28:20 +02:00
return sol::make_object(lua, "ESM3_Dialogue_Info[" + rec.mId.toDebugString() + "]");
};
2024-04-20 15:47:34 +02:00
recordInfoBindingsClass["id"] = sol::readonly_property(
[lua](const ESM::DialInfo& rec) { return sol::make_object(lua, rec.mId.serializeText()); });
recordInfoBindingsClass["text"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) { return sol::make_object(lua, rec.mResponse); });
recordInfoBindingsClass["questStage"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
2024-04-20 14:28:20 +02:00
if (rec.mData.mType != ESM::Dialogue::Type::Journal)
2024-04-15 22:01:41 +02:00
{
2024-04-20 14:28:20 +02:00
return sol::nil;
2024-04-15 22:01:41 +02:00
}
2024-04-20 14:28:20 +02:00
return sol::make_object(lua, rec.mData.mJournalIndex);
});
recordInfoBindingsClass["isQuestFinished"]
2024-04-20 15:47:34 +02:00
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType != ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
return sol::make_object(lua, rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Finished);
});
recordInfoBindingsClass["isQuestRestart"]
2024-04-20 15:47:34 +02:00
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType != ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
return sol::make_object(lua, rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Restart);
});
recordInfoBindingsClass["isQuestName"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType != ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
return sol::make_object(lua, rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Name);
});
2024-04-20 15:47:34 +02:00
recordInfoBindingsClass["filterActorId"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType == ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
const auto result = rec.mActor.serializeText();
return result.empty() ? sol::nil : sol::make_object(lua, result);
});
recordInfoBindingsClass["filterActorRace"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType == ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
const auto result = rec.mRace.serializeText();
return result.empty() ? sol::nil : sol::make_object(lua, result);
});
recordInfoBindingsClass["filterActorClass"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType == ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
const auto result = rec.mClass.serializeText();
return result.empty() ? sol::nil : sol::make_object(lua, result);
});
recordInfoBindingsClass["filterActorFaction"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType == ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
const auto result = rec.mFaction.serializeText();
return result.empty() ? sol::nil : sol::make_object(lua, result);
});
recordInfoBindingsClass["filterActorFactionRank"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType == ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
const auto result = rec.mData.mRank;
return result == -1 ? sol::nil : sol::make_object(lua, result);
});
recordInfoBindingsClass["filterActorCell"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType == ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
const auto result = rec.mCell.serializeText();
return result.empty() ? sol::nil : sol::make_object(lua, result);
});
recordInfoBindingsClass["filterActorDisposition"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType == ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
return sol::make_object(lua, rec.mData.mDisposition);
});
recordInfoBindingsClass["filterActorGender"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType == ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
const auto result = rec.mData.mGender;
return result == -1 ? sol::nil : sol::make_object(lua, result);
});
recordInfoBindingsClass["filterPlayerFaction"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType == ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
const auto result = rec.mPcFaction.serializeText();
return result.empty() ? sol::nil : sol::make_object(lua, result);
});
recordInfoBindingsClass["filterPlayerFactionRank"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
if (rec.mData.mType == ESM::Dialogue::Type::Journal)
{
return sol::nil;
}
const auto result = rec.mData.mPCrank;
return result == -1 ? sol::nil : sol::make_object(lua, result);
});
recordInfoBindingsClass["sound"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
2024-04-27 13:44:08 +02:00
if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mSound == "")
2024-04-20 14:28:20 +02:00
{
return sol::nil;
}
2024-04-20 15:47:34 +02:00
return sol::make_object(
lua, Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(rec.mSound)).value());
2024-04-20 14:28:20 +02:00
});
2024-04-20 15:47:34 +02:00
recordInfoBindingsClass["resultScript"]
= sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object {
return rec.mResultScript.empty() ? sol::nil : sol::make_object(lua, rec.mResultScript);
});
2024-04-20 14:28:20 +02:00
}
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"] = journalTable;
api["topic"] = topicTable;
api["greeting"] = greetingTable;
api["persuasion"] = persuasionTable;
api["voice"] = voiceTable;
prepareBindingsForDialogueRecords(lua);
2024-04-15 22:01:41 +02:00
return LuaUtil::makeReadOnly(api);
}
}