| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  | from .Items import UndertaleItem, item_table, required_armor, required_weapons, non_key_items, key_items, \ | 
					
						
							|  |  |  |     junk_weights_all, plot_items, junk_weights_neutral, junk_weights_pacifist, junk_weights_genocide | 
					
						
							|  |  |  | from .Locations import UndertaleAdvancement, advancement_table, exclusion_table | 
					
						
							|  |  |  | from .Regions import undertale_regions, link_undertale_areas | 
					
						
							|  |  |  | from .Rules import set_rules, set_completion_rules | 
					
						
							|  |  |  | from worlds.generic.Rules import exclusion_rules | 
					
						
							|  |  |  | from BaseClasses import Region, Entrance, Tutorial, Item | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  | from .Options import UndertaleOptions | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  | from worlds.AutoWorld import World, WebWorld | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  | from worlds.LauncherComponents import Component, components | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  | from multiprocessing import Process | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def run_client(): | 
					
						
							|  |  |  |     print('running undertale client') | 
					
						
							| 
									
										
										
										
											2023-07-04 14:09:17 -04:00
										 |  |  |     from .UndertaleClient import main  # lazy import | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |     p = Process(target=main) | 
					
						
							|  |  |  |     p.start() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | components.append(Component("Undertale Client", "UndertaleClient")) | 
					
						
							| 
									
										
										
										
											2023-07-04 14:09:17 -04:00
										 |  |  | # components.append(Component("Undertale Client", func=run_client)) | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def data_path(file_name: str): | 
					
						
							|  |  |  |     import pkgutil | 
					
						
							|  |  |  |     return pkgutil.get_data(__name__, "data/" + file_name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class UndertaleWeb(WebWorld): | 
					
						
							|  |  |  |     tutorials = [Tutorial( | 
					
						
							| 
									
										
										
										
											2024-02-20 11:22:32 -05:00
										 |  |  |         "Multiworld Setup Guide", | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |         "A guide to setting up the Archipelago Undertale software on your computer. This guide covers " | 
					
						
							|  |  |  |         "single-player, multiworld, and related software.", | 
					
						
							|  |  |  |         "English", | 
					
						
							| 
									
										
										
										
											2023-09-22 20:45:52 -04:00
										 |  |  |         "setup_en.md", | 
					
						
							|  |  |  |         "setup/en", | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |         ["Mewlif"] | 
					
						
							|  |  |  |     )] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class UndertaleWorld(World): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Undertale is an RPG where every choice you make matters. You could choose to hurt all the enemies, eventually | 
					
						
							|  |  |  |     causing genocide of the monster species. Or you can spare all the enemies, befriending them and freeing them | 
					
						
							|  |  |  |     from their underground prison. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     game = "Undertale" | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |     options_dataclass = UndertaleOptions | 
					
						
							|  |  |  |     options: UndertaleOptions | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |     web = UndertaleWeb() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     item_name_to_id = {name: data.code for name, data in item_table.items()} | 
					
						
							|  |  |  |     location_name_to_id = {name: data.id for name, data in advancement_table.items()} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _get_undertale_data(self): | 
					
						
							|  |  |  |         return { | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |             "world_seed": self.random.getrandbits(32), | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             "seed_name": self.multiworld.seed_name, | 
					
						
							|  |  |  |             "player_name": self.multiworld.get_player_name(self.player), | 
					
						
							|  |  |  |             "player_id": self.player, | 
					
						
							|  |  |  |             "client_version": self.required_client_version, | 
					
						
							|  |  |  |             "race": self.multiworld.is_race, | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |             "route": self.options.route_required.current_key, | 
					
						
							|  |  |  |             "starting_area": self.options.starting_area.current_key, | 
					
						
							|  |  |  |             "temy_armor_include": bool(self.options.temy_include.value), | 
					
						
							|  |  |  |             "only_flakes": bool(self.options.only_flakes.value), | 
					
						
							|  |  |  |             "no_equips": bool(self.options.no_equips.value), | 
					
						
							|  |  |  |             "key_hunt": bool(self.options.key_hunt.value), | 
					
						
							| 
									
										
										
										
											2024-08-11 19:53:40 -04:00
										 |  |  |             "key_pieces": int(self.options.key_pieces.value), | 
					
						
							|  |  |  |             "rando_love": bool(self.options.rando_love and (self.options.route_required == "genocide" or self.options.route_required == "all_routes")), | 
					
						
							|  |  |  |             "rando_stats": bool(self.options.rando_stats and (self.options.route_required == "genocide" or self.options.route_required == "all_routes")), | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |             "prog_armor": bool(self.options.prog_armor.value), | 
					
						
							|  |  |  |             "prog_weapons": bool(self.options.prog_weapons.value), | 
					
						
							| 
									
										
										
										
											2024-08-11 19:53:40 -04:00
										 |  |  |             "rando_item_button": bool(self.options.rando_item_button.value), | 
					
						
							|  |  |  |             "route_required": int(self.options.route_required.value), | 
					
						
							|  |  |  |             "temy_include": int(self.options.temy_include.value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |     def get_filler_item_name(self): | 
					
						
							|  |  |  |         if self.options.route_required == "all_routes": | 
					
						
							|  |  |  |             junk_pool = junk_weights_all | 
					
						
							|  |  |  |         elif self.options.route_required == "genocide": | 
					
						
							|  |  |  |             junk_pool = junk_weights_genocide | 
					
						
							|  |  |  |         elif self.options.route_required == "neutral": | 
					
						
							|  |  |  |             junk_pool = junk_weights_neutral | 
					
						
							|  |  |  |         elif self.options.route_required == "pacifist": | 
					
						
							|  |  |  |             junk_pool = junk_weights_pacifist | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             junk_pool = junk_weights_all | 
					
						
							|  |  |  |         if not self.options.only_flakes: | 
					
						
							|  |  |  |             return self.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()))[0] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return "Temmie Flakes" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |     def create_items(self): | 
					
						
							|  |  |  |         self.multiworld.get_location("Undyne Date", self.player).place_locked_item(self.create_item("Undyne Date")) | 
					
						
							|  |  |  |         self.multiworld.get_location("Alphys Date", self.player).place_locked_item(self.create_item("Alphys Date")) | 
					
						
							|  |  |  |         self.multiworld.get_location("Papyrus Date", self.player).place_locked_item(self.create_item("Papyrus Date")) | 
					
						
							|  |  |  |         # Generate item pool | 
					
						
							|  |  |  |         itempool = [] | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if self.options.route_required == "all_routes": | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             junk_pool = junk_weights_all.copy() | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         elif self.options.route_required == "genocide": | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             junk_pool = junk_weights_genocide.copy() | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         elif self.options.route_required == "neutral": | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             junk_pool = junk_weights_neutral.copy() | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         elif self.options.route_required == "pacifist": | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             junk_pool = junk_weights_pacifist.copy() | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             junk_pool = junk_weights_all.copy() | 
					
						
							|  |  |  |         # Add all required progression items | 
					
						
							|  |  |  |         for name, num in key_items.items(): | 
					
						
							|  |  |  |             itempool += [name] * num | 
					
						
							|  |  |  |         for name, num in required_armor.items(): | 
					
						
							|  |  |  |             itempool += [name] * num | 
					
						
							|  |  |  |         for name, num in required_weapons.items(): | 
					
						
							|  |  |  |             itempool += [name] * num | 
					
						
							|  |  |  |         for name, num in non_key_items.items(): | 
					
						
							|  |  |  |             itempool += [name] * num | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if self.options.rando_item_button: | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             itempool += ["ITEM"] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.multiworld.push_precollected(self.create_item("ITEM")) | 
					
						
							|  |  |  |         self.multiworld.push_precollected(self.create_item("FIGHT")) | 
					
						
							|  |  |  |         self.multiworld.push_precollected(self.create_item("ACT")) | 
					
						
							|  |  |  |         self.multiworld.push_precollected(self.create_item("MERCY")) | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if self.options.route_required == "genocide": | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             itempool = [item for item in itempool if item != "Popato Chisps" and item != "Stained Apron" and | 
					
						
							|  |  |  |                         item != "Nice Cream" and item != "Hot Cat" and item != "Hot Dog...?" and item != "Punch Card"] | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         elif self.options.route_required == "neutral": | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             itempool = [item for item in itempool if item != "Popato Chisps" and item != "Hot Cat" and | 
					
						
							|  |  |  |                         item != "Hot Dog...?"] | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if self.options.route_required == "pacifist" or self.options.route_required == "all_routes": | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             itempool += ["Undyne Letter EX"] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             itempool.remove("Complete Skeleton") | 
					
						
							|  |  |  |             itempool.remove("Fish") | 
					
						
							|  |  |  |             itempool.remove("DT Extractor") | 
					
						
							|  |  |  |             itempool.remove("Hush Puppy") | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if self.options.key_hunt: | 
					
						
							|  |  |  |             itempool += ["Key Piece"] * self.options.key_pieces.value | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |         else: | 
					
						
							|  |  |  |             itempool += ["Left Home Key"] | 
					
						
							|  |  |  |             itempool += ["Right Home Key"] | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if not self.options.rando_love or \ | 
					
						
							|  |  |  |                 (self.options.route_required != "genocide" and self.options.route_required != "all_routes"): | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             itempool = [item for item in itempool if not item == "LOVE"] | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if not self.options.rando_stats or \ | 
					
						
							|  |  |  |                 (self.options.route_required != "genocide" and self.options.route_required != "all_routes"): | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             itempool = [item for item in itempool if not (item == "ATK Up" or item == "DEF Up" or item == "HP Up")] | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if self.options.temy_include: | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             itempool += ["temy armor"] | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if self.options.no_equips: | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             itempool = [item for item in itempool if item not in required_armor and item not in required_weapons] | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |             if self.options.prog_armor: | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |                 itempool = [item if (item not in required_armor and not item == "temy armor") else | 
					
						
							|  |  |  |                             "Progressive Armor" for item in itempool] | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |             if self.options.prog_weapons: | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |                 itempool = [item if item not in required_weapons else "Progressive Weapons" for item in itempool] | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if self.options.route_required == "genocide" or \ | 
					
						
							|  |  |  |                 self.options.route_required == "all_routes": | 
					
						
							|  |  |  |             if not self.options.only_flakes: | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |                 itempool += ["Snowman Piece"] * 2 | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |             if not self.options.no_equips: | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |                 itempool = ["Real Knife" if item == "Worn Dagger" else "The Locket" | 
					
						
							|  |  |  |                             if item == "Heart Locket" else item for item in itempool] | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if self.options.only_flakes: | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             itempool = [item for item in itempool if item not in non_key_items] | 
					
						
							| 
									
										
										
										
											2023-08-16 10:02:01 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         starting_key = self.options.starting_area.current_key.title() + " Key" | 
					
						
							| 
									
										
										
										
											2023-08-16 10:02:01 -04:00
										 |  |  |         itempool.remove(starting_key) | 
					
						
							|  |  |  |         self.multiworld.push_precollected(self.create_item(starting_key)) | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |         # Choose locations to automatically exclude based on settings | 
					
						
							|  |  |  |         exclusion_pool = set() | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         exclusion_pool.update(exclusion_table[self.options.route_required.current_key]) | 
					
						
							|  |  |  |         if not self.options.rando_love or \ | 
					
						
							|  |  |  |                 (self.options.route_required != "genocide" and self.options.route_required != "all_routes"): | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             exclusion_pool.update(exclusion_table["NoLove"]) | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         if not self.options.rando_stats or \ | 
					
						
							|  |  |  |                 (self.options.route_required != "genocide" and self.options.route_required != "all_routes"): | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             exclusion_pool.update(exclusion_table["NoStats"]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Choose locations to automatically exclude based on settings | 
					
						
							|  |  |  |         exclusion_checks = set() | 
					
						
							|  |  |  |         exclusion_checks.update(["Nicecream Punch Card", "Hush Trade"]) | 
					
						
							|  |  |  |         exclusion_rules(self.multiworld, self.player, exclusion_checks) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Convert itempool into real items | 
					
						
							|  |  |  |         itempool = [item for item in map(lambda name: self.create_item(name), itempool)] | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         # Fill remaining items with randomly generated junk or Temmie Flakes | 
					
						
							|  |  |  |         while len(itempool) < len(self.multiworld.get_unfilled_locations(self.player)): | 
					
						
							|  |  |  |             itempool.append(self.create_filler()) | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         self.multiworld.itempool += itempool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def set_rules(self): | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |         set_rules(self) | 
					
						
							|  |  |  |         set_completion_rules(self) | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def create_regions(self): | 
					
						
							|  |  |  |         def UndertaleRegion(region_name: str, exits=[]): | 
					
						
							|  |  |  |             ret = Region(region_name, self.player, self.multiworld) | 
					
						
							| 
									
										
										
										
											2023-10-29 19:47:37 +01:00
										 |  |  |             ret.locations += [UndertaleAdvancement(self.player, loc_name, loc_data.id, ret) | 
					
						
							| 
									
										
										
										
											2024-06-21 12:21:46 -04:00
										 |  |  |                               for loc_name, loc_data in advancement_table.items() | 
					
						
							|  |  |  |                               if loc_data.region == region_name and | 
					
						
							|  |  |  |                               (loc_name not in exclusion_table["NoStats"] or | 
					
						
							|  |  |  |                               (self.options.rando_stats and | 
					
						
							|  |  |  |                                (self.options.route_required == "genocide" or | 
					
						
							|  |  |  |                                 self.options.route_required == "all_routes"))) and | 
					
						
							|  |  |  |                               (loc_name not in exclusion_table["NoLove"] or | 
					
						
							|  |  |  |                               (self.options.rando_love and | 
					
						
							|  |  |  |                                (self.options.route_required == "genocide" or | 
					
						
							|  |  |  |                                 self.options.route_required == "all_routes"))) and | 
					
						
							|  |  |  |                               loc_name not in exclusion_table[self.options.route_required.current_key]] | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  |             for exit in exits: | 
					
						
							|  |  |  |                 ret.exits.append(Entrance(self.player, exit, ret)) | 
					
						
							|  |  |  |             return ret | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.multiworld.regions += [UndertaleRegion(*r) for r in undertale_regions] | 
					
						
							|  |  |  |         link_undertale_areas(self.multiworld, self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def fill_slot_data(self): | 
					
						
							| 
									
										
										
										
											2024-08-11 19:53:40 -04:00
										 |  |  |         return self._get_undertale_data() | 
					
						
							| 
									
										
										
										
											2023-06-26 22:35:41 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def create_item(self, name: str) -> Item: | 
					
						
							|  |  |  |         item_data = item_table[name] | 
					
						
							|  |  |  |         item = UndertaleItem(name, item_data.classification, item_data.code, self.player) | 
					
						
							|  |  |  |         return item |