mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-30 07:45:39 +00:00
Merge branch 'lua_journal' into 'master'
Add Lua bindings for journal (second iteration of !3133) See merge request OpenMW/openmw!3189
This commit is contained in:
commit
429e911da1
11 changed files with 199 additions and 15 deletions
|
@ -49,6 +49,11 @@ namespace MWBase
|
|||
|
||||
virtual ~Journal() {}
|
||||
|
||||
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.
|
||||
virtual MWDialogue::Quest* getQuestOrNull(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;
|
||||
///< Add a journal entry.
|
||||
/// @param actor Used as context for replacing of escape sequences (%name, etc).
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace ESM
|
|||
{
|
||||
class ESMReader;
|
||||
class ESMWriter;
|
||||
class RefId;
|
||||
struct LuaScripts;
|
||||
}
|
||||
|
||||
|
@ -49,6 +50,7 @@ namespace MWBase
|
|||
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 exteriorCreated(MWWorld::CellStore& cell) = 0;
|
||||
virtual void questUpdated(const ESM::RefId& questId, int stage) = 0;
|
||||
// TODO: notify LuaManager about other events
|
||||
// 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;
|
||||
|
|
|
@ -18,20 +18,27 @@
|
|||
|
||||
namespace MWDialogue
|
||||
{
|
||||
Quest& Journal::getQuest(const ESM::RefId& id)
|
||||
Quest& Journal::getOrStartQuest(const ESM::RefId& id)
|
||||
{
|
||||
TQuestContainer::iterator iter = mQuests.find(id);
|
||||
|
||||
if (iter == mQuests.end())
|
||||
{
|
||||
std::pair<TQuestContainer::iterator, bool> result = mQuests.insert(std::make_pair(id, Quest(id)));
|
||||
|
||||
iter = result.first;
|
||||
}
|
||||
iter = mQuests.emplace(id, Quest(id)).first;
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
Quest* Journal::getQuestOrNull(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)
|
||||
{
|
||||
TTopicContainer::iterator iter = mTopics.find(id);
|
||||
|
@ -89,7 +96,7 @@ namespace MWDialogue
|
|||
|
||||
StampedJournalEntry entry = StampedJournalEntry::makeFromQuest(id, index, actor);
|
||||
|
||||
Quest& quest = getQuest(id);
|
||||
Quest& quest = getOrStartQuest(id);
|
||||
if (quest.addEntry(entry)) // we are doing slicing on purpose here
|
||||
{
|
||||
// Restart all "other" quests with the same name as well
|
||||
|
@ -111,7 +118,7 @@ namespace MWDialogue
|
|||
|
||||
void Journal::setJournalIndex(const ESM::RefId& id, int index)
|
||||
{
|
||||
Quest& quest = getQuest(id);
|
||||
Quest& quest = getOrStartQuest(id);
|
||||
|
||||
quest.setIndex(index);
|
||||
}
|
||||
|
@ -253,7 +260,7 @@ namespace MWDialogue
|
|||
{
|
||||
case ESM::JournalEntry::Type_Quest:
|
||||
|
||||
getQuest(record.mTopic).insertEntry(record);
|
||||
getOrStartQuest(record.mTopic).insertEntry(record);
|
||||
break;
|
||||
|
||||
case ESM::JournalEntry::Type_Journal:
|
||||
|
|
|
@ -15,8 +15,6 @@ namespace MWDialogue
|
|||
TTopicContainer mTopics;
|
||||
|
||||
private:
|
||||
Quest& getQuest(const ESM::RefId& id);
|
||||
|
||||
Topic& getTopic(const ESM::RefId& id);
|
||||
|
||||
bool isThere(const ESM::RefId& topicId, const ESM::RefId& infoId = ESM::RefId()) const;
|
||||
|
@ -26,6 +24,12 @@ namespace MWDialogue
|
|||
|
||||
void clear() override;
|
||||
|
||||
Quest* getQuestOrNull(const ESM::RefId& id) override;
|
||||
///< Gets a pointer to the requested quest. Will return nullptr if the quest has not been started.
|
||||
|
||||
Quest& getOrStartQuest(const ESM::RefId& id) override;
|
||||
///< Gets the quest requested. Attempts to create it and inserts it in quests if it is not yet started.
|
||||
|
||||
void addEntry(const ESM::RefId& id, int index, const MWWorld::Ptr& actor) override;
|
||||
///< Add a journal entry.
|
||||
/// @param actor Used as context for replacing of escape sequences (%name, etc).
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <components/esm3/queststate.hpp>
|
||||
|
||||
#include "../mwbase/luamanager.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
@ -51,6 +52,7 @@ namespace MWDialogue
|
|||
void Quest::setIndex(int index)
|
||||
{
|
||||
// The index must be set even if no related journal entry was found
|
||||
MWBase::Environment::get().getLuaManager()->questUpdated(mTopic, index);
|
||||
mIndex = index;
|
||||
}
|
||||
|
||||
|
@ -79,7 +81,10 @@ namespace MWDialogue
|
|||
mFinished = info->mQuestStatus == ESM::DialInfo::QS_Finished;
|
||||
|
||||
if (info->mData.mJournalIndex > mIndex)
|
||||
{
|
||||
mIndex = info->mData.mJournalIndex;
|
||||
MWBase::Environment::get().getLuaManager()->questUpdated(mTopic, mIndex);
|
||||
}
|
||||
|
||||
for (TEntryIter iter(mEntries.begin()); iter != mEntries.end(); ++iter)
|
||||
if (iter->mInfoId == entry.mInfoId)
|
||||
|
|
|
@ -178,6 +178,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()
|
||||
{
|
||||
if (mPlayer.isEmpty())
|
||||
|
|
|
@ -75,6 +75,7 @@ namespace MWLua
|
|||
{
|
||||
mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell });
|
||||
}
|
||||
void questUpdated(const ESM::RefId& questId, int stage) override;
|
||||
|
||||
MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace MWLua
|
|||
{
|
||||
registerEngineHandlers({ &mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers,
|
||||
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mOnFrameHandlers,
|
||||
&mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved });
|
||||
&mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved, &mQuestUpdate });
|
||||
}
|
||||
|
||||
void processInputEvent(const MWBase::LuaManager::InputEvent& event)
|
||||
|
@ -56,6 +56,7 @@ namespace MWLua
|
|||
}
|
||||
|
||||
void onFrame(float dt) { callEngineHandlers(mOnFrameHandlers, dt); }
|
||||
void onQuestUpdate(std::string_view questId, int stage) { callEngineHandlers(mQuestUpdate, questId, stage); }
|
||||
|
||||
bool consoleCommand(
|
||||
const std::string& consoleMode, const std::string& command, const sol::object& selectedObject)
|
||||
|
@ -75,6 +76,7 @@ namespace MWLua
|
|||
EngineHandlerList mTouchpadPressed{ "onTouchPress" };
|
||||
EngineHandlerList mTouchpadReleased{ "onTouchRelease" };
|
||||
EngineHandlerList mTouchpadMoved{ "onTouchMove" };
|
||||
EngineHandlerList mQuestUpdate{ "onQuestUpdate" };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,124 @@
|
|||
#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/mwworld/class.hpp>
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
struct Quests
|
||||
{
|
||||
bool mMutable = false;
|
||||
};
|
||||
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();
|
||||
|
||||
player["quests"] = [](const Object& player) {
|
||||
if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||
throw std::runtime_error("The argument must be a player!");
|
||||
bool allowChanges = dynamic_cast<const GObject*>(&player) != nullptr
|
||||
|| dynamic_cast<const SelfObject*>(&player) != nullptr;
|
||||
return Quests{ .mMutable = allowChanges };
|
||||
};
|
||||
sol::usertype<Quests> quests = context.mLua->sol().new_usertype<Quests>("Quests");
|
||||
quests[sol::meta_function::to_string] = [](const Quests& quests) { return "Quests"; };
|
||||
quests[sol::meta_function::index] = sol::overload([](const Quests& quests, std::string_view questId) -> Quest {
|
||||
ESM::RefId quest = ESM::RefId::deserializeText(questId);
|
||||
const ESM::Dialogue* dial = MWBase::Environment::get().getESMStore()->get<ESM::Dialogue>().find(quest);
|
||||
if (dial->mType != ESM::Dialogue::Journal)
|
||||
throw std::runtime_error("Not a quest:" + std::string(questId));
|
||||
return Quest{ .mQuestId = quest, .mMutable = quests.mMutable };
|
||||
});
|
||||
quests[sol::meta_function::pairs] = [journal](const Quests& quests) {
|
||||
std::vector<ESM::RefId> ids;
|
||||
for (auto it = journal->questBegin(); it != journal->questEnd(); ++it)
|
||||
ids.push_back(it->first);
|
||||
size_t i = 0;
|
||||
return [ids = std::move(ids), i,
|
||||
allowChanges = quests.mMutable]() mutable -> sol::optional<std::tuple<std::string, Quest>> {
|
||||
if (i >= ids.size())
|
||||
return sol::nullopt;
|
||||
const ESM::RefId& id = ids[i++];
|
||||
return std::make_tuple(id.serializeText(), Quest{ .mQuestId = id, .mMutable = allowChanges });
|
||||
};
|
||||
};
|
||||
|
||||
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() + "]"; };
|
||||
|
||||
auto getQuestStage = [journal](const Quest& q) -> int {
|
||||
const MWDialogue::Quest* quest = journal->getQuestOrNull(q.mQuestId);
|
||||
if (quest == nullptr)
|
||||
return 0;
|
||||
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 or player scripts!");
|
||||
context.mLuaManager->addAction(
|
||||
[q, stage] { MWBase::Environment::get().getJournal()->setJournalIndex(q.mQuestId, stage); },
|
||||
"setQuestStageAction");
|
||||
};
|
||||
quest["stage"] = sol::property(getQuestStage, setQuestStage);
|
||||
|
||||
quest["id"] = sol::readonly_property([](const Quest& q) -> std::string { return q.mQuestId.serializeText(); });
|
||||
quest["started"] = sol::readonly_property(
|
||||
[journal](const Quest& q) { return journal->getQuestOrNull(q.mQuestId) != nullptr; });
|
||||
quest["finished"] = sol::property(
|
||||
[journal](const Quest& q) -> bool {
|
||||
const MWDialogue::Quest* quest = journal->getQuestOrNull(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 or player scripts!");
|
||||
context.mLuaManager->addAction(
|
||||
[q, finished, journal] { journal->getOrStartQuest(q.mQuestId).setFinished(finished); },
|
||||
"setQuestFinishedAction");
|
||||
});
|
||||
quest["addJournalEntry"] = [context](const Quest& q, int stage, sol::optional<GObject> actor) {
|
||||
if (!q.mMutable)
|
||||
throw std::runtime_error("Can only be used in global or player scripts!");
|
||||
// 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(
|
||||
[actor, q, stage] {
|
||||
MWWorld::Ptr actorPtr;
|
||||
if (actor)
|
||||
actorPtr = actor->ptr();
|
||||
MWBase::Environment::get().getJournal()->addEntry(q.mQuestId, stage, actorPtr);
|
||||
},
|
||||
"addJournalEntryAction");
|
||||
};
|
||||
}
|
||||
|
||||
void addPlayerBindings(sol::table player, const Context& context)
|
||||
{
|
||||
|
@ -12,5 +126,6 @@ namespace MWLua
|
|||
const MWWorld::Class& cls = o.ptr().getClass();
|
||||
return cls.getNpcStats(o.ptr()).getBounty();
|
||||
};
|
||||
addPlayerQuestBindings(player, context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
| Usage example:
|
||||
| ``if key.symbol == 'z' and key.withShift then ...``
|
||||
* - onQuestUpdate(questId, stage)
|
||||
- | Called when a quest is updated.
|
||||
* - onKeyRelease(key)
|
||||
- | `Key <openmw_input.html##(KeyboardEvent)>`_ is released.
|
||||
| Usage example:
|
||||
|
|
|
@ -697,7 +697,9 @@
|
|||
-- @field #string head Path to the head body part model
|
||||
-- @field #bool isMale The gender setting of the NPC
|
||||
|
||||
--- @{#Player} functions
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @{#Player} functions
|
||||
-- @field [parent=#types] #Player Player
|
||||
|
||||
---
|
||||
|
@ -714,10 +716,38 @@
|
|||
---
|
||||
-- Returns the bounty or crime level of the player
|
||||
-- @function [parent=#Player] getCrimeLevel
|
||||
-- @param openmw.core#GameObject actor
|
||||
-- @param openmw.core#GameObject player
|
||||
-- @return #number
|
||||
|
||||
--- @{#Armor} functions
|
||||
---
|
||||
-- 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 -- Get stage of a specific quest
|
||||
-- stage = types.Player.quests(player)["ms_fargothring"].stage
|
||||
-- @usage -- Get names of all started quests
|
||||
-- for x, quest in pairs(types.Player.quests(player)) do print (quest.name) end
|
||||
-- @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
|
||||
|
||||
---
|
||||
-- @type PlayerQuest
|
||||
-- @field #string id The quest id.
|
||||
-- @field #number stage The quest stage (global and player scripts can change it). Changing the stage starts the quest if it wasn't started.
|
||||
-- @field #bool started Whether the quest is started.
|
||||
-- @field #bool finished Whether the quest is finished (global and player scripts can change it).
|
||||
|
||||
---
|
||||
-- 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 or player scripts.
|
||||
-- @function [parent=#PlayerQuest] addJournalEntry
|
||||
-- @param self
|
||||
-- @param #number stage Quest stage
|
||||
-- @param openmw.core#GameObject actor (optional) The actor who is the source of the journal entry, it may be used in journal entries with variables such as `%name(The speaker's name)` or `%race(The speaker's race)`.
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @{#Armor} functions
|
||||
-- @field [parent=#types] #Armor Armor
|
||||
|
||||
---
|
||||
|
|
Loading…
Reference in a new issue