mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-04 02:41:19 +00:00
293 lines
8.6 KiB
C++
293 lines
8.6 KiB
C++
#include "journalimp.hpp"
|
|
|
|
#include <iterator>
|
|
|
|
#include <components/esm3/esmreader.hpp>
|
|
#include <components/esm3/esmwriter.hpp>
|
|
#include <components/esm3/journalentry.hpp>
|
|
#include <components/esm3/queststate.hpp>
|
|
|
|
#include <components/misc/strings/algorithm.hpp>
|
|
|
|
#include "../mwworld/class.hpp"
|
|
#include "../mwworld/esmstore.hpp"
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
|
|
namespace MWDialogue
|
|
{
|
|
Quest& Journal::getOrStartQuest(const ESM::RefId& id)
|
|
{
|
|
TQuestContainer::iterator iter = mQuests.find(id);
|
|
|
|
if (iter == mQuests.end())
|
|
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);
|
|
|
|
if (iter == mTopics.end())
|
|
{
|
|
std::pair<TTopicContainer::iterator, bool> result = mTopics.insert(std::make_pair(id, Topic(id)));
|
|
|
|
iter = result.first;
|
|
}
|
|
|
|
return iter->second;
|
|
}
|
|
|
|
bool Journal::isThere(const ESM::RefId& topicId, const ESM::RefId& infoId) const
|
|
{
|
|
if (const ESM::Dialogue* dialogue
|
|
= MWBase::Environment::get().getESMStore()->get<ESM::Dialogue>().search(topicId))
|
|
{
|
|
if (infoId.empty())
|
|
return true;
|
|
|
|
for (ESM::Dialogue::InfoContainer::const_iterator iter(dialogue->mInfo.begin());
|
|
iter != dialogue->mInfo.end(); ++iter)
|
|
if (iter->mId == infoId)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Journal::Journal() {}
|
|
|
|
void Journal::clear()
|
|
{
|
|
mJournal.clear();
|
|
mQuests.clear();
|
|
mTopics.clear();
|
|
}
|
|
|
|
void Journal::addEntry(const ESM::RefId& id, int index, const MWWorld::Ptr& actor)
|
|
{
|
|
// bail out if we already have heard this...
|
|
const ESM::RefId& infoId = JournalEntry::idFromIndex(id, index);
|
|
for (TEntryIter i = mJournal.begin(); i != mJournal.end(); ++i)
|
|
if (i->mTopic == id && i->mInfoId == infoId)
|
|
{
|
|
if (getJournalIndex(id) < index)
|
|
{
|
|
setJournalIndex(id, index);
|
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sJournalEntry}");
|
|
}
|
|
return;
|
|
}
|
|
|
|
StampedJournalEntry entry = StampedJournalEntry::makeFromQuest(id, index, actor);
|
|
|
|
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
|
|
std::string_view name = quest.getName();
|
|
for (auto& it : mQuests)
|
|
{
|
|
if (it.second.isFinished() && Misc::StringUtils::ciEqual(it.second.getName(), name))
|
|
it.second.setFinished(false);
|
|
}
|
|
}
|
|
|
|
// there is no need to show empty entries in journal
|
|
if (!entry.getText().empty())
|
|
{
|
|
mJournal.push_back(entry);
|
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sJournalEntry}");
|
|
}
|
|
}
|
|
|
|
void Journal::setJournalIndex(const ESM::RefId& id, int index)
|
|
{
|
|
Quest& quest = getOrStartQuest(id);
|
|
|
|
quest.setIndex(index);
|
|
}
|
|
|
|
void Journal::addTopic(const ESM::RefId& topicId, const ESM::RefId& infoId, const MWWorld::Ptr& actor)
|
|
{
|
|
Topic& topic = getTopic(topicId);
|
|
|
|
JournalEntry entry(topicId, infoId, actor);
|
|
entry.mActorName = actor.getClass().getName(actor);
|
|
topic.addEntry(entry);
|
|
}
|
|
|
|
void Journal::removeLastAddedTopicResponse(const ESM::RefId& topicId, std::string_view actorName)
|
|
{
|
|
Topic& topic = getTopic(topicId);
|
|
|
|
topic.removeLastAddedResponse(actorName);
|
|
|
|
if (topic.begin() == topic.end())
|
|
mTopics.erase(mTopics.find(topicId)); // All responses removed -> remove topic
|
|
}
|
|
|
|
int Journal::getJournalIndex(const ESM::RefId& id) const
|
|
{
|
|
TQuestContainer::const_iterator iter = mQuests.find(id);
|
|
|
|
if (iter == mQuests.end())
|
|
return 0;
|
|
|
|
return iter->second.getIndex();
|
|
}
|
|
|
|
Journal::TEntryIter Journal::begin() const
|
|
{
|
|
return mJournal.begin();
|
|
}
|
|
|
|
Journal::TEntryIter Journal::end() const
|
|
{
|
|
return mJournal.end();
|
|
}
|
|
|
|
Journal::TQuestIter Journal::questBegin() const
|
|
{
|
|
return mQuests.begin();
|
|
}
|
|
|
|
Journal::TQuestIter Journal::questEnd() const
|
|
{
|
|
return mQuests.end();
|
|
}
|
|
|
|
Journal::TTopicIter Journal::topicBegin() const
|
|
{
|
|
return mTopics.begin();
|
|
}
|
|
|
|
Journal::TTopicIter Journal::topicEnd() const
|
|
{
|
|
return mTopics.end();
|
|
}
|
|
|
|
int Journal::countSavedGameRecords() const
|
|
{
|
|
int count = static_cast<int>(mQuests.size());
|
|
|
|
for (TQuestIter iter(mQuests.begin()); iter != mQuests.end(); ++iter)
|
|
count += std::distance(iter->second.begin(), iter->second.end());
|
|
|
|
count += std::distance(mJournal.begin(), mJournal.end());
|
|
|
|
for (TTopicIter iter(mTopics.begin()); iter != mTopics.end(); ++iter)
|
|
count += std::distance(iter->second.begin(), iter->second.end());
|
|
|
|
return count;
|
|
}
|
|
|
|
void Journal::write(ESM::ESMWriter& writer, Loading::Listener& progress) const
|
|
{
|
|
for (TQuestIter iter(mQuests.begin()); iter != mQuests.end(); ++iter)
|
|
{
|
|
const Quest& quest = iter->second;
|
|
|
|
ESM::QuestState state;
|
|
quest.write(state);
|
|
writer.startRecord(ESM::REC_QUES);
|
|
state.save(writer);
|
|
writer.endRecord(ESM::REC_QUES);
|
|
|
|
for (Topic::TEntryIter entryIter(quest.begin()); entryIter != quest.end(); ++entryIter)
|
|
{
|
|
ESM::JournalEntry entry;
|
|
entry.mType = ESM::JournalEntry::Type_Quest;
|
|
entry.mTopic = quest.getTopic();
|
|
entryIter->write(entry);
|
|
writer.startRecord(ESM::REC_JOUR);
|
|
entry.save(writer);
|
|
writer.endRecord(ESM::REC_JOUR);
|
|
}
|
|
}
|
|
|
|
for (TEntryIter iter(mJournal.begin()); iter != mJournal.end(); ++iter)
|
|
{
|
|
ESM::JournalEntry entry;
|
|
entry.mType = ESM::JournalEntry::Type_Journal;
|
|
iter->write(entry);
|
|
writer.startRecord(ESM::REC_JOUR);
|
|
entry.save(writer);
|
|
writer.endRecord(ESM::REC_JOUR);
|
|
}
|
|
|
|
for (TTopicIter iter(mTopics.begin()); iter != mTopics.end(); ++iter)
|
|
{
|
|
const Topic& topic = iter->second;
|
|
|
|
for (Topic::TEntryIter entryIter(topic.begin()); entryIter != topic.end(); ++entryIter)
|
|
{
|
|
ESM::JournalEntry entry;
|
|
entry.mType = ESM::JournalEntry::Type_Topic;
|
|
entry.mTopic = topic.getTopic();
|
|
entryIter->write(entry);
|
|
writer.startRecord(ESM::REC_JOUR);
|
|
entry.save(writer);
|
|
writer.endRecord(ESM::REC_JOUR);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Journal::readRecord(ESM::ESMReader& reader, uint32_t type)
|
|
{
|
|
if (type == ESM::REC_JOUR)
|
|
{
|
|
ESM::JournalEntry record;
|
|
record.load(reader);
|
|
|
|
if (isThere(record.mTopic, record.mInfo))
|
|
switch (record.mType)
|
|
{
|
|
case ESM::JournalEntry::Type_Quest:
|
|
|
|
getOrStartQuest(record.mTopic).insertEntry(record);
|
|
break;
|
|
|
|
case ESM::JournalEntry::Type_Journal:
|
|
|
|
mJournal.push_back(record);
|
|
break;
|
|
|
|
case ESM::JournalEntry::Type_Topic:
|
|
|
|
getTopic(record.mTopic).insertEntry(record);
|
|
break;
|
|
}
|
|
}
|
|
else if (type == ESM::REC_QUES)
|
|
{
|
|
ESM::QuestState record;
|
|
record.load(reader);
|
|
|
|
if (isThere(record.mTopic))
|
|
{
|
|
std::pair<TQuestContainer::iterator, bool> result
|
|
= mQuests.insert(std::make_pair(record.mTopic, record));
|
|
// reapply quest index, this is to handle users upgrading from only
|
|
// Morrowind.esm (no quest states) to Morrowind.esm + Tribunal.esm
|
|
result.first->second.setIndex(record.mState);
|
|
}
|
|
}
|
|
}
|
|
}
|