338 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			338 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | """
 | ||
|  |     Ensures a target level can be reached with available resources | ||
|  |     """
 | ||
|  | from worlds.generic.Rules import CollectionRule, add_rule | ||
|  | from .Names import RegionNames, ItemNames | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_fishing_skill_rule(level, player, options) -> CollectionRule: | ||
|  |     if options.max_fishing_level < level: | ||
|  |         return lambda state: False | ||
|  | 
 | ||
|  |     if options.brutal_grinds or level < 5: | ||
|  |         return lambda state: state.can_reach_region(RegionNames.Shrimp, player) | ||
|  |     if level < 20: | ||
|  |         return lambda state: state.can_reach_region(RegionNames.Shrimp, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Port_Sarim, player) | ||
|  |     else: | ||
|  |         return lambda state: state.can_reach_region(RegionNames.Shrimp, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Port_Sarim, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Fly_Fish, player) | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_mining_skill_rule(level, player, options) -> CollectionRule: | ||
|  |     if options.max_mining_level < level: | ||
|  |         return lambda state: False | ||
|  | 
 | ||
|  |     if options.brutal_grinds or level < 15: | ||
|  |         return lambda state: state.can_reach_region(RegionNames.Bronze_Ores, player) or \ | ||
|  |                              state.can_reach_region(RegionNames.Clay_Rock, player) | ||
|  |     else: | ||
|  |         # Iron is the best way to train all the way to 99, so having access to iron is all you need to check for | ||
|  |         return lambda state: (state.can_reach_region(RegionNames.Bronze_Ores, player) or | ||
|  |                               state.can_reach_region(RegionNames.Clay_Rock, player)) and \ | ||
|  |                              state.can_reach_region(RegionNames.Iron_Rock, player) | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_woodcutting_skill_rule(level, player, options) -> CollectionRule: | ||
|  |     if options.max_woodcutting_level < level: | ||
|  |         return lambda state: False | ||
|  | 
 | ||
|  |     if options.brutal_grinds or level < 15: | ||
|  |         # I've checked. There is not a single chunk in the f2p that does not have at least one normal tree. | ||
|  |         # Even the desert. | ||
|  |         return lambda state: True | ||
|  |     if level < 30: | ||
|  |         return lambda state: state.can_reach_region(RegionNames.Oak_Tree, player) | ||
|  |     else: | ||
|  |         return lambda state: state.can_reach_region(RegionNames.Oak_Tree, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Willow_Tree, player) | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_smithing_skill_rule(level, player, options) -> CollectionRule: | ||
|  |     if options.max_smithing_level < level: | ||
|  |         return lambda state: False | ||
|  | 
 | ||
|  |     if options.brutal_grinds: | ||
|  |         return lambda state: state.can_reach_region(RegionNames.Bronze_Ores, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Furnace, player) | ||
|  |     if level < 15: | ||
|  |         # Lumbridge has a special bronze-only anvil. This is the only anvil of its type so it's not included | ||
|  |         # in the "Anvil" resource region. We still need to check for it though. | ||
|  |         return lambda state: state.can_reach_region(RegionNames.Bronze_Ores, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Furnace, player) and \ | ||
|  |                              (state.can_reach_region(RegionNames.Anvil, player) or | ||
|  |                               state.can_reach_region(RegionNames.Lumbridge, player)) | ||
|  |     if level < 30: | ||
|  |         # For levels between 15 and 30, the lumbridge anvil won't cut it. Only a real one will do | ||
|  |         return lambda state: state.can_reach_region(RegionNames.Bronze_Ores, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Iron_Rock, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Furnace, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Anvil, player) | ||
|  |     else: | ||
|  |         return lambda state: state.can_reach_region(RegionNames.Bronze_Ores, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Iron_Rock, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Coal_Rock, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Furnace, player) and \ | ||
|  |                              state.can_reach_region(RegionNames.Anvil, player) | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_crafting_skill_rule(level, player, options): | ||
|  |     if options.max_crafting_level < level: | ||
|  |         return lambda state: False | ||
|  | 
 | ||
