Example #1
0
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;
}
Example #2
0
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;
}
Example #3
0
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
}
Example #4
0
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"));
    }
}
Example #5
0
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"));
        }
}
Example #6
0
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"));
}
Example #7
0
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;
}
Example #8
0
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;
}
Example #9
0
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;
}
Example #10
0
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;
}
Example #11
0
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
}
Example #12
0
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
}
Example #13
0
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
}
Example #14
0
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;
}
Example #15
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
}
Example #16
0
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
}
Example #17
0
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;
}
Example #18
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." ));
}
Example #19
0
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
}
Example #20
0
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;
}
Example #21
0
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
}
Example #22
0
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);
    }
}
Example #23
0
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);
        }
    }
}
Example #24
0
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));
}
Example #25
0
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;
}