231 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			231 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | import typing | ||
|  | from BaseClasses import CollectionState | ||
|  | from Options import OptionError | ||
|  | from .options import (EnableOrbsanity, | ||
|  |                       GlobalOrbsanityBundleSize, | ||
|  |                       PerLevelOrbsanityBundleSize, | ||
|  |                       FireCanyonCellCount, | ||
|  |                       MountainPassCellCount, | ||
|  |                       LavaTubeCellCount, | ||
|  |                       CitizenOrbTradeAmount, | ||
|  |                       OracleOrbTradeAmount) | ||
|  | from .locs import cell_locations as cells | ||
|  | from .locations import location_table | ||
|  | from .levels import level_table | ||
|  | 
 | ||
|  | if typing.TYPE_CHECKING: | ||
|  |     from . import JakAndDaxterWorld | ||
|  | 
 | ||
|  | 
 | ||
|  | def set_orb_trade_rule(world: "JakAndDaxterWorld"): | ||
|  |     options = world.options | ||
|  |     player = world.player | ||
|  | 
 | ||
|  |     if options.enable_orbsanity == EnableOrbsanity.option_off: | ||
|  |         world.can_trade = lambda state, required_orbs, required_previous_trade: ( | ||
|  |             can_trade_vanilla(state, player, world, required_orbs, required_previous_trade)) | ||
|  |     else: | ||
|  |         world.can_trade = lambda state, required_orbs, required_previous_trade: ( | ||
|  |             can_trade_orbsanity(state, player, world, required_orbs, required_previous_trade)) | ||
|  | 
 | ||
|  | 
 | ||
|  | def recalculate_reachable_orbs(state: CollectionState, player: int, world: "JakAndDaxterWorld") -> None: | ||
|  | 
 | ||
|  |     # Recalculate every level, every time the cache is stale, because you don't know | ||
|  |     # when a specific bundle of orbs in one level may unlock access to another. | ||
|  |     accessible_total_orbs = 0 | ||
|  |     for level in level_table: | ||
|  |         accessible_level_orbs = count_reachable_orbs_level(state, world, level) | ||
|  |         accessible_total_orbs += accessible_level_orbs | ||
|  |         state.prog_items[player][f"{level} Reachable Orbs".lstrip()] = accessible_level_orbs | ||
|  | 
 | ||
|  |     # Also recalculate the global count, still used even when Orbsanity is Off. | ||
|  |     state.prog_items[player]["Reachable Orbs"] = accessible_total_orbs | ||
|  |     state.prog_items[player]["Reachable Orbs Fresh"] = True | ||
|  | 
 | ||
|  | 
 | ||
|  | def count_reachable_orbs_global(state: CollectionState, | ||
|  |                                 world: "JakAndDaxterWorld") -> int: | ||
|  | 
 | ||
|  |     accessible_orbs = 0 | ||
|  |     for level_regions in world.level_to_orb_regions.values(): | ||
|  |         for region in level_regions: | ||
|  |             if region.can_reach(state): | ||
|  |                 accessible_orbs += region.orb_count | ||
|  |     return accessible_orbs | ||
|  | 
 | ||
|  | 
 | ||
|  | def count_reachable_orbs_level(state: CollectionState, | ||
|  |                                world: "JakAndDaxterWorld", | ||
|  |                                level_name: str = "") -> int: | ||
|  | 
 | ||
|  |     accessible_orbs = 0 | ||
|  |     for region in world.level_to_orb_regions[level_name]: | ||
|  |         if region.can_reach(state): | ||
|  |             accessible_orbs += region.orb_count | ||
|  |     return accessible_orbs | ||
|  | 
 | ||
|  | 
 | ||
|  | def can_reach_orbs_global(state: CollectionState, | ||
|  |                           player: int, | ||
|  |                           world: "JakAndDaxterWorld", | ||
|  |                           orb_amount: int) -> bool: | ||
|  | 
 | ||
|  |     if not state.prog_items[player]["Reachable Orbs Fresh"]: | ||
|  |         recalculate_reachable_orbs(state, player, world) | ||
|  | 
 | ||
|  |     return state.has("Reachable Orbs", player, orb_amount) | ||
|  | 
 | ||
|  | 
 | ||
|  | def can_reach_orbs_level(state: CollectionState, | ||
|  |                          player: int, | ||
|  |                          world: "JakAndDaxterWorld", | ||
|  |                          level_name: str, | ||
|  |                          orb_amount: int) -> bool: | ||
|  | 
 | ||
|  |     if not state.prog_items[player]["Reachable Orbs Fresh"]: | ||
|  |         recalculate_reachable_orbs(state, player, world) | ||
|  | 
 | ||
|  |     return state.has(f"{level_name} Reachable Orbs", player, orb_amount) | ||
|  | 
 | ||
|  | 
 | ||
