mirror of
https://github.com/OpenMW/openmw.git
synced 2025-10-16 18:46:36 +00:00
Merge branch 'journal-lua-api' into 'master'
Add Lua API for the player journal text data Closes #7966 See merge request OpenMW/openmw!4203
This commit is contained in:
commit
cb0316e6be
10 changed files with 349 additions and 6 deletions
|
@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
|
||||||
set(OPENMW_VERSION_MAJOR 0)
|
set(OPENMW_VERSION_MAJOR 0)
|
||||||
set(OPENMW_VERSION_MINOR 50)
|
set(OPENMW_VERSION_MINOR 50)
|
||||||
set(OPENMW_VERSION_RELEASE 0)
|
set(OPENMW_VERSION_RELEASE 0)
|
||||||
set(OPENMW_LUA_API_REVISION 90)
|
set(OPENMW_LUA_API_REVISION 91)
|
||||||
set(OPENMW_POSTPROCESSING_API_REVISION 3)
|
set(OPENMW_POSTPROCESSING_API_REVISION 3)
|
||||||
|
|
||||||
set(OPENMW_VERSION_COMMITHASH "")
|
set(OPENMW_VERSION_COMMITHASH "")
|
||||||
|
|
|
@ -47,7 +47,7 @@ namespace MWBase
|
||||||
|
|
||||||
virtual void clear() = 0;
|
virtual void clear() = 0;
|
||||||
|
|
||||||
virtual ~Journal() {}
|
virtual ~Journal() = default;
|
||||||
|
|
||||||
virtual MWDialogue::Quest& getOrStartQuest(const ESM::RefId& id) = 0;
|
virtual MWDialogue::Quest& getOrStartQuest(const ESM::RefId& id) = 0;
|
||||||
///< Gets the quest requested. Creates it and inserts it in quests if it is not yet started.
|
///< Gets the quest requested. Creates it and inserts it in quests if it is not yet started.
|
||||||
|
@ -79,6 +79,8 @@ namespace MWBase
|
||||||
virtual TEntryIter end() const = 0;
|
virtual TEntryIter end() const = 0;
|
||||||
///< Iterator pointing past the end of the main journal.
|
///< Iterator pointing past the end of the main journal.
|
||||||
|
|
||||||
|
virtual const TEntryContainer& getEntries() const = 0;
|
||||||
|
|
||||||
virtual TQuestIter questBegin() const = 0;
|
virtual TQuestIter questBegin() const = 0;
|
||||||
///< Iterator pointing to the first quest (sorted by topic ID)
|
///< Iterator pointing to the first quest (sorted by topic ID)
|
||||||
|
|
||||||
|
@ -93,6 +95,8 @@ namespace MWBase
|
||||||
virtual TTopicIter topicEnd() const = 0;
|
virtual TTopicIter topicEnd() const = 0;
|
||||||
///< Iterator pointing past the last topic.
|
///< Iterator pointing past the last topic.
|
||||||
|
|
||||||
|
virtual const TTopicContainer& getTopics() const = 0;
|
||||||
|
|
||||||
virtual int countSavedGameRecords() const = 0;
|
virtual int countSavedGameRecords() const = 0;
|
||||||
|
|
||||||
virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) const = 0;
|
virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) const = 0;
|
||||||
|
|
|
@ -55,6 +55,8 @@ namespace MWDialogue
|
||||||
TEntryIter end() const override;
|
TEntryIter end() const override;
|
||||||
///< Iterator pointing past the end of the main journal.
|
///< Iterator pointing past the end of the main journal.
|
||||||
|
|
||||||
|
const TEntryContainer& getEntries() const override { return mJournal; }
|
||||||
|
|
||||||
TQuestIter questBegin() const override;
|
TQuestIter questBegin() const override;
|
||||||
///< Iterator pointing to the first quest (sorted by topic ID)
|
///< Iterator pointing to the first quest (sorted by topic ID)
|
||||||
|
|
||||||
|
@ -69,6 +71,8 @@ namespace MWDialogue
|
||||||
TTopicIter topicEnd() const override;
|
TTopicIter topicEnd() const override;
|
||||||
///< Iterator pointing past the last topic.
|
///< Iterator pointing past the last topic.
|
||||||
|
|
||||||
|
const TTopicContainer& getTopics() const override { return mTopics; }
|
||||||
|
|
||||||
int countSavedGameRecords() const override;
|
int countSavedGameRecords() const override;
|
||||||
|
|
||||||
void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override;
|
void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override;
|
||||||
|
|
|
@ -14,8 +14,6 @@ namespace MWDialogue
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Topic::~Topic() {}
|
|
||||||
|
|
||||||
bool Topic::addEntry(const JournalEntry& entry)
|
bool Topic::addEntry(const JournalEntry& entry)
|
||||||
{
|
{
|
||||||
if (entry.mTopic != mTopic)
|
if (entry.mTopic != mTopic)
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace MWDialogue
|
||||||
|
|
||||||
Topic(const ESM::RefId& topic);
|
Topic(const ESM::RefId& topic);
|
||||||
|
|
||||||
virtual ~Topic();
|
virtual ~Topic() = default;
|
||||||
|
|
||||||
virtual bool addEntry(const JournalEntry& entry);
|
virtual bool addEntry(const JournalEntry& entry);
|
||||||
///< Add entry
|
///< Add entry
|
||||||
|
@ -53,6 +53,10 @@ namespace MWDialogue
|
||||||
|
|
||||||
TEntryIter end() const;
|
TEntryIter end() const;
|
||||||
///< Iterator pointing past the end of the journal for this topic.
|
///< Iterator pointing past the end of the journal for this topic.
|
||||||
|
|
||||||
|
std::size_t size() const { return mEntries.size(); }
|
||||||
|
|
||||||
|
const Entry& operator[](std::size_t i) const { return mEntries[i]; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <components/esm3/loadbsgn.hpp>
|
#include <components/esm3/loadbsgn.hpp>
|
||||||
#include <components/esm3/loadfact.hpp>
|
#include <components/esm3/loadfact.hpp>
|
||||||
|
#include <components/lua/util.hpp>
|
||||||
|
|
||||||
#include "../birthsignbindings.hpp"
|
#include "../birthsignbindings.hpp"
|
||||||
#include "../luamanagerimp.hpp"
|
#include "../luamanagerimp.hpp"
|
||||||
|
@ -28,6 +29,16 @@ namespace MWLua
|
||||||
ESM::RefId mQuestId;
|
ESM::RefId mQuestId;
|
||||||
bool mMutable = false;
|
bool mMutable = false;
|
||||||
};
|
};
|
||||||
|
struct Topics
|
||||||
|
{
|
||||||
|
};
|
||||||
|
struct JournalEntries
|
||||||
|
{
|
||||||
|
};
|
||||||
|
struct TopicEntries
|
||||||
|
{
|
||||||
|
ESM::RefId mTopicId;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace sol
|
namespace sol
|
||||||
|
@ -40,6 +51,34 @@ namespace sol
|
||||||
struct is_automagical<MWLua::Quest> : std::false_type
|
struct is_automagical<MWLua::Quest> : std::false_type
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
template <>
|
||||||
|
struct is_automagical<MWBase::Journal> : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct is_automagical<MWLua::Topics> : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct is_automagical<MWLua::JournalEntries> : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct is_automagical<MWDialogue::StampedJournalEntry> : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct is_automagical<MWDialogue::Topic> : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct is_automagical<MWLua::TopicEntries> : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
template <>
|
||||||
|
struct is_automagical<MWDialogue::Entry> : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -62,6 +101,14 @@ namespace
|
||||||
return ESM::RefId();
|
return ESM::RefId();
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MWDialogue::Topic& getTopicDataOrThrow(const ESM::RefId& topicId, const MWBase::Journal* journal)
|
||||||
|
{
|
||||||
|
const auto it = journal->getTopics().find(topicId);
|
||||||
|
if (it == journal->topicEnd())
|
||||||
|
throw std::runtime_error("Topic " + topicId.toDebugString() + " could not be found in the journal");
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWLua
|
namespace MWLua
|
||||||
|
@ -78,17 +125,176 @@ namespace MWLua
|
||||||
throw std::runtime_error("The argument must be a NPC!");
|
throw std::runtime_error("The argument must be a NPC!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addJournalClassBindings(sol::state_view& lua, const MWBase::Journal* journal)
|
||||||
|
{
|
||||||
|
auto journalBindingsClass = lua.new_usertype<MWBase::Journal>("MWDialogue_Journal");
|
||||||
|
journalBindingsClass[sol::meta_function::to_string] = [](const MWBase::Journal& store) {
|
||||||
|
const size_t numberOfTopics = store.getTopics().size();
|
||||||
|
const size_t numberOfJournalEntries = store.getEntries().size();
|
||||||
|
return "{MWDialogue_Journal: " + std::to_string(numberOfTopics) + " topic entries, "
|
||||||
|
+ std::to_string(numberOfJournalEntries) + " journal entries}";
|
||||||
|
};
|
||||||
|
journalBindingsClass["topics"]
|
||||||
|
= sol::readonly_property([](const MWBase::Journal& store) { return MWLua::Topics{}; });
|
||||||
|
journalBindingsClass["journalTextEntries"]
|
||||||
|
= sol::readonly_property([](const MWBase::Journal& store) { return MWLua::JournalEntries{}; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void addJournalClassTopicsListBindings(sol::state_view& lua, const MWBase::Journal* journal)
|
||||||
|
{
|
||||||
|
auto topicsBindingsClass = lua.new_usertype<MWLua::Topics>("MWDialogue_Journal_Topics");
|
||||||
|
topicsBindingsClass[sol::meta_function::to_string] = [journal](const MWLua::Topics& topicEntriesStore) {
|
||||||
|
const size_t numberOfTopics = journal->getTopics().size();
|
||||||
|
return "{MWDialogue_Journal_Topics: " + std::to_string(numberOfTopics) + " topics}";
|
||||||
|
};
|
||||||
|
topicsBindingsClass[sol::meta_function::index]
|
||||||
|
= [journal](
|
||||||
|
const MWLua::Topics& topicEntriesStore, std::string_view givenTopicId) -> const MWDialogue::Topic* {
|
||||||
|
const auto it = journal->getTopics().find(ESM::RefId::deserializeText(givenTopicId));
|
||||||
|
if (it == journal->topicEnd())
|
||||||
|
return nullptr;
|
||||||
|
return &it->second;
|
||||||
|
};
|
||||||
|
topicsBindingsClass[sol::meta_function::length]
|
||||||
|
= [journal](const MWLua::Topics&) -> size_t { return journal->getTopics().size(); };
|
||||||
|
topicsBindingsClass[sol::meta_function::pairs] = [journal](const MWLua::Topics&) {
|
||||||
|
MWBase::Journal::TTopicIter iterator = journal->topicBegin();
|
||||||
|
return sol::as_function(
|
||||||
|
[iterator, journal]() mutable -> std::pair<sol::optional<std::string>, const MWDialogue::Topic*> {
|
||||||
|
if (iterator != journal->topicEnd())
|
||||||
|
{
|
||||||
|
return { iterator->first.serializeText(), &((iterator++)->second) };
|
||||||
|
}
|
||||||
|
return { sol::nullopt, nullptr };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void addJournalClassTopicBindings(sol::state_view& lua, const MWBase::Journal* journal)
|
||||||
|
{
|
||||||
|
auto topicBindingsClass = lua.new_usertype<MWDialogue::Topic>("MWDialogue_Journal_Topic");
|
||||||
|
topicBindingsClass[sol::meta_function::to_string] = [](const MWDialogue::Topic& topic) {
|
||||||
|
return "MWDialogue_Journal_Topic: \"" + std::string{ topic.getName() } + "\"";
|
||||||
|
};
|
||||||
|
topicBindingsClass["id"]
|
||||||
|
= sol::readonly_property([](const MWDialogue::Topic& topic) { return topic.getTopic().serializeText(); });
|
||||||
|
topicBindingsClass["name"]
|
||||||
|
= sol::readonly_property([](const MWDialogue::Topic& topic) { return topic.getName(); });
|
||||||
|
topicBindingsClass["entries"] = sol::readonly_property(
|
||||||
|
[](const MWDialogue::Topic& topic) { return MWLua::TopicEntries{ topic.getTopic() }; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void addJournalClassTopicEntriesListBindings(sol::state_view& lua, const MWBase::Journal* journal)
|
||||||
|
{
|
||||||
|
auto topicEntriesBindingsClass
|
||||||
|
= lua.new_usertype<MWLua::TopicEntries>("MWDialogue_Journal_Topic_WrittenEntries");
|
||||||
|
topicEntriesBindingsClass[sol::meta_function::to_string] = [journal](const MWLua::TopicEntries& topicEntries) {
|
||||||
|
const MWDialogue::Topic& topic = getTopicDataOrThrow(topicEntries.mTopicId, journal);
|
||||||
|
return "MWDialogue_Journal_Topic_WrittenEntries for \"" + std::string{ topic.getName() }
|
||||||
|
+ "\": " + std::to_string(topic.size()) + " elements";
|
||||||
|
};
|
||||||
|
topicEntriesBindingsClass[sol::meta_function::length] = [journal](const MWLua::TopicEntries& topicEntries) {
|
||||||
|
const MWDialogue::Topic& topic = getTopicDataOrThrow(topicEntries.mTopicId, journal);
|
||||||
|
return topic.size();
|
||||||
|
};
|
||||||
|
topicEntriesBindingsClass[sol::meta_function::index]
|
||||||
|
= [journal](const MWLua::TopicEntries& topicEntries, size_t index) -> const MWDialogue::Entry* {
|
||||||
|
const MWDialogue::Topic& topic = getTopicDataOrThrow(topicEntries.mTopicId, journal);
|
||||||
|
|
||||||
|
if (index == 0 || index > topic.size())
|
||||||
|
return nullptr;
|
||||||
|
index = LuaUtil::fromLuaIndex(index);
|
||||||
|
return &topic[index];
|
||||||
|
};
|
||||||
|
topicEntriesBindingsClass[sol::meta_function::ipairs] = lua["ipairsForArray"].template get<sol::function>();
|
||||||
|
topicEntriesBindingsClass[sol::meta_function::pairs] = lua["ipairsForArray"].template get<sol::function>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addJournalClassTopicEntryBindings(sol::state_view& lua, const MWBase::Journal* journal)
|
||||||
|
{
|
||||||
|
auto topicEntryBindingsClass = lua.new_usertype<MWDialogue::Entry>("MWDialogue_Journal_Topic_WrittenEntry");
|
||||||
|
topicEntryBindingsClass[sol::meta_function::to_string] = [](const MWDialogue::Entry& topicEntry) {
|
||||||
|
return "MWDialogue_Journal_Topic_WrittenEntry: " + topicEntry.mInfoId.toDebugString();
|
||||||
|
};
|
||||||
|
topicEntryBindingsClass["id"] = sol::readonly_property(
|
||||||
|
[](const MWDialogue::Entry& topicEntry) { return topicEntry.mInfoId.serializeText(); });
|
||||||
|
topicEntryBindingsClass["text"]
|
||||||
|
= sol::readonly_property([](const MWDialogue::Entry& topicEntry) { return topicEntry.mText; });
|
||||||
|
topicEntryBindingsClass["actor"]
|
||||||
|
= sol::readonly_property([](const MWDialogue::Entry& topicEntry) { return topicEntry.mActorName; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void addJournalClassJournalEntriesListBindings(sol::state_view& lua, const MWBase::Journal* journal)
|
||||||
|
{
|
||||||
|
auto journalEntriesBindingsClass = lua.new_usertype<MWLua::JournalEntries>("MWDialogue_Journal_WrittenEntries");
|
||||||
|
journalEntriesBindingsClass[sol::meta_function::to_string] = [journal](const MWLua::JournalEntries&) {
|
||||||
|
const size_t numberOfEntries = journal->getEntries().size();
|
||||||
|
return "{MWDialogue_Journal_WrittenEntries: " + std::to_string(numberOfEntries) + " journal text entries}";
|
||||||
|
};
|
||||||
|
journalEntriesBindingsClass[sol::meta_function::length]
|
||||||
|
= [journal](const MWLua::JournalEntries&) { return journal->getEntries().size(); };
|
||||||
|
journalEntriesBindingsClass[sol::meta_function::index]
|
||||||
|
= [journal](const MWLua::JournalEntries&, size_t index) -> const MWDialogue::StampedJournalEntry* {
|
||||||
|
if (index == 0 || index > journal->getEntries().size())
|
||||||
|
return nullptr;
|
||||||
|
index = LuaUtil::fromLuaIndex(index);
|
||||||
|
return &journal->getEntries()[index];
|
||||||
|
};
|
||||||
|
journalEntriesBindingsClass[sol::meta_function::ipairs] = lua["ipairsForArray"].template get<sol::function>();
|
||||||
|
journalEntriesBindingsClass[sol::meta_function::pairs] = lua["ipairsForArray"].template get<sol::function>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addJournalClassJournalEntryBindings(sol::state_view& lua, const MWBase::Journal* journal)
|
||||||
|
{
|
||||||
|
auto journalEntryBindingsClass
|
||||||
|
= lua.new_usertype<MWDialogue::StampedJournalEntry>("MWDialogue_Journal_WrittenEntry");
|
||||||
|
journalEntryBindingsClass[sol::meta_function::to_string]
|
||||||
|
= [](const MWDialogue::StampedJournalEntry& journalEntry) {
|
||||||
|
return "MWDialogue_Journal_WrittenEntry: " + journalEntry.mTopic.toDebugString();
|
||||||
|
};
|
||||||
|
journalEntryBindingsClass["id"] = sol::readonly_property(
|
||||||
|
[](const MWDialogue::StampedJournalEntry& journalEntry) { return journalEntry.mInfoId.serializeText(); });
|
||||||
|
journalEntryBindingsClass["text"] = sol::readonly_property(
|
||||||
|
[](const MWDialogue::StampedJournalEntry& journalEntry) { return journalEntry.mText; });
|
||||||
|
journalEntryBindingsClass["questId"] = sol::readonly_property(
|
||||||
|
[](const MWDialogue::StampedJournalEntry& journalEntry) { return journalEntry.mTopic.serializeText(); });
|
||||||
|
journalEntryBindingsClass["day"] = sol::readonly_property(
|
||||||
|
[](const MWDialogue::StampedJournalEntry& journalEntry) { return journalEntry.mDay; });
|
||||||
|
journalEntryBindingsClass["month"] = sol::readonly_property(
|
||||||
|
[](const MWDialogue::StampedJournalEntry& journalEntry) { return journalEntry.mMonth + 1; });
|
||||||
|
journalEntryBindingsClass["dayOfMonth"] = sol::readonly_property(
|
||||||
|
[](const MWDialogue::StampedJournalEntry& journalEntry) { return journalEntry.mDayOfMonth; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void addJournalEntryBindings(sol::table& playerBindings, sol::state_view lua, const MWBase::Journal* journal)
|
||||||
|
{
|
||||||
|
playerBindings["journal"] = [journal](const Object& player) -> const MWBase::Journal* {
|
||||||
|
verifyPlayer(player);
|
||||||
|
return journal;
|
||||||
|
};
|
||||||
|
|
||||||
|
addJournalClassBindings(lua, journal);
|
||||||
|
addJournalClassTopicsListBindings(lua, journal);
|
||||||
|
addJournalClassTopicBindings(lua, journal);
|
||||||
|
addJournalClassTopicEntriesListBindings(lua, journal);
|
||||||
|
addJournalClassTopicEntryBindings(lua, journal);
|
||||||
|
addJournalClassJournalEntriesListBindings(lua, journal);
|
||||||
|
addJournalClassJournalEntryBindings(lua, journal);
|
||||||
|
}
|
||||||
|
|
||||||
void addPlayerBindings(sol::table player, const Context& context)
|
void addPlayerBindings(sol::table player, const Context& context)
|
||||||
{
|
{
|
||||||
MWBase::Journal* const journal = MWBase::Environment::get().getJournal();
|
MWBase::Journal* const journal = MWBase::Environment::get().getJournal();
|
||||||
|
|
||||||
|
sol::state_view lua = context.sol();
|
||||||
|
addJournalEntryBindings(player, lua, journal);
|
||||||
|
|
||||||
player["quests"] = [](const Object& player) {
|
player["quests"] = [](const Object& player) {
|
||||||
verifyPlayer(player);
|
verifyPlayer(player);
|
||||||
bool allowChanges = dynamic_cast<const GObject*>(&player) != nullptr
|
bool allowChanges = dynamic_cast<const GObject*>(&player) != nullptr
|
||||||
|| dynamic_cast<const SelfObject*>(&player) != nullptr;
|
|| dynamic_cast<const SelfObject*>(&player) != nullptr;
|
||||||
return Quests{ .mMutable = allowChanges };
|
return Quests{ .mMutable = allowChanges };
|
||||||
};
|
};
|
||||||
sol::state_view lua = context.sol();
|
|
||||||
sol::usertype<Quests> quests = lua.new_usertype<Quests>("Quests");
|
sol::usertype<Quests> quests = lua.new_usertype<Quests>("Quests");
|
||||||
quests[sol::meta_function::to_string] = [](const Quests& quests) { return "Quests"; };
|
quests[sol::meta_function::to_string] = [](const Quests& quests) { return "Quests"; };
|
||||||
quests[sol::meta_function::index] = [](const Quests& quests, std::string_view questId) -> sol::optional<Quest> {
|
quests[sol::meta_function::index] = [](const Quests& quests, std::string_view questId) -> sol::optional<Quest> {
|
||||||
|
|
|
@ -1224,6 +1224,79 @@
|
||||||
-- @usage -- Start a new quest, add it to the player's quest list but don't add any journal entries
|
-- @usage -- Start a new quest, add it to the player's quest list but don't add any journal entries
|
||||||
-- types.Player.quests(player)["ms_fargothring"].stage = 0
|
-- types.Player.quests(player)["ms_fargothring"].stage = 0
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Returns @{#PlayerJournal}, which contains the read-only access to journal text data accumulated by the player.
|
||||||
|
-- Not the same as @{openmw_core#Dialogue.journal} which holds raw game records: with placeholders for dynamic variables and no player-specific info.
|
||||||
|
-- @function [parent=#Player] journal
|
||||||
|
-- @param openmw.core#GameObject player
|
||||||
|
-- @return #PlayerJournal
|
||||||
|
-- @usage -- Get text of the 1st journal entry player made
|
||||||
|
-- local entryText = types.Player.journal(player).journalTextEntries[1].text
|
||||||
|
-- @usage -- Get the number of "my trade" conversation topic lines the player journal accumulated
|
||||||
|
-- local num = #types.Player.journal(player).topics["my trade"].entries
|
||||||
|
|
||||||
|
---
|
||||||
|
-- A read-only list of player's accumulated journal (quest etc.) entries (@{#PlayerJournalTextEntry} elements), ordered from oldest entry to newest.
|
||||||
|
-- Implements [iterables#list-iterable](iterables.html#list-iterable) of @{#PlayerJournalTextEntry}.
|
||||||
|
-- @field [parent=#PlayerJournal] #list<#PlayerJournalTextEntry> journalTextEntries
|
||||||
|
-- @usage -- The `firstQuestName` variable below is likely to be "a1_1_findspymaster" in vanilla MW
|
||||||
|
-- local firstQuestName = types.Player.journal(player).journalTextEntries[1].questId
|
||||||
|
-- @usage -- The number of journal entries accumulated in the player journal
|
||||||
|
-- local num = #types.Player.journal(player).journalTextEntries
|
||||||
|
-- @usage -- Print all journal entries accumulated in the player journal
|
||||||
|
-- for idx, journalEntry in pairs(types.Player.journal(player).journalTextEntries) do
|
||||||
|
-- print(idx, journalEntry.text)
|
||||||
|
-- end
|
||||||
|
|
||||||
|
---
|
||||||
|
-- A read-only table of player's accumulated @{#PlayerJournalTopic}s, indexed by the topic name.
|
||||||
|
-- Implements [iterables#Map](iterables.html#map-iterable) of @{#PlayerJournalTopic}.
|
||||||
|
-- Topic name index doesn't have to be lowercase.
|
||||||
|
-- @field [parent=#PlayerJournal] #map<#string, #PlayerJournalTopic> topics
|
||||||
|
-- @usage local record = types.Player.journal(player).topics["my trade"]
|
||||||
|
-- @usage local record = types.Player.journal(player).topics["Vivec"]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @type PlayerJournalTopic
|
||||||
|
-- @field #string id Topic id. It's a lowercase version of name.
|
||||||
|
-- @field #string name Topic name. Same as id, but with upper cases preserved.
|
||||||
|
|
||||||
|
---
|
||||||
|
-- A read-only list of player's accumulated conversation lines (@{#PlayerJournalTopicEntry}) for this topic.
|
||||||
|
-- Implements [iterables#list-iterable](iterables.html#list-iterable) of #PlayerJournalTopicEntry.
|
||||||
|
-- @field [parent=#PlayerJournalTopic] #list<#PlayerJournalTopicEntry> entries
|
||||||
|
-- @usage -- First NPC topic line entry in the "Background" topic
|
||||||
|
-- local firstBackgroundLine = types.Player.journal(player).topics["Background"].entries[1]
|
||||||
|
-- @usage -- The number of topic entries accumulated in the player journal for "Vivec"
|
||||||
|
-- local num = #types.Player.journal(player).topics["vivec"].entries
|
||||||
|
-- @usage -- Print all conversation lines accumulated in the player journal for "Balmora"
|
||||||
|
-- for idx, topicEntry in pairs(types.Player.journal(player).topics["balmora"].entries) do
|
||||||
|
-- print(idx, topicEntry.text)
|
||||||
|
-- end
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @type PlayerJournalTopicEntry
|
||||||
|
-- @field #string text Text of this topic line.
|
||||||
|
-- @field #string actor Name of an NPC who is recorded in the player journal as an origin of this topic line.
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Identifier for this topic line. Is unique only within the @{#PlayerJournalTopic} it belongs to.
|
||||||
|
-- Has a counterpart in raw data game dialogue records at @{openmw_core#DialogueRecordInfo} held by @{openmw_core#Dialogue.topic}
|
||||||
|
-- @field [parent=#PlayerJournalTopicEntry] #string id
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @type PlayerJournalTextEntry
|
||||||
|
-- @field #string text Text of this journal entry.
|
||||||
|
-- @field #string questId Quest id this journal entry is associated with. Can be nil if there is no quest associated with this entry or if journal quest sorting functionality is not available in game.
|
||||||
|
-- @field #number day Number of the day this journal entry was written at.
|
||||||
|
-- @field #number month Number of the month this journal entry was written at.
|
||||||
|
-- @field #number dayOfMonth Number of the day in the month this journal entry was written at.
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Identifier for this journal entry line. Is unique only within the @{#PlayerJournalTextEntry} it belongs to.
|
||||||
|
-- Has a counterpart in raw data game dialogue records at @{openmw_core#DialogueRecordInfo} held by @{openmw_core#Dialogue.journal}
|
||||||
|
-- @field [parent=#PlayerJournalTextEntry] #string id
|
||||||
|
|
||||||
---
|
---
|
||||||
-- @type PlayerQuest
|
-- @type PlayerQuest
|
||||||
-- @field #string id The quest id.
|
-- @field #string id The quest id.
|
||||||
|
|
|
@ -137,6 +137,11 @@ testing.registerGlobalTest('record stores', function()
|
||||||
|
|
||||||
testRecordStore(core.sound, "sound")
|
testRecordStore(core.sound, "sound")
|
||||||
testRecordStore(core.factions, "factions")
|
testRecordStore(core.factions, "factions")
|
||||||
|
testRecordStore(core.dialogue.greeting, "dialogue greeting")
|
||||||
|
testRecordStore(core.dialogue.topic, "dialogue topic")
|
||||||
|
testRecordStore(core.dialogue.journal, "dialogue journal")
|
||||||
|
testRecordStore(core.dialogue.persuasion, "dialogue persuasion")
|
||||||
|
testRecordStore(core.dialogue.voice, "dialogue voice")
|
||||||
|
|
||||||
testRecordStore(types.NPC.classes, "classes")
|
testRecordStore(types.NPC.classes, "classes")
|
||||||
testRecordStore(types.NPC.races, "races")
|
testRecordStore(types.NPC.races, "races")
|
||||||
|
|
|
@ -38,3 +38,8 @@ testing.registerGlobalTest('[issues] Should keep reference to an object moved in
|
||||||
end
|
end
|
||||||
testing.expectThat(types.Container.inventory(barrel):find('ring_keley'), isFargothRing)
|
testing.expectThat(types.Container.inventory(barrel):find('ring_keley'), isFargothRing)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
testing.registerGlobalTest('[regression] Player quest status should update and its journal entries should be accessible', function()
|
||||||
|
coroutine.yield()
|
||||||
|
testing.runLocalTest(world.players[1], 'Player quest status should update and its journal entries should be accessible')
|
||||||
|
end)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local core = require('openmw.core')
|
local core = require('openmw.core')
|
||||||
|
local calendar = require('openmw_aux.calendar')
|
||||||
local input = require('openmw.input')
|
local input = require('openmw.input')
|
||||||
local self = require('openmw.self')
|
local self = require('openmw.self')
|
||||||
local testing = require('testing_util')
|
local testing = require('testing_util')
|
||||||
|
@ -77,6 +78,49 @@ testing.registerLocalTest('Guard in Imperial Prison Ship should find path (#7241
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
testing.registerLocalTest('Player quest status should update and its journal entries should be accessible',
|
||||||
|
function()
|
||||||
|
testing.expectEqual(#types.Player.journal(self).topics, 0, 'Fresh player has more journal topics than zero')
|
||||||
|
testing.expectEqual(#types.Player.journal(self).journalTextEntries, 0, 'Fresh player has more journal text entries than zero')
|
||||||
|
testing.expectEqual(types.Player.quests(self)["ms_fargothring"].stage, 0, "Player's not started quest has an unexpected stage")
|
||||||
|
types.Player.quests(self)["ms_fargothring"]:addJournalEntry(10)
|
||||||
|
coroutine.yield()
|
||||||
|
testing.expectEqual(types.Player.quests(self)["ms_fargothring"].stage, 10, "Unexpected quest stage number")
|
||||||
|
testing.expectEqual(types.Player.quests(self)["ms_fargothring"].finished, false, "Quest should not have been finished yet")
|
||||||
|
testing.expectEqual(#types.Player.journal(self).journalTextEntries, 1, 'Unexpected number of entries in the player journal')
|
||||||
|
|
||||||
|
local expectedJournalEntry = core.dialogue.journal.records["ms_fargothring"].infos[#core.dialogue.journal.records["ms_fargothring"].infos-1]
|
||||||
|
testing.expectEqual(
|
||||||
|
types.Player.journal(self).journalTextEntries[1].id,
|
||||||
|
expectedJournalEntry.id, 'Quest journal entries ids differ')
|
||||||
|
testing.expectEqual(
|
||||||
|
types.Player.journal(self).journalTextEntries[1].text,
|
||||||
|
expectedJournalEntry.text, 'Quest journal entries texts differ')
|
||||||
|
|
||||||
|
local dateWhenStageShouldHaveBeenUpdated = calendar.formatGameTime('*t')
|
||||||
|
testing.expectEqual(
|
||||||
|
types.Player.journal(self).journalTextEntries[1].dayOfMonth,
|
||||||
|
dateWhenStageShouldHaveBeenUpdated.day, 'Unexpected journal update day (of month)')
|
||||||
|
testing.expectEqual(
|
||||||
|
types.Player.journal(self).journalTextEntries[1].month,
|
||||||
|
dateWhenStageShouldHaveBeenUpdated.month, 'Unexpected journal update month')
|
||||||
|
|
||||||
|
types.Player.quests(self)["ms_fargothring"]:addJournalEntry(100)
|
||||||
|
types.Player.quests(self)["ms_fargothring"].finished = true
|
||||||
|
coroutine.yield()
|
||||||
|
testing.expectEqual(types.Player.quests(self)["ms_fargothring"].stage, 100, "Unexpected quest stage number")
|
||||||
|
testing.expectEqual(types.Player.quests(self)["ms_fargothring"].finished, true, "Quest should have been finished now")
|
||||||
|
testing.expectEqual(#types.Player.journal(self).journalTextEntries, 2, 'Unexpected number of entries in the player journal')
|
||||||
|
expectedJournalEntry = core.dialogue.journal.records["ms_fargothring"].infos[#core.dialogue.journal.records["ms_fargothring"].infos]
|
||||||
|
testing.expectEqual(
|
||||||
|
types.Player.journal(self).journalTextEntries[2].id,
|
||||||
|
expectedJournalEntry.id, 'Quest journal entries ids differ')
|
||||||
|
testing.expectEqual(
|
||||||
|
types.Player.journal(self).journalTextEntries[2].text,
|
||||||
|
expectedJournalEntry.text,
|
||||||
|
'Quest journal entries texts differ')
|
||||||
|
end)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
engineHandlers = {
|
engineHandlers = {
|
||||||
onFrame = testing.updateLocal,
|
onFrame = testing.updateLocal,
|
||||||
|
|
Loading…
Reference in a new issue