| 
									
										
										
											
												Stardew Valley 6.x.x: The Content Update (#3478)
Focus of the Update: Compatibility with Stardew Valley 1.6 Released on March 19th 2024
This includes randomization for pretty much all of the new content, including but not limited to
- Raccoon Bundles
- Booksanity
- Skill Masteries
- New Recipes, Craftables, Fish, Maps, Farm Type, Festivals and Quests
This also includes a significant reorganisation of the code into "Content Packs", to allow for easier modularity of various game mechanics between the settings and the supported mods. This improves maintainability quite a bit.
In addition to that, a few **very** requested new features have been introduced, although they weren't the focus of this update
- Walnutsanity
- Player Buffs
- More customizability in settings, such as shorter special orders, ER without farmhouse
- New Remixed Bundles
											
										 
											2024-07-07 16:04:25 +03:00
										 |  |  | """
 | 
					
						
							|  |  |  | Copy of the script in test/benchmark, adapted to Stardew Valley. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Run with `python -m worlds.stardew_valley.test.script.benchmark_locations --options minimal_locations_maximal_items` | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import argparse | 
					
						
							|  |  |  | import collections | 
					
						
							|  |  |  | import gc | 
					
						
							|  |  |  | import logging | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | import time | 
					
						
							|  |  |  | import typing | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from BaseClasses import CollectionState, Location | 
					
						
							|  |  |  | from Utils import init_logging | 
					
						
							| 
									
										
										
										
											2025-05-13 03:58:03 -04:00
										 |  |  | from ..bases import setup_solo_multiworld | 
					
						
							|  |  |  | from ..options import presets | 
					
						
							|  |  |  | from ...stardew_rule.rule_explain import explain | 
					
						
							| 
									
										
										
											
												Stardew Valley 6.x.x: The Content Update (#3478)
Focus of the Update: Compatibility with Stardew Valley 1.6 Released on March 19th 2024
This includes randomization for pretty much all of the new content, including but not limited to
- Raccoon Bundles
- Booksanity
- Skill Masteries
- New Recipes, Craftables, Fish, Maps, Farm Type, Festivals and Quests
This also includes a significant reorganisation of the code into "Content Packs", to allow for easier modularity of various game mechanics between the settings and the supported mods. This improves maintainability quite a bit.
In addition to that, a few **very** requested new features have been introduced, although they weren't the focus of this update
- Walnutsanity
- Player Buffs
- More customizability in settings, such as shorter special orders, ER without farmhouse
- New Remixed Bundles
											
										 
											2024-07-07 16:04:25 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def run_locations_benchmark(): | 
					
						
							|  |  |  |     init_logging("Benchmark Runner") | 
					
						
							|  |  |  |     logger = logging.getLogger("Benchmark") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class BenchmarkRunner: | 
					
						
							|  |  |  |         gen_steps: typing.Tuple[str, ...] = ( | 
					
						
							|  |  |  |             "generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill") | 
					
						
							|  |  |  |         rule_iterations: int = 100_000 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @staticmethod | 
					
						
							|  |  |  |         def format_times_from_counter(counter: collections.Counter[str], top: int = 5) -> str: | 
					
						
							|  |  |  |             return "\n".join(f"  {time:.4f} in {name}" for name, time in counter.most_common(top)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def location_test(self, test_location: Location, state: CollectionState, state_name: str) -> float: | 
					
						
							|  |  |  |             with TimeIt(f"{test_location.game} {self.rule_iterations} " | 
					
						
							|  |  |  |                         f"runs of {test_location}.access_rule({state_name})", logger) as t: | 
					
						
							|  |  |  |                 for _ in range(self.rule_iterations): | 
					
						
							|  |  |  |                     test_location.access_rule(state) | 
					
						
							|  |  |  |                 # if time is taken to disentangle complex ref chains, | 
					
						
							|  |  |  |                 # this time should be attributed to the rule. | 
					
						
							|  |  |  |                 gc.collect() | 
					
						
							|  |  |  |             return t.dif | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def main(self): | 
					
						
							|  |  |  |             game = "Stardew Valley" | 
					
						
							|  |  |  |             summary_data: typing.Dict[str, collections.Counter[str]] = { | 
					
						
							|  |  |  |                 "empty_state": collections.Counter(), | 
					
						
							|  |  |  |                 "all_state": collections.Counter(), | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 parser = argparse.ArgumentParser() | 
					
						
							|  |  |  |                 parser.add_argument('--options', help="Define the option set to use, from the preset in test/__init__.py .", type=str, required=True) | 
					
						
							|  |  |  |                 parser.add_argument('--seed', help="Define the seed to use.", type=int, required=True) | 
					
						
							|  |  |  |                 parser.add_argument('--location', help="Define the specific location to benchmark.", type=str, default=None) | 
					
						
							|  |  |  |                 parser.add_argument('--state', help="Define the state in which the location will be benchmarked.", type=str, default=None) | 
					
						
							|  |  |  |                 args = parser.parse_args() | 
					
						
							|  |  |  |                 options_set = args.options | 
					
						
							| 
									
										
										
										
											2025-05-13 03:58:03 -04:00
										 |  |  |                 options = getattr(presets, options_set)() | 
					
						
							| 
									
										
										
											
												Stardew Valley 6.x.x: The Content Update (#3478)
Focus of the Update: Compatibility with Stardew Valley 1.6 Released on March 19th 2024
This includes randomization for pretty much all of the new content, including but not limited to
- Raccoon Bundles
- Booksanity
- Skill Masteries
- New Recipes, Craftables, Fish, Maps, Farm Type, Festivals and Quests
This also includes a significant reorganisation of the code into "Content Packs", to allow for easier modularity of various game mechanics between the settings and the supported mods. This improves maintainability quite a bit.
In addition to that, a few **very** requested new features have been introduced, although they weren't the focus of this update
- Walnutsanity
- Player Buffs
- More customizability in settings, such as shorter special orders, ER without farmhouse
- New Remixed Bundles
											
										 
											2024-07-07 16:04:25 +03:00
										 |  |  |                 seed = args.seed | 
					
						
							|  |  |  |                 location = args.location | 
					
						
							|  |  |  |                 state = args.state | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-13 03:58:03 -04:00
										 |  |  |                 multiworld = setup_solo_multiworld(options, seed) | 
					
						
							| 
									
										
										
											
												Stardew Valley 6.x.x: The Content Update (#3478)
Focus of the Update: Compatibility with Stardew Valley 1.6 Released on March 19th 2024
This includes randomization for pretty much all of the new content, including but not limited to
- Raccoon Bundles
- Booksanity
- Skill Masteries
- New Recipes, Craftables, Fish, Maps, Farm Type, Festivals and Quests
This also includes a significant reorganisation of the code into "Content Packs", to allow for easier modularity of various game mechanics between the settings and the supported mods. This improves maintainability quite a bit.
In addition to that, a few **very** requested new features have been introduced, although they weren't the focus of this update
- Walnutsanity
- Player Buffs
- More customizability in settings, such as shorter special orders, ER without farmhouse
- New Remixed Bundles
											
										 
											2024-07-07 16:04:25 +03:00
										 |  |  |                 gc.collect() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if location: | 
					
						
							|  |  |  |                     locations = [multiworld.get_location(location, 1)] | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     locations = sorted(multiworld.get_unfilled_locations()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 all_state = multiworld.get_all_state(False) | 
					
						
							|  |  |  |                 for location in locations: | 
					
						
							|  |  |  |                     if state != "all_state": | 
					
						
							|  |  |  |                         time_taken = self.location_test(location, multiworld.state, "empty_state") | 
					
						
							|  |  |  |                         summary_data["empty_state"][location.name] = time_taken | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if state != "empty_state": | 
					
						
							|  |  |  |                         time_taken = self.location_test(location, all_state, "all_state") | 
					
						
							|  |  |  |                         summary_data["all_state"][location.name] = time_taken | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 total_empty_state = sum(summary_data["empty_state"].values()) | 
					
						
							|  |  |  |                 total_all_state = sum(summary_data["all_state"].values()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 logger.info(f"{game} took {total_empty_state / len(locations):.4f} " | 
					
						
							|  |  |  |                             f"seconds per location in empty_state and {total_all_state / len(locations):.4f} " | 
					
						
							|  |  |  |                             f"in all_state. (all times summed for {self.rule_iterations} runs.)") | 
					
						
							|  |  |  |                 logger.info(f"Top times in empty_state:\n" | 
					
						
							|  |  |  |                             f"{self.format_times_from_counter(summary_data['empty_state'])}") | 
					
						
							|  |  |  |                 logger.info(f"Top times in all_state:\n" | 
					
						
							|  |  |  |                             f"{self.format_times_from_counter(summary_data['all_state'])}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if len(locations) == 1: | 
					
						
							|  |  |  |                     logger.info(str(explain(locations[0].access_rule, all_state, False))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             except Exception as e: | 
					
						
							|  |  |  |                 logger.exception(e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     runner = BenchmarkRunner() | 
					
						
							|  |  |  |     runner.main() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TimeIt: | 
					
						
							|  |  |  |     def __init__(self, name: str, time_logger=None): | 
					
						
							|  |  |  |         self.name = name | 
					
						
							|  |  |  |         self.logger = time_logger | 
					
						
							|  |  |  |         self.timer = None | 
					
						
							|  |  |  |         self.end_timer = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __enter__(self): | 
					
						
							|  |  |  |         self.timer = time.perf_counter() | 
					
						
							|  |  |  |         return self | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def dif(self): | 
					
						
							|  |  |  |         return self.end_timer - self.timer | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __exit__(self, exc_type, exc_val, exc_tb): | 
					
						
							|  |  |  |         if not self.end_timer: | 
					
						
							|  |  |  |             self.end_timer = time.perf_counter() | 
					
						
							|  |  |  |         if self.logger: | 
					
						
							|  |  |  |             self.logger.info(f"{self.dif:.4f} seconds in {self.name}.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def change_home(): | 
					
						
							|  |  |  |     """Allow scripts to run from "this" folder.""" | 
					
						
							|  |  |  |     old_home = os.path.dirname(__file__) | 
					
						
							|  |  |  |     sys.path.remove(old_home) | 
					
						
							|  |  |  |     new_home = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) | 
					
						
							|  |  |  |     os.chdir(new_home) | 
					
						
							|  |  |  |     sys.path.append(new_home) | 
					
						
							|  |  |  |     # fallback to local import | 
					
						
							|  |  |  |     sys.path.append(old_home) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     from Utils import local_path | 
					
						
							|  |  |  |     local_path.cached_path = new_home | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     run_locations_benchmark() |