|  | def can_trade_vanilla(state: CollectionState, | ||
|  |                       player: int, | ||
|  |                       world: "JakAndDaxterWorld", | ||
|  |                       required_orbs: int, | ||
|  |                       required_previous_trade: typing.Optional[int] = None) -> bool: | ||
|  | 
 | ||
|  |     # With Orbsanity Off, Reachable Orbs are in fact Tradeable Orbs. | ||
|  |     if not state.prog_items[player]["Reachable Orbs Fresh"]: | ||
|  |         recalculate_reachable_orbs(state, player, world) | ||
|  | 
 | ||
|  |     if required_previous_trade: | ||
|  |         name_of_previous_trade = location_table[cells.to_ap_id(required_previous_trade)] | ||
|  |         return (state.has("Reachable Orbs", player, required_orbs) | ||
|  |                 and state.can_reach_location(name_of_previous_trade, player=player)) | ||
|  |     return state.has("Reachable Orbs", player, required_orbs) | ||
|  | 
 | ||
|  | 
 | ||
|  | def can_trade_orbsanity(state: CollectionState, | ||
|  |                         player: int, | ||
|  |                         world: "JakAndDaxterWorld", | ||
|  |                         required_orbs: int, | ||
|  |                         required_previous_trade: typing.Optional[int] = None) -> bool: | ||
|  | 
 | ||
|  |     # Yes, even Orbsanity trades may unlock access to new Reachable Orbs. | ||
|  |     if not state.prog_items[player]["Reachable Orbs Fresh"]: | ||
|  |         recalculate_reachable_orbs(state, player, world) | ||
|  | 
 | ||
|  |     if required_previous_trade: | ||
|  |         name_of_previous_trade = location_table[cells.to_ap_id(required_previous_trade)] | ||
|  |         return (state.has("Tradeable Orbs", player, required_orbs) | ||
|  |                 and state.can_reach_location(name_of_previous_trade, player=player)) | ||
|  |     return state.has("Tradeable Orbs", player, required_orbs) | ||
|  | 
 | ||
|  | 
 | ||
|  | def can_free_scout_flies(state: CollectionState, player: int) -> bool: | ||
|  |     return state.has("Jump Dive", player) or state.has_all({"Crouch", "Crouch Uppercut"}, player) | ||
|  | 
 | ||
|  | 
 | ||
|  | def can_fight(state: CollectionState, player: int) -> bool: | ||
|  |     return state.has_any(("Jump Dive", "Jump Kick", "Punch", "Kick"), player) | ||
|  | 
 | ||
|  | 
 | ||
|  | def enforce_multiplayer_limits(world: "JakAndDaxterWorld"): | ||
|  |     options = world.options | ||
|  |     friendly_message = "" | ||
|  | 
 | ||
|  |     if (options.enable_orbsanity == EnableOrbsanity.option_global | ||
|  |             and (options.global_orbsanity_bundle_size.value < GlobalOrbsanityBundleSize.friendly_minimum | ||
|  |                  or options.global_orbsanity_bundle_size.value > GlobalOrbsanityBundleSize.friendly_maximum)): | ||
|  |         friendly_message += (f"  " | ||
|  |                              f"{options.global_orbsanity_bundle_size.display_name} must be no less than " | ||
|  |                              f"{GlobalOrbsanityBundleSize.friendly_minimum} and no greater than " | ||
|  |                              f"{GlobalOrbsanityBundleSize.friendly_maximum} (currently " | ||
|  |                              f"{options.global_orbsanity_bundle_size.value}).\n") | ||
|  | 
 | ||
|  |     if (options.enable_orbsanity == EnableOrbsanity.option_per_level | ||
|  |             and options.level_orbsanity_bundle_size.value < PerLevelOrbsanityBundleSize.friendly_minimum): | ||
|  |         friendly_message += (f"  " | ||
|  |                              f"{options.level_orbsanity_bundle_size.display_name} must be no less than " | ||
|  |                              f"{PerLevelOrbsanityBundleSize.friendly_minimum} (currently " | ||
|  |                              f"{options.level_orbsanity_bundle_size.value}).\n") | ||
|  | 
 | ||
|  |     if options.fire_canyon_cell_count.value > FireCanyonCellCount.friendly_maximum: | ||
|  |         friendly_message += (f"  " | ||
|  |                              f"{options.fire_canyon_cell_count.display_name} must be no greater than " | ||
|  |                              f"{FireCanyonCellCount.friendly_maximum} (currently " | ||
|  |                              f"{options.fire_canyon_cell_count.value}).\n") | ||
|  | 
 | ||
|  |     if options.mountain_pass_cell_count.value > MountainPassCellCount.friendly_maximum: | ||
|  |         friendly_message += (f"  " | ||
|  |                              f"{options.mountain_pass_cell_count.display_name} must be no greater than " | ||
|  |                              f"{MountainPassCellCount.friendly_maximum} (currently " | ||
|  |                              f"{options.mountain_pass_cell_count.value}).\n") | ||
|  | 
 | ||
