From 252ee7f8c428559a27f4fa811a9898b07545f962 Mon Sep 17 00:00:00 2001
From: Alexei Kotov <alexdobrohotov@yandex.ru>
Date: Fri, 7 Jun 2024 13:10:20 +0300
Subject: [PATCH] Deduplicate effect list verification

Drop Potion-specific range check, it's irrelevant
---
 apps/opencs/CMakeLists.txt                    |  2 +-
 apps/opencs/model/tools/effectlistcheck.cpp   | 88 +++++++++++++++++++
 apps/opencs/model/tools/effectlistcheck.hpp   | 30 +++++++
 apps/opencs/model/tools/enchantmentcheck.cpp  | 46 +---------
 .../opencs/model/tools/referenceablecheck.cpp | 66 +-------------
 apps/opencs/model/tools/spellcheck.cpp        | 45 +---------
 6 files changed, 129 insertions(+), 148 deletions(-)
 create mode 100644 apps/opencs/model/tools/effectlistcheck.cpp
 create mode 100644 apps/opencs/model/tools/effectlistcheck.hpp

diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt
index d10882e0ae..f1cd246410 100644
--- a/apps/opencs/CMakeLists.txt
+++ b/apps/opencs/CMakeLists.txt
@@ -42,7 +42,7 @@ opencs_units (model/tools
     mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
     birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck
     startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck
-    mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck
+    mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck effectlistcheck
     )
 
 opencs_hdrs (model/tools
diff --git a/apps/opencs/model/tools/effectlistcheck.cpp b/apps/opencs/model/tools/effectlistcheck.cpp
new file mode 100644
index 0000000000..b8695bc419
--- /dev/null
+++ b/apps/opencs/model/tools/effectlistcheck.cpp
@@ -0,0 +1,88 @@
+#include "effectlistcheck.hpp"
+
+#include <components/esm/attr.hpp>
+#include <components/esm3/effectlist.hpp>
+#include <components/esm3/loadingr.hpp>
+#include <components/esm3/loadmgef.hpp>
+#include <components/esm3/loadskil.hpp>
+
+#include <apps/opencs/model/doc/messages.hpp>
+#include <apps/opencs/model/world/universalid.hpp>
+
+namespace CSMTools
+{
+    void effectListCheck(
+        const std::vector<ESM::IndexedENAMstruct>& list, CSMDoc::Messages& messages, const CSMWorld::UniversalId& id)
+    {
+        if (list.empty())
+        {
+            messages.add(id, "No magic effects", "", CSMDoc::Message::Severity_Warning);
+            return;
+        }
+
+        size_t i = 1;
+        for (const ESM::IndexedENAMstruct& effect : list)
+        {
+            const std::string number = std::to_string(i);
+
+            // At the time of writing this effects, attributes and skills are mostly hardcoded
+            if (effect.mData.mEffectID < 0 || effect.mData.mEffectID >= ESM::MagicEffect::Length)
+                messages.add(id, "Effect #" + number + ": invalid effect ID", "", CSMDoc::Message::Severity_Error);
+            if (effect.mData.mSkill < -1 || effect.mData.mSkill >= ESM::Skill::Length)
+                messages.add(id, "Effect #" + number + ": invalid skill", "", CSMDoc::Message::Severity_Error);
+            if (effect.mData.mAttribute < -1 || effect.mData.mAttribute >= ESM::Attribute::Length)
+                messages.add(id, "Effect #" + number + ": invalid attribute", "", CSMDoc::Message::Severity_Error);
+
+            if (effect.mData.mRange < ESM::RT_Self || effect.mData.mRange > ESM::RT_Target)
+                messages.add(id, "Effect #" + number + ": invalid range", "", CSMDoc::Message::Severity_Error);
+
+            if (effect.mData.mArea < 0)
+                messages.add(id, "Effect #" + number + ": negative area", "", CSMDoc::Message::Severity_Error);
+
+            if (effect.mData.mDuration < 0)
+                messages.add(id, "Effect #" + number + ": negative duration", "", CSMDoc::Message::Severity_Error);
+
+            if (effect.mData.mMagnMin < 0)
+                messages.add(
+                    id, "Effect #" + number + ": negative minimum magnitude", "", CSMDoc::Message::Severity_Error);
+
+            if (effect.mData.mMagnMax < 0)
+                messages.add(
+                    id, "Effect #" + number + ": negative maximum magnitude", "", CSMDoc::Message::Severity_Error);
+            else if (effect.mData.mMagnMax == 0)
+                messages.add(
+                    id, "Effect #" + number + ": zero maximum magnitude", "", CSMDoc::Message::Severity_Warning);
+
+            if (effect.mData.mMagnMin > effect.mData.mMagnMax)
+                messages.add(id, "Effect #" + number + ": minimum magnitude is higher than maximum magnitude", "",
+                    CSMDoc::Message::Severity_Error);
+
+            ++i;
+        }
+    }
+
+    void ingredientEffectListCheck(
+        const ESM::Ingredient& ingredient, CSMDoc::Messages& messages, const CSMWorld::UniversalId& id)
+    {
+        bool hasEffects = false;
+
+        for (size_t i = 0; i < 4; i++)
+        {
+            if (ingredient.mData.mEffectID[i] == -1)
+                continue;
+
+            hasEffects = true;
+
+            const std::string number = std::to_string(i + 1);
+            if (ingredient.mData.mEffectID[i] < -1 || ingredient.mData.mEffectID[i] >= ESM::MagicEffect::Length)
+                messages.add(id, "Effect #" + number + ": invalid effect ID", "", CSMDoc::Message::Severity_Error);
+            if (ingredient.mData.mSkills[i] < -1 || ingredient.mData.mSkills[i] >= ESM::Skill::Length)
+                messages.add(id, "Effect #" + number + ": invalid skill", "", CSMDoc::Message::Severity_Error);
+            if (ingredient.mData.mAttributes[i] < -1 || ingredient.mData.mAttributes[i] >= ESM::Attribute::Length)
+                messages.add(id, "Effect #" + number + ": invalid attribute", "", CSMDoc::Message::Severity_Error);
+        }
+
+        if (!hasEffects)
+            messages.add(id, "No magic effects", "", CSMDoc::Message::Severity_Warning);
+    }
+}
diff --git a/apps/opencs/model/tools/effectlistcheck.hpp b/apps/opencs/model/tools/effectlistcheck.hpp
new file mode 100644
index 0000000000..832f3650eb
--- /dev/null
+++ b/apps/opencs/model/tools/effectlistcheck.hpp
@@ -0,0 +1,30 @@
+#ifndef CSM_TOOLS_EFFECTLISTCHECK_H
+#define CSM_TOOLS_EFFECTLISTCHECK_H
+
+#include <vector>
+
+namespace ESM
+{
+    struct IndexedENAMstruct;
+    struct Ingredient;
+}
+
+namespace CSMDoc
+{
+    class Messages;
+}
+
+namespace CSMWorld
+{
+    class UniversalId;
+}
+
+namespace CSMTools
+{
+    void effectListCheck(
+        const std::vector<ESM::IndexedENAMstruct>& list, CSMDoc::Messages& messages, const CSMWorld::UniversalId& id);
+    void ingredientEffectListCheck(
+        const ESM::Ingredient& ingredient, CSMDoc::Messages& messages, const CSMWorld::UniversalId& id);
+}
+
+#endif
diff --git a/apps/opencs/model/tools/enchantmentcheck.cpp b/apps/opencs/model/tools/enchantmentcheck.cpp
index ef1def6959..8bd7d6cc4c 100644
--- a/apps/opencs/model/tools/enchantmentcheck.cpp
+++ b/apps/opencs/model/tools/enchantmentcheck.cpp
@@ -10,14 +10,14 @@
 #include <apps/opencs/model/world/idcollection.hpp>
 #include <apps/opencs/model/world/record.hpp>
 
-#include <components/esm/attr.hpp>
-#include <components/esm3/effectlist.hpp>
 #include <components/esm3/loadench.hpp>
 
 #include "../prefs/state.hpp"
 
 #include "../world/universalid.hpp"
 
+#include "effectlistcheck.hpp"
+
 CSMTools::EnchantmentCheckStage::EnchantmentCheckStage(const CSMWorld::IdCollection<ESM::Enchantment>& enchantments)
     : mEnchantments(enchantments)
 {
@@ -55,45 +55,5 @@ void CSMTools::EnchantmentCheckStage::perform(int stage, CSMDoc::Messages& messa
     if (enchantment.mData.mCost > enchantment.mData.mCharge)
         messages.add(id, "Cost is higher than charge", "", CSMDoc::Message::Severity_Error);
 
-    if (enchantment.mEffects.mList.empty())
-    {
-        messages.add(id, "Enchantment doesn't have any magic effects", "", CSMDoc::Message::Severity_Warning);
-    }
-    else
-    {
-        std::vector<ESM::IndexedENAMstruct>::const_iterator effect = enchantment.mEffects.mList.begin();
-
-        for (size_t i = 1; i <= enchantment.mEffects.mList.size(); i++)
-        {
-            const std::string number = std::to_string(i);
-            // At the time of writing this effects, attributes and skills are mostly hardcoded
-            if (effect->mData.mEffectID < 0 || effect->mData.mEffectID > ESM::MagicEffect::Length)
-                messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mSkill < -1 || effect->mData.mSkill > ESM::Skill::Length)
-                messages.add(
-                    id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mAttribute < -1 || effect->mData.mAttribute > ESM::Attribute::Length)
-                messages.add(
-                    id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mRange < 0 || effect->mData.mRange > 2)
-                messages.add(id, "Effect #" + number + " range is invalid", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mArea < 0)
-                messages.add(id, "Effect #" + number + " area is negative", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mDuration < 0)
-                messages.add(id, "Effect #" + number + " duration is negative", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mMagnMin < 0)
-                messages.add(
-                    id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mMagnMax < 0)
-                messages.add(
-                    id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error);
-            else if (effect->mData.mMagnMax == 0)
-                messages.add(
-                    id, "Effect #" + number + " maximum magnitude is zero", "", CSMDoc::Message::Severity_Warning);
-            if (effect->mData.mMagnMin > effect->mData.mMagnMax)
-                messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "",
-                    CSMDoc::Message::Severity_Error);
-            ++effect;
-        }
-    }
+    effectListCheck(enchantment.mEffects.mList, messages, id);
 }
diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp
index 55ed204aef..a00c3acd1c 100644
--- a/apps/opencs/model/tools/referenceablecheck.cpp
+++ b/apps/opencs/model/tools/referenceablecheck.cpp
@@ -38,6 +38,8 @@
 #include "../world/record.hpp"
 #include "../world/universalid.hpp"
 
+#include "effectlistcheck.hpp"
+
 namespace ESM
 {
     class Script;
@@ -331,47 +333,7 @@ void CSMTools::ReferenceableCheckStage::potionCheck(
 
     inventoryItemCheck<ESM::Potion>(potion, messages, id.toString());
 
-    if (potion.mEffects.mList.empty())
-    {
-        messages.add(id, "Potion doesn't have any magic effects", "", CSMDoc::Message::Severity_Warning);
-    }
-    else
-    {
-        std::vector<ESM::IndexedENAMstruct>::const_iterator effect = potion.mEffects.mList.begin();
-
-        for (size_t i = 1; i <= potion.mEffects.mList.size(); i++)
-        {
-            const std::string number = std::to_string(i);
-            // At the time of writing this effects, attributes and skills are mostly hardcoded
-            if (effect->mData.mEffectID < 0 || effect->mData.mEffectID > ESM::MagicEffect::Length)
-                messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mSkill < -1 || effect->mData.mSkill > ESM::Skill::Length)
-                messages.add(
-                    id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mAttribute < -1 || effect->mData.mAttribute > ESM::Attribute::Length)
-                messages.add(
-                    id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mRange != ESM::RT_Self)
-                messages.add(id, "Effect #" + number + " range is not Self", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mArea < 0)
-                messages.add(id, "Effect #" + number + " area is negative", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mDuration < 0)
-                messages.add(id, "Effect #" + number + " duration is negative", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mMagnMin < 0)
-                messages.add(
-                    id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mMagnMax < 0)
-                messages.add(
-                    id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error);
-            else if (effect->mData.mMagnMax == 0)
-                messages.add(
-                    id, "Effect #" + number + " maximum magnitude is zero", "", CSMDoc::Message::Severity_Warning);
-            if (effect->mData.mMagnMin > effect->mData.mMagnMax)
-                messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "",
-                    CSMDoc::Message::Severity_Error);
-            ++effect;
-        }
-    }
+    effectListCheck(potion.mEffects.mList, messages, id);
 
     // Check that mentioned scripts exist
     scriptCheck<ESM::Potion>(potion, messages, id.toString());
