mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-26 18:35:20 +00:00
20da0892ef
Slowly moving through the open-cs errors Good progress in openCS Very good progress on openCS Getting closer with openCS OpenCS compiles and runs! Didn't have time to test it all though ix openMW everything compiles on windows?? Fix gcc Fix Clang
450 lines
15 KiB
C++
450 lines
15 KiB
C++
#include "topicinfocheck.hpp"
|
|
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <apps/opencs/model/doc/messages.hpp>
|
|
#include <apps/opencs/model/prefs/category.hpp>
|
|
#include <apps/opencs/model/prefs/setting.hpp>
|
|
#include <apps/opencs/model/world/cell.hpp>
|
|
#include <apps/opencs/model/world/idcollection.hpp>
|
|
#include <apps/opencs/model/world/info.hpp>
|
|
#include <apps/opencs/model/world/infocollection.hpp>
|
|
#include <apps/opencs/model/world/record.hpp>
|
|
#include <apps/opencs/model/world/refiddata.hpp>
|
|
#include <apps/opencs/model/world/resources.hpp>
|
|
#include <apps/opencs/model/world/universalid.hpp>
|
|
|
|
#include <components/esm3/loadclas.hpp>
|
|
#include <components/esm3/loaddial.hpp>
|
|
#include <components/esm3/loadfact.hpp>
|
|
#include <components/esm3/loadglob.hpp>
|
|
#include <components/esm3/loadgmst.hpp>
|
|
#include <components/esm3/loadinfo.hpp>
|
|
#include <components/esm3/loadrace.hpp>
|
|
#include <components/esm3/loadregn.hpp>
|
|
#include <components/esm3/variant.hpp>
|
|
|
|
#include "../prefs/state.hpp"
|
|
|
|
#include "../world/infoselectwrapper.hpp"
|
|
|
|
CSMTools::TopicInfoCheckStage::TopicInfoCheckStage(const CSMWorld::InfoCollection& topicInfos,
|
|
const CSMWorld::IdCollection<CSMWorld::Cell>& cells, const CSMWorld::IdCollection<ESM::Class>& classes,
|
|
const CSMWorld::IdCollection<ESM::Faction>& factions, const CSMWorld::IdCollection<ESM::GameSetting>& gmsts,
|
|
const CSMWorld::IdCollection<ESM::Global>& globals, const CSMWorld::IdCollection<ESM::Dialogue>& journals,
|
|
const CSMWorld::IdCollection<ESM::Race>& races, const CSMWorld::IdCollection<ESM::Region>& regions,
|
|
const CSMWorld::IdCollection<ESM::Dialogue>& topics, const CSMWorld::RefIdData& referencables,
|
|
const CSMWorld::Resources& soundFiles)
|
|
: mTopicInfos(topicInfos)
|
|
, mCells(cells)
|
|
, mClasses(classes)
|
|
, mFactions(factions)
|
|
, mGameSettings(gmsts)
|
|
, mGlobals(globals)
|
|
, mJournals(journals)
|
|
, mRaces(races)
|
|
, mRegions(regions)
|
|
, mTopics(topics)
|
|
, mReferencables(referencables)
|
|
, mSoundFiles(soundFiles)
|
|
{
|
|
mIgnoreBaseRecords = false;
|
|
}
|
|
|
|
int CSMTools::TopicInfoCheckStage::setup()
|
|
{
|
|
// Generate list of cell names for reference checking
|
|
|
|
mCellNames.clear();
|
|
for (int i = 0; i < mCells.getSize(); ++i)
|
|
{
|
|
const CSMWorld::Record<CSMWorld::Cell>& cellRecord = mCells.getRecord(i);
|
|
|
|
if (cellRecord.isDeleted())
|
|
continue;
|
|
|
|
mCellNames.insert(cellRecord.get().mName);
|
|
}
|
|
// Cell names can also include region names
|
|
for (int i = 0; i < mRegions.getSize(); ++i)
|
|
{
|
|
const CSMWorld::Record<ESM::Region>& regionRecord = mRegions.getRecord(i);
|
|
|
|
if (regionRecord.isDeleted())
|
|
continue;
|
|
|
|
mCellNames.insert(regionRecord.get().mName);
|
|
}
|
|
// Default cell name
|
|
int index = mGameSettings.searchId("sDefaultCellname");
|
|
if (index != -1)
|
|
{
|
|
const CSMWorld::Record<ESM::GameSetting>& gmstRecord = mGameSettings.getRecord(index);
|
|
|
|
if (!gmstRecord.isDeleted() && gmstRecord.get().mValue.getType() == ESM::VT_String)
|
|
{
|
|
mCellNames.insert(gmstRecord.get().mValue.getString());
|
|
}
|
|
}
|
|
|
|
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
|
|
return mTopicInfos.getSize();
|
|
}
|
|
|
|
void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& messages)
|
|
{
|
|
const CSMWorld::Record<CSMWorld::Info>& infoRecord = mTopicInfos.getRecord(stage);
|
|
|
|
// Skip "Base" records (setting!) and "Deleted" records
|
|
if ((mIgnoreBaseRecords && infoRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || infoRecord.isDeleted())
|
|
return;
|
|
|
|
const CSMWorld::Info& topicInfo = infoRecord.get();
|
|
|
|
// There should always be a topic that matches
|
|
int topicIndex = mTopics.searchId(topicInfo.mTopicId);
|
|
|
|
const CSMWorld::Record<ESM::Dialogue>& topicRecord = mTopics.getRecord(topicIndex);
|
|
|
|
if (topicRecord.isDeleted())
|
|
return;
|
|
|
|
const ESM::Dialogue& topic = topicRecord.get();
|
|
|
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_TopicInfo, topicInfo.mId);
|
|
|
|
// Check fields
|
|
|
|
if (!topicInfo.mActor.empty())
|
|
{
|
|
verifyActor(topicInfo.mActor, id, messages);
|
|
}
|
|
|
|
if (!topicInfo.mClass.empty())
|
|
{
|
|
verifyId(topicInfo.mClass, mClasses, id, messages);
|
|
}
|
|
|
|
if (!topicInfo.mCell.empty())
|
|
{
|
|
verifyCell(topicInfo.mCell, id, messages);
|
|
}
|
|
|
|
if (!topicInfo.mFaction.empty() && !topicInfo.mFactionLess)
|
|
{
|
|
if (verifyId(topicInfo.mFaction, mFactions, id, messages))
|
|
{
|
|
verifyFactionRank(topicInfo.mFaction, topicInfo.mData.mRank, id, messages);
|
|
}
|
|
}
|
|
|
|
if (!topicInfo.mPcFaction.empty())
|
|
{
|
|
if (verifyId(topicInfo.mPcFaction, mFactions, id, messages))
|
|
{
|
|
verifyFactionRank(topicInfo.mPcFaction, topicInfo.mData.mPCrank, id, messages);
|
|
}
|
|
}
|
|
|
|
if (topicInfo.mData.mGender < -1 || topicInfo.mData.mGender > 1)
|
|
{
|
|
messages.add(id, "Gender is invalid", "", CSMDoc::Message::Severity_Error);
|
|
}
|
|
|
|
if (!topicInfo.mRace.empty())
|
|
{
|
|
verifyId(topicInfo.mRace, mRaces, id, messages);
|
|
}
|
|
|
|
if (!topicInfo.mSound.empty())
|
|
{
|
|
verifySound(topicInfo.mSound, id, messages);
|
|
}
|
|
|
|
if (topicInfo.mResponse.empty() && topic.mType != ESM::Dialogue::Voice)
|
|
{
|
|
messages.add(id, "Response is empty", "", CSMDoc::Message::Severity_Warning);
|
|
}
|
|
|
|
// Check info conditions
|
|
|
|
for (std::vector<ESM::DialInfo::SelectStruct>::const_iterator it = topicInfo.mSelects.begin();
|
|
it != topicInfo.mSelects.end(); ++it)
|
|
{
|
|
verifySelectStruct((*it), id, messages);
|
|
}
|
|
}
|
|
|
|
// Verification functions
|
|
|
|
bool CSMTools::TopicInfoCheckStage::verifyActor(
|
|
const ESM::RefId& actor, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages)
|
|
{
|
|
std::string actorString = actor.getRefIdString();
|
|
CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(actor);
|
|
|
|
if (index.first == -1)
|
|
{
|
|
messages.add(id, "Actor '" + actorString + "' does not exist", "", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
else if (mReferencables.getRecord(index).isDeleted())
|
|
{
|
|
messages.add(id, "Deleted actor '" + actorString + "' is being referenced", "", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
else if (index.second != CSMWorld::UniversalId::Type_Npc && index.second != CSMWorld::UniversalId::Type_Creature)
|
|
{
|
|
CSMWorld::UniversalId tempId(index.second, actor);
|
|
std::ostringstream stream;
|
|
stream << "Object '" << actor << "' has invalid type " << tempId.getTypeName()
|
|
<< " (an actor must be an NPC or a creature)";
|
|
messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CSMTools::TopicInfoCheckStage::verifyCell(
|
|
const ESM::RefId& cell, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages)
|
|
{
|
|
std::string cellName = cell.getRefIdString();
|
|
if (mCellNames.find(cellName) == mCellNames.end())
|
|
{
|
|
messages.add(id, "Cell '" + cellName + "' does not exist", "", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CSMTools::TopicInfoCheckStage::verifyFactionRank(
|
|
const ESM::RefId& factionName, int rank, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages)
|
|
{
|
|
if (rank < -1)
|
|
{
|
|
std::ostringstream stream;
|
|
stream << "Faction rank is set to " << rank << ", but it should be set to -1 if there are no rank requirements";
|
|
messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning);
|
|
return false;
|
|
}
|
|
|
|
int index = mFactions.searchId(factionName);
|
|
|
|
const ESM::Faction& faction = mFactions.getRecord(index).get();
|
|
|
|
int limit = 0;
|
|
for (; limit < 10; ++limit)
|
|
{
|
|
if (faction.mRanks[limit].empty())
|
|
break;
|
|
}
|
|
|
|
if (rank >= limit)
|
|
{
|
|
std::ostringstream stream;
|
|
stream << "Faction rank is set to " << rank << " which is more than the maximum of " << limit - 1
|
|
<< " for the '" << factionName << "' faction";
|
|
|
|
messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CSMTools::TopicInfoCheckStage::verifyItem(
|
|
const ESM::RefId& item, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages)
|
|
{
|
|
std::string idString = item.getRefIdString();
|
|
CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(item);
|
|
|
|
if (index.first == -1)
|
|
{
|
|
messages.add(id, ("Item '" + idString + "' does not exist"), "", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
else if (mReferencables.getRecord(index).isDeleted())
|
|
{
|
|
messages.add(id, ("Deleted item '" + idString + "' is being referenced"), "", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
switch (index.second)
|
|
{
|
|
case CSMWorld::UniversalId::Type_Potion:
|
|
case CSMWorld::UniversalId::Type_Apparatus:
|
|
case CSMWorld::UniversalId::Type_Armor:
|
|
case CSMWorld::UniversalId::Type_Book:
|
|
case CSMWorld::UniversalId::Type_Clothing:
|
|
case CSMWorld::UniversalId::Type_Ingredient:
|
|
case CSMWorld::UniversalId::Type_Light:
|
|
case CSMWorld::UniversalId::Type_Lockpick:
|
|
case CSMWorld::UniversalId::Type_Miscellaneous:
|
|
case CSMWorld::UniversalId::Type_Probe:
|
|
case CSMWorld::UniversalId::Type_Repair:
|
|
case CSMWorld::UniversalId::Type_Weapon:
|
|
case CSMWorld::UniversalId::Type_ItemLevelledList:
|
|
break;
|
|
|
|
default:
|
|
{
|
|
CSMWorld::UniversalId tempId(index.second, item);
|
|
std::ostringstream stream;
|
|
stream << "Object '" << item << "' has invalid type " << tempId.getTypeName()
|
|
<< " (an item can be a potion, an armor piece, a book and so on)";
|
|
messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CSMTools::TopicInfoCheckStage::verifySelectStruct(
|
|
const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages)
|
|
{
|
|
CSMWorld::ConstInfoSelectWrapper infoCondition(select);
|
|
|
|
if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None)
|
|
{
|
|
messages.add(id, "Invalid condition '" + infoCondition.toString() + "'", "", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
else if (!infoCondition.variantTypeIsValid())
|
|
{
|
|
std::ostringstream stream;
|
|
stream << "Value of condition '" << infoCondition.toString() << "' has invalid ";
|
|
|
|
switch (select.mValue.getType())
|
|
{
|
|
case ESM::VT_None:
|
|
stream << "None";
|
|
break;
|
|
case ESM::VT_Short:
|
|
stream << "Short";
|
|
break;
|
|
case ESM::VT_Int:
|
|
stream << "Int";
|
|
break;
|
|
case ESM::VT_Long:
|
|
stream << "Long";
|
|
break;
|
|
case ESM::VT_Float:
|
|
stream << "Float";
|
|
break;
|
|
case ESM::VT_String:
|
|
stream << "String";
|
|
break;
|
|
default:
|
|
stream << "unknown";
|
|
break;
|
|
}
|
|
stream << " type";
|
|
|
|
messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
else if (infoCondition.conditionIsAlwaysTrue())
|
|
{
|
|
messages.add(
|
|
id, "Condition '" + infoCondition.toString() + "' is always true", "", CSMDoc::Message::Severity_Warning);
|
|
return false;
|
|
}
|
|
else if (infoCondition.conditionIsNeverTrue())
|
|
{
|
|
messages.add(
|
|
id, "Condition '" + infoCondition.toString() + "' is never true", "", CSMDoc::Message::Severity_Warning);
|
|
return false;
|
|
}
|
|
|
|
// Id checks
|
|
if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global
|
|
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mGlobals, id, messages))
|
|
{
|
|
return false;
|
|
}
|
|
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal
|
|
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mJournals, id, messages))
|
|
{
|
|
return false;
|
|
}
|
|
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item
|
|
&& !verifyItem(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages))
|
|
{
|
|
return false;
|
|
}
|
|
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead
|
|
&& !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages))
|
|
{
|
|
return false;
|
|
}
|
|
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId
|
|
&& !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages))
|
|
{
|
|
return false;
|
|
}
|
|
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction
|
|
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mFactions, id, messages))
|
|
{
|
|
return false;
|
|
}
|
|
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass
|
|
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mClasses, id, messages))
|
|
{
|
|
return false;
|
|
}
|
|
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace
|
|
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mRaces, id, messages))
|
|
{
|
|
return false;
|
|
}
|
|
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell
|
|
&& !verifyCell(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CSMTools::TopicInfoCheckStage::verifySound(
|
|
const std::string& sound, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages)
|
|
{
|
|
if (mSoundFiles.searchId(sound) == -1)
|
|
{
|
|
messages.add(id, "Sound file '" + sound + "' does not exist", "", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
bool CSMTools::TopicInfoCheckStage::verifyId(const ESM::RefId& name, const CSMWorld::IdCollection<T>& collection,
|
|
const CSMWorld::UniversalId& id, CSMDoc::Messages& messages)
|
|
{
|
|
int index = collection.searchId(name);
|
|
|
|
if (index == -1)
|
|
{
|
|
messages.add(id, std::string(T::getRecordType()) + " '" + name.getRefIdString() + "' does not exist", "",
|
|
CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
else if (collection.getRecord(index).isDeleted())
|
|
{
|
|
messages.add(id, "Deleted " + std::string(T::getRecordType()) + " record '" + name.getRefIdString() + "' is being referenced",
|
|
"", CSMDoc::Message::Severity_Error);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|