|  |     # Crafting is really complex. Need a lot of sub-rules to make this even remotely readable | ||
|  |     def can_spin(state): | ||
|  |         return state.can_reach_region(RegionNames.Sheep, player) and \ | ||
|  |             state.can_reach_region(RegionNames.Spinning_Wheel, player) | ||
|  | 
 | ||
|  |     def can_pot(state): | ||
|  |         return state.can_reach_region(RegionNames.Clay_Rock, player) and \ | ||
|  |             state.can_reach_region(RegionNames.Barbarian_Village, player) | ||
|  | 
 | ||
|  |     def can_tan(state): | ||
|  |         return state.can_reach_region(RegionNames.Milk, player) and \ | ||
|  |             state.can_reach_region(RegionNames.Al_Kharid, player) | ||
|  | 
 | ||
|  |     def mould_access(state): | ||
|  |         return state.can_reach_region(RegionNames.Al_Kharid, player) or \ | ||
|  |             state.can_reach_region(RegionNames.Rimmington, player) | ||
|  | 
 | ||
|  |     def can_silver(state): | ||
|  |         return state.can_reach_region(RegionNames.Silver_Rock, player) and \ | ||
|  |             state.can_reach_region(RegionNames.Furnace, player) and mould_access(state) | ||
|  | 
 | ||
|  |     def can_gold(state): | ||
|  |         return state.can_reach_region(RegionNames.Gold_Rock, player) and \ | ||
|  |             state.can_reach_region(RegionNames.Furnace, player) and mould_access(state) | ||
|  | 
 | ||
|  |     if options.brutal_grinds or level < 5: | ||
|  |         return lambda state: can_spin(state) or can_pot(state) or can_tan(state) | ||
|  | 
 | ||
|  |     can_smelt_gold = get_smithing_skill_rule(40, player, options) | ||
|  |     can_smelt_silver = get_smithing_skill_rule(20, player, options) | ||
|  |     if level < 16: | ||
|  |         return lambda state: can_pot(state) or can_tan(state) or (can_gold(state) and can_smelt_gold(state)) | ||
|  |     else: | ||
|  |         return lambda state: can_tan(state) or (can_silver(state) and can_smelt_silver(state)) or \ | ||
|  |                              (can_gold(state) and can_smelt_gold(state)) | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_cooking_skill_rule(level, player, options) -> CollectionRule: | ||
|  |     if options.max_cooking_level < level: | ||
|  |         return lambda state: False | ||
|  | 
 | ||
|  |     if options.brutal_grinds or level < 15: | ||
|  |         return lambda state: state.can_reach_region(RegionNames.Milk, player) or \ | ||
|  |                              state.can_reach_region(RegionNames.Egg, player) or \ | ||
|  |                              state.can_reach_region(RegionNames.Shrimp, player) or \ | ||
|  |                              (state.can_reach_region(RegionNames.Wheat, player) and | ||
|  |                               state.can_reach_region(RegionNames.Windmill, player)) | ||
|  |     else: | ||
|  |         can_catch_fly_fish = get_fishing_skill_rule(20, player, options) | ||
|  | 
 | ||
