|  | 
| 3 | 3 | import json | 
| 4 | 4 | from worlds.AutoWorld import World | 
| 5 | 5 | from BaseClasses import MultiWorld, ItemClassification | 
|  | 6 | +from typing import Any | 
| 6 | 7 | 
 | 
| 7 | 8 | 
 | 
| 8 | 9 | class ValidationError(Exception): | 
| 9 | 10 |     pass | 
| 10 | 11 | 
 | 
| 11 | 12 | class DataValidation(): | 
| 12 |  | -    game_table = {} | 
| 13 |  | -    item_table = [] | 
| 14 |  | -    location_table = [] | 
| 15 |  | -    region_table = {} | 
|  | 13 | +    game_table: dict[str, Any] = {} | 
|  | 14 | +    item_table: list[dict[str, Any]] = [] | 
|  | 15 | +    location_table: list[dict[str, Any]] = [] | 
|  | 16 | +    region_table: dict[str, Any] = {} | 
| 16 | 17 | 
 | 
| 17 | 18 | 
 | 
| 18 | 19 |     @staticmethod | 
| @@ -176,17 +177,79 @@ def checkRegionNamesInLocations(): | 
| 176 | 177 |             if not region_exists: | 
| 177 | 178 |                 raise ValidationError("Region %s is set for location %s, but the region is misspelled or does not exist." % (location["region"], location["name"])) | 
| 178 | 179 | 
 | 
|  | 180 | +    @staticmethod | 
|  | 181 | +    def checkItemsHasValidClassificationCount(): | 
|  | 182 | +        for item in DataValidation.item_table: | 
|  | 183 | +            if not item.get("classification_count"): | 
|  | 184 | +                continue | 
|  | 185 | +            for cat, count in item["classification_count"].items(): | 
|  | 186 | +                cat = str(cat) | 
|  | 187 | +                if count == 0: | 
|  | 188 | +                    continue | 
|  | 189 | +                try: | 
|  | 190 | +                    def stringCheck(string: str): | 
|  | 191 | +                        if string.isdigit(): | 
|  | 192 | +                            ItemClassification(int(string)) | 
|  | 193 | +                        elif string.startswith('0b'): | 
|  | 194 | +                            ItemClassification(int(string, base=0)) | 
|  | 195 | +                        else: | 
|  | 196 | +                            ItemClassification[string] | 
|  | 197 | + | 
|  | 198 | +                    if "+" in cat: | 
|  | 199 | +                        for substring in cat.split("+"): | 
|  | 200 | +                            stringCheck(substring.strip()) | 
|  | 201 | + | 
|  | 202 | +                    else: | 
|  | 203 | +                        stringCheck(cat) | 
|  | 204 | + | 
|  | 205 | +                except KeyError as ex: | 
|  | 206 | +                    raise ValidationError(f"Item '{item['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}") | 
|  | 207 | +                except Exception as ex: | 
|  | 208 | +                    raise ValidationError(f"Item '{item['name']}''s classification_count '{cat}' was improperly defined\n\n{type(ex).__name__}:{ex}") | 
|  | 209 | + | 
| 179 | 210 |     @staticmethod | 
| 180 | 211 |     def checkItemsThatShouldBeRequired(): | 
| 181 | 212 |         for item in DataValidation.item_table: | 
| 182 | 213 |             # if the item is already progression, no need to check | 
| 183 |  | -            if "progression" in item and item["progression"]: | 
|  | 214 | +            if item.get("progression"): | 
| 184 | 215 |                 continue | 
| 185 | 216 | 
 | 
| 186 | 217 |             # progression_skip_balancing is also progression, so no check needed | 
| 187 |  | -            if "progression_skip_balancing" in item and item["progression_skip_balancing"]: | 
|  | 218 | +            if item.get("progression_skip_balancing"): | 
| 188 | 219 |                 continue | 
| 189 |  | - | 
|  | 220 | +            # if any of the advanced type is already progression then no check needed | 
|  | 221 | +            if item.get("classification_count"): | 
|  | 222 | +                has_progression = False | 
|  | 223 | +                for cat, count in item["classification_count"].items(): | 
|  | 224 | +                    cat = str(cat) | 
|  | 225 | +                    if count == 0: | 
|  | 226 | +                        continue | 
|  | 227 | +                    try: | 
|  | 228 | +                        def stringCheck(string: str) -> ItemClassification: | 
|  | 229 | +                            if string.isdigit(): | 
|  | 230 | +                                true_class = ItemClassification(int(string)) | 
|  | 231 | +                            elif string.startswith('0b'): | 
|  | 232 | +                                true_class = ItemClassification(int(string, base=0)) | 
|  | 233 | +                            else: | 
|  | 234 | +                                true_class = ItemClassification[string] | 
|  | 235 | +                            return true_class | 
|  | 236 | + | 
|  | 237 | +                        if "+" in cat: | 
|  | 238 | +                            true_class = ItemClassification.filler | 
|  | 239 | +                            for substring in cat.split("+"): | 
|  | 240 | +                                true_class |= stringCheck(substring.strip()) | 
|  | 241 | +                        else: | 
|  | 242 | +                            true_class = stringCheck(cat) | 
|  | 243 | + | 
|  | 244 | +                    except: | 
|  | 245 | +                        # Skip since this validation error is dealt with in checkItemsHasValidClassificationCount | 
|  | 246 | +                        true_class = ItemClassification.filler | 
|  | 247 | +                    if ItemClassification.progression in true_class: | 
|  | 248 | +                        has_progression = True | 
|  | 249 | +                        break | 
|  | 250 | + | 
|  | 251 | +                if has_progression: | 
|  | 252 | +                    continue | 
| 190 | 253 |             # check location requires for the presence of item name | 
| 191 | 254 |             for location in DataValidation.location_table: | 
| 192 | 255 |                 if "requires" not in location: | 
| @@ -466,6 +529,10 @@ def runGenerationDataValidation(cls) -> None: | 
| 466 | 529 |     try: DataValidation.checkRegionNamesInLocations() | 
| 467 | 530 |     except ValidationError as e: validation_errors.append(e) | 
| 468 | 531 | 
 | 
|  | 532 | +    # check that any classification_count used in items are valid | 
|  | 533 | +    try: DataValidation.checkItemsHasValidClassificationCount() | 
|  | 534 | +    except ValidationError as e: validation_errors.append(e) | 
|  | 535 | + | 
| 469 | 536 |     # check that items that are required by locations and regions are also marked required | 
| 470 | 537 |     try: DataValidation.checkItemsThatShouldBeRequired() | 
| 471 | 538 |     except ValidationError as e: validation_errors.append(e) | 
|  | 
0 commit comments