diff --git a/apps/openmw/mwlua/dialoguebindings.cpp b/apps/openmw/mwlua/dialoguebindings.cpp index 0457ac7bce..f9c60d8ba6 100644 --- a/apps/openmw/mwlua/dialoguebindings.cpp +++ b/apps/openmw/mwlua/dialoguebindings.cpp @@ -1,7 +1,10 @@ #include "dialoguebindings.hpp" +#include +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/store.hpp" #include "context.hpp" -#include "recordstore.hpp" +#include "object.hpp" #include #include #include @@ -9,79 +12,384 @@ namespace { - sol::table prepareJournalRecord(sol::state_view& lua, const ESM::Dialogue& mwDialogue) + template + class FilteredDialogueStore { - const auto dialogueRecordId = mwDialogue.mId.serializeText(); - sol::table result(lua, sol::create); - result["text"] = mwDialogue.mStringId; + const MWWorld::Store& mDialogueStore; - sol::table preparedInfos(lua, sol::create); - unsigned index = 1; - for (const auto& mwDialogueInfo : mwDialogue.mInfo) + const ESM::Dialogue* foundDialogueFilteredOut(const ESM::Dialogue* possibleResult) const { - if (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Name) + if (possibleResult and possibleResult->mType == filter) { - result["questName"] = mwDialogueInfo.mResponse; - continue; + return possibleResult; } - sol::table infoElement(lua, sol::create); - infoElement["id"] = (dialogueRecordId + '#' + mwDialogueInfo.mId.serializeText()); - infoElement["text"] = mwDialogueInfo.mResponse; - infoElement["questStage"] = mwDialogueInfo.mData.mJournalIndex; - infoElement["questFinished"] = (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Finished); - infoElement["questRestart"] = (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Restart); - preparedInfos[index++] = infoElement; + return nullptr; + } + public: + FilteredDialogueStore() + : mDialogueStore{MWBase::Environment::get().getESMStore()->get()} + {} + + class FilteredDialogueIterator + { + using DecoratedIterator = MWWorld::Store::iterator; + DecoratedIterator mIter; + public: + FilteredDialogueIterator(const DecoratedIterator& decoratedIterator) + : mIter{decoratedIterator} + {} + + FilteredDialogueIterator& operator++() + { + do + { + std::advance(mIter, 1); + } while (mIter->mType != filter); + return *this; + } + + FilteredDialogueIterator operator++(int) + { + FilteredDialogueIterator iter = *this; + do + { + std::advance(mIter, 1); + } while (mIter->mType != filter); + return iter; + } + + bool operator==(const FilteredDialogueIterator& x) const { return mIter == x.mIter; } + + bool operator!=(const FilteredDialogueIterator& x) const { return not (*this == x); } + + const ESM::Dialogue& operator*() const { return *mIter; } + + const ESM::Dialogue* operator->() const { return &(*mIter); } + }; + using iterator = FilteredDialogueIterator; + + const ESM::Dialogue* search(const ESM::RefId& id) const + { + return foundDialogueFilteredOut(mDialogueStore.search(id)); } - result["infos"] = LuaUtil::makeStrictReadOnly(preparedInfos); - return result; - } - - sol::table prepareNonJournalRecord(sol::state_view& lua, const ESM::Dialogue& mwDialogue) - { - const auto dialogueRecordId = mwDialogue.mId.serializeText(); - sol::table result(lua, sol::create); - result["text"] = mwDialogue.mStringId; - - sol::table preparedInfos(lua, sol::create); - unsigned index = 1; - for (const auto& mwDialogueInfo : mwDialogue.mInfo) + const ESM::Dialogue* at(size_t index) const { - sol::table infoElement(lua, sol::create); - infoElement["id"] = (dialogueRecordId + '#' + mwDialogueInfo.mId.serializeText()); - infoElement["text"] = mwDialogueInfo.mResponse; - infoElement["actorId"] = mwDialogueInfo.mActor.serializeText(); - infoElement["actorRace"] = mwDialogueInfo.mRace.serializeText(); - infoElement["actorClass"] = mwDialogueInfo.mClass.serializeText(); - infoElement["actorFaction"] = mwDialogueInfo.mFaction.serializeText(); - if (mwDialogueInfo.mData.mRank != -1) + if (index >= getSize()) { - infoElement["actorFactionRank"] = mwDialogueInfo.mData.mRank; + return nullptr; } - infoElement["actorCell"] = mwDialogueInfo.mClass.serializeText(); - infoElement["actorDisposition"] = mwDialogueInfo.mData.mDisposition; - if (mwDialogueInfo.mData.mGender != ESM::DialInfo::Gender::NA) - { - infoElement["actorGender"] = mwDialogueInfo.mData.mGender; - } - infoElement["playerFaction"] = mwDialogueInfo.mPcFaction.serializeText(); - if (mwDialogueInfo.mData.mPCrank != -1) - { - infoElement["playerFactionRank"] = mwDialogueInfo.mData.mPCrank; - } - if (not mwDialogueInfo.mSound.empty()) - { - infoElement["sound"] - = Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(mwDialogueInfo.mSound)).value(); - } - // mResultScript TODO - // mSelects TODO - preparedInfos[index++] = infoElement; + + auto result = begin(); + std::advance(result, index); + + return &(*result); } - result["infos"] = LuaUtil::makeStrictReadOnly(preparedInfos); - return result; + size_t getSize() const + { + return std::count_if(mDialogueStore.begin(), mDialogueStore.end(), [this](const auto& d) { return d.mType == filter; }); + } + + iterator begin() const + { + iterator result{mDialogueStore.begin()}; + while (result != end() and result->mType != filter) + { + std::advance(result, 1); + } + return result; + } + + iterator end() const + { + return iterator{mDialogueStore.end()}; + } + }; + + template + void prepareBindingsForDialogueRecordStores(sol::table& table, const MWLua::Context& context) + { + using StoreT = FilteredDialogueStore; + + table["record"] = sol::overload( + [](const MWLua::Object& obj) -> const ESM::Dialogue* { return obj.ptr().get()->mBase; }, + [](std::string_view id) -> const ESM::Dialogue* + { + return StoreT{}.search(ESM::RefId::deserializeText(Misc::StringUtils::lowerCase(id))); + }); + + sol::state_view& lua = context.mLua->sol(); + sol::usertype storeBindingsClass = lua.new_usertype("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 or index > store.getSize()) + { + return nullptr; + } + return store.at(index - 1); + }, + [](const StoreT& store, std::string_view id) -> const ESM::Dialogue* + { + return store.search(ESM::RefId::deserializeText(Misc::StringUtils::lowerCase(id))); + }); + storeBindingsClass[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + storeBindingsClass[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + + table["records"] = StoreT{}; } + + struct DialogueInfos + { + ESM::RefId mDialogueRecordId; + + const ESM::Dialogue* getDialogueRecord() const + { + const auto& dialogueStore{MWBase::Environment::get().getESMStore()->get()}; + return dialogueStore.search(mDialogueRecordId); + } + }; + + void prepareBindingsForDialogueRecord(sol::state_view& lua) + { + auto recordBindingsClass = lua.new_usertype("ESM3_Dialogue"); + recordBindingsClass[sol::meta_function::to_string] = [lua](const ESM::Dialogue& rec) -> sol::object + { + return sol::make_object(lua, "ESM3_Dialogue[" + rec.mId.toDebugString() + "]"); + }; + 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 + { + 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; + }); + recordBindingsClass["infos"] = sol::readonly_property([lua](const ESM::Dialogue& rec) + { + return DialogueInfos{rec.mId}; + }); + } + + void prepareBindingsForDialogueRecordInfoList(sol::state_view& lua) + { + auto recordInfosBindingsClass = lua.new_usertype("ESM3_Dialogue_Infos"); + recordInfosBindingsClass[sol::meta_function::to_string] = [lua](const DialogueInfos& store) -> sol::object + { + if (const ESM::Dialogue* dialogueRecord = store.getDialogueRecord()) + { + return sol::make_object(lua, "{" + std::to_string(dialogueRecord->mInfo.size()) + " ESM3_Dialogue[" + dialogueRecord->mId.toDebugString() + "] info elements}"); + } + return sol::nil; + }; + recordInfosBindingsClass[sol::meta_function::length] = [](const DialogueInfos& store) + { + const ESM::Dialogue* dialogueRecord = store.getDialogueRecord(); + return dialogueRecord ? dialogueRecord->mInfo.size() : 0; + }; + recordInfosBindingsClass[sol::meta_function::index] = [](const DialogueInfos& store, size_t index) -> const ESM::DialInfo* + { + const ESM::Dialogue* dialogueRecord = store.getDialogueRecord(); + if (not dialogueRecord or index == 0 or 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(); + recordInfosBindingsClass[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + } + + void prepareBindingsForDialogueRecordInfoListElement(sol::state_view& lua) + { + auto recordInfoBindingsClass = lua.new_usertype("ESM3_Dialogue_Info"); + + recordInfoBindingsClass[sol::meta_function::to_string] = [lua](const ESM::DialInfo& rec) + { + return sol::make_object(lua, "ESM3_Dialogue_Info[" + rec.mId.toDebugString() + "]"); + }; + 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 + { + if (rec.mData.mType != ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + return sol::make_object(lua, rec.mData.mJournalIndex); + }); + recordInfoBindingsClass["questFinished"] = 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["questRestart"] = 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["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 + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal or rec.mSound == "") + { + return sol::nil; + } + return sol::make_object(lua, Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(rec.mSound)).value()); + }); + // // mResultScript TODO + // // mSelects TODO + } + + void prepareBindingsForDialogueRecords(sol::state_view& lua) + { + prepareBindingsForDialogueRecord(lua); + prepareBindingsForDialogueRecordInfoList(lua); + prepareBindingsForDialogueRecordInfoListElement(lua); + } +} + +namespace sol +{ + template + struct is_automagical> : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -92,43 +400,23 @@ namespace MWLua sol::state_view& lua = context.mLua->sol(); sol::table api(lua, sol::create); - const MWWorld::Store& mwDialogueStore - = MWBase::Environment::get().getESMStore()->get(); + 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(journalTable, context); + prepareBindingsForDialogueRecordStores(topicTable, context); + prepareBindingsForDialogueRecordStores(greetingTable, context); + prepareBindingsForDialogueRecordStores(persuasionTable, context); + prepareBindingsForDialogueRecordStores(voiceTable, context); + api["journal"] = journalTable; + api["topic"] = topicTable; + api["greeting"] = greetingTable; + api["persuasion"] = persuasionTable; + api["voice"] = voiceTable; - sol::table journalRecordsByQuestId(lua, sol::create); - sol::table topicRecordsByTopicId(lua, sol::create); - sol::table voiceRecordsById(lua, sol::create); - sol::table greetingRecordsById(lua, sol::create); - sol::table persuasionRecordsById(lua, sol::create); - for (const auto& mwDialogue : mwDialogueStore) - { - const auto dialogueRecordId = mwDialogue.mId.serializeText(); - if (mwDialogue.mType == ESM::Dialogue::Type::Journal) - { - journalRecordsByQuestId[dialogueRecordId] = prepareJournalRecord(lua, mwDialogue); - } - else if (mwDialogue.mType == ESM::Dialogue::Type::Topic) - { - topicRecordsByTopicId[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); - } - else if (mwDialogue.mType == ESM::Dialogue::Type::Voice) - { - voiceRecordsById[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); - } - else if (mwDialogue.mType == ESM::Dialogue::Type::Greeting) - { - greetingRecordsById[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); - } - else if (mwDialogue.mType == ESM::Dialogue::Type::Persuasion) - { - persuasionRecordsById[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); - } - } - api["journalRecords"] = LuaUtil::makeStrictReadOnly(journalRecordsByQuestId); - api["topicRecords"] = LuaUtil::makeStrictReadOnly(topicRecordsByTopicId); - api["voiceRecords"] = LuaUtil::makeStrictReadOnly(voiceRecordsById); - api["greetingRecords"] = LuaUtil::makeStrictReadOnly(greetingRecordsById); - api["persuasionRecords"] = LuaUtil::makeStrictReadOnly(persuasionRecordsById); + prepareBindingsForDialogueRecords(lua); return LuaUtil::makeReadOnly(api); }