1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-28 15:09:43 +00:00

Add Lua bindings for journal

This commit is contained in:
Tobias Tribble 2023-06-12 21:35:00 -05:00 committed by Petr Mikheev
parent 63e9a63c67
commit c792582376
11 changed files with 218 additions and 4 deletions

View file

@ -49,6 +49,11 @@ namespace MWBase
virtual ~Journal() {} virtual ~Journal() {}
virtual MWDialogue::Quest& getQuest(const ESM::RefId& id) = 0;
///< Gets the quest requested. Creates it and inserts it in quests if it does not yet exist.
virtual MWDialogue::Quest* getQuestPtr(const ESM::RefId& id) = 0;
///< Gets a pointer to the requested quest. Will return nullptr if the quest has not been started.
virtual void addEntry(const ESM::RefId& id, int index, const MWWorld::Ptr& actor) = 0; virtual void addEntry(const ESM::RefId& id, int index, const MWWorld::Ptr& actor) = 0;
///< Add a journal entry. ///< Add a journal entry.
/// @param actor Used as context for replacing of escape sequences (%name, etc). /// @param actor Used as context for replacing of escape sequences (%name, etc).
@ -56,6 +61,9 @@ namespace MWBase
virtual void setJournalIndex(const ESM::RefId& id, int index) = 0; virtual void setJournalIndex(const ESM::RefId& id, int index) = 0;
///< Set the journal index without adding an entry. ///< Set the journal index without adding an entry.
virtual int getQuestCount() const = 0;
///< Get the count of quests stored.
virtual int getJournalIndex(const ESM::RefId& id) const = 0; virtual int getJournalIndex(const ESM::RefId& id) const = 0;
///< Get the journal index. ///< Get the journal index.

View file

@ -24,6 +24,7 @@ namespace ESM
{ {
class ESMReader; class ESMReader;
class ESMWriter; class ESMWriter;
class RefId;
struct LuaScripts; struct LuaScripts;
} }
@ -49,6 +50,7 @@ namespace MWBase
virtual void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) = 0; virtual void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) = 0;
virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;
virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0;
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;
// TODO: notify LuaManager about other events // TODO: notify LuaManager about other events
// virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, // virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object,
// const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0; // const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0;

View file

