#include "dialoguebindings.hpp" #include "context.hpp" #include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/store.hpp" #include #include #include #include #include namespace { std::vector makeIndex(const MWWorld::Store& store, ESM::Dialogue::Type type) { std::vector result; for (const ESM::Dialogue& v : store) if (v.mType == type) result.push_back(&v); return result; } template class FilteredDialogueStore { const MWWorld::Store& mDialogueStore; std::vector mIndex; public: explicit FilteredDialogueStore(const MWWorld::Store& store) : mDialogueStore(store) , mIndex{ makeIndex(store, type) } { } const ESM::Dialogue* search(const ESM::RefId& id) const { const ESM::Dialogue* dialogue = mDialogueStore.search(id); if (dialogue != nullptr && dialogue->mType == type) return dialogue; return nullptr; } const ESM::Dialogue* at(std::size_t index) const { if (index >= mIndex.size()) return nullptr; return mIndex[index]; } std::size_t getSize() const { return mIndex.size(); } }; template void prepareBindingsForDialogueRecordStores(sol::table& table, const MWLua::Context& context) { using StoreT = FilteredDialogueStore; sol::state_view lua = context.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) { return nullptr; } return store.at(LuaUtil::fromLuaIndex(index)); }, [](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(); storeBindingsClass[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); table["records"] = StoreT{ MWBase::Environment::get().getESMStore()->get() }; } struct DialogueInfos { const ESM::Dialogue& parentDialogueRecord; }; void prepareBindingsForDialogueRecord(sol::state_view& lua) { auto recordBindingsClass = lua.new_usertype("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 { 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(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("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, LuaUtil::fromLuaIndex(index)); 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] = [](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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mFaction.empty()) { return sol::nullopt; } if (rec.mFactionLess) { return sol::optional(""); } return rec.mFaction.serializeText(); }); recordInfoBindingsClass["filterActorFactionRank"] = sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional { if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mData.mRank == -1) { return sol::nullopt; } return LuaUtil::toLuaIndex(rec.mData.mRank); }); recordInfoBindingsClass["filterPlayerCell"] = sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional { 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 { 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 { if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mData.mGender == -1) { return sol::nullopt; } return sol::optional(rec.mData.mGender == 0 ? "male" : "female"); }); recordInfoBindingsClass["filterPlayerFaction"] = sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional { 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 { if (rec.mData.mType == ESM::Dialogue::Type::Journal || rec.mData.mPCrank == -1) { return sol::nullopt; } return LuaUtil::toLuaIndex(rec.mData.mPCrank); }); recordInfoBindingsClass["sound"] = sol::readonly_property([](const ESM::DialInfo& rec) -> sol::optional { 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 { if (rec.mResultScript.empty()) { return sol::nullopt; } return sol::optional(rec.mResultScript); }); } 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 { sol::table initCoreDialogueBindings(const Context& context) { sol::state_view lua = context.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(journalTable, context); prepareBindingsForDialogueRecordStores(topicTable, context); prepareBindingsForDialogueRecordStores(greetingTable, context); prepareBindingsForDialogueRecordStores(persuasionTable, context); prepareBindingsForDialogueRecordStores(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); } }