574 lines
29 KiB
Python
574 lines
29 KiB
Python
import logging
|
|
import re
|
|
import json
|
|
from worlds.AutoWorld import World
|
|
from BaseClasses import MultiWorld, ItemClassification
|
|
from typing import Any, Counter
|
|
|
|
from .Helpers import convert_string_to_itemclassification
|
|
|
|
class ValidationError(Exception):
|
|
pass
|
|
|
|
class DataValidation():
|
|
game_table: dict[str, Any] = {}
|
|
item_table: list[dict[str, Any]] = []
|
|
item_table_with_events: list[dict[str, Any]] = []
|
|
event_table: list[dict[str, Any]] = []
|
|
location_table: list[dict[str, Any]] = []
|
|
region_table: dict[str, Any] = {}
|
|
location_table_with_events: list[dict[str, Any]] = []
|
|
location_name_to_location: dict[str, dict[str, Any]] = {}
|
|
|
|
@staticmethod
|
|
def checkItemNamesInLocationRequires():
|
|
from .Rules import ITEM_REGEX
|
|
for location in DataValidation.location_table_with_events:
|
|
if "requires" not in location:
|
|
continue
|
|
|
|
if isinstance(location["requires"], str):
|
|
# parse user written statement into list of each item
|
|
for match in ITEM_REGEX.finditer(location["requires"]):
|
|
is_category = bool(match.group(1))
|
|
item_name = match.group(2)
|
|
item_count = (str(match.group(3) or "1")).lstrip(':').strip()
|
|
|
|
if not item_count.isnumeric() and item_count.lower() not in ["all", "half"] and not item_count.endswith('%'):
|
|
item_name = match.group(0).strip("|")
|
|
logging.debug(f'Invalid item_count "{item_count}" found, reverting to initial item_name "{item_name}"')
|
|
|
|
# if it's a category, validate that the category exists
|
|
if is_category:
|
|
item_category_exists = len([item for item in DataValidation.item_table_with_events if item_name in item.get('category', [])]) > 0
|
|
|
|
if not item_category_exists:
|
|
raise ValidationError("Item category %s is required by location %s but is misspelled or does not exist." % (item_name, location.get("name")))
|
|
|
|
continue
|
|
else:
|
|
item_exists = len([item.get("name") for item in DataValidation.item_table_with_events if item.get("name") == item_name]) > 0
|
|
|
|
if not item_exists:
|
|
raise ValidationError("Item %s is required by location %s but is misspelled or does not exist." % (item_name, location.get("name")))
|
|
|
|
else: # item access is in dict form
|
|
for item in location["requires"]:
|
|
# if the require entry is an object with "or" or a list of items, treat it as a standalone require of its own
|
|
if (isinstance(item, dict) and "or" in item and isinstance(item["or"], list)) or (isinstance(item, list)):
|
|
or_items = item
|
|
|
|
if isinstance(item, dict):
|
|
or_items = item["or"]
|
|
|
|
for or_item in or_items:
|
|
or_item_parts = or_item.split(":")
|
|
or_item_name = or_item
|
|
|
|
if len(or_item_parts) > 1:
|
|
or_item_name = or_item_parts[0]
|
|
|
|
item_exists = len([item.get("name") for item in DataValidation.item_table_with_events if item.get("name") == or_item_name]) > 0
|
|
|
|
if not item_exists:
|
|
raise ValidationError("Item %s is required by location %s but is misspelled or does not exist." % (or_item_name, location["name"]))
|
|
else:
|
|
item_parts = item.split(":")
|
|
item_name = item
|
|
|
|
if len(item_parts) > 1:
|
|
item_name = item_parts[0]
|
|
|
|
item_exists = len([item.get("name") for item in DataValidation.item_table_with_events if item.get("name") == item_name]) > 0
|
|
|
|
if not item_exists:
|
|
raise ValidationError("Item %s is required by location %s but is misspelled or does not exist." % (item_name, location["name"]))
|
|
|
|
@staticmethod
|
|
def checkItemNamesInRegionRequires():
|
|
from .Rules import ITEM_REGEX
|
|
for region_name in DataValidation.region_table:
|
|
region = DataValidation.region_table[region_name]
|
|
|
|
if "requires" not in region:
|
|
continue
|
|
|
|
if isinstance(region["requires"], str):
|
|
# parse user written statement into list of each item
|
|
for match in ITEM_REGEX.finditer(region["requires"]):
|
|
is_category = bool(match.group(1))
|
|
item_name = match.group(2)
|
|
item_count = (str(match.group(3) or "1")).lstrip(':').strip()
|
|
|
|
if not item_count.isnumeric() and item_count.lower() not in ["all", "half"] and not item_count.endswith('%'):
|
|
item_name = match.group(0).strip("|")
|
|
logging.debug(f'Invalid item_count "{item_count}" found, reverting to initial item_name "{item_name}"')
|
|
|
|
# if it's a category, validate that the category exists
|
|
if is_category:
|
|
item_category_exists = len([item for item in DataValidation.item_table_with_events if item_name in item.get('category', [])]) > 0
|
|
|
|
if not item_category_exists:
|
|
raise ValidationError("Item category %s is required by region %s but is misspelled or does not exist." % (item_name, region_name))
|
|
|
|
else:
|
|
item_exists = len([item.get("name") for item in DataValidation.item_table_with_events if item.get("name") == item_name]) > 0
|
|
|
|
if not item_exists:
|
|
raise ValidationError("Item %s is required by region %s but is misspelled or does not exist." % (item_name, region_name))
|
|
|
|
else: # item access is in dict form
|
|
for item in region["requires"]:
|
|
# if the require entry is an object with "or" or a list of items, treat it as a standalone require of its own
|
|
if (isinstance(item, dict) and "or" in item and isinstance(item["or"], list)) or (isinstance(item, list)):
|
|
or_items = item
|
|
|
|
if isinstance(item, dict):
|
|
or_items = item["or"]
|
|
|
|
for or_item in or_items:
|
|
or_item_parts = or_item.split(":")
|
|
or_item_name = or_item
|
|
|
|
if len(or_item_parts) > 1:
|
|
or_item_name = or_item_parts[0]
|
|
|
|
item_exists = len([item.get("name") for item in DataValidation.item_table_with_events if item.get("name") == or_item_name]) > 0
|
|
|
|
if not item_exists:
|
|
raise ValidationError("Item %s is required by region %s but is misspelled or does not exist." % (or_item_name, region_name))
|
|
else:
|
|
item_parts = item.split(":")
|
|
item_name = item
|
|
|
|
if len(item_parts) > 1:
|
|
item_name = item_parts[0]
|
|
|
|
item_exists = len([item.get("name") for item in DataValidation.item_table_with_events if item.get("name") == item_name]) > 0
|
|
|
|
if not item_exists:
|
|
raise ValidationError("Item %s is required by region %s but is misspelled or does not exist." % (item_name, region_name))
|
|
|
|
@staticmethod
|
|
def checkRegionNamesInLocations():
|
|
for location in DataValidation.location_table_with_events:
|
|
if "region" not in location or location["region"] in ["Menu", "Manual"]:
|
|
continue
|
|
|
|
region_exists = len([name for name in DataValidation.region_table if name == location["region"]]) > 0
|
|
|
|
if not region_exists:
|
|
raise ValidationError("Region %s is set for location %s, but the region is misspelled or does not exist." % (location["region"], location["name"]))
|
|
|
|
@staticmethod
|
|
def checkItemsHasValidClassificationCount():
|
|
for item in DataValidation.item_table:
|
|
if not item.get("classification_count"):
|
|
continue
|
|
for cat, count in item["classification_count"].items():
|
|
cat = str(cat)
|
|
if count == 0:
|
|
continue
|
|
try:
|
|
convert_string_to_itemclassification(cat)
|
|
|
|
except KeyError as ex:
|
|
raise ValidationError(f"Item '{item.get('name', '')}''s classification_count '{cat}' is misspelled or does not exist.\n Valid names are {', '.join(ItemClassification.__members__.keys())} \n\n{type(ex).__name__}:{ex}")
|
|
except Exception as ex:
|
|
raise ValidationError(f"Item '{item.get('name', '')}''s classification_count '{cat}' was improperly defined\n\n{type(ex).__name__}:{ex}")
|
|
|
|
@staticmethod
|
|
def checkItemsThatShouldBeRequired():
|
|
for item in DataValidation.item_table:
|
|
# if the item is already progression, no need to check
|
|
if item.get("progression"):
|
|
continue
|
|
|
|
# progression_skip_balancing is also progression, so no check needed
|
|
if item.get("progression_skip_balancing"):
|
|
continue
|
|
# if any of the advanced type is already progression then no check needed
|
|
if item.get("classification_count"):
|
|
has_progression = False
|
|
for cat, count in item["classification_count"].items():
|
|
cat = str(cat)
|
|
if count == 0:
|
|
continue
|
|
try:
|
|
true_class = convert_string_to_itemclassification(cat)
|
|
|
|
except:
|
|
# Skip since this validation error is dealt with in checkItemsHasValidClassificationCount
|
|
true_class = ItemClassification.filler
|
|
if ItemClassification.progression in true_class:
|
|
has_progression = True
|
|
break
|
|
|
|
if has_progression:
|
|
continue
|
|
# check location requires for the presence of item name
|
|
for location in DataValidation.location_table_with_events:
|
|
if "requires" not in location:
|
|
continue
|
|
|
|
# convert to json so we don't have to guess the data type
|
|
location_requires = json.dumps(location["requires"])
|
|
|
|
# if boolean, else legacy
|
|
if isinstance(location_requires, str):
|
|
if '|{}|'.format(item.get("name")) in location_requires:
|
|
raise ValidationError("Item %s is required by location %s, but the item is not marked as progression." % (item.get("name"), location["name"]))
|
|
else:
|
|
if item.get("name") in location_requires:
|
|
raise ValidationError("Item %s is required by location %s, but the item is not marked as progression." % (item.get("name"), location["name"]))
|
|
|
|
# check region requires for the presence of item name
|
|
for region_name in DataValidation.region_table:
|
|
region = DataValidation.region_table[region_name]
|
|
|
|
if "requires" not in region:
|
|
continue
|
|
|
|
# convert to json so we don't have to guess the data type
|
|
region_requires = json.dumps(region["requires"])
|
|
|
|
# if boolean, else legacy
|
|
if isinstance(region_requires, str):
|
|
if '|{}|'.format(item.get("name")) in region_requires:
|
|
raise ValidationError("Item %s is required by region %s, but the item is not marked as progression." % (item.get("name"), region_name))
|
|
else:
|
|
if item.get("name") in region_requires:
|
|
raise ValidationError("Item %s is required by region %s, but the item is not marked as progression." % (item.get("name"), region_name))
|
|
|
|
@staticmethod
|
|
def _checkLocationRequiresForItemValueWithRegex(values_requested: dict[str, int], requires) -> dict[str, int]:
|
|
if isinstance(requires, str) and 'ItemValue' in requires:
|
|
for result in re.findall(r'\{ItemValue\(([^:]*)\:(.*?)\)\}', requires):
|
|
value = result[0].lower().strip()
|
|
count = int(result[1].split(",")[0])
|
|
if not values_requested.get(value):
|
|
values_requested[value] = count
|
|
else:
|
|
values_requested[value] = max(values_requested[value], count)
|
|
return values_requested
|
|
|
|
|
|
@staticmethod
|
|
def preFillCheckIfEnoughItemsForValue(world: World, multiworld: MultiWorld):
|
|
from .Helpers import get_items_with_value, get_items_for_player, filter_used_regions
|
|
player = world.player
|
|
values_requested = {}
|
|
player_regions = []
|
|
|
|
#Grab all the player's regions
|
|
for region in multiworld.regions:
|
|
if region.player != player:
|
|
continue
|
|
player_regions.append(region)
|
|
|
|
used_regions = filter_used_regions(player_regions)
|
|
used_regions_names = {r.name for r in set(used_regions)}
|
|
|
|
#Check used regions (and their parent(s)) for ItemValue requirement
|
|
for region in used_regions:
|
|
manualregion = DataValidation.region_table.get(region.name, {})
|
|
if manualregion:
|
|
if manualregion.get("requires"):
|
|
DataValidation._checkLocationRequiresForItemValueWithRegex(values_requested, json.dumps(manualregion["requires"]))
|
|
|
|
for region_entrance, require in manualregion.get('entrance_requires', {}).items():
|
|
if region_entrance in used_regions_names:
|
|
DataValidation._checkLocationRequiresForItemValueWithRegex(values_requested, json.dumps(require))
|
|
|
|
for region_exit, require in manualregion.get('exit_requires', {}).items():
|
|
if region_exit in used_regions_names:
|
|
DataValidation._checkLocationRequiresForItemValueWithRegex(values_requested, json.dumps(require))
|
|
|
|
for location in region.locations:
|
|
manualLocation = world.location_name_to_location.get(location.name, {})
|
|
if "requires" in manualLocation and manualLocation["requires"]:
|
|
DataValidation._checkLocationRequiresForItemValueWithRegex(values_requested, json.dumps(manualLocation["requires"]))
|
|
|
|
# compare whats available vs requested but only if there's anything requested
|
|
if values_requested:
|
|
errors = []
|
|
existing_items = [item for item in get_items_for_player(multiworld, player, True) if
|
|
item.code is not None and ItemClassification.progression in item.classification]
|
|
for value, val_count in values_requested.items():
|
|
items_value = get_items_with_value(world, multiworld, value, player)
|
|
found_count = 0
|
|
if items_value:
|
|
for item in existing_items:
|
|
if item.name in items_value:
|
|
found_count += items_value[item.name]
|
|
|
|
if found_count < val_count:
|
|
errors.append(f" '{value}': {found_count} out of the {val_count} {value} worth of progression items required can be found.")
|
|
if errors:
|
|
raise ValidationError("There are not enough progression items for the following value(s): \n" + "\n".join(errors))
|
|
|
|
@staticmethod
|
|
def checkRegionsConnectingToOtherRegions():
|
|
for region_name in DataValidation.region_table:
|
|
region = DataValidation.region_table[region_name]
|
|
|
|
if "connects_to" not in region:
|
|
continue
|
|
|
|
for connecting_region in region["connects_to"]:
|
|
region_exists = len([name for name in DataValidation.region_table if name == connecting_region]) > 0
|
|
|
|
if not region_exists:
|
|
raise ValidationError("Region %s connects to a region %s, which is misspelled or does not exist." % (region_name, connecting_region))
|
|
|
|
@staticmethod
|
|
def checkForMissingItemNames():
|
|
missing_name_count = len([i for i in DataValidation.item_table if not i.get("name")])
|
|
|
|
if missing_name_count > 0:
|
|
raise ValidationError("At least one of your items is missing the 'name' field.")
|
|
|
|
@staticmethod
|
|
def checkForDuplicateItemNames():
|
|
names: Counter[str] = Counter()
|
|
problems: list[str] = []
|
|
for item in DataValidation.item_table:
|
|
name = item.get("name")
|
|
if name is not None:
|
|
# Counter don't require checking that the key exists
|
|
names[name] += 1
|
|
if names[name] > 1:
|
|
problems.append(name)
|
|
if problems:
|
|
if len(problems) == 1:
|
|
raise ValidationError(f"Item {problems[0]} is defined more than once.")
|
|
else:
|
|
separator = '", "'
|
|
raise ValidationError(f"The following Items are defined more than once.\n \"{separator.join(problems)}\"")
|
|
|
|
@staticmethod
|
|
def checkForMissingLocationNames():
|
|
missing_name_count = len([l for l in DataValidation.location_table if not l.get("name")])
|
|
|
|
if missing_name_count > 0:
|
|
raise ValidationError("At least one of your locations is missing the 'name' field.")
|
|
|
|
@staticmethod
|
|
def checkForDuplicateLocationNames():
|
|
names: Counter[str] = Counter()
|
|
problems: list[str] = []
|
|
for location in DataValidation.location_table:
|
|
name = location.get("name")
|
|
if name is not None:
|
|
# Counter don't require checking that the key exists
|
|
names[name] += 1
|
|
if names[name] > 1:
|
|
problems.append(name)
|
|
if problems:
|
|
if len(problems) == 1:
|
|
raise ValidationError(f"Location {problems[0]} is defined more than once.")
|
|
else:
|
|
separator = '", "'
|
|
raise ValidationError(f"The following Locations are defined more than once.\n \"{separator.join(problems)}\"")
|
|
|
|
@staticmethod
|
|
def checkForInvalidRegionNames():
|
|
# check for regions that Manual defines itself; currently limited to "Menu" and "Manual"
|
|
for region_name in ["Menu", "Manual"]:
|
|
if region_name in DataValidation.region_table:
|
|
raise ValidationError(f"You cannot define a '{region_name}' region because Manual already defines a region with the same name.")
|
|
|
|
@staticmethod
|
|
def checkStartingItemsForValidItemsAndCategories():
|
|
if "starting_items" not in DataValidation.game_table:
|
|
return
|
|
|
|
starting_items = DataValidation.game_table["starting_items"]
|
|
|
|
for starting_block in starting_items:
|
|
if "items" in starting_block and "item_categories" in starting_block:
|
|
raise ValidationError("One of your starting item definitions has both 'items' and 'item_categories' defined, but only one will be applied.")
|
|
|
|
if "items" in starting_block:
|
|
for item_name in starting_block["items"]:
|
|
if not item_name in [item.get("name") for item in DataValidation.item_table]:
|
|
raise ValidationError("Item %s is set as a starting item, but is misspelled or is not defined." % (item_name))
|
|
|
|
if "item_categories" in starting_block:
|
|
for category_name in starting_block["item_categories"]:
|
|
if len([item for item in DataValidation.item_table if "category" in item and category_name in item["category"]]) == 0:
|
|
raise ValidationError("Item category %s is set as a starting item category, but is misspelled or is not defined on any items." % (category_name))
|
|
|
|
@staticmethod
|
|
def checkStartingItemsForBadSyntax():
|
|
if not (starting_items := DataValidation.game_table.get("starting_items", False)):
|
|
return
|
|
|
|
for starting_block in starting_items:
|
|
if type(starting_block) is not dict or len(starting_block.keys()) == 0:
|
|
raise ValidationError("One of your starting item definitions is not a valid dictionary.\n Each definition must be inside {}, as demonstrated in the Manual documentation.")
|
|
|
|
valid_keys = ["items", "item_categories", "random", "if_previous_item", "_comment", "yaml_option"] # _comment is provided by schema
|
|
invalid_keys = [f'"{key}"' for key in starting_block.keys() if key not in valid_keys]
|
|
|
|
if len(invalid_keys) > 0:
|
|
raise ValidationError("One of your starting item definitions is invalid and may have unexpected results.\n The invalid starting item definition specifies the following incorrect keys: {}".format(", ".join(invalid_keys)))
|
|
|
|
@staticmethod
|
|
def checkPlacedItemsAndCategoriesForBadSyntax():
|
|
for location in DataValidation.location_table:
|
|
place_item = location.get("place_item", False)
|
|
place_item_category = location.get("place_item_category", False)
|
|
|
|
if not place_item and not place_item_category:
|
|
continue
|
|
|
|
if place_item and type(place_item) is not list:
|
|
raise ValidationError("One of your location has an incorrectly formatted place_item.\n The items, even just one, must be inside [].")
|
|
|
|
if place_item_category and type(place_item_category) is not list:
|
|
raise ValidationError("One of your location has an incorrectly formatted place_item_category.\n The categories, even just one, must be inside [].")
|
|
|
|
@staticmethod
|
|
def checkPlacedItemsForValidItems():
|
|
for location in DataValidation.location_table:
|
|
if not (place_item := location.get("place_item", False)):
|
|
continue
|
|
|
|
# don't bother checking for valid items if the syntax is wrong
|
|
if type(place_item) is not list:
|
|
continue
|
|
|
|
for item_name in place_item:
|
|
if not item_name in [item.get("name") for item in DataValidation.item_table]:
|
|
raise ValidationError("Item %s is placed (using place_item) on a location, but is misspelled or is not defined." % (item_name))
|
|
|
|
@staticmethod
|
|
def checkPlacedItemCategoriesForValidItemCategories():
|
|
for location in DataValidation.location_table:
|
|
if not (place_item_category := location.get("place_item_category", False)):
|
|
continue
|
|
|
|
# don't bother checking for valid item categories if the syntax is wrong
|
|
if type(place_item_category) is not list:
|
|
continue
|
|
|
|
for category_name in place_item_category:
|
|
if len([item for item in DataValidation.item_table if "category" in item and category_name in item["category"]]) == 0:
|
|
raise ValidationError("Item category %s is placed (using place_item_category) on a location, but is misspelled or is not defined." % (category_name))
|
|
|
|
@staticmethod
|
|
def checkForGameBeingInvalidJSON():
|
|
if len(DataValidation.game_table) == 0:
|
|
raise ValidationError("No settings were found in your game.json. This likely indicates that your JSON is incorrectly formatted. Use https://jsonlint.com/ to validate your JSON files.")
|
|
|
|
@staticmethod
|
|
def checkForItemsBeingInvalidJSON():
|
|
if len(DataValidation.item_table) == 0:
|
|
raise ValidationError("No items were found in your items.json. This likely indicates that your JSON is incorrectly formatted. Use https://jsonlint.com/ to validate your JSON files.")
|
|
|
|
@staticmethod
|
|
def checkForLocationsBeingInvalidJSON():
|
|
if len(DataValidation.location_table) == 0:
|
|
raise ValidationError("No locations were found in your locations.json. This likely indicates that your JSON is incorrectly formatted. Use https://jsonlint.com/ to validate your JSON files.")
|
|
|
|
@staticmethod
|
|
def checkForNonStartingRegionsThatAreUnreachable():
|
|
using_starting_regions = len([region for region in DataValidation.region_table if DataValidation.region_table[region].get("starting")]) > 0
|
|
|
|
if not using_starting_regions:
|
|
return
|
|
|
|
nonstarting_regions = [region for region in DataValidation.region_table if not DataValidation.region_table[region].get("starting")]
|
|
|
|
for nonstarter in nonstarting_regions:
|
|
regions_that_connect_to = [region for region in DataValidation.region_table if "connects_to" in DataValidation.region_table[region] and nonstarter in DataValidation.region_table[region]["connects_to"]]
|
|
|
|
if len(regions_that_connect_to) == 0:
|
|
raise ValidationError("The region '%s' is set as a non-starting region, but has no regions that connect to it. It will be inaccessible." % nonstarter)
|
|
|
|
|
|
def runPreFillDataValidation(world: World, multiworld: MultiWorld):
|
|
validation_errors: list[ValidationError] = []
|
|
|
|
# check if there is enough items with values
|
|
try: DataValidation.preFillCheckIfEnoughItemsForValue(world, multiworld)
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
if validation_errors:
|
|
heading = f"ValidationError(s) for pre_fill of {world.game}:";
|
|
newline = "\n"
|
|
raise Exception(f"\n\n{heading} \n\n{newline.join([' - ' + str(validation_error) for validation_error in validation_errors])}\n\n")
|
|
|
|
# Called during stage_assert_generate
|
|
def runGenerationDataValidation(cls) -> None:
|
|
validation_errors: list[ValidationError] = []
|
|
|
|
try: DataValidation.checkForMissingItemNames()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
try: DataValidation.checkForMissingLocationNames()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check that requires have correct item names in locations and regions
|
|
try: DataValidation.checkItemNamesInLocationRequires()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
try: DataValidation.checkItemNamesInRegionRequires()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check that region names are valid (i.e., not already defined, etc.)
|
|
try: DataValidation.checkForInvalidRegionNames()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check that region names are correct in locations
|
|
try: DataValidation.checkRegionNamesInLocations()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check that any classification_count used in items are valid
|
|
try: DataValidation.checkItemsHasValidClassificationCount()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check that items that are required by locations and regions are also marked required
|
|
try: DataValidation.checkItemsThatShouldBeRequired()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check that regions that are connected to are correct
|
|
try: DataValidation.checkRegionsConnectingToOtherRegions()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check for duplicate names in items, locations, and regions
|
|
try: DataValidation.checkForDuplicateItemNames()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
try: DataValidation.checkForDuplicateLocationNames()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check that starting items are actually valid starting item definitions
|
|
try: DataValidation.checkStartingItemsForBadSyntax()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check that starting items and starting item categories actually exist in the items json
|
|
try: DataValidation.checkStartingItemsForValidItemsAndCategories()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check that placed items are actually valid place item definitions
|
|
try: DataValidation.checkPlacedItemsAndCategoriesForBadSyntax()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check placed item and item categories for valid options for each
|
|
try: DataValidation.checkPlacedItemsForValidItems()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
try: DataValidation.checkPlacedItemCategoriesForValidItemCategories()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
# check for regions that are set as non-starting regions and have no connectors to them (so are unreachable)
|
|
try: DataValidation.checkForNonStartingRegionsThatAreUnreachable()
|
|
except ValidationError as e: validation_errors.append(e)
|
|
|
|
if len(validation_errors) > 0:
|
|
heading = f"ValidationError(s) in {cls.game}:";
|
|
|
|
raise Exception("\n\n%s \n\n%s\n\n" % (heading, "\n".join([' - ' + str(validation_error) for validation_error in validation_errors])))
|