@@ -607,27 +569,7 @@ void CSMTools::ReferenceableCheckStage::ingredientCheck(
     // Check that mentioned scripts exist
     scriptCheck<ESM::Ingredient>(ingredient, messages, id.toString());
 
-    bool hasEffects = false;
-    for (size_t i = 0; i < 4; i++)
-    {
-        if (ingredient.mData.mEffectID[i] == -1)
-            continue;
-
-        hasEffects = true;
-
-        const std::string number = std::to_string(i + 1);
-        // At the time of writing this effects, attributes and skills are mostly hardcoded
-        if (ingredient.mData.mEffectID[i] < -1 || ingredient.mData.mEffectID[i] > ESM::MagicEffect::Length)
-            messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error);
-        if (ingredient.mData.mSkills[i] < -1 || ingredient.mData.mSkills[i] > ESM::Skill::Length)
-            messages.add(id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error);
-        if (ingredient.mData.mAttributes[i] < -1 || ingredient.mData.mAttributes[i] > ESM::Attribute::Length)
-            messages.add(
-                id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error);
-    }
-
-    if (!hasEffects)
-        messages.add(id, "Ingredient doesn't have any magic effects", "", CSMDoc::Message::Severity_Warning);
+    ingredientEffectListCheck(ingredient, messages, id);
 }
 
 void CSMTools::ReferenceableCheckStage::creaturesLevListCheck(
diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp
index e4f7322bcf..07973bf08b 100644
--- a/apps/opencs/model/tools/spellcheck.cpp
+++ b/apps/opencs/model/tools/spellcheck.cpp
@@ -9,11 +9,12 @@
 #include <apps/opencs/model/world/record.hpp>
 #include <apps/opencs/model/world/universalid.hpp>
 
-#include <components/esm/attr.hpp>
 #include <components/esm3/loadspel.hpp>
 
 #include "../prefs/state.hpp"
 
+#include "effectlistcheck.hpp"
+
 CSMTools::SpellCheckStage::SpellCheckStage(const CSMWorld::IdCollection<ESM::Spell>& spells)
     : mSpells(spells)
 {
@@ -47,45 +48,5 @@ void CSMTools::SpellCheckStage::perform(int stage, CSMDoc::Messages& messages)
     if (spell.mData.mCost < 0)
         messages.add(id, "Spell cost is negative", "", CSMDoc::Message::Severity_Error);
 
-    if (spell.mEffects.mList.empty())
-    {
-        messages.add(id, "Spell doesn't have any magic effects", "", CSMDoc::Message::Severity_Warning);
-    }
-    else
-    {
-        std::vector<ESM::IndexedENAMstruct>::const_iterator effect = spell.mEffects.mList.begin();
-
-        for (size_t i = 1; i <= spell.mEffects.mList.size(); i++)
-        {
-            const std::string number = std::to_string(i);
-            // At the time of writing this effects, attributes and skills are mostly hardcoded
-            if (effect->mData.mEffectID < 0 || effect->mData.mEffectID > ESM::MagicEffect::Length)
-                messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mSkill < -1 || effect->mData.mSkill > ESM::Skill::Length)
-                messages.add(
-                    id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mAttribute < -1 || effect->mData.mAttribute > ESM::Attribute::Length)
-                messages.add(
-                    id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mRange < 0 || effect->mData.mRange > 2)
-                messages.add(id, "Effect #" + number + " range is invalid", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mArea < 0)
-                messages.add(id, "Effect #" + number + " area is negative", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mDuration < 0)
-                messages.add(id, "Effect #" + number + " duration is negative", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mMagnMin < 0)
-                messages.add(
-                    id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error);
-            if (effect->mData.mMagnMax < 0)
-                messages.add(
-                    id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error);
-            else if (effect->mData.mMagnMax == 0)
-                messages.add(
-                    id, "Effect #" + number + " maximum magnitude is zero", "", CSMDoc::Message::Severity_Warning);
-            if (effect->mData.mMagnMin > effect->mData.mMagnMax)
-                messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "",
-                    CSMDoc::Message::Severity_Error);
-            ++effect;
-        }
-    }
+    effectListCheck(spell.mEffects.mList, messages, id);
 }