 2424b79626
			
		
	
	2424b79626
	
	
	
		
			
			* Removes explicit indirect conditions * Changes special rules function add rule instead of setting, and call it unconditionally * Fixes issues in rule generation that have been around but unused the whole time * Finally moves rules out into a separate file. Fixes level-related logic * Removes redundant max skill level checks on canoes, since they're in the skill training rules now * For some reason, canoe logic assumed you could always walk from lumbridge to south varrock without farms. This has been fixed * Apply suggestions from code review Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Quests now respect skill limits and can be excluded. Tasks that take multiple skills how actually check all skills * Adds alternative route for cooking that doesn't require fishing * Remove debug code --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
		
			
				
	
	
		
			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
 |