|  |         return lambda state: ( | ||
|  |                                  (state.can_reach_region(RegionNames.Fly_Fish, player) and can_catch_fly_fish(state)) or | ||
|  |                                  (state.can_reach_region(RegionNames.Port_Sarim, player)) | ||
|  |                              ) and ( | ||
|  |                               state.can_reach_region(RegionNames.Milk, player) or | ||
|  |                               state.can_reach_region(RegionNames.Egg, player) or | ||
|  |                               state.can_reach_region(RegionNames.Shrimp, player) or | ||
|  |                               (state.can_reach_region(RegionNames.Wheat, player) and | ||
|  |                                state.can_reach_region(RegionNames.Windmill, player)) | ||
|  |                              ) | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_runecraft_skill_rule(level, player, options) -> CollectionRule: | ||
|  |     if options.max_runecraft_level < level: | ||
|  |         return lambda state: False | ||
|  |     if not options.brutal_grinds: | ||
|  |         # Ensure access to the relevant altars | ||
|  |         if level >= 5: | ||
|  |             return lambda state: state.has(ItemNames.QP_Rune_Mysteries, player) and \ | ||
|  |                                  state.can_reach_region(RegionNames.Falador_Farm, player) and \ | ||
|  |                                  state.can_reach_region(RegionNames.Lumbridge_Swamp, player) | ||
|  |         if level >= 9: | ||
|  |             return lambda state: state.has(ItemNames.QP_Rune_Mysteries, player) and \ | ||
|  |                                  state.can_reach_region(RegionNames.Falador_Farm, player) and \ | ||
|  |                                  state.can_reach_region(RegionNames.Lumbridge_Swamp, player) and \ | ||
|  |                                  state.can_reach_region(RegionNames.East_Of_Varrock, player) | ||
|  |         if level >= 14: | ||
|  |             return lambda state: state.has(ItemNames.QP_Rune_Mysteries, player) and \ | ||
|  |                                  state.can_reach_region(RegionNames.Falador_Farm, player) and \ | ||
|  |                                  state.can_reach_region(RegionNames.Lumbridge_Swamp, player) and \ | ||
|  |                                  state.can_reach_region(RegionNames.East_Of_Varrock, player) and \ | ||
|  |                                  state.can_reach_region(RegionNames.Al_Kharid, player) | ||
|  | 
 | ||
|  |     return lambda state: state.has(ItemNames.QP_Rune_Mysteries, player) and \ | ||
|  |                          state.can_reach_region(RegionNames.Falador_Farm, player) | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_magic_skill_rule(level, player, options) -> CollectionRule: | ||
|  |     if options.max_magic_level < level: | ||
|  |         return lambda state: False | ||
|  | 
 | ||
|  |     return lambda state: state.can_reach_region(RegionNames.Mind_Runes, player) | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_firemaking_skill_rule(level, player, options) -> CollectionRule: | ||
|  |     if options.max_firemaking_level < level: | ||
|  |         return lambda state: False | ||
|  |     if not options.brutal_grinds: | ||
|  |         if level >= 30: | ||
|  |             can_chop_willows = get_woodcutting_skill_rule(30, player, options) | ||
|  |             return lambda state: state.can_reach_region(RegionNames.Willow_Tree, player) and can_chop_willows(state) | ||
|  |         if level >= 15: | ||
|  |             can_chop_oaks = get_woodcutting_skill_rule(15, player, options) | ||
|  |             return lambda state: state.can_reach_region(RegionNames.Oak_Tree, player) and can_chop_oaks(state) | ||
|  |     # If brutal grinds are on, or if the level is less than 15, you can train it. | ||
|  |     return lambda state: True | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_skill_rule(skill, level, player, options) -> CollectionRule: | ||
|  |     if skill.lower() == "fishing": | ||
|  |         return get_fishing_skill_rule(level, player, options) | ||
|  |     if skill.lower() == "mining": | ||
|  |         return get_mining_skill_rule(level, player, options) | ||
|  |     if skill.lower() == "woodcutting": | ||
|  |         return get_woodcutting_skill_rule(level, player, options) | ||
|  |     if skill.lower() == "smithing": | ||
|  |         return get_smithing_skill_rule(level, player, options) | ||
|  |     if skill.lower() == "crafting": | ||
|  |         return get_crafting_skill_rule(level, player, options) | ||
|  |     if skill.lower() == "cooking": | ||
|  |         return get_cooking_skill_rule(level, player, options) | ||
|  |     if skill.lower() == "runecraft": | ||
|  |         return get_runecraft_skill_rule(level, player, options) | ||
|  |     if skill.lower() == "magic": | ||
|  |         return get_magic_skill_rule(level, player, options) | ||
|  |     if skill.lower() == "firemaking": | ||
|  |         return get_firemaking_skill_rule(level, player, options) | ||
|  | 
 | ||
|  |     return lambda state: True | ||
|  | 
 | ||
|  | 
 | ||