@ -31,6 +31,16 @@ namespace MWDialogue
return iter->second; return iter->second;
} }
Quest* Journal::getQuestPtr(const ESM::RefId& id)
{
TQuestContainer::iterator iter = mQuests.find(id);
if (iter == mQuests.end())
{
return nullptr;
}
return &(iter->second);
}
Topic& Journal::getTopic(const ESM::RefId& id) Topic& Journal::getTopic(const ESM::RefId& id)
{ {
@ -135,6 +145,10 @@ namespace MWDialogue
mTopics.erase(mTopics.find(topicId)); // All responses removed -> remove topic mTopics.erase(mTopics.find(topicId)); // All responses removed -> remove topic
} }
int Journal::getQuestCount() const
{
return static_cast<int>(mQuests.size());
}
int Journal::getJournalIndex(const ESM::RefId& id) const int Journal::getJournalIndex(const ESM::RefId& id) const
{ {
TQuestContainer::const_iterator iter = mQuests.find(id); TQuestContainer::const_iterator iter = mQuests.find(id);

View file

@ -15,8 +15,6 @@ namespace MWDialogue
TTopicContainer mTopics; TTopicContainer mTopics;
private: private:
Quest& getQuest(const ESM::RefId& id);
Topic& getTopic(const ESM::RefId& id); Topic& getTopic(const ESM::RefId& id);
bool isThere(const ESM::RefId& topicId, const ESM::RefId& infoId = ESM::RefId()) const; bool isThere(const ESM::RefId& topicId, const ESM::RefId& infoId = ESM::RefId()) const;
@ -26,10 +24,19 @@ namespace MWDialogue
void clear() override; void clear() override;
Quest* getQuestPtr(const ESM::RefId& id) override;
///< Gets a pointer to the requested quest. Will return nullptr if the quest has not been started.
Quest& getQuest(const ESM::RefId& id) override;
///< Gets the quest requested. Attempts to create it and inserts it in quests if it does not yet exist.
void addEntry(const ESM::RefId& id, int index, const MWWorld::Ptr& actor) override; void addEntry(const ESM::RefId& id, int index, const MWWorld::Ptr& actor) override;
///< Add a journal entry. ///< Add a journal entry.
/// @param actor Used as context for replacing of escape sequences (%name, etc). /// @param actor Used as context for replacing of escape sequences (%name, etc).
int getQuestCount() const override;
///< Get the count of saved quests.
void setJournalIndex(const ESM::RefId& id, int index) override; void setJournalIndex(const ESM::RefId& id, int index) override;
///< Set the journal index without adding an entry. ///< Set the journal index without adding an entry.

View file

@ -4,6 +4,7 @@
#include <components/esm3/queststate.hpp> #include <components/esm3/queststate.hpp>
#include "../mwbase/luamanager.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -52,6 +53,7 @@ namespace MWDialogue
void Quest::setIndex(int index) void Quest::setIndex(int index)
{ {
// The index must be set even if no related journal entry was found // The index must be set even if no related journal entry was found
MWBase::Environment::get().getLuaManager()->questUpdated(mTopic, index);
mIndex = index; mIndex = index;
} }
@ -80,7 +82,10 @@ namespace MWDialogue
mFinished = info->mQuestStatus == ESM::DialInfo::QS_Finished; mFinished = info->mQuestStatus == ESM::DialInfo::QS_Finished;
if (info->mData.mJournalIndex > mIndex) if (info->mData.mJournalIndex > mIndex)
{
mIndex = info->mData.mJournalIndex; mIndex = info->mData.mJournalIndex;
MWBase::Environment::get().getLuaManager()->questUpdated(mTopic, mIndex);
}
for (TEntryIter iter(mEntries.begin()); iter != mEntries.end(); ++iter) for (TEntryIter iter(mEntries.begin()); iter != mEntries.end(); ++iter)
if (iter->mInfoId == entry.mInfoId) if (iter->mInfoId == entry.mInfoId)

View file

@ -179,6 +179,17 @@ namespace MWLua
} }
} }
void LuaManager::questUpdated(const ESM::RefId& questId, int stage)
{
if (mPlayer.isEmpty())
return; // The game is not started yet.
PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
if (playerScripts)
{
playerScripts->onQuestUpdate(questId.serializeText(), stage);
}
}
void LuaManager::synchronizedUpdate() void LuaManager::synchronizedUpdate()
{ {
if (mPlayer.isEmpty()) if (mPlayer.isEmpty())

View file

@ -75,6 +75,7 @@ namespace MWLua
{ {
mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell });
} }
void questUpdated(const ESM::RefId& questId, int stage) override;
MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override;

View file

@ -20,7 +20,7 @@ namespace MWLua
{ {
registerEngineHandlers({ &mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers, registerEngineHandlers({ &mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers,
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mOnFrameHandlers, &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mOnFrameHandlers,
&mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved }); &mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved, &mQuestUpdate });
} }
void processInputEvent(const MWBase::LuaManager::InputEvent& event) void processInputEvent(const MWBase::LuaManager::InputEvent& event)
@ -56,6 +56,7 @@ namespace MWLua
} }
void onFrame(float dt) { callEngineHandlers(mOnFrameHandlers, dt); } void onFrame(float dt) { callEngineHandlers(mOnFrameHandlers, dt); }
void onQuestUpdate(std::string_view questId, int stage) { callEngineHandlers(mQuestUpdate, questId, stage); }
bool consoleCommand( bool consoleCommand(
const std::string& consoleMode, const std::string& command, const sol::object& selectedObject) const std::string& consoleMode, const std::string& command, const sol::object& selectedObject)
@ -75,6 +76,7 @@ namespace MWLua
EngineHandlerList mTouchpadPressed{ "onTouchPress" }; EngineHandlerList mTouchpadPressed{ "onTouchPress" };
EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; EngineHandlerList mTouchpadReleased{ "onTouchRelease" };
EngineHandlerList mTouchpadMoved{ "onTouchMove" }; EngineHandlerList mTouchpadMoved{ "onTouchMove" };
EngineHandlerList mQuestUpdate{ "onQuestUpdate" };
}; };
} }

