1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-29 09:32:45 +00:00
OpenMW/apps/openmw/mwlua/dialoguebindings.cpp

344 lines
15 KiB
C++

#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 <components/esm3/loaddial.hpp>
#include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/vfs/pathutil.hpp>
namespace
{
std::vector<const ESM::Dialogue*> makeIndex(const MWWorld::Store<ESM::Dialogue>& store, ESM::Dialogue::Type type)
{
std::vector<const ESM::Dialogue*> result;
for (const ESM::Dialogue& v : store)
if (v.mType == type)
result.push_back(&v);
return result;
}
template <ESM::Dialogue::Type type>
class FilteredDialogueStore
{
const MWWorld::Store<ESM::Dialogue>& mDialogueStore;
std::vector<const ESM::Dialogue*> mIndex;
public:
explicit FilteredDialogueStore(const MWWorld::Store<ESM::Dialogue>& 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 <ESM::Dialogue::Type filter>
void prepareBindingsForDialogueRecordStores(sol::table& table, const MWLua::Context& context)
{
using StoreT = FilteredDialogueStore<filter>;
sol::state_view lua = context.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(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<sol::function>();
storeBindingsClass[sol::meta_function::pairs] = lua["ipairsForArray"].template get<sol::function>();
table["records"] = StoreT{ MWBase::Environment::get().getESMStore()->get<ESM::Dialogue>() };
}
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, LuaUtil::fromLuaIndex(index));
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 LuaUtil::toLuaIndex(rec.mData.mRank);
});
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 LuaUtil::toLuaIndex(rec.mData.mPCrank);
});
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.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);
}
}