|  | def generate_special_rules_for(entrance, region_row, outbound_region_name, player, options): | ||
|  |     if outbound_region_name == RegionNames.Cooks_Guild: | ||
|  |         add_rule(entrance, get_cooking_skill_rule(32, player, options)) | ||
|  |     elif outbound_region_name == RegionNames.Crafting_Guild: | ||
|  |         add_rule(entrance, get_crafting_skill_rule(40, player, options)) | ||
|  |     elif outbound_region_name == RegionNames.Corsair_Cove: | ||
|  |         # Need to be able to start Corsair Curse in addition to having the item | ||
|  |         add_rule(entrance, lambda state: state.can_reach(RegionNames.Falador_Farm, "Region", player)) | ||
|  |     elif outbound_region_name == "Camdozaal*": | ||
|  |         add_rule(entrance, lambda state: state.has(ItemNames.QP_Below_Ice_Mountain, player)) | ||
|  |     elif region_row.name == "Dwarven Mountain Pass" and outbound_region_name == "Anvil*": | ||
|  |         add_rule(entrance, lambda state: state.has(ItemNames.QP_Dorics_Quest, player)) | ||
|  | 
 | ||
|  |     # Special logic for canoes | ||
|  |     canoe_regions = [RegionNames.Lumbridge, RegionNames.South_Of_Varrock, RegionNames.Barbarian_Village, | ||
|  |                      RegionNames.Edgeville, RegionNames.Wilderness] | ||
|  |     if region_row.name in canoe_regions: | ||
|  |         # Skill rules for greater distances | ||
|  |         woodcutting_rule_d1 = get_woodcutting_skill_rule(12, player, options) | ||
|  |         woodcutting_rule_d2 = get_woodcutting_skill_rule(27, player, options) | ||
|  |         woodcutting_rule_d3 = get_woodcutting_skill_rule(42, player, options) | ||
|  |         woodcutting_rule_all = get_woodcutting_skill_rule(57, player, options) | ||
|  | 
 | ||
|  |         if region_row.name == RegionNames.Lumbridge: | ||
|  |             # Canoe Tree access for the Location | ||
|  |             if outbound_region_name == RegionNames.Canoe_Tree: | ||
|  |                 add_rule(entrance, | ||
|  |                     lambda state: (state.can_reach_region(RegionNames.South_Of_Varrock, player) | ||
|  |                                    and woodcutting_rule_d1(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.Barbarian_Village, player) | ||
|  |                                    and woodcutting_rule_d2(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.Edgeville, player) | ||
|  |                                    and woodcutting_rule_d3(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.Wilderness, player) | ||
|  |                                    and woodcutting_rule_all(state))) | ||
|  | 
 | ||
|  |             # Access to other chunks based on woodcutting settings | ||
|  |             elif outbound_region_name == RegionNames.South_Of_Varrock: | ||
|  |                 add_rule(entrance, woodcutting_rule_d1) | ||
|  |             elif outbound_region_name == RegionNames.Barbarian_Village: | ||
|  |                 add_rule(entrance, woodcutting_rule_d2) | ||
|  |             elif outbound_region_name == RegionNames.Edgeville: | ||
|  |                 add_rule(entrance, woodcutting_rule_d3) | ||
|  |             elif outbound_region_name == RegionNames.Wilderness: | ||
|  |                 add_rule(entrance, woodcutting_rule_all) | ||
|  | 
 | ||
|  |         elif region_row.name == RegionNames.South_Of_Varrock: | ||
|  |             if outbound_region_name == RegionNames.Canoe_Tree: | ||
|  |                 add_rule(entrance, | ||
|  |                     lambda state: (state.can_reach_region(RegionNames.Lumbridge, player) | ||
|  |                                    and woodcutting_rule_d1(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.Barbarian_Village, player) | ||
|  |                                    and woodcutting_rule_d1(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.Edgeville, player) | ||
|  |                                    and woodcutting_rule_d2(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.Wilderness, player) | ||
|  |                                    and woodcutting_rule_d3(state))) | ||
|  | 
 | ||