View file

@ -1,10 +1,143 @@
#include "types.hpp" #include "types.hpp"
#include "../luamanagerimp.hpp"
#include <apps/openmw/mwbase/journal.hpp>
#include <apps/openmw/mwbase/world.hpp>
#include <apps/openmw/mwmechanics/npcstats.hpp> #include <apps/openmw/mwmechanics/npcstats.hpp>
#include <apps/openmw/mwworld/class.hpp> #include <apps/openmw/mwworld/class.hpp>
namespace MWLua namespace MWLua
{ {
struct Quests
{
bool mMutable = false;
MWWorld::SafePtr::Id playerId;
using Iterator = typename MWBase::Journal::TQuestIter;
Iterator mIterator;
MWBase::Journal* const journal = MWBase::Environment::get().getJournal();
void reset() { mIterator = journal->questBegin(); }
bool isEnd() const { return mIterator == journal->questEnd(); }
void advance() { mIterator++; }
};
struct Quest
{
ESM::RefId mQuestId;
bool mMutable = false;
};
}
namespace sol
{
template <>
struct is_automagical<MWLua::Quests> : std::false_type
{
};
template <>
struct is_automagical<MWLua::Quest> : std::false_type
{
};
}
namespace MWLua
{
void addPlayerQuestBindings(sol::table& player, const Context& context)
{
MWBase::Journal* const journal = MWBase::Environment::get().getJournal();
// Quests
player["quests"] = [](const Object& player) {
MWBase::World* world = MWBase::Environment::get().getWorld();
Quests q = {};
if (player.ptr() != world->getPlayerPtr())
throw std::runtime_error("Must provide a player!");
if (dynamic_cast<const GObject*>(&player))
q.mMutable = true;
q.playerId = player.id();
return q;
};
sol::usertype<Quests> quests = context.mLua->sol().new_usertype<Quests>("Quests");
quests[sol::meta_function::to_string]
= [](const Quests& quests) { return "Quests[" + quests.playerId.toString() + "]"; };
quests[sol::meta_function::length] = [journal]() { return journal->getQuestCount(); };
quests[sol::meta_function::index] = sol::overload([](const Quests& quests, std::string_view index) -> Quest {
Quest q;
q.mQuestId = ESM::RefId::deserializeText(index);
q.mMutable = quests.mMutable;
return q;
});
quests[sol::meta_function::pairs] = [](sol::this_state ts, Quests& self) {
sol::state_view lua(ts);
self.reset();
return sol::as_function([lua, &self]() mutable -> std::pair<sol::object, sol::object> {
if (!self.isEnd())
{
Quest q;
q.mQuestId = (self.mIterator->first);
q.mMutable = self.mMutable;
auto result = sol::make_object(lua, q);
auto index = sol::make_object(lua, self.mIterator->first);
self.advance();
return { index, result };
}
else
{
return { sol::lua_nil, sol::lua_nil };
}
});
};
// Quest Functions
auto getQuestStage = [journal](const Quest& q) -> int {
auto quest = journal->getQuestPtr(q.mQuestId);
if (quest == nullptr)
return -1;
return journal->getJournalIndex(q.mQuestId);
};
auto setQuestStage = [context](const Quest& q, int stage) {
if (!q.mMutable)
throw std::runtime_error("Value can only be changed in global scripts!");
context.mLuaManager->addAction(
[q, stage] { MWBase::Environment::get().getJournal()->setJournalIndex(q.mQuestId, stage); },
"setQuestStageAction");
};
// Player quests
sol::usertype<Quest> quest = context.mLua->sol().new_usertype<Quest>("Quest");
quest[sol::meta_function::to_string]
= [](const Quest& quest) { return "Quest [" + quest.mQuestId.serializeText() + "]"; };
quest["stage"] = sol::property(getQuestStage, setQuestStage);
quest["name"] = sol::readonly_property([journal](const Quest& q) -> sol::optional<std::string_view> {
auto quest = journal->getQuestPtr(q.mQuestId);
if (quest == nullptr)
return sol::nullopt;
return quest->getName();
});
quest["id"] = sol::readonly_property([](const Quest& q) -> std::string { return q.mQuestId.serializeText(); });
quest["isFinished"] = sol::property(
[journal](const Quest& q) -> bool {
auto quest = journal->getQuestPtr(q.mQuestId);
if (quest == nullptr)
return false;
return quest->isFinished();
},
[journal, context](const Quest& q, bool finished) {
if (!q.mMutable)
throw std::runtime_error("Value can only be changed in global scripts!");
context.mLuaManager->addAction(
[q, finished, journal] { journal->getQuest(q.mQuestId).setFinished(finished); },
"setQuestFinishedAction");
});
quest["addJournalEntry"] = [context](const Quest& q, const GObject& actor, int stage) {
MWWorld::Ptr ptr = actor.ptr();
// The journal mwscript function has a try function here, we will make the lua function throw an
// error. However, the addAction will cause it to error outside of this function.
context.mLuaManager->addAction(
[ptr, q, stage] { MWBase::Environment::get().getJournal()->addEntry(q.mQuestId, stage, ptr); },
"addJournalEntryAction");
};
}
void addPlayerBindings(sol::table player, const Context& context) void addPlayerBindings(sol::table player, const Context& context)
{ {
@ -12,5 +145,6 @@ namespace MWLua
const MWWorld::Class& cls = o.ptr().getClass(); const MWWorld::Class& cls = o.ptr().getClass();
return cls.getNpcStats(o.ptr()).getBounty(); return cls.getNpcStats(o.ptr()).getBounty();
}; };
addPlayerQuestBindings(player, context);
} }
} }

