11import logging
22import re
33import json
4+ from worlds .AutoWorld import World
5+ from BaseClasses import MultiWorld , ItemClassification
6+
47
58class ValidationError (Exception ):
69 pass
@@ -192,6 +195,109 @@ def checkItemsThatShouldBeRequired():
192195 if item ["name" ] in region_requires :
193196 raise ValidationError ("Item %s is required by region %s, but the item is not marked as progression." % (item ["name" ], region_name ))
194197
198+ @staticmethod
199+ def _checkLocationRequiresForItemValueWithRegex (values_requested : dict [str , int ], requires ) -> dict [str , int ]:
200+ if isinstance (requires , str ) and 'ItemValue' in requires :
201+ for result in re .findall (r'\{ItemValue\(([^:]*)\:([^)]+)\)\}' , requires ):
202+ value = result [0 ].lower ().strip ()
203+ count = int (result [1 ])
204+ if not values_requested .get (value ):
205+ values_requested [value ] = count
206+ else :
207+ values_requested [value ] = max (values_requested [value ], count )
208+ return values_requested
209+
210+ @staticmethod
211+ def checkIfEnoughItemsForValue ():
212+ values_available = {}
213+ values_requested = {}
214+
215+ # First find the biggest values required by locations
216+ for location in DataValidation .location_table :
217+ if "requires" not in location :
218+ continue
219+
220+ # convert to json so we don't have to guess the data type
221+ location_requires = json .dumps (location ["requires" ])
222+
223+ DataValidation ._checkLocationRequiresForItemValueWithRegex (values_requested , location_requires )
224+ # Second, check region requires for the presence of item name
225+ for region_name in DataValidation .region_table :
226+ region = DataValidation .region_table [region_name ]
227+
228+ if "requires" not in region :
229+ continue
230+
231+ # convert to json so we don't have to guess the data type
232+ region_requires = json .dumps (region ["requires" ])
233+
234+ DataValidation ._checkLocationRequiresForItemValueWithRegex (values_requested , region_requires )
235+ # then if something is requested, we loop items
236+ if values_requested :
237+
238+ # get all the available values with total count
239+ for item in DataValidation .item_table :
240+ # if the item is already progression, no need to check
241+ if not item .get ("progression" ) and not item .get ("progression_skip_balancing" ):
242+ continue
243+
244+ item_count = item .get ('count' , None )
245+ if item_count is None : #check with none because 0 == false
246+ item_count = '1'
247+
248+ for key , count in item .get ("value" , {}).items ():
249+ if not values_available .get (key .lower ().strip ()):
250+ values_available [key ] = 0
251+ values_available [key ] += int (count ) * int (item_count )
252+
253+ # compare whats available vs requested
254+ errors = []
255+ for value , count in values_requested .items ():
256+ if values_available .get (value , 0 ) < count :
257+ errors .append (f" '{ value } ': { values_available .get (value , 0 )} out of the { count } { value } worth of progression items required can be found." )
258+ if errors :
259+ raise ValidationError ("There are not enough progression items for the following values: \n " + "\n " .join (errors ))
260+
261+ @staticmethod
262+ def preFillCheckIfEnoughItemsForValue (world : World , multiworld : MultiWorld ):
263+ from .Helpers import get_items_with_value , get_items_for_player
264+ player = world .player
265+ values_requested = {}
266+
267+ for region in multiworld .regions :
268+ if region .player != player :
269+ continue
270+
271+ manualregion = DataValidation .region_table .get (region .name , {})
272+ if "requires" in manualregion and manualregion ["requires" ]:
273+ region_requires = json .dumps (manualregion ["requires" ])
274+
275+ DataValidation ._checkLocationRequiresForItemValueWithRegex (values_requested , region_requires )
276+
277+ for location in region .locations :
278+ manualLocation = world .location_name_to_location .get (location .name , {})
279+ if "requires" in manualLocation and manualLocation ["requires" ]:
280+ DataValidation ._checkLocationRequiresForItemValueWithRegex (values_requested , manualLocation ["requires" ])
281+
282+ # compare whats available vs requested but only if there's anything requested
283+ if values_requested :
284+ errors = []
285+ existing_items = [item for item in get_items_for_player (multiworld , player ) if item .code is not None and
286+ item .classification == ItemClassification .progression or item .classification == ItemClassification .progression_skip_balancing ]
287+
288+ for value , val_count in values_requested .items ():
289+ items_value = get_items_with_value (world , multiworld , value , player , True )
290+ found_count = 0
291+ if items_value :
292+ for item in existing_items :
293+ if item .name in items_value :
294+ found_count += items_value [item .name ]
295+
296+ if found_count < val_count :
297+ errors .append (f" '{ value } ': { found_count } out of the { val_count } { value } worth of progression items required can be found." )
298+ if errors :
299+ raise ValidationError ("There are not enough progression items for the following value(s): \n " + "\n " .join (errors ))
300+
195301 @staticmethod
196302 def checkRegionsConnectingToOtherRegions ():
197303 for region_name in DataValidation .region_table :
@@ -338,7 +444,16 @@ def checkForNonStartingRegionsThatAreUnreachable():
338444 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 )
339445
340446
447+ def runPreFillDataValidation (world : World , multiworld : MultiWorld ):
448+ validation_errors = []
341449
450+ # check if there is enough items with values
451+ try : DataValidation .preFillCheckIfEnoughItemsForValue (world , multiworld )
452+ except ValidationError as e : validation_errors .append (e )
453+
454+ if validation_errors :
455+ newline = "\n "
456+ raise Exception (f"\n ValidationError(s) for pre_fill of player { world .player } : \n \n { newline .join ([' - ' + str (validation_error ) for validation_error in validation_errors ])} \n \n " )
342457# Called during stage_assert_generate
343458def runGenerationDataValidation () -> None :
344459 validation_errors = []
@@ -358,6 +473,10 @@ def runGenerationDataValidation() -> None:
358473 try : DataValidation .checkItemsThatShouldBeRequired ()
359474 except ValidationError as e : validation_errors .append (e )
360475
476+ # check if there's enough Items with values to get to every location requesting it
477+ try : DataValidation .checkIfEnoughItemsForValue ()
478+ except ValidationError as e : validation_errors .append (e )
479+
361480 # check that regions that are connected to are correct
362481 try : DataValidation .checkRegionsConnectingToOtherRegions ()
363482 except ValidationError as e : validation_errors .append (e )
0 commit comments