|  |             # Access to other chunks based on woodcutting settings | ||
|  |             elif outbound_region_name == RegionNames.Lumbridge: | ||
|  |                 add_rule(entrance, woodcutting_rule_d1) | ||
|  |             elif outbound_region_name == RegionNames.Barbarian_Village: | ||
|  |                 add_rule(entrance, woodcutting_rule_d1) | ||
|  |             elif outbound_region_name == RegionNames.Edgeville: | ||
|  |                 add_rule(entrance, woodcutting_rule_d3) | ||
|  |             elif outbound_region_name == RegionNames.Wilderness: | ||
|  |                 add_rule(entrance, woodcutting_rule_all) | ||
|  |         elif region_row.name == RegionNames.Barbarian_Village: | ||
|  |             if outbound_region_name == RegionNames.Canoe_Tree: | ||
|  |                 add_rule(entrance, | ||
|  |                     lambda state: (state.can_reach_region(RegionNames.Lumbridge, player) | ||
|  |                                    and woodcutting_rule_d2(state)) or (state.can_reach_region(RegionNames.South_Of_Varrock, player) | ||
|  |                                    and woodcutting_rule_d1(state)) or (state.can_reach_region(RegionNames.Edgeville, player) | ||
|  |                                    and woodcutting_rule_d1(state)) or (state.can_reach_region(RegionNames.Wilderness, player) | ||
|  |                                    and woodcutting_rule_d2(state))) | ||
|  | 
 | ||
|  |             # Access to other chunks based on woodcutting settings | ||
|  |             elif outbound_region_name == RegionNames.Lumbridge: | ||
|  |                 add_rule(entrance, woodcutting_rule_d2) | ||
|  |             elif outbound_region_name == RegionNames.South_Of_Varrock: | ||
|  |                 add_rule(entrance, woodcutting_rule_d1) | ||
|  |             # Edgeville does not need to be checked, because it's already adjacent | ||
|  |             elif outbound_region_name == RegionNames.Wilderness: | ||
|  |                 add_rule(entrance, woodcutting_rule_d3) | ||
|  |         elif region_row.name == RegionNames.Edgeville: | ||
|  |             if outbound_region_name == RegionNames.Canoe_Tree: | ||
|  |                 add_rule(entrance, | ||
|  |                     lambda state: (state.can_reach_region(RegionNames.Lumbridge, player) | ||
|  |                                    and woodcutting_rule_d3(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.South_Of_Varrock, player) | ||
|  |                                    and woodcutting_rule_d2(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.Barbarian_Village, player) | ||
|  |                                    and woodcutting_rule_d1(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.Wilderness, player) | ||
|  |                                    and woodcutting_rule_d1(state))) | ||
|  | 
 | ||
|  |             # Access to other chunks based on woodcutting settings | ||
|  |             elif outbound_region_name == RegionNames.Lumbridge: | ||
|  |                 add_rule(entrance, woodcutting_rule_d3) | ||
|  |             elif outbound_region_name == RegionNames.South_Of_Varrock: | ||
|  |                 add_rule(entrance, woodcutting_rule_d2) | ||
|  |             # Barbarian Village does not need to be checked, because it's already adjacent | ||
|  |             # Wilderness does not need to be checked, because it's already adjacent | ||
|  |         elif region_row.name == RegionNames.Wilderness: | ||
|  |             if outbound_region_name == RegionNames.Canoe_Tree: | ||
|  |                 add_rule(entrance, | ||
|  |                     lambda state: (state.can_reach_region(RegionNames.Lumbridge, player) | ||
|  |                                    and woodcutting_rule_all(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.South_Of_Varrock, player) | ||
|  |                                    and woodcutting_rule_d3(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.Barbarian_Village, player) | ||
|  |                                    and woodcutting_rule_d2(state)) or | ||
|  |                                   (state.can_reach_region(RegionNames.Edgeville, player) | ||
|  |                                    and woodcutting_rule_d1(state))) | ||
|  | 
 | ||
|  |             # Access to other chunks based on woodcutting settings | ||
|  |             elif outbound_region_name == RegionNames.Lumbridge: | ||
|  |                 add_rule(entrance, woodcutting_rule_all) | ||
|  |             elif outbound_region_name == RegionNames.South_Of_Varrock: | ||
|  |                 add_rule(entrance, woodcutting_rule_d3) | ||
|  |             elif outbound_region_name == RegionNames.Barbarian_Village: | ||
|  |                 add_rule(entrance, woodcutting_rule_d2) | ||
|  |             # Edgeville does not need to be checked, because it's already adjacent |