diff --git a/.gitignore b/.gitignore index e7dd66f..8ff5e5b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ __pycache__ *.aprac2 *.iso test/test_output -lib \ No newline at end of file +lib +/.idea/ diff --git a/ItemPool.py b/ItemPool.py index a95a497..26fe0b0 100644 --- a/ItemPool.py +++ b/ItemPool.py @@ -1,15 +1,20 @@ from typing import TYPE_CHECKING from BaseClasses import ItemClassification, Item + from .data import Items, Locations from .data.Items import CoordData, EquipmentData, ProgressiveUpgradeData, ItemData -from .Rac2Options import StartingWeapons +from .Logic import GLITCH_LOGIC_HARD +from .Rac2Options import StartingWeapons, Rac2Options if TYPE_CHECKING: from . import Rac2World -def get_classification(item: ItemData) -> ItemClassification: +def get_classification(item: ItemData, options: Rac2Options = None) -> ItemClassification: + if options is not None: + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD and item == Items.CHARGE_BOOTS: + return ItemClassification.progression if item in Items.COORDS: return ItemClassification.progression if item in [ diff --git a/Logic.py b/Logic.py index 9cb44c0..237b439 100644 --- a/Logic.py +++ b/Logic.py @@ -27,6 +27,10 @@ def can_heli(state: CollectionState, player: int) -> bool: return state.has(Items.HELI_PACK.name, player) +def can_thruster(state: CollectionState, player: int) -> bool: + return state.has(Items.THRUSTER_PACK.name, player) + + def can_grind(state: CollectionState, player: int) -> bool: return state.has(Items.GRIND_BOOTS.name, player) @@ -66,6 +70,15 @@ def can_spiderbot(state: CollectionState, player: int) -> bool: return state.has(Items.SPIDERBOT_GLOVE.name, player) +def can_clip(state: CollectionState, player: int) -> bool: + return state.has_any([Items.DECOY_GLOVE.name, + Items.MEGA_DECOY_GLOVE.name, + Items.MINITURRET_GLOVE.name, + Items.MEGATURRET_GLOVE.name, + Items.MEGA_MEGATURRET_GLOVE.name, + Items.ULTRA_MEGATURRET_GLOVE.name], player) + + def has_qwark_statuette(state: CollectionState, player: int) -> bool: return state.has(Items.QWARK_STATUETTE.name, player) @@ -74,9 +87,9 @@ def has_hypnomatic_parts(state: CollectionState, player: int) -> bool: return state.has(Items.HYPNOMATIC_PART.name, player, 3) -FIRST_PERSON_EASY = 1 -FIRST_PERSON_MEDIUM = 2 -FIRST_PERSON_HARD = 3 +GLITCH_LOGIC_MEDIUM = 1 +GLITCH_LOGIC_HARD = 2 +GLITCH_LOGIC_EXPERT = 3 def get_options(state: CollectionState, player: int) -> Rac2Options: @@ -84,30 +97,42 @@ def get_options(state: CollectionState, player: int) -> Rac2Options: def oozla_end_store_cutscene_rule(state: CollectionState, player: int) -> bool: + if can_dynamo(state, player): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # clip-out -> activate checkpoint -> death abuse return True - return can_dynamo(state, player) + return False def oozla_tractor_puzzle_pb_rule(state: CollectionState, player: int) -> bool: + if can_tractor(state, player): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # tight hyperstrike return True - return can_tractor(state, player) + return False def oozla_swamp_ruins_pb_rule(state: CollectionState, player: int) -> bool: + if can_dynamo(state, player): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # same as end store cutscene return True - return can_dynamo(state, player) + return False def oozla_swamp_monster_ii_rule(state: CollectionState, player: int) -> bool: @@ -116,21 +141,44 @@ def oozla_swamp_monster_ii_rule(state: CollectionState, player: int) -> bool: def maktar_photo_booth_rule(state: CollectionState, player: int) -> bool: + if can_electrolyze(state, player): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return (can_electrolyze(state, player) - or can_heli(state, player)) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # wrench jump or pack switch + return (can_charge(state, player) + or (can_heli(state, player) + and can_thruster(state, player))) - return can_electrolyze(state, player) + return False def maktar_deactivate_jamming_array_rule(state: CollectionState, player: int) -> bool: - return can_tractor(state, player) + if can_tractor(state, player): + return True + + options = get_options(state, player) + + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # wrench jump + return can_charge(state, player) + + return False def maktar_jamming_array_pb_rule(state: CollectionState, player: int) -> bool: - return can_tractor(state, player) + if can_tractor(state, player): + return True + + options = get_options(state, player) + + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # same as deactivate jamming array + return can_charge(state, player) + + return False def endako_rescue_clank_rule(state: CollectionState, player: int) -> bool: @@ -142,27 +190,36 @@ def endako_crane_pb_rule(state: CollectionState, player: int) -> bool: def endako_crane_nt_rule(state: CollectionState, player: int) -> bool: - return (can_electrolyze(state, player) - and can_infiltrate(state, player)) - + if (can_electrolyze(state, player) + and can_infiltrate(state, player)): + return True -def barlow_inventor_rule(state: CollectionState, player: int) -> bool: options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return True + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # decoy or turret clip + return can_clip(state, player) + return False + + +def barlow_inventor_rule(state: CollectionState, player: int) -> bool: return can_swingshot(state, player) def barlow_overbike_race_rule(state: CollectionState, player: int) -> bool: + if (can_improved_jump(state, player) + and can_electrolyze(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # tight hyperstrike + # not sure if tiptoes are doable all the way return can_electrolyze(state, player) - return (can_improved_jump(state, player) - and can_electrolyze(state, player)) + return False def barlow_hound_cave_pb_rule(state: CollectionState, player: int) -> bool: @@ -170,125 +227,203 @@ def barlow_hound_cave_pb_rule(state: CollectionState, player: int) -> bool: def notak_top_pier_telescreen_rule(state: CollectionState, player: int) -> bool: + if (can_improved_jump(state, player) + and can_thermanate(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return True + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # wall jump in the thermanator room + return can_thermanate(state, player) - return (can_improved_jump(state, player) - and can_thermanate(state, player)) + return False def notak_worker_bots_rule(state: CollectionState, player: int) -> bool: + if (can_heli(state, player) + and can_thermanate(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return True + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # clip through the purple barrier and thermanate at the right time + return (can_clip(state, player) + and can_thermanate(state, player)) - return (can_heli(state, player) - and can_thermanate(state, player)) + return False def notak_timed_dynamo_rule(state: CollectionState, player: int) -> bool: + if (can_thermanate(state, player) + and can_improved_jump(state, player) + and can_dynamo(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return True + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # wall jump in the thermanator room and decoy or turret clip + return (can_thermanate(state, player) + and can_clip(state, player)) - return (can_dynamo(state, player) - and can_thermanate(state, player) - and can_improved_jump(state, player)) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # wall jump in the thermanator room + return (can_thermanate(state, player) + and can_dynamo(state, player)) + + return False def siberius_defeat_thief_rule(state: CollectionState, player: int) -> bool: + # TODO is wrench jump possible ? return can_swingshot(state, player) def siberius_flamebot_ledge_pb_rule(state: CollectionState, player: int) -> bool: + if (can_tractor(state, player) + and can_improved_jump(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return True + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # ledge grab with high-jump + # or sideflip hyperstrike with tractor beam + return (can_improved_jump(state, player) + or can_tractor(state, player)) - return can_tractor(state, player) + return False def siberius_fenced_area_pb_rule(state: CollectionState, player: int) -> bool: + if can_heli(state, player): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # wrench jump + # or clip + return (can_charge(state, player) + or can_clip(state, player)) + + return False + + +def tabora_meet_angela_rule(state: CollectionState, player: int) -> bool: + if (can_heli(state, player) + and can_swingshot(state, player)): return True - return can_heli(state, player) + options = get_options(state, player) + + if options.glitch_logic_difficulty >= GLITCH_LOGIC_EXPERT: + # conserve swing momentum -> slope interception -> tiptoes + return can_swingshot(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # hold charge boots + return can_charge(state, player) -def tabora_meet_angelar_rule(state: CollectionState, player: int) -> bool: - return (can_heli(state, player) - and can_swingshot(state, player)) + return False def tabora_underground_mines_end_rule(state: CollectionState, player: int) -> bool: + if (can_heli(state, player) + and can_swingshot(state, player) + and can_thermanate(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return (can_heli(state, player) - and can_swingshot(state, player)) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_EXPERT: + # same as meet angela + thermanator + return (can_swingshot(state, player) + and can_thermanate(state, player)) + + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # same as meet angela + thermanator + return (can_charge(state, player) + and can_thermanate(state, player)) + + return False - return (can_heli(state, player) + +def tabora_underground_mines_pb_rule(state: CollectionState, player: int) -> bool: + if (can_heli(state, player) and can_swingshot(state, player) - and can_thermanate(state, player)) + and can_thermanate(state, player)): + return True + + options = get_options(state, player) + + if options.glitch_logic_difficulty >= GLITCH_LOGIC_EXPERT: + # same as meet angela + thermanator + return (can_swingshot(state, player) + and can_thermanate(state, player)) + + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # long jump above water and high-jump with heli to ledge grab + # or thermanator and sideflip hyperstrike + return ((can_heli(state, player) + and can_swingshot(state, player)) + or (can_charge(state, player) + and can_thermanate(state, player))) + + return False def tabora_canyon_glide_pb_rule(state: CollectionState, player: int) -> bool: + if (can_heli(state, player) + and can_swingshot(state, player) + and can_thermanate(state, player) + and can_glide(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return (can_heli(state, player) - and can_swingshot(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_EXPERT: + # same as meet angela + thermanator + glider + return (can_swingshot(state, player) + and can_thermanate(state, player) and can_glide(state, player)) - return (can_heli(state, player) - and can_swingshot(state, player) - and can_thermanate(state, player) - and can_glide(state, player)) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # same as meet angela + thermanator + glider + return (can_charge(state, player) + and can_thermanate(state, player) + and can_glide(state, player)) + + return False def tabora_northeast_desert_pb_rule(state: CollectionState, player: int) -> bool: - return (can_heli(state, player) - and can_swingshot(state, player)) + return tabora_meet_angela_rule(state, player) def tabora_canyon_glide_pillar_nt_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return (can_heli(state, player) - and can_swingshot(state, player) - and can_glide(state, player)) - - return (can_heli(state, player) - and can_swingshot(state, player) - and can_thermanate(state, player) - and can_glide(state, player)) + return tabora_canyon_glide_pb_rule(state, player) def dobbo_defeat_thug_leader_rule(state: CollectionState, player: int) -> bool: + if (can_swingshot(state, player) + and can_improved_jump(state, player) + and can_dynamo(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return can_swingshot(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # wrench jump above dynamo platforms + return (can_swingshot(state, player) + and can_charge(state, player)) - return (can_swingshot(state, player) - and can_improved_jump(state, player) - and can_dynamo(state, player)) + return False def dobbo_facility_terminal_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return True - return (can_swingshot(state, player) and can_glide(state, player) and can_dynamo(state, player) @@ -296,281 +431,231 @@ def dobbo_facility_terminal_rule(state: CollectionState, player: int) -> bool: def dobbo_spiderbot_room_pb_rule(state: CollectionState, player: int) -> bool: + if (can_dynamo(state, player) + and can_swingshot(state, player) + and can_spiderbot(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return can_swingshot(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # wrench jump above dynamo platforms and turret or decoy clip + return (can_swingshot(state, player) + and (can_dynamo(state, player) + or can_charge(state, player)) + and (can_spiderbot(state, player) + or can_clip(state, player))) - return (can_swingshot(state, player) - and can_dynamo(state, player) - and can_spiderbot(state, player)) + return False def dobbo_facility_glide_pb_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return True - return (can_swingshot(state, player) and can_glide(state, player) and can_dynamo(state, player)) def dobbo_facility_glide_nt_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return True - return (can_swingshot(state, player) and can_glide(state, player) and can_dynamo(state, player)) def joba_hoverbike_race_rule(state: CollectionState, player: int) -> bool: - return can_swingshot(state, player) - + if can_swingshot(state, player): + return True -def joba_shady_salesman_rule(state: CollectionState, player: int) -> bool: options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return True + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # wrench jump + return can_charge(state, player) - return (can_dynamo(state, player) - and can_improved_jump(state, player)) + return False -def joba_arena_battle_rule(state: CollectionState, player: int) -> bool: +def joba_shady_salesman_rule(state: CollectionState, player: int) -> bool: + if (can_dynamo(state, player) + and can_improved_jump(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return can_levitate(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # tight double jumps in dynamo platform section + return can_dynamo(state, player) - return (can_dynamo(state, player) - and can_improved_jump(state, player) - and can_levitate(state, player)) + return False -def joba_arena_cage_match_rule(state: CollectionState, player: int) -> bool: +def joba_arena_battle_rule(state: CollectionState, player: int) -> bool: + if (can_dynamo(state, player) + and can_improved_jump(state, player) + and can_levitate(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return can_levitate(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # same as shady salesman + levitator + return (can_dynamo(state, player) + and can_levitate(state, player)) - return (can_dynamo(state, player) - and can_improved_jump(state, player) - and can_levitate(state, player)) + return False def joba_hidden_cliff_pb_rule(state: CollectionState, player: int) -> bool: + if (can_dynamo(state, player) + and can_swingshot(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return True + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # wrench jump + return (can_dynamo(state, player) + and can_charge(state, player)) - return (can_dynamo(state, player) - and can_swingshot(state, player)) + return False def joba_levitator_tower_pb_rule(state: CollectionState, player: int) -> bool: + if (can_dynamo(state, player) + and can_improved_jump(state, player) + and can_levitate(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return can_levitate(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # same as shady salesman + levitator + return (can_dynamo(state, player) + and can_levitate(state, player)) - return (can_dynamo(state, player) - and can_improved_jump(state, player) - and can_levitate(state, player)) + return False def joba_timed_dynamo_nt_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return True - return can_dynamo(state, player) def todano_search_rocket_silo_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: + if (can_electrolyze(state, player) + and can_infiltrate(state, player) + and can_improved_jump(state, player)): return True - return (can_electrolyze(state, player) - and can_improved_jump(state, player) - and can_infiltrate(state, player)) + options = get_options(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # clip through electrolyzer barrier and tight hyperstrike + return (can_electrolyze(state, player) + and can_clip(state, player)) -def todano_stuart_zurgo_trade_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # tight hyperstrike + return (can_electrolyze(state, player) + and can_infiltrate(state, player)) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_HARD: - return has_qwark_statuette(state, player) + return False - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return (can_tractor(state, player) - and has_qwark_statuette(state, player)) +def todano_stuart_zurgo_trade_rule(state: CollectionState, player: int) -> bool: return (can_electrolyze(state, player) and can_tractor(state, player) and has_qwark_statuette(state, player)) def todano_facility_interior_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_HARD: - return True - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return can_tractor(state, player) - return (can_electrolyze(state, player) and can_tractor(state, player)) def todano_near_stuart_zurgo_pb_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_HARD: - return True - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return can_tractor(state, player) - return (can_electrolyze(state, player) and can_tractor(state, player)) def todano_spiderbot_conveyor_pb_rule(state: CollectionState, player: int) -> bool: + if (can_electrolyze(state, player) + and can_tractor(state, player) + and can_improved_jump(state, player) + and can_spiderbot(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return (can_tractor(state, player) - and can_improved_jump(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # sideflip hyperstrike + return (can_electrolyze(state, player) + and can_tractor(state, player) and can_spiderbot(state, player)) - return (can_electrolyze(state, player) - and can_tractor(state, player) - and can_improved_jump(state, player) - and can_spiderbot(state, player)) + return False def todano_rocket_silo_nt_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return True - return (can_electrolyze(state, player) and can_infiltrate(state, player)) def boldan_find_fizzwidget_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_HARD: - return True - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return (can_gravity(state, player) - and can_improved_jump(state, player)) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return (can_swingshot(state, player) - and can_gravity(state, player)) - + # TODO can we sideflip hyperstrike to skip the levitator ? return (can_levitate(state, player) and can_swingshot(state, player) and can_gravity(state, player)) def boldan_spiderbot_alley_pb_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return True - + # TODO can we sideflip hyperstrike to skip the levitator ? can we clip ? return (can_levitate(state, player) and can_spiderbot(state, player)) def boldan_floating_platform_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return True - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return can_gravity(state, player) - + # TODO can we sideflip hyperstrike to skip the levitator ? return (can_levitate(state, player) and can_gravity(state, player)) def boldan_fountain_nt_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return True - + # TODO can we sideflip hyperstrike to skip the levitator ? return can_levitate(state, player) def aranos_control_room_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_HARD: - return True - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return (can_infiltrate(state, player) - and can_levitate(state, player)) - + # TODO can we clip through infiltrator ? return (can_gravity(state, player) and can_infiltrate(state, player) and can_levitate(state, player)) def aranos_plumber_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return True - return (can_gravity(state, player) and can_levitate(state, player)) def aranos_under_ship_pb_rule(state: CollectionState, player: int) -> bool: + if (can_gravity(state, player) + and can_heli(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return can_heli(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # charge and hold at the right angle + return (can_gravity(state, player) + and can_charge(state, player)) - return (can_gravity(state, player) - and can_heli(state, player)) + return False def aranos_omniwrench_12000_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: - return True - return can_gravity(state, player) def snivelak_rescue_angelak_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >+ FIRST_PERSON_EASY: - return can_swingshot(state, player) - + # TODO are grind boots skippable ? return (can_swingshot(state, player) and can_grind(state, player) and can_gravity(state, player) @@ -578,93 +663,136 @@ def snivelak_rescue_angelak_rule(state: CollectionState, player: int) -> bool: def snivelak_dynamo_pb_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return can_swingshot(state, player) - - return (can_swingshot(state, player) + # TODO are grind boots skippable ? + if (can_swingshot(state, player) and can_grind(state, player) and can_gravity(state, player) and can_dynamo(state, player) - and can_heli(state, player)) + and can_heli(state, player)): + return True + + options = get_options(state, player) + + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # charge over lava pit + return (can_swingshot(state, player) + and can_grind(state, player) + and can_gravity(state, player) + and can_dynamo(state, player) + and can_charge(state, player)) + + return False def snivelak_swingshot_tower_nt_rule(state: CollectionState, player: int) -> bool: + # TODO momentum conservation ? + if (can_swingshot(state, player) + and can_heli(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: return can_swingshot(state, player) - return (can_swingshot(state, player) - and can_heli(state, player)) + return False def smolg_balloon_transmission_rule(state: CollectionState, player: int) -> bool: + if (can_improved_jump(state, player) + and can_dynamo(state, player) + and can_electrolyze(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return can_electrolyze(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # double jumps + return (can_dynamo(state, player) + and can_electrolyze(state, player)) - return (can_improved_jump(state, player) - and can_dynamo(state, player) - and can_electrolyze(state, player)) + return False def smolg_distribution_facility_end_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return can_electrolyze(state, player) - - return (can_improved_jump(state, player) + if (can_improved_jump(state, player) and can_dynamo(state, player) and can_electrolyze(state, player) and can_grind(state, player) - and can_infiltrate(state, player)) - + and can_infiltrate(state, player)): + return True -def smolg_mutant_crab_rule(state: CollectionState, player: int) -> bool: options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - if not can_levitate(state, player): - return False - return (can_swingshot(state, player) - or can_electrolyze(state, player)) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # double jumps and walk on grind section + return (can_dynamo(state, player) + and can_electrolyze(state, player) + and can_infiltrate(state, player)) - return (can_swingshot(state, player) - and can_levitate(state, player)) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # double jumps + return (can_dynamo(state, player) + and can_electrolyze(state, player) + and can_grind(state, player) + and can_infiltrate(state, player)) + return False + + +def smolg_mutant_crab_rule(state: CollectionState, player: int) -> bool: + if (can_swingshot(state, player) + and can_levitate(state, player)): + return True -def smolg_floating_platform_pb_rule(state: CollectionState, player: int) -> bool: options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - if not can_levitate(state, player): - return False - return (can_swingshot(state, player) - or can_electrolyze(state, player)) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # wrench jump + return (can_levitate(state, player) + and can_charge(state, player)) + + return False - return (can_swingshot(state, player) - and can_levitate(state, player)) + +def smolg_floating_platform_pb_rule(state: CollectionState, player: int) -> bool: + return smolg_mutant_crab_rule(state, player) def smolg_warehouse_pb_rule(state: CollectionState, player: int) -> bool: + if (can_dynamo(state, player) + and can_improved_jump(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return True + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # wall jump and tight double jump + return can_dynamo(state, player) - return (can_dynamo(state, player) - and can_improved_jump(state, player)) + return False def damosel_hypnotist_rule(state: CollectionState, player: int) -> bool: - return (can_swingshot(state, player) + if (can_swingshot(state, player) and can_improved_jump(state, player) and can_thermanate(state, player) - and has_hypnomatic_parts(state, player)) + and has_hypnomatic_parts(state, player)): + return True + + options = get_options(state, player) + + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # charge over pit and wrench jump + # or sideflip hyperstrike on thermanated water + if not has_hypnomatic_parts(state, player): + return False + + return (can_charge(state, player) + or (can_swingshot(state, player) + and can_thermanate(state, player))) + + return False def damosel_train_rails_rule(state: CollectionState, player: int) -> bool: @@ -672,35 +800,67 @@ def damosel_train_rails_rule(state: CollectionState, player: int) -> bool: def damosel_frozen_mountain_pb_rule(state: CollectionState, player: int) -> bool: - return (can_swingshot(state, player) + if (can_swingshot(state, player) and can_improved_jump(state, player) and can_thermanate(state, player) + and can_grind(state, player)): + return True + + options = get_options(state, player) + + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # charge over the pit and double jumps + return ((can_charge(state, player) + or can_swingshot(state, player)) + and can_thermanate(state, player) and can_grind(state, player)) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # double jumps + return (can_swingshot(state, player) + and can_thermanate(state, player) + and can_grind(state, player)) + + return False + def damosel_pyramid_pb_rule(state: CollectionState, player: int) -> bool: - return (can_swingshot(state, player) + if (can_swingshot(state, player) and can_improved_jump(state, player) - and can_hypnotize(state, player)) - + and can_hypnotize(state, player)): + return True -def grelbin_find_angela_rule(state: CollectionState, player: int) -> bool: options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: - return True + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + if not can_hypnotize(state, player): + return False + + # charge over the pit and double jumps + return (can_charge(state, player) + or can_swingshot(state, player)) + + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # double jumps + return (can_swingshot(state, player) + and can_hypnotize(state, player)) + return False + + +def grelbin_find_angela_rule(state: CollectionState, player: int) -> bool: + # TODO sideflip hyperstrike ? return can_hypnotize(state, player) def grelbin_mystic_more_moonstones_rule(state: CollectionState, player: int) -> bool: + # TODO clip through infiltrator gate ? return (can_glide(state, player) and can_infiltrate(state, player)) def grelbin_ice_plains_pb_rule(state: CollectionState, player: int) -> bool: - return (can_glide(state, player) - and can_infiltrate(state, player)) + return grelbin_mystic_more_moonstones_rule(state, player) def grelbin_underwater_tunnel_pb_rule(state: CollectionState, player: int) -> bool: @@ -708,71 +868,95 @@ def grelbin_underwater_tunnel_pb_rule(state: CollectionState, player: int) -> bo def grelbin_yeti_cave_pb_rule(state: CollectionState, player: int) -> bool: + # TODO clip through infiltrator gate ? return (can_glide(state, player) and can_infiltrate(state, player) and can_hypnotize(state, player)) +def yeedil_bridge_grindrail_pb_rule(state: CollectionState, player: int) -> bool: + return can_grind(state, player) + + def yeedil_defeat_mutated_protopet_rule(state: CollectionState, player: int) -> bool: + # TODO can we clip through infiltrator ? + if (can_hypnotize(state, player) + and can_swingshot(state, player) + and can_infiltrate(state, player) + and can_dynamo(state, player) + and can_improved_jump(state, player) + and can_electrolyze(state, player)): + return True + options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_HARD: - return can_infiltrate(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_EXPERT: + # charge cancels + wrench jumps + clip + hyperstrike + return (can_charge(state, player) + and state.has(Items.HOVERBOMB_GUN.name, player) + and can_clip(state, player) + and can_electrolyze(state, player)) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # clip + hyperstrike return (can_hypnotize(state, player) and can_swingshot(state, player) - and can_infiltrate(state, player)) + and can_clip(state, player) + and can_dynamo(state, player) + and can_electrolyze(state, player)) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # hyperstrike return (can_hypnotize(state, player) and can_swingshot(state, player) and can_infiltrate(state, player) and can_dynamo(state, player) - and can_improved_jump(state, player)) + and can_electrolyze(state, player)) + + return False - return (can_hypnotize(state, player) + +def yeedil_tractor_pillar_pb_rule(state: CollectionState, player: int) -> bool: + # TODO can we clip through infiltrator and tractor ? + if (can_hypnotize(state, player) and can_swingshot(state, player) and can_infiltrate(state, player) and can_dynamo(state, player) and can_improved_jump(state, player) - and can_electrolyze(state, player)) - - -def yeedil_bridge_grindrail_pb_rule(state: CollectionState, player: int) -> bool: - options = get_options(state, player) - - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: + and can_electrolyze(state, player) + and can_tractor(state, player) + and can_grind(state, player)): return True - return can_grind(state, player) - - -def yeedil_tractor_pillar_pb_rule(state: CollectionState, player: int) -> bool: options = get_options(state, player) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_HARD: - return can_infiltrate(state, player) + if options.glitch_logic_difficulty >= GLITCH_LOGIC_EXPERT: + # charge cancels + wrench jumps + clip + hyperstrike + return (can_charge(state, player) + and state.has(Items.HOVERBOMB_GUN.name, player) + and can_clip(state, player) + and can_electrolyze(state, player) + and can_tractor(state, player) + and can_grind(state, player)) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_MEDIUM: + if options.glitch_logic_difficulty >= GLITCH_LOGIC_HARD: + # clip + hyperstrike return (can_hypnotize(state, player) and can_swingshot(state, player) - and can_infiltrate(state, player)) + and can_clip(state, player) + and can_dynamo(state, player) + and can_electrolyze(state, player) + and can_tractor(state, player) + and can_grind(state, player)) - if options.first_person_mode_glitch_in_logic >= FIRST_PERSON_EASY: + if options.glitch_logic_difficulty >= GLITCH_LOGIC_MEDIUM: + # hyperstrike return (can_hypnotize(state, player) and can_swingshot(state, player) and can_infiltrate(state, player) and can_dynamo(state, player) - and can_improved_jump(state, player) + and can_electrolyze(state, player) and can_tractor(state, player) and can_grind(state, player)) - return (can_hypnotize(state, player) - and can_swingshot(state, player) - and can_infiltrate(state, player) - and can_dynamo(state, player) - and can_improved_jump(state, player) - and can_electrolyze(state, player) - and can_tractor(state, player) - and can_grind(state, player)) + return False \ No newline at end of file diff --git a/Rac2Options.py b/Rac2Options.py index ea21e4b..22d613b 100644 --- a/Rac2Options.py +++ b/Rac2Options.py @@ -103,17 +103,18 @@ class ExtendWeaponProgression(Toggle): display_name = "Extended Weapon Progression" -class FirstPersonModeGlitchInLogic(Choice): - """Determines if logic should take first person mode glitches into account when evaluating which locations are - reachable. Various difficulty levels can be picked: - - Easy: simple climbs (e.g. getting the Platinum Bolt near Oozla scientist) - - Medium: harder climbs and basic lateral movement (e.g. Getting to the Notak Worker Bots without the Heli-pack nor the Thermanator) - - Hard: full navigation following walls (e.g. Getting to the Mutated Protopet only with the Infiltrator)""" - display_name = "First Person Mode Glitch In Logic" - option_disabled = 0 - option_easy = 1 - option_medium = 2 - option_hard = 3 +class GlitchLogicDifficulty(Choice): + """Determines the kind of logic that should taken into account when evaluating which locations are reachable. + Various difficulty levels can be picked: + - Beginner: Intended way of doing things, or things you can deduce from a casual playthrough + - Medium: Obscure knowledge, out-of-the-box thinking, unintuitive usage of game mechanics but no glitch involved (e.g. tight heli-pack glide, tight hyperstrike wrench jumps, simple macaroni & sideflip movement tech etc...) + - Hard: Simple to intermediate glitches allowed (e.g. most decoy glove clip-outs, most charge boots tech, most decoy proxies, pack switch, etc...) + - Expert: All known glitches allowed (e.g. hardest clip-outs, complex movement chains where keeping momentum is required, etc...)""" + display_name = "Glitch Logic Difficulty" + option_beginner = 0 + option_medium = 1 + option_hard = 2 + option_expert = 3 default = 0 @@ -134,4 +135,4 @@ class Rac2Options(PerGameCommonOptions): weapon_xp_multiplier: WeaponExperienceMultiplier extra_spaceship_challenge_locations: ExtraSpaceshipChallengeLocations extend_weapon_progression: ExtendWeaponProgression - first_person_mode_glitch_in_logic: FirstPersonModeGlitchInLogic + glitch_logic_difficulty: GlitchLogicDifficulty diff --git a/Regions.py b/Regions.py index 6e2dd1d..bdd8dc7 100644 --- a/Regions.py +++ b/Regions.py @@ -1,7 +1,6 @@ import typing from BaseClasses import CollectionState, Region, Location -from .Logic import can_heli, can_swingshot from .data import Planets from .data import Items from .data.Planets import PlanetData @@ -24,23 +23,7 @@ def create_regions(world: 'Rac2World'): if planet_data.locations: def generate_planet_access_rule(planet: PlanetData) -> typing.Callable[[CollectionState], bool]: def planet_access_rule(state: CollectionState): - # Connect with special case access rules - if planet == Planets.TABORA: - return ( - state.has(Items.coord_for_planet(planet.number).name, world.player) - and can_heli(state, world.player) - and can_swingshot(state, world.player) - ) - if planet == Planets.ARANOS_PRISON: - return ( - state.has(Items.coord_for_planet(planet.number).name, world.player) - and state.has_all([ - Items.GRAVITY_BOOTS.name, Items.LEVITATOR.name, Items.INFILTRATOR.name], world.player - ) - ) - # Connect with general case access rule - else: - return state.has(Items.coord_for_planet(planet.number).name, world.player) + return state.has(Items.coord_for_planet(planet.number).name, world.player) return planet_access_rule region = Region(planet_data.name, world.player, world.multiworld) diff --git a/Simplified Ratchet & Clank 2.yaml b/Simplified Ratchet & Clank 2.yaml index 23787e5..70dd7f3 100644 --- a/Simplified Ratchet & Clank 2.yaml +++ b/Simplified Ratchet & Clank 2.yaml @@ -101,13 +101,13 @@ Ratchet & Clank 2: # This effectively makes all weapons that are usually restricted to NG+ available with enough grinding. extend_weapon_progression: false - # Determines if logic should take first person mode glitches into account when evaluating which locations are reachable. + # Determines the kind of logic that should taken into account when evaluating which locations are reachable. # Various difficulty levels can be picked: - # - Easy: simple climbs (e.g. getting the Platinum Bolt near Oozla scientist) - # - Medium: harder climbs and basic lateral movement (e.g. Getting to the Notak Worker Bots without the Heli-pack nor the Thermanator) - # - Hard: full navigation following walls (e.g. Getting to the Mutated Protopet only with the Infiltrator) - # Can be disabled with value disabled. - first_person_mode_glitch_in_logic: disabled + # - Beginner: Intended way of doing things, or things you can deduce from a casual playthrough + # - Medium: Obscure knowledge, out-of-the-box thinking, unintuitive usage of game mechanics but no glitch involved (e.g. tight heli-pack glide, tight hyperstrike wrench jumps, simple macaroni & sideflip movement tech etc...) + # - Hard: Simple to intermediate glitches allowed (e.g. most decoy glove clip-outs, most charge boots tech, most decoy proxies, pack switch, etc...) + # - Expert: All known glitches allowed (e.g. hardest clip-outs, complex movement chains where keeping momentum is required, etc...) + glitch_logic_difficulty: beginner # Item & Location Options local_items: diff --git a/__init__.py b/__init__.py index 8f656aa..bf1e69a 100644 --- a/__init__.py +++ b/__init__.py @@ -95,7 +95,7 @@ def create_item(self, name: str, override: Optional[ItemClassification] = None) if override: return Rac2Item(name, override, self.item_name_to_id[name], self.player) item_data = Items.from_name(name) - return Rac2Item(name, ItemPool.get_classification(item_data), self.item_name_to_id[name], self.player) + return Rac2Item(name, ItemPool.get_classification(item_data, self.options), self.item_name_to_id[name], self.player) def create_event(self, name: str) -> "Item": return Rac2Item(name, ItemClassification.progression, None, self.player) diff --git a/data/Locations.py b/data/Locations.py index ba21fe6..e551248 100644 --- a/data/Locations.py +++ b/data/Locations.py @@ -78,9 +78,9 @@ class LocationData(NamedTuple): SIBERIUS_FENCED_AREA_PB = LocationData(72, "Siberius: Fenced Area - Platinum Bolt", siberius_fenced_area_pb_rule) """ Tabora """ -TABORA_MEET_ANGELA = LocationData(80, "Tabora: Meet Angela", tabora_meet_angelar_rule) +TABORA_MEET_ANGELA = LocationData(80, "Tabora: Meet Angela", tabora_meet_angela_rule) TABORA_UNDERGROUND_MINES_END = LocationData(81, "Tabora: Underground Mines - Glider", tabora_underground_mines_end_rule) -TABORA_UNDERGROUND_MINES_PB = LocationData(82, "Tabora: Underground Mines - Platinum Bolt", tabora_underground_mines_end_rule) +TABORA_UNDERGROUND_MINES_PB = LocationData(82, "Tabora: Underground Mines - Platinum Bolt", tabora_underground_mines_pb_rule) TABORA_CANYON_GLIDE_PB = LocationData(83, "Tabora: Canyon Glide - Platinum Bolt", tabora_canyon_glide_pb_rule) TABORA_NORTHEAST_DESERT_PB = LocationData(84, "Tabora: Northeast Desert - Platinum Bolt", tabora_northeast_desert_pb_rule) TABORA_CANYON_GLIDE_PILLAR_NT = LocationData(85, "Tabora: Canyon Glide Pillar - Nanotech Boost", tabora_canyon_glide_pillar_nt_rule) @@ -119,7 +119,7 @@ class LocationData(NamedTuple): JOBA_FIRST_HOVERBIKE_RACE = LocationData(110, "Joba: First Hoverbike Race - Charge Boots", joba_hoverbike_race_rule) JOBA_SHADY_SALESMAN = LocationData(111, "Joba: Shady Salesman - Levitator", joba_shady_salesman_rule) JOBA_ARENA_BATTLE = LocationData(112, "Joba: Arena Battle - Gravity Boots", joba_arena_battle_rule) -JOBA_ARENA_CAGE_MATCH = LocationData(113, "Joba: Arena Cage Match - Infiltrator", joba_arena_cage_match_rule) +JOBA_ARENA_CAGE_MATCH = LocationData(113, "Joba: Arena Cage Match - Infiltrator", joba_arena_battle_rule) JOBA_HIDDEN_CLIFF_PB = LocationData(114, "Joba: Hidden Cliff - Platinum Bolt", joba_hidden_cliff_pb_rule) JOBA_LEVITATOR_TOWER_PB = LocationData(115, "Joba: Levitator Tower - Platinum Bolt", joba_levitator_tower_pb_rule) JOBA_HOVERBIKE_RACE_SHORTCUT_NT = LocationData(116, "Joba: Hoverbike Race Shortcut - Nanotech Boost", joba_hoverbike_race_rule)