| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | from __future__ import annotations | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |  | import logging | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | import typing | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | from copy import deepcopy | 
					
						
							|  |  |  |  | import itertools | 
					
						
							|  |  |  |  | import operator | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | logger = logging.getLogger("Hollow Knight") | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-04 00:15:16 +02:00
										 |  |  |  | from .Items import item_table, lookup_type_to_names, item_name_groups | 
					
						
							| 
									
										
										
										
											2021-02-24 06:02:51 +01:00
										 |  |  |  | from .Regions import create_regions | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  | from .Rules import set_rules, cost_terms, _hk_can_beat_thk, _hk_siblings_ending, _hk_can_beat_radiance | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | from .Options import hollow_knight_options, hollow_knight_randomize_options, Goal, WhitePalace, CostSanity, \ | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |     shop_to_option, HKOptions | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | from .ExtractedData import locations, starts, multi_locations, location_to_region_lookup, \ | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |     event_names, item_effects, connectors, one_ways, vanilla_shop_costs, vanilla_location_costs | 
					
						
							| 
									
										
										
										
											2022-04-08 19:22:50 +02:00
										 |  |  |  | from .Charms import names as charm_names | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-25 03:47:38 +02:00
										 |  |  |  | from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification | 
					
						
							|  |  |  |  | from worlds.AutoWorld import World, LogicMixin, WebWorld | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:
- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te
- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene
- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.
* Hollow Knight updates
  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
											
										 
											2022-06-12 23:23:03 -07:00
										 |  |  |  | path_of_pain_locations = { | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     "Soul_Totem-Path_of_Pain_Below_Thornskip", | 
					
						
							|  |  |  |  |     "Lore_Tablet-Path_of_Pain_Entrance", | 
					
						
							|  |  |  |  |     "Soul_Totem-Path_of_Pain_Left_of_Lever", | 
					
						
							|  |  |  |  |     "Soul_Totem-Path_of_Pain_Hidden", | 
					
						
							|  |  |  |  |     "Soul_Totem-Path_of_Pain_Entrance", | 
					
						
							|  |  |  |  |     "Soul_Totem-Path_of_Pain_Final", | 
					
						
							|  |  |  |  |     "Soul_Totem-Path_of_Pain_Below_Lever", | 
					
						
							|  |  |  |  |     "Soul_Totem-Path_of_Pain_Second", | 
					
						
							| 
									
										
											  
											
												Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:
- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te
- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene
- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.
* Hollow Knight updates
  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
											
										 
											2022-06-12 23:23:03 -07:00
										 |  |  |  |     "Journal_Entry-Seal_of_Binding", | 
					
						
							|  |  |  |  |     "Warp-Path_of_Pain_Complete", | 
					
						
							|  |  |  |  |     "Defeated_Path_of_Pain_Arena", | 
					
						
							|  |  |  |  |     "Completed_Path_of_Pain", | 
					
						
							|  |  |  |  |     # Path of Pain transitions | 
					
						
							|  |  |  |  |     "White_Palace_17[right1]", "White_Palace_17[bot1]", | 
					
						
							|  |  |  |  |     "White_Palace_18[top1]", "White_Palace_18[right1]", | 
					
						
							|  |  |  |  |     "White_Palace_19[left1]", "White_Palace_19[top1]", | 
					
						
							|  |  |  |  |     "White_Palace_20[bot1]", | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | white_palace_transitions = { | 
					
						
							|  |  |  |  |     # Event-Transitions: | 
					
						
							|  |  |  |  |     # "Grubfather_2", | 
					
						
							|  |  |  |  |     "White_Palace_01[left1]", "White_Palace_01[right1]", "White_Palace_01[top1]", | 
					
						
							|  |  |  |  |     "White_Palace_02[left1]", | 
					
						
							|  |  |  |  |     "White_Palace_03_hub[bot1]", "White_Palace_03_hub[left1]", "White_Palace_03_hub[left2]", | 
					
						
							|  |  |  |  |     "White_Palace_03_hub[right1]", "White_Palace_03_hub[top1]", | 
					
						
							|  |  |  |  |     "White_Palace_04[right2]", "White_Palace_04[top1]", | 
					
						
							|  |  |  |  |     "White_Palace_05[left1]", "White_Palace_05[left2]", "White_Palace_05[right1]", "White_Palace_05[right2]", | 
					
						
							|  |  |  |  |     "White_Palace_06[bot1]", "White_Palace_06[left1]", "White_Palace_06[top1]", "White_Palace_07[bot1]", | 
					
						
							|  |  |  |  |     "White_Palace_07[top1]", "White_Palace_08[left1]", "White_Palace_08[right1]", | 
					
						
							|  |  |  |  |     "White_Palace_09[right1]", | 
					
						
							|  |  |  |  |     "White_Palace_11[door2]", | 
					
						
							|  |  |  |  |     "White_Palace_12[bot1]", "White_Palace_12[right1]", | 
					
						
							|  |  |  |  |     "White_Palace_13[left1]", "White_Palace_13[left2]", "White_Palace_13[left3]", "White_Palace_13[right1]", | 
					
						
							|  |  |  |  |     "White_Palace_14[bot1]", "White_Palace_14[right1]", | 
					
						
							|  |  |  |  |     "White_Palace_15[left1]", "White_Palace_15[right1]", "White_Palace_15[right2]", | 
					
						
							|  |  |  |  |     "White_Palace_16[left1]", "White_Palace_16[left2]", | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | white_palace_checks = { | 
					
						
							|  |  |  |  |     "Soul_Totem-White_Palace_Final", | 
					
						
							|  |  |  |  |     "Soul_Totem-White_Palace_Entrance", | 
					
						
							|  |  |  |  |     "Lore_Tablet-Palace_Throne", | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     "Soul_Totem-White_Palace_Left", | 
					
						
							|  |  |  |  |     "Lore_Tablet-Palace_Workshop", | 
					
						
							|  |  |  |  |     "Soul_Totem-White_Palace_Hub", | 
					
						
							| 
									
										
											  
											
												Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:
- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te
- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene
- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.
* Hollow Knight updates
  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
											
										 
											2022-06-12 23:23:03 -07:00
										 |  |  |  |     "Soul_Totem-White_Palace_Right" | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | white_palace_events = { | 
					
						
							|  |  |  |  |     "White_Palace_03_hub", | 
					
						
							|  |  |  |  |     "White_Palace_13", | 
					
						
							|  |  |  |  |     "White_Palace_01", | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     "Palace_Entrance_Lantern_Lit", | 
					
						
							|  |  |  |  |     "Palace_Left_Lantern_Lit", | 
					
						
							|  |  |  |  |     "Palace_Right_Lantern_Lit", | 
					
						
							|  |  |  |  |     "Palace_Atrium_Gates_Opened", | 
					
						
							|  |  |  |  |     "Warp-White_Palace_Atrium_to_Palace_Grounds", | 
					
						
							|  |  |  |  |     "Warp-White_Palace_Entrance_to_Palace_Grounds", | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-06 00:41:15 +02:00
										 |  |  |  | progression_charms = { | 
					
						
							| 
									
										
											  
											
												Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:
- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te
- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene
- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.
* Hollow Knight updates
  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
											
										 
											2022-06-12 23:23:03 -07:00
										 |  |  |  |     # Baldur Killers | 
					
						
							| 
									
										
										
										
											2022-04-06 00:41:15 +02:00
										 |  |  |  |     "Grubberfly's_Elegy", "Weaversong", "Glowing_Womb", | 
					
						
							| 
									
										
											  
											
												Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:
- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te
- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene
- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.
* Hollow Knight updates
  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
											
										 
											2022-06-12 23:23:03 -07:00
										 |  |  |  |     # Spore Shroom spots in fungal wastes and elsewhere | 
					
						
							| 
									
										
										
										
											2022-04-06 00:41:15 +02:00
										 |  |  |  |     "Spore_Shroom", | 
					
						
							|  |  |  |  |     # Tuk gives egg, | 
					
						
							|  |  |  |  |     "Defender's_Crest", | 
					
						
							|  |  |  |  |     # Unlocks Grimm Troupe | 
					
						
							|  |  |  |  |     "Grimmchild1", "Grimmchild2" | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:
- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te
- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene
- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.
* Hollow Knight updates
  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
											
										 
											2022-06-12 23:23:03 -07:00
										 |  |  |  | # Vanilla placements of the following items have no impact on logic, thus we can avoid creating these items and | 
					
						
							|  |  |  |  | # locations entirely when the option to randomize them is disabled. | 
					
						
							|  |  |  |  | logicless_options = { | 
					
						
							|  |  |  |  |     "RandomizeVesselFragments", "RandomizeGeoChests", "RandomizeJunkPitChests", "RandomizeRelics", | 
					
						
							|  |  |  |  |     "RandomizeMaps", "RandomizeJournalEntries", "RandomizeGeoRocks", "RandomizeBossGeo", | 
					
						
							|  |  |  |  |     "RandomizeLoreTablets", "RandomizeSoulTotems", | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | # Options that affect vanilla starting items | 
					
						
							|  |  |  |  | randomizable_starting_items: typing.Dict[str, typing.Tuple[str, ...]] = { | 
					
						
							|  |  |  |  |     "RandomizeFocus": ("Focus",), | 
					
						
							|  |  |  |  |     "RandomizeSwim": ("Swim",), | 
					
						
							|  |  |  |  |     "RandomizeNail": ('Upslash', 'Leftslash', 'Rightslash') | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | # Shop cost types. | 
					
						
							|  |  |  |  | shop_cost_types: typing.Dict[str, typing.Tuple[str, ...]] = { | 
					
						
							|  |  |  |  |     "Egg_Shop": ("RANCIDEGGS",), | 
					
						
							|  |  |  |  |     "Grubfather": ("GRUBS",), | 
					
						
							|  |  |  |  |     "Seer": ("ESSENCE",), | 
					
						
							|  |  |  |  |     "Salubra_(Requires_Charms)": ("CHARMS", "GEO"), | 
					
						
							|  |  |  |  |     "Sly": ("GEO",), | 
					
						
							|  |  |  |  |     "Sly_(Key)": ("GEO",), | 
					
						
							|  |  |  |  |     "Iselda": ("GEO",), | 
					
						
							|  |  |  |  |     "Salubra": ("GEO",), | 
					
						
							|  |  |  |  |     "Leg_Eater": ("GEO",), | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-03-18 18:19:21 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-03 07:41:27 -05:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-11 13:05:53 -05:00
										 |  |  |  | class HKWeb(WebWorld): | 
					
						
							|  |  |  |  |     tutorials = [Tutorial( | 
					
						
							|  |  |  |  |         "Mod Setup and Use Guide", | 
					
						
							|  |  |  |  |         "A guide to playing Hollow Knight with Archipelago.", | 
					
						
							|  |  |  |  |         "English", | 
					
						
							|  |  |  |  |         "setup_en.md", | 
					
						
							|  |  |  |  |         "setup/en", | 
					
						
							|  |  |  |  |         ["Ijwu"] | 
					
						
							|  |  |  |  |     )] | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-03 07:41:27 -05:00
										 |  |  |  |     bug_report_page = "https://github.com/Ijwu/Archipelago.HollowKnight/issues/new?assignees=&labels=bug%2C+needs+investigation&template=bug_report.md&title=" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-11 13:05:53 -05:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |  | class HKWorld(World): | 
					
						
							| 
									
										
										
										
											2022-04-02 20:49:27 +02:00
										 |  |  |  |     """Beneath the fading town of Dirtmouth sleeps a vast, ancient kingdom. Many are drawn beneath the surface, 
 | 
					
						
							|  |  |  |  |     searching for riches, or glory, or answers to old secrets. | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-02 20:56:26 +02:00
										 |  |  |  |     As the enigmatic Knight, you’ll traverse the depths, unravel its mysteries and conquer its evils. | 
					
						
							| 
									
										
										
										
											2022-04-02 20:49:27 +02:00
										 |  |  |  |     """  # from https://www.hollowknight.com
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |  |     game: str = "Hollow Knight" | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |     options_dataclass = HKOptions | 
					
						
							|  |  |  |  |     options: HKOptions | 
					
						
							| 
									
										
										
										
											2021-06-26 11:18:12 -05:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-11 13:05:53 -05:00
										 |  |  |  |     web = HKWeb() | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     item_name_to_id = {name: data.id for name, data in item_table.items()} | 
					
						
							|  |  |  |  |     location_name_to_id = {location_name: location_id for location_id, location_name in | 
					
						
							|  |  |  |  |                            enumerate(locations, start=0x1000000)} | 
					
						
							| 
									
										
										
										
											2022-04-04 00:15:16 +02:00
										 |  |  |  |     item_name_groups = item_name_groups | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     ranges: typing.Dict[str, typing.Tuple[int, int]] | 
					
						
							|  |  |  |  |     charm_costs: typing.List[int] | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |     cached_filler_items = {} | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |     def __init__(self, multiworld, player): | 
					
						
							|  |  |  |  |         super(HKWorld, self).__init__(multiworld, player) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         self.created_multi_locations: typing.Dict[str, typing.List[HKLocation]] = { | 
					
						
							|  |  |  |  |             location: list() for location in multi_locations | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |         self.ranges = {} | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         self.created_shop_items = 0 | 
					
						
							|  |  |  |  |         self.vanilla_shop_costs = deepcopy(vanilla_shop_costs) | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     def generate_early(self): | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         options = self.options | 
					
						
							|  |  |  |  |         charm_costs = options.RandomCharmCosts.get_costs(self.random) | 
					
						
							|  |  |  |  |         self.charm_costs = options.PlandoCharmCosts.get_costs(charm_costs) | 
					
						
							|  |  |  |  |         # options.exclude_locations.value.update(white_palace_locations) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         for term, data in cost_terms.items(): | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             mini = getattr(options, f"Minimum{data.option}Price") | 
					
						
							|  |  |  |  |             maxi = getattr(options, f"Maximum{data.option}Price") | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |             # if minimum > maximum, set minimum to maximum | 
					
						
							|  |  |  |  |             mini.value = min(mini.value, maxi.value) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |             self.ranges[term] = mini.value, maxi.value | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         self.multiworld.push_precollected(HKItem(starts[options.StartLocation.current_key], | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |                                        True, None, "Event", self.player)) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:
- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te
- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene
- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.
* Hollow Knight updates
  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
											
										 
											2022-06-12 23:23:03 -07:00
										 |  |  |  |     def white_palace_exclusions(self): | 
					
						
							|  |  |  |  |         exclusions = set() | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         wp = self.options.WhitePalace | 
					
						
							| 
									
										
											  
											
												Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:
- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te
- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene
- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.
* Hollow Knight updates
  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
											
										 
											2022-06-12 23:23:03 -07:00
										 |  |  |  |         if wp <= WhitePalace.option_nopathofpain: | 
					
						
							|  |  |  |  |             exclusions.update(path_of_pain_locations) | 
					
						
							|  |  |  |  |         if wp <= WhitePalace.option_kingfragment: | 
					
						
							|  |  |  |  |             exclusions.update(white_palace_checks) | 
					
						
							| 
									
										
										
										
											2022-06-25 11:15:03 -07:00
										 |  |  |  |         if wp == WhitePalace.option_exclude: | 
					
						
							| 
									
										
											  
											
												Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:
- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te
- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene
- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.
* Hollow Knight updates
  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
											
										 
											2022-06-12 23:23:03 -07:00
										 |  |  |  |             exclusions.add("King_Fragment") | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             if self.options.RandomizeCharms: | 
					
						
							| 
									
										
										
										
											2022-06-25 11:15:03 -07:00
										 |  |  |  |                 # If charms are randomized, this will be junk-filled -- so transitions and events are not progression | 
					
						
							|  |  |  |  |                 exclusions.update(white_palace_transitions) | 
					
						
							|  |  |  |  |                 exclusions.update(white_palace_events) | 
					
						
							| 
									
										
											  
											
												Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:
- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te
- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene
- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.
* Hollow Knight updates
  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
											
										 
											2022-06-12 23:23:03 -07:00
										 |  |  |  |         return exclusions | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     def create_regions(self): | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |  |         menu_region: Region = create_region(self.multiworld, self.player, 'Menu') | 
					
						
							|  |  |  |  |         self.multiworld.regions.append(menu_region) | 
					
						
							| 
									
										
										
										
											2022-06-25 11:15:03 -07:00
										 |  |  |  |         # wp_exclusions = self.white_palace_exclusions() | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-02 08:26:17 -05:00
										 |  |  |  |         # check for any goal that godhome events are relevant to | 
					
						
							|  |  |  |  |         all_event_names = event_names.copy() | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower]: | 
					
						
							| 
									
										
										
										
											2024-05-02 08:26:17 -05:00
										 |  |  |  |             from .GodhomeData import godhome_event_names | 
					
						
							|  |  |  |  |             all_event_names.update(set(godhome_event_names)) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-26 11:18:12 -05:00
										 |  |  |  |         # Link regions | 
					
						
							| 
									
										
										
										
											2024-05-02 08:26:17 -05:00
										 |  |  |  |         for event_name in all_event_names: | 
					
						
							| 
									
										
										
										
											2022-06-25 11:15:03 -07:00
										 |  |  |  |             #if event_name in wp_exclusions: | 
					
						
							|  |  |  |  |             #    continue | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |             loc = HKLocation(self.player, event_name, None, menu_region) | 
					
						
							|  |  |  |  |             loc.place_locked_item(HKItem(event_name, | 
					
						
							| 
									
										
										
										
											2022-06-25 11:15:03 -07:00
										 |  |  |  |                                          True, #event_name not in wp_exclusions, | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |                                          None, "Event", self.player)) | 
					
						
							|  |  |  |  |             menu_region.locations.append(loc) | 
					
						
							|  |  |  |  |         for entry_transition, exit_transition in connectors.items(): | 
					
						
							| 
									
										
										
										
											2022-06-25 11:15:03 -07:00
										 |  |  |  |             #if entry_transition in wp_exclusions: | 
					
						
							|  |  |  |  |             #    continue | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |             if exit_transition: | 
					
						
							|  |  |  |  |                 # if door logic fulfilled -> award vanilla target as event | 
					
						
							|  |  |  |  |                 loc = HKLocation(self.player, entry_transition, None, menu_region) | 
					
						
							|  |  |  |  |                 loc.place_locked_item(HKItem(exit_transition, | 
					
						
							| 
									
										
										
										
											2022-06-25 11:15:03 -07:00
										 |  |  |  |                                              True, #exit_transition not in wp_exclusions, | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |                                              None, "Event", self.player)) | 
					
						
							|  |  |  |  |                 menu_region.locations.append(loc) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     def create_items(self): | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         unfilled_locations = 0 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |         # Generate item pool and associated locations (paired in HK) | 
					
						
							|  |  |  |  |         pool: typing.List[HKItem] = [] | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         wp_exclusions = self.white_palace_exclusions() | 
					
						
							|  |  |  |  |         junk_replace: typing.Set[str] = set() | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         if self.options.RemoveSpellUpgrades: | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |             junk_replace.update(("Abyss_Shriek", "Shade_Soul", "Descending_Dark")) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         randomized_starting_items = set() | 
					
						
							|  |  |  |  |         for attr, items in randomizable_starting_items.items(): | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             if getattr(self.options, attr): | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                 randomized_starting_items.update(items) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # noinspection PyShadowingNames | 
					
						
							| 
									
										
										
										
											2024-03-13 06:45:43 -05:00
										 |  |  |  |         def _add(item_name: str, location_name: str, randomized: bool): | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |             """
 | 
					
						
							|  |  |  |  |             Adds a pairing of an item and location, doing appropriate checks to see if it should be vanilla or not. | 
					
						
							|  |  |  |  |             """
 | 
					
						
							|  |  |  |  |             nonlocal unfilled_locations | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             vanilla = not randomized | 
					
						
							|  |  |  |  |             excluded = False | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if not vanilla and location_name in wp_exclusions: | 
					
						
							|  |  |  |  |                 if location_name == 'King_Fragment': | 
					
						
							|  |  |  |  |                     excluded = True | 
					
						
							|  |  |  |  |                 else: | 
					
						
							|  |  |  |  |                     vanilla = True | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if item_name in junk_replace: | 
					
						
							|  |  |  |  |                 item_name = self.get_filler_item_name() | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             item = self.create_item(item_name) if not vanilla or location_name == "Start" or self.options.AddUnshuffledLocations else self.create_event(item_name) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |             if location_name == "Start": | 
					
						
							|  |  |  |  |                 if item_name in randomized_starting_items: | 
					
						
							| 
									
										
										
										
											2022-07-25 16:19:07 -04:00
										 |  |  |  |                     if item_name == "Focus": | 
					
						
							|  |  |  |  |                         self.create_location("Focus") | 
					
						
							|  |  |  |  |                         unfilled_locations += 1 | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                     pool.append(item) | 
					
						
							|  |  |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |  |                     self.multiworld.push_precollected(item) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                 return | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if vanilla: | 
					
						
							|  |  |  |  |                 location = self.create_vanilla_location(location_name, item) | 
					
						
							|  |  |  |  |             else: | 
					
						
							|  |  |  |  |                 pool.append(item) | 
					
						
							|  |  |  |  |                 if location_name in multi_locations:  # Create shop locations later. | 
					
						
							|  |  |  |  |                     return | 
					
						
							|  |  |  |  |                 location = self.create_location(location_name) | 
					
						
							|  |  |  |  |                 unfilled_locations += 1 | 
					
						
							|  |  |  |  |             if excluded: | 
					
						
							|  |  |  |  |                 location.progress_type = LocationProgressType.EXCLUDED | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         for option_key, option in hollow_knight_randomize_options.items(): | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             randomized = getattr(self.options, option_key) | 
					
						
							|  |  |  |  |             if all([not randomized, option_key in logicless_options, not self.options.AddUnshuffledLocations]): | 
					
						
							| 
									
										
										
										
											2024-03-13 06:45:43 -05:00
										 |  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2022-06-25 11:15:03 -07:00
										 |  |  |  |             for item_name, location_name in zip(option.items, option.locations): | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                 if item_name in junk_replace: | 
					
						
							|  |  |  |  |                     item_name = self.get_filler_item_name() | 
					
						
							| 
									
										
										
										
											2022-06-25 11:15:03 -07:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |                 if (item_name == "Crystal_Heart" and self.options.SplitCrystalHeart) or \ | 
					
						
							|  |  |  |  |                         (item_name == "Mothwing_Cloak" and self.options.SplitMothwingCloak): | 
					
						
							| 
									
										
										
										
											2024-03-13 06:45:43 -05:00
										 |  |  |  |                     _add("Left_" + item_name, location_name, randomized) | 
					
						
							|  |  |  |  |                     _add("Right_" + item_name, "Split_" + location_name, randomized) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                     continue | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |                 if item_name == "Mantis_Claw" and self.options.SplitMantisClaw: | 
					
						
							| 
									
										
										
										
											2024-03-13 06:45:43 -05:00
										 |  |  |  |                     _add("Left_" + item_name, "Left_" + location_name, randomized) | 
					
						
							|  |  |  |  |                     _add("Right_" + item_name, "Right_" + location_name, randomized) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                     continue | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |                 if item_name == "Shade_Cloak" and self.options.SplitMothwingCloak: | 
					
						
							|  |  |  |  |                     if self.random.randint(0, 1): | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                         item_name = "Left_Mothwing_Cloak" | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                         item_name = "Right_Mothwing_Cloak" | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |                 if item_name == "Grimmchild2" and self.options.RandomizeGrimmkinFlames and self.options.RandomizeCharms: | 
					
						
							| 
									
										
										
										
											2024-03-13 06:45:43 -05:00
										 |  |  |  |                     _add("Grimmchild1", location_name, randomized) | 
					
						
							|  |  |  |  |                     continue | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-13 06:45:43 -05:00
										 |  |  |  |                 _add(item_name, location_name, randomized) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         if self.options.RandomizeElevatorPass: | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |             randomized = True | 
					
						
							| 
									
										
										
										
											2024-03-13 06:45:43 -05:00
										 |  |  |  |             _add("Elevator_Pass", "Elevator_Pass", randomized) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         for shop, locations in self.created_multi_locations.items(): | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             for _ in range(len(locations), getattr(self.options, shop_to_option[shop]).value): | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                 loc = self.create_location(shop) | 
					
						
							|  |  |  |  |                 unfilled_locations += 1 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # Balance the pool | 
					
						
							|  |  |  |  |         item_count = len(pool) | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         additional_shop_items = max(item_count - unfilled_locations, self.options.ExtraShopSlots.value) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         # Add additional shop items, as needed. | 
					
						
							|  |  |  |  |         if additional_shop_items > 0: | 
					
						
							|  |  |  |  |             shops = list(shop for shop, locations in self.created_multi_locations.items() if len(locations) < 16) | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             if not self.options.EggShopSlots:  # No eggshop, so don't place items there | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                 shops.remove('Egg_Shop') | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-14 18:57:47 -06:00
										 |  |  |  |             if shops: | 
					
						
							|  |  |  |  |                 for _ in range(additional_shop_items): | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |                     shop = self.random.choice(shops) | 
					
						
							| 
									
										
										
										
											2022-11-14 18:57:47 -06:00
										 |  |  |  |                     loc = self.create_location(shop) | 
					
						
							|  |  |  |  |                     unfilled_locations += 1 | 
					
						
							|  |  |  |  |                     if len(self.created_multi_locations[shop]) >= 16: | 
					
						
							|  |  |  |  |                         shops.remove(shop) | 
					
						
							|  |  |  |  |                         if not shops: | 
					
						
							|  |  |  |  |                             break | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         # Create filler items, if needed | 
					
						
							|  |  |  |  |         if item_count < unfilled_locations: | 
					
						
							|  |  |  |  |             pool.extend(self.create_item(self.get_filler_item_name()) for _ in range(unfilled_locations - item_count)) | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |  |         self.multiworld.itempool += pool | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         self.apply_costsanity() | 
					
						
							|  |  |  |  |         self.sort_shops_by_cost() | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     def sort_shops_by_cost(self): | 
					
						
							|  |  |  |  |         for shop, locations in self.created_multi_locations.items(): | 
					
						
							|  |  |  |  |             randomized_locations = list(loc for loc in locations if not loc.vanilla) | 
					
						
							|  |  |  |  |             prices = sorted( | 
					
						
							|  |  |  |  |                 (loc.costs for loc in randomized_locations), | 
					
						
							|  |  |  |  |                 key=lambda costs: (len(costs),) + tuple(costs.values()) | 
					
						
							|  |  |  |  |             ) | 
					
						
							|  |  |  |  |             for loc, costs in zip(randomized_locations, prices): | 
					
						
							|  |  |  |  |                 loc.costs = costs | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     def apply_costsanity(self): | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         setting = self.options.CostSanity.value | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         if not setting: | 
					
						
							|  |  |  |  |             return  # noop | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         def _compute_weights(weights: dict, desc: str) -> typing.Dict[str, int]: | 
					
						
							|  |  |  |  |             if all(x == 0 for x in weights.values()): | 
					
						
							|  |  |  |  |                 logger.warning( | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |  |                     f"All {desc} weights were zero for {self.multiworld.player_name[self.player]}." | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                     f" Setting them to one instead." | 
					
						
							|  |  |  |  |                 ) | 
					
						
							|  |  |  |  |                 weights = {k: 1 for k in weights} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             return {k: v for k, v in weights.items() if v} | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         random = self.random | 
					
						
							|  |  |  |  |         hybrid_chance = getattr(self.options, f"CostSanityHybridChance").value | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         weights = { | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             data.term: getattr(self.options, f"CostSanity{data.option}Weight").value | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |             for data in cost_terms.values() | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |         weights_geoless = dict(weights) | 
					
						
							|  |  |  |  |         del weights_geoless["GEO"] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         weights = _compute_weights(weights, "CostSanity") | 
					
						
							|  |  |  |  |         weights_geoless = _compute_weights(weights_geoless, "Geoless CostSanity") | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         if hybrid_chance > 0: | 
					
						
							|  |  |  |  |             if len(weights) == 1: | 
					
						
							|  |  |  |  |                 logger.warning( | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |  |                     f"Only one cost type is available for CostSanity in {self.multiworld.player_name[self.player]}'s world." | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                     f" CostSanityHybridChance will not trigger." | 
					
						
							|  |  |  |  |                 ) | 
					
						
							|  |  |  |  |             if len(weights_geoless) == 1: | 
					
						
							|  |  |  |  |                 logger.warning( | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |  |                     f"Only one cost type is available for CostSanity in {self.multiworld.player_name[self.player]}'s world." | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                     f" CostSanityHybridChance will not trigger in geoless locations." | 
					
						
							|  |  |  |  |                 ) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |  |         for region in self.multiworld.get_regions(self.player): | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |             for location in region.locations: | 
					
						
							|  |  |  |  |                 if location.vanilla: | 
					
						
							|  |  |  |  |                     continue | 
					
						
							|  |  |  |  |                 if not location.costs: | 
					
						
							|  |  |  |  |                     continue | 
					
						
							|  |  |  |  |                 if location.name == "Vessel_Fragment-Basin": | 
					
						
							|  |  |  |  |                     continue | 
					
						
							|  |  |  |  |                 if setting == CostSanity.option_notshops and location.basename in multi_locations: | 
					
						
							|  |  |  |  |                     continue | 
					
						
							|  |  |  |  |                 if setting == CostSanity.option_shopsonly and location.basename not in multi_locations: | 
					
						
							|  |  |  |  |                     continue | 
					
						
							| 
									
										
										
										
											2024-06-02 21:39:34 -05:00
										 |  |  |  |                 if location.basename in {'Grubfather', 'Seer', 'Egg_Shop'}: | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                     our_weights = dict(weights_geoless) | 
					
						
							| 
									
										
										
										
											2022-06-25 11:15:03 -07:00
										 |  |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                     our_weights = dict(weights) | 
					
						
							| 
									
										
										
										
											2022-06-25 11:15:03 -07:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                 rolls = 1 | 
					
						
							|  |  |  |  |                 if random.randrange(100) < hybrid_chance: | 
					
						
							|  |  |  |  |                     rolls = 2 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |                 if rolls > len(our_weights): | 
					
						
							|  |  |  |  |                     terms = list(our_weights.keys())  # Can't randomly choose cost types, using all of them. | 
					
						
							|  |  |  |  |                 else: | 
					
						
							|  |  |  |  |                     terms = [] | 
					
						
							|  |  |  |  |                     for _ in range(rolls): | 
					
						
							|  |  |  |  |                         term = random.choices(list(our_weights.keys()), list(our_weights.values()))[0] | 
					
						
							|  |  |  |  |                         del our_weights[term] | 
					
						
							|  |  |  |  |                         terms.append(term) | 
					
						
							| 
									
										
										
										
											2021-06-26 11:18:12 -05:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                 location.costs = {term: random.randint(*self.ranges[term]) for term in terms} | 
					
						
							|  |  |  |  |                 location.sort_costs() | 
					
						
							| 
									
										
										
										
											2022-04-03 22:05:20 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-26 11:18:12 -05:00
										 |  |  |  |     def set_rules(self): | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         multiworld = self.multiworld | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |         player = self.player | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         goal = self.options.Goal | 
					
						
							| 
									
										
										
										
											2023-12-12 20:11:10 -06:00
										 |  |  |  |         if goal == Goal.option_hollowknight: | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player) | 
					
						
							| 
									
										
										
										
											2023-12-12 20:11:10 -06:00
										 |  |  |  |         elif goal == Goal.option_siblings: | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             multiworld.completion_condition[player] = lambda state: _hk_siblings_ending(state, player) | 
					
						
							| 
									
										
										
										
											2023-12-12 20:11:10 -06:00
										 |  |  |  |         elif goal == Goal.option_radiance: | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             multiworld.completion_condition[player] = lambda state: _hk_can_beat_radiance(state, player) | 
					
						
							| 
									
										
										
										
											2024-04-09 14:12:50 -05:00
										 |  |  |  |         elif goal == Goal.option_godhome: | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             multiworld.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player) | 
					
						
							| 
									
										
										
										
											2024-04-09 14:12:50 -05:00
										 |  |  |  |         elif goal == Goal.option_godhome_flower: | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             multiworld.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player) | 
					
						
							| 
									
										
										
										
											2023-12-12 20:11:10 -06:00
										 |  |  |  |         else: | 
					
						
							|  |  |  |  |             # Any goal | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player) or _hk_can_beat_radiance(state, player) | 
					
						
							| 
									
										
											  
											
												Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:
- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te
- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene
- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.
* Hollow Knight updates
  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
											
										 
											2022-06-12 23:23:03 -07:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |         set_rules(self) | 
					
						
							| 
									
										
										
										
											2021-06-26 11:18:12 -05:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     def fill_slot_data(self): | 
					
						
							| 
									
										
										
										
											2021-06-26 11:18:12 -05:00
										 |  |  |  |         slot_data = {} | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         options = slot_data["options"] = {} | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         for option_name in hollow_knight_options: | 
					
						
							|  |  |  |  |             option = getattr(self.options, option_name) | 
					
						
							| 
									
										
										
										
											2022-04-12 17:13:52 +02:00
										 |  |  |  |             try: | 
					
						
							|  |  |  |  |                 optionvalue = int(option.value) | 
					
						
							|  |  |  |  |             except TypeError: | 
					
						
							|  |  |  |  |                 pass  # C# side is currently typed as dict[str, int], drop what doesn't fit | 
					
						
							|  |  |  |  |             else: | 
					
						
							|  |  |  |  |                 options[option_name] = optionvalue | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 32 bit int | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         slot_data["seed"] = self.random.randint(-2147483647, 2147483646) | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         # Backwards compatibility for shop cost data (HKAP < 0.1.0) | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         if not self.options.CostSanity: | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |             for shop, terms in shop_cost_types.items(): | 
					
						
							|  |  |  |  |                 unit = cost_terms[next(iter(terms))].option | 
					
						
							|  |  |  |  |                 if unit == "Geo": | 
					
						
							|  |  |  |  |                     continue | 
					
						
							|  |  |  |  |                 slot_data[f"{unit}_costs"] = { | 
					
						
							|  |  |  |  |                     loc.name: next(iter(loc.costs.values())) | 
					
						
							|  |  |  |  |                     for loc in self.created_multi_locations[shop] | 
					
						
							|  |  |  |  |                 } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # HKAP 0.1.0 and later cost data. | 
					
						
							|  |  |  |  |         location_costs = {} | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |  |         for region in self.multiworld.get_regions(self.player): | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |             for location in region.locations: | 
					
						
							|  |  |  |  |                 if location.costs: | 
					
						
							|  |  |  |  |                     location_costs[location.name] = location.costs | 
					
						
							|  |  |  |  |         slot_data["location_costs"] = location_costs | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         slot_data["notch_costs"] = self.charm_costs | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-26 11:18:12 -05:00
										 |  |  |  |         return slot_data | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     def create_item(self, name: str) -> HKItem: | 
					
						
							| 
									
										
										
										
											2021-07-12 13:54:47 +02:00
										 |  |  |  |         item_data = item_table[name] | 
					
						
							|  |  |  |  |         return HKItem(name, item_data.advancement, item_data.id, item_data.type, self.player) | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-13 06:45:43 -05:00
										 |  |  |  |     def create_event(self, name: str) -> HKItem: | 
					
						
							|  |  |  |  |         item_data = item_table[name] | 
					
						
							|  |  |  |  |         return HKItem(name, item_data.advancement, None, item_data.type, self.player) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |     def create_location(self, name: str, vanilla=False) -> HKLocation: | 
					
						
							|  |  |  |  |         costs = None | 
					
						
							|  |  |  |  |         basename = name | 
					
						
							|  |  |  |  |         if name in shop_cost_types: | 
					
						
							|  |  |  |  |             costs = { | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |                 term: self.random.randint(*self.ranges[term]) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                 for term in shop_cost_types[name] | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |         elif name in vanilla_location_costs: | 
					
						
							|  |  |  |  |             costs = vanilla_location_costs[name] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         multi = self.created_multi_locations.get(name) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         if multi is not None: | 
					
						
							|  |  |  |  |             i = len(multi) + 1 | 
					
						
							|  |  |  |  |             name = f"{name}_{i}" | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |  |         region = self.multiworld.get_region("Menu", self.player) | 
					
						
							| 
									
										
										
										
											2024-03-13 06:45:43 -05:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         if vanilla and not self.options.AddUnshuffledLocations: | 
					
						
							| 
									
										
										
										
											2024-03-13 06:45:43 -05:00
										 |  |  |  |             loc = HKLocation(self.player, name, | 
					
						
							|  |  |  |  |                              None, region, costs=costs, vanilla=vanilla, | 
					
						
							|  |  |  |  |                              basename=basename) | 
					
						
							|  |  |  |  |         else: | 
					
						
							|  |  |  |  |             loc = HKLocation(self.player, name, | 
					
						
							|  |  |  |  |                              self.location_name_to_id[name], region, costs=costs, vanilla=vanilla, | 
					
						
							|  |  |  |  |                              basename=basename) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         if multi is not None: | 
					
						
							|  |  |  |  |             multi.append(loc) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |         region.locations.append(loc) | 
					
						
							|  |  |  |  |         return loc | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |     def create_vanilla_location(self, location: str, item: Item): | 
					
						
							|  |  |  |  |         costs = self.vanilla_shop_costs.get((location, item.name)) | 
					
						
							|  |  |  |  |         location = self.create_location(location, vanilla=True) | 
					
						
							|  |  |  |  |         location.place_locked_item(item) | 
					
						
							|  |  |  |  |         if costs: | 
					
						
							|  |  |  |  |             location.costs = costs.pop() | 
					
						
							| 
									
										
										
										
											2022-07-25 16:19:07 -04:00
										 |  |  |  |         return location | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     def collect(self, state, item: HKItem) -> bool: | 
					
						
							|  |  |  |  |         change = super(HKWorld, self).collect(state, item) | 
					
						
							| 
									
										
										
										
											2022-04-08 21:30:38 +02:00
										 |  |  |  |         if change: | 
					
						
							|  |  |  |  |             for effect_name, effect_value in item_effects.get(item.name, {}).items(): | 
					
						
							| 
									
										
										
										
											2023-11-02 00:41:20 -05:00
										 |  |  |  |                 state.prog_items[item.player][effect_name] += effect_value | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         if item.name in {"Left_Mothwing_Cloak", "Right_Mothwing_Cloak"}: | 
					
						
							| 
									
										
										
										
											2023-11-02 00:41:20 -05:00
										 |  |  |  |             if state.prog_items[item.player].get('RIGHTDASH', 0) and \ | 
					
						
							|  |  |  |  |                     state.prog_items[item.player].get('LEFTDASH', 0): | 
					
						
							|  |  |  |  |                 (state.prog_items[item.player]["RIGHTDASH"], state.prog_items[item.player]["LEFTDASH"]) = \ | 
					
						
							|  |  |  |  |                     ([max(state.prog_items[item.player]["RIGHTDASH"], state.prog_items[item.player]["LEFTDASH"])] * 2) | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |         return change | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     def remove(self, state, item: HKItem) -> bool: | 
					
						
							|  |  |  |  |         change = super(HKWorld, self).remove(state, item) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-08 21:30:38 +02:00
										 |  |  |  |         if change: | 
					
						
							|  |  |  |  |             for effect_name, effect_value in item_effects.get(item.name, {}).items(): | 
					
						
							| 
									
										
										
										
											2023-11-02 00:41:20 -05:00
										 |  |  |  |                 if state.prog_items[item.player][effect_name] == effect_value: | 
					
						
							|  |  |  |  |                     del state.prog_items[item.player][effect_name] | 
					
						
							| 
									
										
										
										
											2024-07-24 20:08:58 -05:00
										 |  |  |  |                 else: | 
					
						
							|  |  |  |  |                     state.prog_items[item.player][effect_name] -= effect_value | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         return change | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-03 00:12:37 +02:00
										 |  |  |  |     @classmethod | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |     def stage_write_spoiler(cls, multiworld: MultiWorld, spoiler_handle): | 
					
						
							|  |  |  |  |         hk_players = multiworld.get_game_players(cls.game) | 
					
						
							| 
									
										
										
										
											2022-04-03 00:12:37 +02:00
										 |  |  |  |         spoiler_handle.write('\n\nCharm Notches:') | 
					
						
							|  |  |  |  |         for player in hk_players: | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             name = multiworld.get_player_name(player) | 
					
						
							| 
									
										
										
										
											2022-04-03 00:12:37 +02:00
										 |  |  |  |             spoiler_handle.write(f'\n{name}\n') | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             hk_world: HKWorld = multiworld.worlds[player] | 
					
						
							| 
									
										
										
										
											2022-04-08 19:22:50 +02:00
										 |  |  |  |             for charm_number, cost in enumerate(hk_world.charm_costs): | 
					
						
							|  |  |  |  |                 spoiler_handle.write(f"\n{charm_names[charm_number]}: {cost}") | 
					
						
							| 
									
										
										
										
											2022-04-03 00:12:37 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-04 00:40:19 +02:00
										 |  |  |  |         spoiler_handle.write('\n\nShop Prices:') | 
					
						
							|  |  |  |  |         for player in hk_players: | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             name = multiworld.get_player_name(player) | 
					
						
							| 
									
										
										
										
											2022-04-04 00:40:19 +02:00
										 |  |  |  |             spoiler_handle.write(f'\n{name}\n') | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             hk_world: HKWorld = multiworld.worlds[player] | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |             if hk_world.options.CostSanity: | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                 for loc in sorted( | 
					
						
							|  |  |  |  |                     ( | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |                         loc for loc in itertools.chain(*(region.locations for region in multiworld.get_regions(player))) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                         if loc.costs | 
					
						
							|  |  |  |  |                     ), key=operator.attrgetter('name') | 
					
						
							|  |  |  |  |                 ): | 
					
						
							|  |  |  |  |                     spoiler_handle.write(f"\n{loc}: {loc.item} costing {loc.cost_text()}") | 
					
						
							|  |  |  |  |             else: | 
					
						
							|  |  |  |  |                 for shop_name, locations in hk_world.created_multi_locations.items(): | 
					
						
							|  |  |  |  |                     for loc in locations: | 
					
						
							|  |  |  |  |                         spoiler_handle.write(f"\n{loc}: {loc.item} costing {loc.cost_text()}") | 
					
						
							| 
									
										
										
										
											2022-04-04 00:40:19 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-03 22:05:20 +02:00
										 |  |  |  |     def get_multi_location_name(self, base: str, i: typing.Optional[int]) -> str: | 
					
						
							|  |  |  |  |         if i is None: | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |             i = len(self.created_multi_locations[base]) + 1 | 
					
						
							|  |  |  |  |         assert 1 <= 16, "limited number of multi location IDs reserved." | 
					
						
							| 
									
										
										
										
											2022-04-03 22:05:20 +02:00
										 |  |  |  |         return f"{base}_{i}" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |     def get_filler_item_name(self) -> str: | 
					
						
							|  |  |  |  |         if self.player not in self.cached_filler_items: | 
					
						
							|  |  |  |  |             fillers = ["One_Geo", "Soul_Refill"] | 
					
						
							|  |  |  |  |             exclusions = self.white_palace_exclusions() | 
					
						
							|  |  |  |  |             for group in ( | 
					
						
							|  |  |  |  |                     'RandomizeGeoRocks', 'RandomizeSoulTotems', 'RandomizeLoreTablets', 'RandomizeJunkPitChests', | 
					
						
							|  |  |  |  |                     'RandomizeRancidEggs' | 
					
						
							|  |  |  |  |             ): | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |                 if getattr(self.options, group): | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |                     fillers.extend(item for item in hollow_knight_randomize_options[group].items if item not in | 
					
						
							|  |  |  |  |                                    exclusions) | 
					
						
							|  |  |  |  |             self.cached_filler_items[self.player] = fillers | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         return self.random.choice(self.cached_filler_items[self.player]) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  | def create_region(multiworld: MultiWorld, player: int, name: str, location_names=None) -> Region: | 
					
						
							|  |  |  |  |     ret = Region(name, player, multiworld) | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     if location_names: | 
					
						
							|  |  |  |  |         for location in location_names: | 
					
						
							|  |  |  |  |             loc_id = HKWorld.location_name_to_id.get(location, None) | 
					
						
							| 
									
										
										
										
											2021-02-24 06:02:51 +01:00
										 |  |  |  |             location = HKLocation(player, location, loc_id, ret) | 
					
						
							|  |  |  |  |             ret.locations.append(location) | 
					
						
							|  |  |  |  |     return ret | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-01 11:40:58 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |  | class HKLocation(Location): | 
					
						
							|  |  |  |  |     game: str = "Hollow Knight" | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |     costs: typing.Dict[str, int] = None | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     unit: typing.Optional[str] = None | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |     vanilla = False | 
					
						
							|  |  |  |  |     basename: str | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     def sort_costs(self): | 
					
						
							|  |  |  |  |         if self.costs is None: | 
					
						
							|  |  |  |  |             return | 
					
						
							|  |  |  |  |         self.costs = {k: self.costs[k] for k in sorted(self.costs.keys(), key=lambda x: cost_terms[x].sort)} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     def __init__( | 
					
						
							|  |  |  |  |             self, player: int, name: str, code=None, parent=None, | 
					
						
							|  |  |  |  |             costs: typing.Dict[str, int] = None, vanilla: bool = False, basename: str = None | 
					
						
							|  |  |  |  |     ): | 
					
						
							|  |  |  |  |         self.basename = basename or name | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |         super(HKLocation, self).__init__(player, name, code if code else None, parent) | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         self.vanilla = vanilla | 
					
						
							|  |  |  |  |         if costs: | 
					
						
							|  |  |  |  |             self.costs = dict(costs) | 
					
						
							|  |  |  |  |             self.sort_costs() | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     def cost_text(self, separator=" and "): | 
					
						
							|  |  |  |  |         if self.costs is None: | 
					
						
							|  |  |  |  |             return None | 
					
						
							|  |  |  |  |         return separator.join( | 
					
						
							|  |  |  |  |             f"{value} {cost_terms[term].singular if value == 1 else cost_terms[term].plural}" | 
					
						
							|  |  |  |  |             for term, value in self.costs.items() | 
					
						
							|  |  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-01 11:40:58 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |  | class HKItem(Item): | 
					
						
							| 
									
										
										
										
											2021-02-22 11:18:53 +01:00
										 |  |  |  |     game = "Hollow Knight" | 
					
						
							| 
									
										
										
										
											2022-08-06 00:49:54 +02:00
										 |  |  |  |     type: str | 
					
						
							| 
									
										
										
										
											2021-02-22 11:18:53 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-06 00:49:54 +02:00
										 |  |  |  |     def __init__(self, name, advancement, code, type: str, player: int = None): | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |         if name == "Mimic_Grub": | 
					
						
							| 
									
										
										
										
											2022-06-17 03:23:27 +02:00
										 |  |  |  |             classification = ItemClassification.trap | 
					
						
							| 
									
										
										
										
											2024-05-28 20:37:44 -05:00
										 |  |  |  |         elif name == "Godtuner": | 
					
						
							|  |  |  |  |             classification = ItemClassification.progression | 
					
						
							| 
									
										
										
										
											2022-10-09 19:26:33 -05:00
										 |  |  |  |         elif type in ("Grub", "DreamWarrior", "Root", "Egg", "Dreamer"): | 
					
						
							| 
									
										
										
										
											2022-06-17 03:23:27 +02:00
										 |  |  |  |             classification = ItemClassification.progression_skip_balancing | 
					
						
							|  |  |  |  |         elif type == "Charm" and name not in progression_charms: | 
					
						
							|  |  |  |  |             classification = ItemClassification.progression_skip_balancing | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |         elif type in ("Map", "Journal"): | 
					
						
							|  |  |  |  |             classification = ItemClassification.filler | 
					
						
							| 
									
										
										
										
											2023-07-11 04:49:40 -05:00
										 |  |  |  |         elif type in ("Ore", "Vessel"): | 
					
						
							| 
									
										
										
										
											2022-07-03 08:10:10 -07:00
										 |  |  |  |             classification = ItemClassification.useful | 
					
						
							| 
									
										
										
										
											2022-06-17 03:23:27 +02:00
										 |  |  |  |         elif advancement: | 
					
						
							|  |  |  |  |             classification = ItemClassification.progression | 
					
						
							|  |  |  |  |         else: | 
					
						
							|  |  |  |  |             classification = ItemClassification.filler | 
					
						
							|  |  |  |  |         super(HKItem, self).__init__(name, classification, code if code else None, player) | 
					
						
							|  |  |  |  |         self.type = type | 
					
						
							| 
									
										
										
										
											2022-04-06 00:41:15 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-24 06:02:51 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  | class HKLogicMixin(LogicMixin): | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |  |     multiworld: MultiWorld | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-01 03:23:52 +02:00
										 |  |  |  |     def _hk_notches(self, player: int, *notches: int) -> int: | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |  |         return sum(self.multiworld.worlds[player].charm_costs[notch] for notch in notches) | 
					
						
							| 
									
										
										
										
											2021-07-15 13:31:33 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-05 15:01:33 +02:00
										 |  |  |  |     def _hk_option(self, player: int, option_name: str) -> int: | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         return getattr(self.multiworld.worlds[player].options, option_name).value | 
					
						
							| 
									
										
										
										
											2021-07-15 13:31:33 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-05 15:01:33 +02:00
										 |  |  |  |     def _hk_start(self, player, start_location: str) -> bool: | 
					
						
							| 
									
										
										
										
											2024-07-28 16:27:39 -05:00
										 |  |  |  |         return self.multiworld.worlds[player].options.StartLocation == start_location |