|  |     if options.lava_tube_cell_count.value > LavaTubeCellCount.friendly_maximum: | ||
|  |         friendly_message += (f"  " | ||
|  |                              f"{options.lava_tube_cell_count.display_name} must be no greater than " | ||
|  |                              f"{LavaTubeCellCount.friendly_maximum} (currently " | ||
|  |                              f"{options.lava_tube_cell_count.value}).\n") | ||
|  | 
 | ||
|  |     if options.citizen_orb_trade_amount.value > CitizenOrbTradeAmount.friendly_maximum: | ||
|  |         friendly_message += (f"  " | ||
|  |                              f"{options.citizen_orb_trade_amount.display_name} must be no greater than " | ||
|  |                              f"{CitizenOrbTradeAmount.friendly_maximum} (currently " | ||
|  |                              f"{options.citizen_orb_trade_amount.value}).\n") | ||
|  | 
 | ||
|  |     if options.oracle_orb_trade_amount.value > OracleOrbTradeAmount.friendly_maximum: | ||
|  |         friendly_message += (f"  " | ||
|  |                              f"{options.oracle_orb_trade_amount.display_name} must be no greater than " | ||
|  |                              f"{OracleOrbTradeAmount.friendly_maximum} (currently " | ||
|  |                              f"{options.oracle_orb_trade_amount.value}).\n") | ||
|  | 
 | ||
|  |     if friendly_message != "": | ||
|  |         raise OptionError(f"{world.player_name}: The options you have chosen may disrupt the multiworld. \n" | ||
|  |                           f"Please adjust the following Options for a multiplayer game. \n" | ||
|  |                           f"{friendly_message}" | ||
|  |                           f"Or use 'random-range-x-y' instead of 'random' in your player yaml.\n" | ||
|  |                           f"Or set 'enforce_friendly_options' in the seed generator's host.yaml to false. " | ||
|  |                           f"(Use at your own risk!)") | ||
|  | 
 | ||
|  | 
 | ||
|  | def enforce_singleplayer_limits(world: "JakAndDaxterWorld"): | ||
|  |     options = world.options | ||
|  |     friendly_message = "" | ||
|  | 
 | ||
|  |     if options.fire_canyon_cell_count.value > FireCanyonCellCount.friendly_maximum: | ||
|  |         friendly_message += (f"  " | ||
|  |                              f"{options.fire_canyon_cell_count.display_name} must be no greater than " | ||
|  |                              f"{FireCanyonCellCount.friendly_maximum} (currently " | ||
|  |                              f"{options.fire_canyon_cell_count.value}).\n") | ||
|  | 
 | ||
|  |     if options.mountain_pass_cell_count.value > MountainPassCellCount.friendly_maximum: | ||
|  |         friendly_message += (f"  " | ||
|  |                              f"{options.mountain_pass_cell_count.display_name} must be no greater than " | ||
|  |                              f"{MountainPassCellCount.friendly_maximum} (currently " | ||
|  |                              f"{options.mountain_pass_cell_count.value}).\n") | ||
|  | 
 | ||
|  |     if options.lava_tube_cell_count.value > LavaTubeCellCount.friendly_maximum: | ||
|  |         friendly_message += (f"  " | ||
|  |                              f"{options.lava_tube_cell_count.display_name} must be no greater than " | ||
|  |                              f"{LavaTubeCellCount.friendly_maximum} (currently " | ||
|  |                              f"{options.lava_tube_cell_count.value}).\n") | ||
|  | 
 | ||
|  |     if friendly_message != "": | ||
|  |         raise OptionError(f"The options you have chosen may result in seed generation failures. \n" | ||
|  |                           f"Please adjust the following Options for a singleplayer game. \n" | ||
|  |                           f"{friendly_message}" | ||
|  |                           f"Or use 'random-range-x-y' instead of 'random' in your player yaml.\n" | ||
|  |                           f"Or set 'enforce_friendly_options' in your host.yaml to false. " | ||
|  |                           f"(Use at your own risk!)") | ||
|  | 
 | ||
|  | 
 | ||
|  | def verify_orb_trade_amounts(world: "JakAndDaxterWorld"): | ||
|  | 
 | ||
|  |     if world.total_trade_orbs > 2000: | ||
|  |         raise OptionError(f"{world.player_name}: Required number of orbs for all trades ({world.total_trade_orbs}) " | ||
|  |                           f"is more than all the orbs in the game (2000). Reduce the value of either " | ||
|  |                           f"{world.options.citizen_orb_trade_amount.display_name} " | ||
|  |                           f"or {world.options.oracle_orb_trade_amount.display_name}.") |