bool CSMTools::TopicInfoCheckStage::verifyActor(const std::string& actor, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(actor); if (index.first == -1) { messages.add(id, "Actor '" + actor + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } else if (mReferencables.getRecord(index).isDeleted()) { messages.add(id, "Deleted actor '" + actor + "' 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::verifyFactionRank(const std::string& 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; }
void CSMTools::BirthsignCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record<ESM::BirthSign>& record = mBirthsigns.getRecord (stage); if (record.isDeleted()) return; const ESM::BirthSign& birthsign = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Birthsign, birthsign.mId); // test for empty name, description and texture if (birthsign.mName.empty()) messages.push_back (std::make_pair (id, birthsign.mId + " has an empty name")); if (birthsign.mDescription.empty()) messages.push_back (std::make_pair (id, birthsign.mId + " has an empty description")); if (birthsign.mTexture.empty()) messages.push_back (std::make_pair (id, birthsign.mId + " is missing a texture")); /// \todo test if the texture exists /// \todo check data members that can't be edited in the table view }
void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messages) { const CSMWorld::Record<ESM::MagicEffect> &record = mMagicEffects.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; ESM::MagicEffect effect = record.get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId); if (effect.mData.mBaseCost < 0.0f) { messages.push_back(std::make_pair(id, "Base Cost is negative")); } if (effect.mIcon.empty()) { messages.push_back(std::make_pair(id, "Icon is not specified")); } else if (!isTextureExists(effect.mIcon, true)) { messages.push_back(std::make_pair(id, "No such Icon '" + effect.mIcon + "'")); } if (!effect.mParticle.empty() && !isTextureExists(effect.mParticle, false)) { messages.push_back(std::make_pair(id, "No such Particle '" + effect.mParticle + "'")); } addMessageIfNotEmpty(messages, id, checkReferenceable(effect.mCasting, CSMWorld::UniversalId::Type_Static, "Casting Object")); addMessageIfNotEmpty(messages, id, checkReferenceable(effect.mHit, CSMWorld::UniversalId::Type_Static, "Hit Object")); addMessageIfNotEmpty(messages, id, checkReferenceable(effect.mArea, CSMWorld::UniversalId::Type_Static, "Area Object")); addMessageIfNotEmpty(messages, id, checkReferenceable(effect.mBolt, CSMWorld::UniversalId::Type_Weapon, "Bolt Object")); addMessageIfNotEmpty(messages, id, checkSound(effect.mCastSound, "Casting Sound")); addMessageIfNotEmpty(messages, id, checkSound(effect.mHitSound, "Hit Sound")); addMessageIfNotEmpty(messages, id, checkSound(effect.mAreaSound, "Area Sound")); addMessageIfNotEmpty(messages, id, checkSound(effect.mBoltSound, "Bolt Sound")); if (effect.mDescription.empty()) { messages.push_back(std::make_pair(id, "Description is empty")); } }
void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record<ESM::Class>& record = mClasses.getRecord (stage); if (record.isDeleted()) return; const ESM::Class& class_ = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Class, class_.mId); // test for empty name and description if (class_.mName.empty()) messages.push_back (std::make_pair (id, class_.mId + " has an empty name")); if (class_.mDescription.empty()) messages.push_back (std::make_pair (id, class_.mId + " has an empty description")); // test for invalid attributes for (int i=0; i<2; ++i) if (class_.mData.mAttribute[i]==-1) { std::ostringstream stream; stream << "Attribute #" << i << " of " << class_.mId << " is not set"; messages.push_back (std::make_pair (id, stream.str())); } if (class_.mData.mAttribute[0]==class_.mData.mAttribute[1] && class_.mData.mAttribute[0]!=-1) { messages.push_back (std::make_pair (id, "Class lists same attribute twice")); } // test for non-unique skill std::map<int, int> skills; // ID, number of occurrences for (int i=0; i<5; ++i) for (int i2=0; i2<2; ++i2) ++skills[class_.mData.mSkills[i][i2]]; for (std::map<int, int>::const_iterator iter (skills.begin()); iter!=skills.end(); ++iter) if (iter->second>1) { messages.push_back (std::make_pair (id, ESM::Skill::indexToId (iter->first) + " is listed more than once")); } }
void CSMTools::RaceCheckStage::performFinal (CSMDoc::Messages& messages) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Races); if (!mPlayable) messages.push_back (std::make_pair (id, "No playable race")); }
bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(item); if (index.first == -1) { messages.add(id, ("Item '" + item + "' does not exist"), "", CSMDoc::Message::Severity_Error); return false; } else if (mReferencables.getRecord(index).isDeleted()) { messages.add(id, ("Deleted item '" + item + "' 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::verifyId(const std::string& name, const CSMWorld::IdCollection<T>& collection, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { int index = collection.searchId(name); if (index == -1) { messages.add(id, T::getRecordType() + " '" + name + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } else if (collection.getRecord(index).isDeleted()) { messages.add(id, "Deleted " + T::getRecordType() + " record '" + name + "' is being referenced", "", CSMDoc::Message::Severity_Error); 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; }
bool CSMTools::TopicInfoCheckStage::verifyCell(const std::string& cell, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { if (mCellNames.find(cell) == mCellNames.end()) { messages.add(id, "Cell '" + cell + "' does not exist", "", CSMDoc::Message::Severity_Error); return false; } return true; }
void CSMTools::SpellCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record<ESM::Spell>& record = mSpells.getRecord (stage); if (record.isDeleted()) return; const ESM::Spell& spell = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Spell, spell.mId); // test for empty name and description if (spell.mName.empty()) messages.push_back (std::make_pair (id, spell.mId + " has an empty name")); // test for invalid cost values if (spell.mData.mCost<0) messages.push_back (std::make_pair (id, spell.mId + " has a negative spell costs")); /// \todo check data members that can't be edited in the table view }
void CSMTools::FactionCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record<ESM::Faction>& record = mFactions.getRecord (stage); if (record.isDeleted()) return; const ESM::Faction& faction = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Faction, faction.mId); // test for empty name if (faction.mName.empty()) messages.push_back (std::make_pair (id, faction.mId + " has an empty name")); // test for invalid attributes if (faction.mData.mAttribute[0]==faction.mData.mAttribute[1] && faction.mData.mAttribute[0]!=-1) { messages.push_back (std::make_pair (id , "Faction lists same attribute twice")); } // test for non-unique skill std::map<int, int> skills; // ID, number of occurrences for (int i=0; i<7; ++i) if (faction.mData.mSkills[i]!=-1) ++skills[faction.mData.mSkills[i]]; for (std::map<int, int>::const_iterator iter (skills.begin()); iter!=skills.end(); ++iter) if (iter->second>1) { messages.push_back (std::make_pair (id, ESM::Skill::indexToId (iter->first) + " is listed more than once")); } /// \todo check data members that can't be edited in the table view }
void CSMTools::SoundCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record<ESM::Sound>& record = mSounds.getRecord (stage); if (record.isDeleted()) return; const ESM::Sound& sound = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Sound, sound.mId); if (sound.mData.mMinRange>sound.mData.mMaxRange) messages.push_back (std::make_pair (id, "Maximum range larger than minimum range")); /// \todo check, if the sound file exists }
void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) { mId = mDocument.getData().getScripts().getId (stage); if (mDocument.isBlacklisted ( CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, mId))) return; mMessages = &messages; switch (mWarningMode) { case Mode_Ignore: setWarningsMode (0); break; case Mode_Normal: setWarningsMode (1); break; case Mode_Strict: setWarningsMode (2); break; } try { const CSMWorld::Data& data = mDocument.getData(); mFile = data.getScripts().getRecord (stage).get().mId; std::istringstream input (data.getScripts().getRecord (stage).get().mScriptText); Compiler::Scanner scanner (*this, input, mContext.getExtensions()); Compiler::FileParser parser (*this, mContext); scanner.scan (parser); } catch (const Compiler::SourceException&) { // error has already been reported via error handler } catch (const std::exception& error) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); std::ostringstream stream; stream << "script " << mFile << ": " << error.what(); messages.add (id, stream.str(), "", CSMDoc::Message::Severity_SeriousError); } mMessages = 0; }
void CSMTools::SoundCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record<ESM::Sound>& record = mSounds.getRecord (stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::Sound& sound = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Sound, sound.mId); if (sound.mData.mMinRange>sound.mData.mMaxRange) messages.push_back (std::make_pair (id, "Minimum range larger than maximum range")); /// \todo check, if the sound file exists }
void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record<ESM::Region>& record = mRegions.getRecord (stage); if (record.isDeleted()) return; const ESM::Region& region = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Region, region.mId); // test for empty name if (region.mName.empty()) messages.push_back (std::make_pair (id, region.mId + " has an empty name")); /// \todo test that the ID in mSleeplist exists /// \todo check data members that can't be edited in the table view }
void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) { mId = mDocument.getData().getScripts().getId (stage); if (mDocument.isBlacklisted ( CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, mId))) return; mMessages = &messages; try { const CSMWorld::Data& data = mDocument.getData(); mFile = data.getScripts().getRecord (stage).get().mId; std::istringstream input (data.getScripts().getRecord (stage).get().mScriptText); Compiler::Scanner scanner (*this, input, mContext.getExtensions()); Compiler::FileParser parser (*this, mContext); scanner.scan (parser); } catch (const Compiler::SourceException&) { // error has already been reported via error handler } catch (const std::exception& error) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); messages.push_back (std::make_pair (id, std::string ("Critical compile error: ") + error.what())); } mMessages = 0; }
void CSMTools::BodyPartCheckStage::perform (int stage, CSMDoc::Messages &messages) { const CSMWorld::Record<ESM::BodyPart> &record = mBodyParts.getRecord(stage); // Skip "Base" records (setting!) and "Deleted" records if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted()) return; const ESM::BodyPart &bodyPart = record.get(); CSMWorld::UniversalId id( CSMWorld::UniversalId::Type_BodyPart, bodyPart.mId ); // Check BYDT if (bodyPart.mData.mPart > 14 ) messages.push_back(std::make_pair( id, bodyPart.mId + " has out of range part value." )); if (bodyPart.mData.mFlags > 3 ) messages.push_back(std::make_pair( id, bodyPart.mId + " has out of range flags value." )); if (bodyPart.mData.mType > 2 ) messages.push_back(std::make_pair( id, bodyPart.mId + " has out of range type value." )); // Check MODL if ( bodyPart.mModel.empty() ) messages.push_back(std::make_pair( id, bodyPart.mId + " has no model." )); else if ( mMeshes.searchId( bodyPart.mModel ) == -1 ) messages.push_back(std::make_pair( id, bodyPart.mId + " has invalid model." )); // Check FNAM if ( bodyPart.mRace.empty() ) messages.push_back(std::make_pair( id, bodyPart.mId + " has no race." )); else if ( mRaces.searchId( bodyPart.mRace ) == -1 ) messages.push_back(std::make_pair( id, bodyPart.mId + " has invalid race." )); }
void CSMTools::RaceCheckStage::performPerRecord (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record<ESM::Race>& record = mRaces.getRecord (stage); if (record.isDeleted()) return; const ESM::Race& race = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Race, race.mId); // test for empty name and description if (race.mName.empty()) messages.push_back (std::make_pair (id, race.mId + " has an empty name")); if (race.mDescription.empty()) messages.push_back (std::make_pair (id, race.mId + " has an empty description")); // test for positive height if (race.mData.mHeight.mMale<=0) messages.push_back (std::make_pair (id, "male " + race.mId + " has non-positive height")); if (race.mData.mHeight.mFemale<=0) messages.push_back (std::make_pair (id, "female " + race.mId + " has non-positive height")); // test for non-negative weight if (race.mData.mWeight.mMale<0) messages.push_back (std::make_pair (id, "male " + race.mId + " has negative weight")); if (race.mData.mWeight.mFemale<0) messages.push_back (std::make_pair (id, "female " + race.mId + " has negative weight")); // remember playable flag if (race.mData.mFlags & 0x1) mPlayable = true; /// \todo check data members that can't be edited in the table view }
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(infoCondition.getVariableName(), mGlobals, id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal && !verifyId(infoCondition.getVariableName(), mJournals, id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item && !verifyItem(infoCondition.getVariableName(), id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead && !verifyActor(infoCondition.getVariableName(), id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId && !verifyActor(infoCondition.getVariableName(), id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction && !verifyId(infoCondition.getVariableName(), mFactions, id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass && !verifyId(infoCondition.getVariableName(), mClasses, id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace && !verifyId(infoCondition.getVariableName(), mRaces, id, messages)) { return false; } else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell && !verifyCell(infoCondition.getVariableName(), id, messages)) { return false; } return true; }
void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record<CSMWorld::Pathgrid>& record = mPathgrids.getRecord (stage); if (record.isDeleted()) return; const CSMWorld::Pathgrid& pathgrid = record.get(); CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Pathgrid, pathgrid.mId); // check the number of pathgrid points if (pathgrid.mData.mS2 > static_cast<int>(pathgrid.mPoints.size())) messages.push_back (std::make_pair (id, pathgrid.mId + " has less points than expected")); else if (pathgrid.mData.mS2 > static_cast<int>(pathgrid.mPoints.size())) messages.push_back (std::make_pair (id, pathgrid.mId + " has more points than expected")); std::vector<Point> pointList(pathgrid.mPoints.size()); std::vector<int> duplList; for (unsigned int i = 0; i < pathgrid.mEdges.size(); ++i) { if (pathgrid.mEdges[i].mV0 < static_cast<int>(pathgrid.mPoints.size()) && pathgrid.mEdges[i].mV0 >= 0) { pointList[pathgrid.mEdges[i].mV0].mConnectionNum++; // first check for duplicate edges unsigned int j = 0; for (; j < pointList[pathgrid.mEdges[i].mV0].mOtherIndex.size(); ++j) { if (pointList[pathgrid.mEdges[i].mV0].mOtherIndex[j] == pathgrid.mEdges[i].mV1) { std::ostringstream ss; ss << "has a duplicate edge between points" << pathgrid.mEdges[i].mV0 << " and " << pathgrid.mEdges[i].mV1; messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); break; } } // only add if not a duplicate if (j == pointList[pathgrid.mEdges[i].mV0].mOtherIndex.size()) pointList[pathgrid.mEdges[i].mV0].mOtherIndex.push_back(pathgrid.mEdges[i].mV1); } else { std::ostringstream ss; ss << " has an edge connecting a non-existent point " << pathgrid.mEdges[i].mV0; messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); } } for (unsigned int i = 0; i < pathgrid.mPoints.size(); ++i) { // check the connection number for each point matches the edge connections if (pathgrid.mPoints[i].mConnectionNum > pointList[i].mConnectionNum) { std::ostringstream ss; ss << " has has less edges than expected for point " << i; messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); } else if (pathgrid.mPoints[i].mConnectionNum < pointList[i].mConnectionNum) { std::ostringstream ss; ss << " has has more edges than expected for point " << i; messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); } // check that edges are bidirectional bool foundReverse = false; for (unsigned int j = 0; j < pointList[i].mOtherIndex.size(); ++j) { for (unsigned int k = 0; k < pointList[pointList[i].mOtherIndex[j]].mOtherIndex.size(); ++k) { if (pointList[pointList[i].mOtherIndex[j]].mOtherIndex[k] == static_cast<int>(i)) { foundReverse = true; break; } } if (!foundReverse) { std::ostringstream ss; ss << " has a missing edge between points " << i << " and " << pointList[i].mOtherIndex[j]; messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); } } // check duplicate points // FIXME: how to do this efficiently? for (unsigned int j = 0; j < pathgrid.mPoints.size(); ++j) { if (j == i) continue; if (pathgrid.mPoints[i].mX == pathgrid.mPoints[j].mX && pathgrid.mPoints[i].mY == pathgrid.mPoints[j].mY && pathgrid.mPoints[i].mZ == pathgrid.mPoints[j].mZ) { std::vector<int>::const_iterator it = find(duplList.begin(), duplList.end(), i); if (it == duplList.end()) { std::ostringstream ss; ss << " has a duplicated point (" << i << ") x=" << pathgrid.mPoints[i].mX << ", y=" << pathgrid.mPoints[i].mY << ", z=" << pathgrid.mPoints[i].mZ; messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); duplList.push_back(i); break; } } } } // check pathgrid points that are not connected to anything for (unsigned int i = 0; i < pointList.size(); ++i) { if (pointList[i].mConnectionNum == 0) { std::ostringstream ss; ss << " has an orphaned point (" << i << ") x=" << pathgrid.mPoints[i].mX << ", y=" << pathgrid.mPoints[i].mY << ", z=" << pathgrid.mPoints[i].mZ; messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); } } // TODO: check whether there are disconnected graphs }
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); } }
void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base, std::map<ESM::RefNum, std::string>& cache, CSMDoc::Messages& messages) { Record<Cell> cell = mCells.getRecord (cellIndex); Cell& cell2 = base ? cell.mBase : cell.mModified; CellRef ref; ESM::MovedCellRef mref; bool isDeleted = false; // hack to initialise mindex while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, isDeleted, true, &mref)) { // Keep mOriginalCell empty when in modified (as an indicator that the // original cell will always be equal the current cell). ref.mOriginalCell = base ? cell2.mId : ""; if (cell.get().isExterior()) { // ignoring moved references sub-record; instead calculate cell from coordinates std::pair<int, int> index = ref.getCellIndex(); std::ostringstream stream; stream << "#" << index.first << " " << index.second; ref.mCell = stream.str(); if (!base && // don't try to update base records mref.mRefNum.mIndex != 0) // MVRF tag found { // there is a requirement for a placeholder where the original object was // // see the forum discussions here for more details: // https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30 ref.mOriginalCell = cell2.mId; // It is not always possibe to ignore moved references sub-record and // calculate from coordinates. Some mods may place the ref in positions // outside normal bounds, resulting in non sensical cell id's. This often // happens if the moved ref was deleted. // // Use the target cell from the MVRF tag but if different output an error // message if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1]) { std::cerr << "The Position of moved ref " << ref.mRefID << " does not match the target cell" << std::endl; std::cerr << "Position: #" << index.first << " " << index.second <<", Target #"<< mref.mTarget[0] << " " << mref.mTarget[1] << std::endl; std::ostringstream stream; stream << "#" << mref.mTarget[0] << " " << mref.mTarget[1]; ref.mCell = stream.str(); // overwrite } } } else ref.mCell = cell2.mId; // ignore content file number std::map<ESM::RefNum, std::string>::iterator iter = cache.begin(); for (; iter != cache.end(); ++iter) { if (ref.mRefNum.mIndex == iter->first.mIndex) break; } if (isDeleted) { if (iter==cache.end()) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex)); messages.add (id, "Attempt to delete a non-existing reference"); continue; } int index = getIndex (iter->second); Record<CellRef> record = getRecord (index); if (base) { removeRows (index, 1); cache.erase (iter); } else { record.mState = RecordBase::State_Deleted; setRecord (index, record); } continue; } if (iter==cache.end()) { // new reference ref.mId = getNewId(); Record<CellRef> record; record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record.mBase : record.mModified) = ref; appendRecord (record); cache.insert (std::make_pair (ref.mRefNum, ref.mId)); } else { // old reference -> merge ref.mId = iter->second; int index = getIndex (ref.mId); Record<CellRef> record = getRecord (index); record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; (base ? record.mBase : record.mModified) = ref; setRecord (index, record); } } }
void CSMTools::MandatoryIdStage::perform (int stage, CSMDoc::Messages& messages) { if (mIdCollection.searchId (mIds.at (stage))==-1 || mIdCollection.getRecord (mIds.at (stage)).isDeleted()) messages.add (mCollectionId, "Missing mandatory record: " + mIds.at (stage)); }
bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) { if (!mReader) throw std::logic_error ("can't continue loading, because no load has been started"); if (!mReader->hasMoreRecs()) { if (mBase) { // Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand loading. // We don't store non-base reader, because everything going into modified will be // fully loaded during the initial loading process. boost::shared_ptr<ESM::ESMReader> ptr(mReader); mReaders.push_back(ptr); } else delete mReader; mReader = 0; mDialogue = 0; return true; } ESM::NAME n = mReader->getRecName(); mReader->getRecHeader(); bool unhandledRecord = false; switch (n.val) { case ESM::REC_GLOB: mGlobals.load (*mReader, mBase); break; case ESM::REC_GMST: mGmsts.load (*mReader, mBase); break; case ESM::REC_SKIL: mSkills.load (*mReader, mBase); break; case ESM::REC_CLAS: mClasses.load (*mReader, mBase); break; case ESM::REC_FACT: mFactions.load (*mReader, mBase); break; case ESM::REC_RACE: mRaces.load (*mReader, mBase); break; case ESM::REC_SOUN: mSounds.load (*mReader, mBase); break; case ESM::REC_SCPT: mScripts.load (*mReader, mBase); break; case ESM::REC_REGN: mRegions.load (*mReader, mBase); break; case ESM::REC_BSGN: mBirthsigns.load (*mReader, mBase); break; case ESM::REC_SPEL: mSpells.load (*mReader, mBase); break; case ESM::REC_ENCH: mEnchantments.load (*mReader, mBase); break; case ESM::REC_BODY: mBodyParts.load (*mReader, mBase); break; case ESM::REC_SNDG: mSoundGens.load (*mReader, mBase); break; case ESM::REC_MGEF: mMagicEffects.load (*mReader, mBase); break; case ESM::REC_PGRD: mPathgrids.load (*mReader, mBase); break; case ESM::REC_SSCR: mStartScripts.load (*mReader, mBase); break; case ESM::REC_LTEX: mLandTextures.load (*mReader, mBase); break; case ESM::REC_LAND: { int index = mLand.load(*mReader, mBase); if (index!=-1 && !mBase) mLand.getRecord (index).mModified.mLand->loadData ( ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM); break; } case ESM::REC_CELL: { int index = mCells.load (*mReader, mBase); if (index < 0 || index >= mCells.getSize()) { // log an error and continue loading the refs to the last loaded cell CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_None); messages.add (id, "Logic error: cell index out of bounds", "", CSMDoc::Message::Severity_Error); index = mCells.getSize()-1; } std::string cellId = Misc::StringUtils::lowerCase (mCells.getId (index)); mRefs.load (*mReader, index, mBase, mRefLoadCache[cellId], messages); break; } case ESM::REC_ACTI: mReferenceables.load (*mReader, mBase, UniversalId::Type_Activator); break; case ESM::REC_ALCH: mReferenceables.load (*mReader, mBase, UniversalId::Type_Potion); break; case ESM::REC_APPA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Apparatus); break; case ESM::REC_ARMO: mReferenceables.load (*mReader, mBase, UniversalId::Type_Armor); break; case ESM::REC_BOOK: mReferenceables.load (*mReader, mBase, UniversalId::Type_Book); break; case ESM::REC_CLOT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Clothing); break; case ESM::REC_CONT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Container); break; case ESM::REC_CREA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Creature); break; case ESM::REC_DOOR: mReferenceables.load (*mReader, mBase, UniversalId::Type_Door); break; case ESM::REC_INGR: mReferenceables.load (*mReader, mBase, UniversalId::Type_Ingredient); break; case ESM::REC_LEVC: mReferenceables.load (*mReader, mBase, UniversalId::Type_CreatureLevelledList); break; case ESM::REC_LEVI: mReferenceables.load (*mReader, mBase, UniversalId::Type_ItemLevelledList); break; case ESM::REC_LIGH: mReferenceables.load (*mReader, mBase, UniversalId::Type_Light); break; case ESM::REC_LOCK: mReferenceables.load (*mReader, mBase, UniversalId::Type_Lockpick); break; case ESM::REC_MISC: mReferenceables.load (*mReader, mBase, UniversalId::Type_Miscellaneous); break; case ESM::REC_NPC_: mReferenceables.load (*mReader, mBase, UniversalId::Type_Npc); break; case ESM::REC_PROB: mReferenceables.load (*mReader, mBase, UniversalId::Type_Probe); break; case ESM::REC_REPA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Repair); break; case ESM::REC_STAT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Static); break; case ESM::REC_WEAP: mReferenceables.load (*mReader, mBase, UniversalId::Type_Weapon); break; case ESM::REC_DIAL: { std::string id = mReader->getHNOString ("NAME"); ESM::Dialogue record; record.mId = id; record.load (*mReader); if (record.mType==ESM::Dialogue::Journal) { mJournals.load (record, mBase); mDialogue = &mJournals.getRecord (id).get(); } else if (record.mType==ESM::Dialogue::Deleted) { mDialogue = 0; // record vector can be shuffled around which would make pointer // to record invalid if (mJournals.tryDelete (id)) { /// \todo handle info records } else if (mTopics.tryDelete (id)) { /// \todo handle info records } else { messages.add (UniversalId::Type_None, "Trying to delete dialogue record " + id + " which does not exist", "", CSMDoc::Message::Severity_Warning); } } else { mTopics.load (record, mBase); mDialogue = &mTopics.getRecord (id).get(); } break; } case ESM::REC_INFO: { if (!mDialogue) { messages.add (UniversalId::Type_None, "Found info record not following a dialogue record", "", CSMDoc::Message::Severity_Error); mReader->skipRecord(); break; } if (mDialogue->mType==ESM::Dialogue::Journal) mJournalInfos.load (*mReader, mBase, *mDialogue); else mTopicInfos.load (*mReader, mBase, *mDialogue); break; } case ESM::REC_FILT: if (!mProject) { unhandledRecord = true; break; } mFilters.load (*mReader, mBase); break; case ESM::REC_DBGP: if (!mProject) { unhandledRecord = true; break; } mDebugProfiles.load (*mReader, mBase); break; default: unhandledRecord = true; } if (unhandledRecord) { messages.add (UniversalId::Type_None, "Unsupported record type: " + n.toString(), "", CSMDoc::Message::Severity_Error); mReader->skipRecord(); } return false; }