View file

@ -89,6 +89,8 @@ Engine handler is a function defined by a script, that can be called by the engi
- | `Key <openmw_input.html##(KeyboardEvent)>`_ is pressed. - | `Key <openmw_input.html##(KeyboardEvent)>`_ is pressed.
| Usage example: | Usage example:
| ``if key.symbol == 'z' and key.withShift then ...`` | ``if key.symbol == 'z' and key.withShift then ...``
* - onQuestUpdate(questId,stage)
- | Called when a quest is updated.
* - onKeyRelease(key) * - onKeyRelease(key)
- | `Key <openmw_input.html##(KeyboardEvent)>`_ is released. - | `Key <openmw_input.html##(KeyboardEvent)>`_ is released.
| Usage example: | Usage example:

View file

@ -717,6 +717,34 @@
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @return #number -- @return #number
---
-- Returns a list containing quests @{<#PlayerQuest>} for the specified player, indexed by quest ID.
-- @function [parent=#Player] quests
-- @param openmw.core#GameObject player
-- @return #list<#PlayerQuest>
-- @usage -- Getting the quest for a specified index
-- stage = types.Player.quests(playerRef)["ms_fargothring].stage
-- --Get the name of all started quests
-- for x, quest in pairs(types.Player.quests(playerRef)) do print (quest.name) end
-- --Start a new quest, add it to the player's quest list but don't add any journal entries
-- types.Player.quests(playerRef)["ms_fargothring].stage = 0
--- @{#PlayerQuest}
---
-- @type PlayerQuest
-- @field #string id The quest ID.
-- @field #number stage The quest Stage. May only be changed by global scripts. Returns -1 if the quest has not been started or does not exist.
-- @field #bool isFinished Returns true if the quest is complete, false if not.
-- @field #string name The Quest's user friendly name. Not all quests have this. Will be nil if the quest has not been started.
---
-- Sets the quest stage for the given quest, on the given player, and adds the entry to the journal, if there is an entry at the specified stage. Can only be used in global scripts.
-- @function [parent=#PlayerQuest] addJournalEntry
-- @param self
-- @param openmw.core#GameObject actor The actor who is the source of the journal entry, can be the same as player, their name is used in a similar manner as in dialogue.
-- @param #number stage Quest Stage
--- @{#Armor} functions --- @{#Armor} functions
-- @field [parent=#types] #Armor Armor -- @field [parent=#types] #Armor Armor