diff --git a/worlds/sc2/Client.py b/worlds/sc2/Client.py index da9c889fc1df..7df551129d5a 100644 --- a/worlds/sc2/Client.py +++ b/worlds/sc2/Client.py @@ -31,7 +31,7 @@ LocationInclusion, ExtraLocations, MasteryLocations, ChallengeLocations, VanillaLocations, DisableForcedCamera, SkipCutscenes, GrantStoryTech, GrantStoryLevels, TakeOverAIAllies, RequiredTactics, SpearOfAdunPresence, SpearOfAdunPresentInNoBuild, SpearOfAdunAutonomouslyCastAbilityPresence, - SpearOfAdunAutonomouslyCastPresentInNoBuild, + SpearOfAdunAutonomouslyCastPresentInNoBuild, LEGACY_GRID_ORDERS, ) @@ -1380,8 +1380,8 @@ def mission_reqs_completed(ctx: SC2Context, mission_name: str, missions_complete else: req_success = False - # Grid-specific logic (to avoid long path checks and infinite recursion) - if ctx.mission_order in (MissionOrder.option_grid, MissionOrder.option_mini_grid, MissionOrder.option_medium_grid): + # Grid and Blitz-specific logic (to avoid long path checks and infinite recursion) + if ctx.mission_order in (MissionOrder.option_grid, MissionOrder.option_blitz) or (ctx.slot_data_version <= 3 and ctx.mission_order in LEGACY_GRID_ORDERS): if req_success: return True else: diff --git a/worlds/sc2/MissionOrders.py b/worlds/sc2/MissionOrders.py new file mode 100644 index 000000000000..dbddffd38720 --- /dev/null +++ b/worlds/sc2/MissionOrders.py @@ -0,0 +1,392 @@ +from .MissionTables import FillMission, MissionPools, MissionConnection, SC2Campaign +from typing import Dict, List +import math + +from worlds.AutoWorld import World + + +def vanilla_shuffle_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.WOL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Colonist", number=7), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Colonist", number=7, removal_priority=1), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.WOL)], "Artifact", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(7, SC2Campaign.WOL)], "Artifact", number=8, completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.WOL)], "Artifact", number=11, completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.WOL)], "Artifact", number=14, completion_critical=True, removal_priority=7), + FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.WOL)], "Artifact", completion_critical=True, removal_priority=6), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.WOL)], "Covert", number=4), + FillMission(MissionPools.MEDIUM, [MissionConnection(12, SC2Campaign.WOL)], "Covert"), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.WOL)], "Covert", number=8, removal_priority=3), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.WOL)], "Covert", number=8, removal_priority=2), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.WOL)], "Rebellion", number=6), + FillMission(MissionPools.HARD, [MissionConnection(16, SC2Campaign.WOL)], "Rebellion"), + FillMission(MissionPools.HARD, [MissionConnection(17, SC2Campaign.WOL)], "Rebellion"), + FillMission(MissionPools.HARD, [MissionConnection(18, SC2Campaign.WOL)], "Rebellion", removal_priority=8), + FillMission(MissionPools.HARD, [MissionConnection(19, SC2Campaign.WOL)], "Rebellion", removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(11, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(21, SC2Campaign.WOL)], "Char", completion_critical=True, removal_priority=4), + FillMission(MissionPools.HARD, [MissionConnection(21, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(22, SC2Campaign.WOL), MissionConnection(23, SC2Campaign.WOL)], "Char", completion_critical=True, or_requirements=True) + ], + SC2Campaign.PROPHECY: [ + FillMission(MissionPools.MEDIUM, [MissionConnection(8, SC2Campaign.WOL)], "_1"), + FillMission(MissionPools.HARD, [MissionConnection(0, SC2Campaign.PROPHECY)], "_2", removal_priority=2), + FillMission(MissionPools.HARD, [MissionConnection(1, SC2Campaign.PROPHECY)], "_3", removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(2, SC2Campaign.PROPHECY)], "_4"), + ], + SC2Campaign.HOTS: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.HOTS)], "Umoja", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Umoja", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.HOTS)], "Umoja", completion_critical=True, removal_priority=1), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.HOTS)], "Kaldir", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.HOTS)], "Kaldir", completion_critical=True, removal_priority=2), + FillMission(MissionPools.MEDIUM, [MissionConnection(4, SC2Campaign.HOTS)], "Kaldir", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.HOTS)], "Char", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(6, SC2Campaign.HOTS)], "Char", completion_critical=True, removal_priority=3), + FillMission(MissionPools.MEDIUM, [MissionConnection(7, SC2Campaign.HOTS)], "Char", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS)], "Zerus", completion_critical=True, or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(9, SC2Campaign.HOTS)], "Zerus", completion_critical=True, removal_priority=4), + FillMission(MissionPools.MEDIUM, [MissionConnection(10, SC2Campaign.HOTS)], "Zerus", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS), MissionConnection(11, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True, removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS), MissionConnection(11, SC2Campaign.HOTS)], "Dominion Space", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(15, SC2Campaign.HOTS)], "Dominion Space", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(14, SC2Campaign.HOTS), MissionConnection(16, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(17, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(18, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + ], + SC2Campaign.PROLOGUE: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.PROLOGUE)], "_1"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.PROLOGUE)], "_2", removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(1, SC2Campaign.PROLOGUE)], "_3") + ], + SC2Campaign.LOTV: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.LOTV)], "Aiur", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.LOTV)], "Aiur", completion_critical=True, removal_priority=3), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.LOTV)], "Aiur", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV)], "Korhal", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.LOTV)], "Korhal", completion_critical=True, removal_priority=7), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV)], "Shakuras", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.LOTV)], "Shakuras", completion_critical=True, removal_priority=6), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV), MissionConnection(6, SC2Campaign.LOTV)], "Purifier", completion_critical=True, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV), MissionConnection(6, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.LOTV)], "Ulnar", completion_critical=True, removal_priority=1), + FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.LOTV)], "Purifier", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(11, SC2Campaign.LOTV)], "Purifier", completion_critical=True, removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True, removal_priority=4), + FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.LOTV), MissionConnection(14, SC2Campaign.LOTV)], "Moebius", completion_critical=True, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.LOTV), MissionConnection(14, SC2Campaign.LOTV), MissionConnection(15, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(16, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True, removal_priority=2), + FillMission(MissionPools.FINAL, [MissionConnection(17, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + ], + SC2Campaign.EPILOGUE: [ + FillMission(MissionPools.VERY_HARD, [MissionConnection(24, SC2Campaign.WOL), MissionConnection(19, SC2Campaign.HOTS), MissionConnection(18, SC2Campaign.LOTV)], "_1", completion_critical=True), + FillMission(MissionPools.VERY_HARD, [MissionConnection(0, SC2Campaign.EPILOGUE)], "_2", completion_critical=True, removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(1, SC2Campaign.EPILOGUE)], "_3", completion_critical=True), + ], + SC2Campaign.NCO: [ + FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.NCO)], "_1", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.NCO)], "_1", completion_critical=True, removal_priority=6), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.NCO)], "_1", completion_critical=True, removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(2, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=7), + FillMission(MissionPools.HARD, [MissionConnection(3, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=4), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=3), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.NCO)], "_3", completion_critical=True, removal_priority=2), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.NCO)], "_3", completion_critical=True, removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(7, SC2Campaign.NCO)], "_3", completion_critical=True), + ] + } + + +def mini_campaign_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.WOL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Artifact", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.WOL)], "Artifact", number=4, completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Artifact", number=8, completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.WOL)], "Covert", number=2), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.WOL)], "Covert"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.WOL)], "Rebellion", number=3), + FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.WOL)], "Rebellion"), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(10, SC2Campaign.WOL), MissionConnection(11, SC2Campaign.WOL)], "Char", completion_critical=True, or_requirements=True) + ], + SC2Campaign.PROPHECY: [ + FillMission(MissionPools.MEDIUM, [MissionConnection(4, SC2Campaign.WOL)], "_1"), + FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.PROPHECY)], "_2"), + ], + SC2Campaign.HOTS: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.HOTS)], "Umoja", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Kaldir"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.HOTS)], "Kaldir"), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Char"), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.HOTS)], "Char"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.HOTS)], "Zerus", number=3), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS)], "Zerus"), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Skygeirr Station", number=5), + FillMission(MissionPools.HARD, [MissionConnection(7, SC2Campaign.HOTS)], "Skygeirr Station"), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Dominion Space", number=5), + FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.HOTS)], "Dominion Space"), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Korhal", completion_critical=True, number=8), + FillMission(MissionPools.FINAL, [MissionConnection(11, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + ], + SC2Campaign.PROLOGUE: [ + FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.PROLOGUE)], "_1"), + FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.PROLOGUE)], "_2") + ], + SC2Campaign.LOTV: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.LOTV)], "Aiur",completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.LOTV)], "Aiur", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.LOTV)], "Korhal", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.LOTV)], "Shakuras", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV), MissionConnection(3, SC2Campaign.LOTV)], "Purifier", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.LOTV)], "Purifier", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(8, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + ], + SC2Campaign.EPILOGUE: [ + FillMission(MissionPools.VERY_HARD, [MissionConnection(12, SC2Campaign.WOL), MissionConnection(12, SC2Campaign.HOTS), MissionConnection(9, SC2Campaign.LOTV)], "_1", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.EPILOGUE)], "_2", completion_critical=True), + ], + SC2Campaign.NCO: [ + FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.NCO)], "_1", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.NCO)], "_1", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.NCO)], "_2", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(2, SC2Campaign.NCO)], "_3", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(3, SC2Campaign.NCO)], "_3", completion_critical=True), + ] + } + + +smooth_difficulty = [MissionPools.EASY, + MissionPools.MEDIUM, MissionPools.MEDIUM, + MissionPools.HARD, MissionPools.HARD, + MissionPools.VERY_HARD] +max_difficulty = len(smooth_difficulty) - 1 + + +def make_golden_path(world: World, num_missions: int) -> Dict[SC2Campaign, List[FillMission]]: + chain_name_options = ['Mar Sara', 'Agria', 'Redstone', 'Meinhoff', 'Haven', 'Tarsonis', 'Valhalla', 'Char', + 'Umoja', 'Kaldir', 'Zerus', 'Skygeirr Station', 'Dominion Space', 'Korhal', + 'Aiur', 'Glacius', 'Shakuras', 'Ulnar', 'Slayn', + 'Antiga', 'Braxis', 'Chau Sara', 'Moria', 'Tyrador', 'Xil', 'Zhakul', + 'Azeroth', 'Crouton', 'Draenor', 'Sanctuary'] + world.random.shuffle(chain_name_options) + + first_chain = chain_name_options.pop() + first_mission = FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.GLOBAL)], first_chain, + completion_critical=True) + + class Campaign: + def __init__(self, root_mission: FillMission, missions_remaining: int): + self.mission_order = [root_mission] + self.mission_counter = 0 + self.last_mission_in_chain = [0] + self.chain_names = [root_mission.category] + self.missions_remaining = missions_remaining + self.padding = 0 + + def add_mission(self, chain: int, difficulty: int, required_missions: int = 0): + if self.missions_remaining == 0 and difficulty is not MissionPools.FINAL: + return + self.mission_counter += 1 + if self.mission_order[self.last_mission_in_chain[chain]].number == required_missions or required_missions <= 1: + required_missions = 0 + mission_connections = [MissionConnection(self.last_mission_in_chain[chain], SC2Campaign.GLOBAL)] + padding = 0 + if self.last_mission_in_chain[chain] == self.last_mission_in_chain[0]: + # Adding padding to the start of new chains + if chain != 0: + padding = self.padding + else: + # Requiring main chain progress for optional chains + mission_connections.append(MissionConnection(self.last_mission_in_chain[0], SC2Campaign.GLOBAL)) + self.mission_order.append(FillMission( + difficulty, + mission_connections, + self.chain_names[chain], + number=required_missions, + completion_critical=chain == 0, + ui_vertical_padding=padding + )) + self.last_mission_in_chain[chain] = self.mission_counter + if chain == 0: + self.padding += 1 + self.missions_remaining -= 1 + + campaign = Campaign(first_mission, num_missions - 2) + current_required_missions = 0 + main_chain_length = 0 + while campaign.missions_remaining > 0: + mission_difficulty = smooth_difficulty[min(main_chain_length, max_difficulty)] + main_chain_length += 1 + if main_chain_length % 2 == 1: # Adding branches + chains_to_make = 0 if len(campaign.chain_names) > 5 else 2 if main_chain_length == 1 else 1 + for new_chain in range(chains_to_make): + campaign.chain_names.append(chain_name_options.pop()) + campaign.last_mission_in_chain.append(campaign.last_mission_in_chain[0]) + # Updating branches + for side_chain in range(len(campaign.chain_names) - 1, 0, -1): + campaign.add_mission(side_chain, mission_difficulty) + # Adding main path mission + current_required_missions = (len(campaign.mission_order) * 3) // 4 + campaign.add_mission(0, mission_difficulty, current_required_missions) + campaign.add_mission(0, MissionPools.FINAL, current_required_missions) + return {SC2Campaign.GLOBAL: campaign.mission_order} + + +def make_gauntlet(num_missions: int) -> Dict[SC2Campaign, List[FillMission]]: + mission_order: List[FillMission] = [] + row_length = 7 + rows = math.ceil(num_missions / row_length) + difficulty_rate = (max_difficulty + 1) / num_missions if num_missions < 21 else 1/3 + if rows == 1: + column_names = ["I", "II", "III", "IV", "V", "VI", "VII"][:num_missions - 1] + column_names.append("Final") + else: + column_names = [f'_{col + 1}' for col in range(row_length)] + first_mission = FillMission(MissionPools.STARTER, [MissionConnection(-1)], column_names[0], + completion_critical=True) + mission_order.append(first_mission) + mission_number = 1 + space_rows = 0 + while mission_number < num_missions: + if mission_number == num_missions - 1: + difficulty = MissionPools.FINAL + else: + difficulty_progress = mission_number * difficulty_rate + difficulty = smooth_difficulty[min(math.floor(difficulty_progress), max_difficulty)] + connection = MissionConnection(mission_number - 1) + mission_order.append(FillMission( + difficulty, + [connection], + column_names[mission_number % row_length], + completion_critical=True, + ui_vertical_padding=space_rows + )) + mission_number += 1 + # Next row + if mission_number == row_length: + space_rows += 1 + return {SC2Campaign.GLOBAL: mission_order} + + +def make_blitz(num_missions: int) -> Dict[SC2Campaign, List[FillMission]]: + min_width, max_width = 2, 5 + mission_divisor = 5 + dynamic_width = num_missions // mission_divisor + width = max(min(dynamic_width, max_width), min_width) + middle_column = width // 2 + connections = [MissionConnection(-1)] + mission_number = 0 + mission_order: List[FillMission] = [] + if num_missions % width > middle_column: + final_row = width * (num_missions // width) + final_mission_number = final_row + middle_column + else: + final_mission_number = num_missions - 1 + while mission_number < num_missions: + column = mission_number % width + row = mission_number // width + if mission_number == middle_column: + difficulty = MissionPools.STARTER + elif mission_number == final_mission_number: + difficulty = MissionPools.FINAL + else: + difficulty = smooth_difficulty[(min(row, max_difficulty))] + mission_order.append(FillMission( + difficulty, + connections, + f'_{column}', + or_requirements=True + )) + mission_number += 1 + # Next row, requires previous row + if mission_number % width == 0: + connections = [MissionConnection(mission_number - 1 - i) for i in range(width)] + connections.reverse() + return {SC2Campaign.GLOBAL: mission_order} + + +def make_hopscotch(two_start_positions: bool, num_missions: int) -> Dict[SC2Campaign, List[FillMission]]: + mission_order: List[FillMission] = [] + menu_connection = [MissionConnection(-1)] + max_width = 7 + difficulty_progress = 0 + difficulty_rate = max(0.5, min(1, 3 * max_difficulty / num_missions)) + x = 0 + y = 0 + difficulty = MissionPools.EASY + first_diagonal = True + creation_cycle = 0 # Root -> Bottom Branch -> Right Branch + # Creating the starter missions + if two_start_positions: + mission_order += [ + FillMission(MissionPools.STARTER, menu_connection, '_1', ui_vertical_padding=1), + FillMission(MissionPools.EASY, menu_connection, '_2') + ] + mission_number = 2 + else: + mission_order.append(FillMission(MissionPools.STARTER, menu_connection, '_1', completion_critical=True)) + creation_cycle = 1 + mission_number = 1 + while mission_number < num_missions: + if mission_number == num_missions - 1: + difficulty = MissionPools.FINAL + elif creation_cycle == 0 and difficulty_progress < max_difficulty: + difficulty_progress += difficulty_rate + difficulty = smooth_difficulty[math.floor(difficulty_progress)] + if creation_cycle == 0: + # Root + if y != -1: + x += 1 + y += 1 + mission_order.append(FillMission( + difficulty, + [MissionConnection(mission_number - 1), MissionConnection(mission_number - 2)], + f'_{x + 1}', + or_requirements=True, + completion_critical=True + )) + elif creation_cycle == 1: + # Bottom branch + mission_order.append(FillMission( + difficulty, + [MissionConnection(mission_number - 1)], + f'_{x + 1}' + )) + else: + # Right branch + column = f'_{x + 2}' + if x >= max_width - 1: + # Wrapping around + x = 0 + y = -1 + column = '_1' + first_diagonal = False + mission_order.append(FillMission( + difficulty, + [MissionConnection(mission_number - 2)], + column, + ui_vertical_padding=y if first_diagonal else 1 + )) + mission_number += 1 + creation_cycle += 1 + if creation_cycle == 3: + creation_cycle = 0 + return {SC2Campaign.GLOBAL: mission_order} diff --git a/worlds/sc2/MissionTables.py b/worlds/sc2/MissionTables.py index 67820580d3ba..f047c18dccd2 100644 --- a/worlds/sc2/MissionTables.py +++ b/worlds/sc2/MissionTables.py @@ -217,7 +217,7 @@ class MissionInfo(NamedTuple): number: int = 0 # number of worlds need beaten completion_critical: bool = False # missions needed to beat game or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed - ui_vertical_padding: int = 0 + ui_vertical_padding: int = 0 # How many blank padding tiles go above this mission in the launcher class FillMission(NamedTuple): @@ -228,279 +228,7 @@ class FillMission(NamedTuple): completion_critical: bool = False # missions needed to beat game or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed removal_priority: int = 0 # how many missions missing from the pool required to remove this mission - - - -def vanilla_shuffle_order() -> Dict[SC2Campaign, List[FillMission]]: - return { - SC2Campaign.WOL: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.WOL)], "Colonist"), - FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.WOL)], "Colonist"), - FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Colonist", number=7), - FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Colonist", number=7, removal_priority=1), - FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.WOL)], "Artifact", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(7, SC2Campaign.WOL)], "Artifact", number=8, completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.WOL)], "Artifact", number=11, completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.WOL)], "Artifact", number=14, completion_critical=True, removal_priority=7), - FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.WOL)], "Artifact", completion_critical=True, removal_priority=6), - FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.WOL)], "Covert", number=4), - FillMission(MissionPools.MEDIUM, [MissionConnection(12, SC2Campaign.WOL)], "Covert"), - FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.WOL)], "Covert", number=8, removal_priority=3), - FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.WOL)], "Covert", number=8, removal_priority=2), - FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.WOL)], "Rebellion", number=6), - FillMission(MissionPools.HARD, [MissionConnection(16, SC2Campaign.WOL)], "Rebellion"), - FillMission(MissionPools.HARD, [MissionConnection(17, SC2Campaign.WOL)], "Rebellion"), - FillMission(MissionPools.HARD, [MissionConnection(18, SC2Campaign.WOL)], "Rebellion", removal_priority=8), - FillMission(MissionPools.HARD, [MissionConnection(19, SC2Campaign.WOL)], "Rebellion", removal_priority=5), - FillMission(MissionPools.HARD, [MissionConnection(11, SC2Campaign.WOL)], "Char", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(21, SC2Campaign.WOL)], "Char", completion_critical=True, removal_priority=4), - FillMission(MissionPools.HARD, [MissionConnection(21, SC2Campaign.WOL)], "Char", completion_critical=True), - FillMission(MissionPools.FINAL, [MissionConnection(22, SC2Campaign.WOL), MissionConnection(23, SC2Campaign.WOL)], "Char", completion_critical=True, or_requirements=True) - ], - SC2Campaign.PROPHECY: [ - FillMission(MissionPools.MEDIUM, [MissionConnection(8, SC2Campaign.WOL)], "_1"), - FillMission(MissionPools.HARD, [MissionConnection(0, SC2Campaign.PROPHECY)], "_2", removal_priority=2), - FillMission(MissionPools.HARD, [MissionConnection(1, SC2Campaign.PROPHECY)], "_3", removal_priority=1), - FillMission(MissionPools.FINAL, [MissionConnection(2, SC2Campaign.PROPHECY)], "_4"), - ], - SC2Campaign.HOTS: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.HOTS)], "Umoja", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Umoja", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.HOTS)], "Umoja", completion_critical=True, removal_priority=1), - FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.HOTS)], "Kaldir", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.HOTS)], "Kaldir", completion_critical=True, removal_priority=2), - FillMission(MissionPools.MEDIUM, [MissionConnection(4, SC2Campaign.HOTS)], "Kaldir", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.HOTS)], "Char", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(6, SC2Campaign.HOTS)], "Char", completion_critical=True, removal_priority=3), - FillMission(MissionPools.MEDIUM, [MissionConnection(7, SC2Campaign.HOTS)], "Char", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS)], "Zerus", completion_critical=True, or_requirements=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(9, SC2Campaign.HOTS)], "Zerus", completion_critical=True, removal_priority=4), - FillMission(MissionPools.MEDIUM, [MissionConnection(10, SC2Campaign.HOTS)], "Zerus", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS), MissionConnection(11, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True, removal_priority=5), - FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS), MissionConnection(11, SC2Campaign.HOTS)], "Dominion Space", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(15, SC2Campaign.HOTS)], "Dominion Space", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(14, SC2Campaign.HOTS), MissionConnection(16, SC2Campaign.HOTS)], "Korhal", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(17, SC2Campaign.HOTS)], "Korhal", completion_critical=True), - FillMission(MissionPools.FINAL, [MissionConnection(18, SC2Campaign.HOTS)], "Korhal", completion_critical=True), - ], - SC2Campaign.PROLOGUE: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.PROLOGUE)], "_1"), - FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.PROLOGUE)], "_2", removal_priority=1), - FillMission(MissionPools.FINAL, [MissionConnection(1, SC2Campaign.PROLOGUE)], "_3") - ], - SC2Campaign.LOTV: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.LOTV)], "Aiur", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.LOTV)], "Aiur", completion_critical=True, removal_priority=3), - FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.LOTV)], "Aiur", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV)], "Korhal", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.LOTV)], "Korhal", completion_critical=True, removal_priority=7), - FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV)], "Shakuras", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.LOTV)], "Shakuras", completion_critical=True, removal_priority=6), - FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV), MissionConnection(6, SC2Campaign.LOTV)], "Purifier", completion_critical=True, or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV), MissionConnection(6, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.LOTV)], "Ulnar", completion_critical=True, removal_priority=1), - FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.LOTV)], "Purifier", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(11, SC2Campaign.LOTV)], "Purifier", completion_critical=True, removal_priority=5), - FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True, removal_priority=4), - FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.LOTV), MissionConnection(14, SC2Campaign.LOTV)], "Moebius", completion_critical=True, or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.LOTV), MissionConnection(14, SC2Campaign.LOTV), MissionConnection(15, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(16, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True, removal_priority=2), - FillMission(MissionPools.FINAL, [MissionConnection(17, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), - ], - SC2Campaign.EPILOGUE: [ - FillMission(MissionPools.VERY_HARD, [MissionConnection(24, SC2Campaign.WOL), MissionConnection(19, SC2Campaign.HOTS), MissionConnection(18, SC2Campaign.LOTV)], "_1", completion_critical=True), - FillMission(MissionPools.VERY_HARD, [MissionConnection(0, SC2Campaign.EPILOGUE)], "_2", completion_critical=True, removal_priority=1), - FillMission(MissionPools.FINAL, [MissionConnection(1, SC2Campaign.EPILOGUE)], "_3", completion_critical=True), - ], - SC2Campaign.NCO: [ - FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.NCO)], "_1", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.NCO)], "_1", completion_critical=True, removal_priority=6), - FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.NCO)], "_1", completion_critical=True, removal_priority=5), - FillMission(MissionPools.HARD, [MissionConnection(2, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=7), - FillMission(MissionPools.HARD, [MissionConnection(3, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=4), - FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=3), - FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.NCO)], "_3", completion_critical=True, removal_priority=2), - FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.NCO)], "_3", completion_critical=True, removal_priority=1), - FillMission(MissionPools.FINAL, [MissionConnection(7, SC2Campaign.NCO)], "_3", completion_critical=True), - ] - } - - -def mini_campaign_order() -> Dict[SC2Campaign, List[FillMission]]: - return { - SC2Campaign.WOL: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Colonist"), - FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.WOL)], "Colonist"), - FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Artifact", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.WOL)], "Artifact", number=4, completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Artifact", number=8, completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.WOL)], "Covert", number=2), - FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.WOL)], "Covert"), - FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.WOL)], "Rebellion", number=3), - FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.WOL)], "Rebellion"), - FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.WOL)], "Char", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.WOL)], "Char", completion_critical=True), - FillMission(MissionPools.FINAL, [MissionConnection(10, SC2Campaign.WOL), MissionConnection(11, SC2Campaign.WOL)], "Char", completion_critical=True, or_requirements=True) - ], - SC2Campaign.PROPHECY: [ - FillMission(MissionPools.MEDIUM, [MissionConnection(4, SC2Campaign.WOL)], "_1"), - FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.PROPHECY)], "_2"), - ], - SC2Campaign.HOTS: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.HOTS)], "Umoja", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Kaldir"), - FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.HOTS)], "Kaldir"), - FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Char"), - FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.HOTS)], "Char"), - FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.HOTS)], "Zerus", number=3), - FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS)], "Zerus"), - FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Skygeirr Station", number=5), - FillMission(MissionPools.HARD, [MissionConnection(7, SC2Campaign.HOTS)], "Skygeirr Station"), - FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Dominion Space", number=5), - FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.HOTS)], "Dominion Space"), - FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Korhal", completion_critical=True, number=8), - FillMission(MissionPools.FINAL, [MissionConnection(11, SC2Campaign.HOTS)], "Korhal", completion_critical=True), - ], - SC2Campaign.PROLOGUE: [ - FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.PROLOGUE)], "_1"), - FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.PROLOGUE)], "_2") - ], - SC2Campaign.LOTV: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.LOTV)], "Aiur",completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.LOTV)], "Aiur", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.LOTV)], "Korhal", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.LOTV)], "Shakuras", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV), MissionConnection(3, SC2Campaign.LOTV)], "Purifier", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.LOTV)], "Purifier", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), - FillMission(MissionPools.FINAL, [MissionConnection(8, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), - ], - SC2Campaign.EPILOGUE: [ - FillMission(MissionPools.VERY_HARD, [MissionConnection(12, SC2Campaign.WOL), MissionConnection(12, SC2Campaign.HOTS), MissionConnection(9, SC2Campaign.LOTV)], "_1", completion_critical=True), - FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.EPILOGUE)], "_2", completion_critical=True), - ], - SC2Campaign.NCO: [ - FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.NCO)], "_1", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.NCO)], "_1", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.NCO)], "_2", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(2, SC2Campaign.NCO)], "_3", completion_critical=True), - FillMission(MissionPools.FINAL, [MissionConnection(3, SC2Campaign.NCO)], "_3", completion_critical=True), - ] - } - - -def gauntlet_order() -> Dict[SC2Campaign, List[FillMission]]: - return { - SC2Campaign.GLOBAL: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1)], "I", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(0)], "II", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(1)], "III", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(2)], "IV", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(3)], "V", completion_critical=True), - FillMission(MissionPools.HARD, [MissionConnection(4)], "VI", completion_critical=True), - FillMission(MissionPools.FINAL, [MissionConnection(5)], "Final", completion_critical=True) - ] - } - - -def mini_gauntlet_order() -> Dict[SC2Campaign, List[FillMission]]: - return { - SC2Campaign.GLOBAL: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1)], "I", completion_critical=True), - FillMission(MissionPools.EASY, [MissionConnection(0)], "II", completion_critical=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(1)], "III", completion_critical=True), - FillMission(MissionPools.FINAL, [MissionConnection(2)], "Final", completion_critical=True) - ] - } - - -def grid_order() -> Dict[SC2Campaign, List[FillMission]]: - return { - SC2Campaign.GLOBAL: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1)], "_1"), - FillMission(MissionPools.EASY, [MissionConnection(0)], "_1"), - FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(6), MissionConnection( 3)], "_1", or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(2), MissionConnection(7)], "_1", or_requirements=True), - FillMission(MissionPools.EASY, [MissionConnection(0)], "_2"), - FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(4)], "_2", or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(2), MissionConnection(5), MissionConnection(10), MissionConnection(7)], "_2", or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(3), MissionConnection(6), MissionConnection(11)], "_2", or_requirements=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(4), MissionConnection(9), MissionConnection(12)], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(5), MissionConnection(8), MissionConnection(10), MissionConnection(13)], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(6), MissionConnection(9), MissionConnection(11), MissionConnection(14)], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(7), MissionConnection(10)], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(8), MissionConnection(13)], "_4", or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(9), MissionConnection(12), MissionConnection(14)], "_4", or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(10), MissionConnection(13)], "_4", or_requirements=True), - FillMission(MissionPools.FINAL, [MissionConnection(11), MissionConnection(14)], "_4", or_requirements=True) - ] - } - -def mini_grid_order() -> Dict[SC2Campaign, List[FillMission]]: - return { - SC2Campaign.GLOBAL: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1)], "_1"), - FillMission(MissionPools.EASY, [MissionConnection(0)], "_1"), - FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(5)], "_1", or_requirements=True), - FillMission(MissionPools.EASY, [MissionConnection(0)], "_2"), - FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(3)], "_2", or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(2), MissionConnection(4)], "_2", or_requirements=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(3), MissionConnection(7)], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(4), MissionConnection(6)], "_3", or_requirements=True), - FillMission(MissionPools.FINAL, [MissionConnection(5), MissionConnection(7)], "_3", or_requirements=True) - ] - } - -def tiny_grid_order() -> Dict[SC2Campaign, List[FillMission]]: - return { - SC2Campaign.GLOBAL: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1)], "_1"), - FillMission(MissionPools.MEDIUM, [MissionConnection(0)], "_1"), - FillMission(MissionPools.EASY, [MissionConnection(0)], "_2"), - FillMission(MissionPools.FINAL, [MissionConnection(1), MissionConnection(2)], "_2", or_requirements=True), - ] - } - -def blitz_order() -> Dict[SC2Campaign, List[FillMission]]: - return { - SC2Campaign.GLOBAL: [ - FillMission(MissionPools.STARTER, [MissionConnection(-1)], "I"), - FillMission(MissionPools.EASY, [MissionConnection(-1)], "I"), - FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "II", number=1, or_requirements=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "II", number=1, or_requirements=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "III", number=2, or_requirements=True), - FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "III", number=2, or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "IV", number=3, or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "IV", number=3, or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "V", number=4, or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "V", number=4, or_requirements=True), - FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "Final", number=5, or_requirements=True), - FillMission(MissionPools.FINAL, [MissionConnection(0), MissionConnection(1)], "Final", number=5, or_requirements=True) - ] - } - - -mission_orders: List[Callable[[], Dict[SC2Campaign, List[FillMission]]]] = [ - vanilla_shuffle_order, - vanilla_shuffle_order, - mini_campaign_order, - grid_order, - mini_grid_order, - blitz_order, - gauntlet_order, - mini_gauntlet_order, - tiny_grid_order -] + ui_vertical_padding: int = 0 # How many blank padding tiles go above this mission in the launcher vanilla_mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = { diff --git a/worlds/sc2/Options.py b/worlds/sc2/Options.py index 31c7d2e03891..c3967dd06641 100644 --- a/worlds/sc2/Options.py +++ b/worlds/sc2/Options.py @@ -7,6 +7,7 @@ from BaseClasses import PlandoOptions from .MissionTables import SC2Campaign, SC2Mission, lookup_name_to_mission, MissionPools, get_no_build_missions, \ campaign_mission_table +from .MissionOrders import vanilla_shuffle_order, mini_campaign_order if TYPE_CHECKING: from worlds.AutoWorld import World @@ -63,29 +64,25 @@ class AllInMap(Choice): class MissionOrder(Choice): """ - Determines the order the missions are played in. The last three mission orders end in a random mission. + Determines the order the missions are played in. The first three mission orders ignore the Maximum Campaign Size option. Vanilla (83 total if all campaigns enabled): Keeps the standard mission order and branching from the vanilla Campaigns. Vanilla Shuffled (83 total if all campaigns enabled): Keeps same branching paths from the vanilla Campaigns but randomizes the order of missions within. Mini Campaign (47 total if all campaigns enabled): Shorter version of the campaign with randomized missions and optional branches. - Medium Grid (16): A 4x4 grid of random missions. Start at the top-left and forge a path towards bottom-right mission to win. - Mini Grid (9): A 3x3 version of Grid. Complete the bottom-right mission to win. - Blitz (12): 12 random missions that open up very quickly. Complete the bottom-right mission to win. - Gauntlet (7): Linear series of 7 random missions to complete the campaign. - Mini Gauntlet (4): Linear series of 4 random missions to complete the campaign. - Tiny Grid (4): A 2x2 version of Grid. Complete the bottom-right mission to win. - Grid (variable): A grid that will resize to use all non-excluded missions. Corners may be omitted to make the grid more square. Complete the bottom-right mission to win. + Blitz: Missions are divided into sets. Complete one mission from a set to advance to the next set. + Gauntlet: A linear path of missions to complete the campaign. + Grid: Missions are arranged into a grid. Completing a mission unlocks the adjacent missions. Corners may be omitted to make the grid more square. Complete the bottom-right mission to win. + Golden Path: A required line of missions with several optional branches, similar to the Wings of Liberty campaign. + Hopscotch: Missions alternate between mandatory missions and pairs of optional missions. """ display_name = "Mission Order" option_vanilla = 0 option_vanilla_shuffled = 1 option_mini_campaign = 2 - option_medium_grid = 3 - option_mini_grid = 4 option_blitz = 5 option_gauntlet = 6 - option_mini_gauntlet = 7 - option_tiny_grid = 8 option_grid = 9 + option_golden_path = 10 + option_hopscotch = 11 class MaximumCampaignSize(Range): @@ -101,8 +98,8 @@ class MaximumCampaignSize(Range): class GridTwoStartPositions(Toggle): """ - If turned on and 'grid' mission order is selected, removes a mission from the starting - corner sets the adjacent two missions as the starter missions. + If turned on and 'grid' or 'hopscotch' mission orders are selected, + removes a mission from the starting corner and sets the adjacent two missions as the starter missions. """ display_name = "Start with two unlocked missions on grid" default = Toggle.option_false @@ -645,7 +642,7 @@ def from_any(cls, data: Union[List[str], Dict[str, int]]) -> 'Sc2ItemDict': return cls(data) else: raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}") - + def verify(self, world: Type['World'], player_name: str, plando_options: PlandoOptions) -> None: """Overridden version of function from Options.VerifyKeys for a better error message""" new_value: dict[str, int] = {} @@ -668,7 +665,7 @@ def get_option_name(self, value): def __getitem__(self, item: str) -> int: return self.value.__getitem__(item) - + def __iter__(self) -> Iterator[str]: return self.value.__iter__() @@ -936,16 +933,11 @@ def get_excluded_missions(world: 'SC2World') -> Set[SC2Mission]: excluded_missions: Set[SC2Mission] = set([lookup_name_to_mission[name] for name in excluded_mission_names]) # Excluding Very Hard missions depending on options - if (world.options.exclude_very_hard_missions == ExcludeVeryHardMissions.option_true - ) or ( - world.options.exclude_very_hard_missions == ExcludeVeryHardMissions.option_default - and ( - mission_order_type not in [MissionOrder.option_vanilla_shuffled, MissionOrder.option_grid] - or ( - mission_order_type == MissionOrder.option_grid - and world.options.maximum_campaign_size < 20 - ) - ) + if world.options.exclude_very_hard_missions == ExcludeVeryHardMissions.option_true or ( + world.options.exclude_very_hard_missions == ExcludeVeryHardMissions.option_default and ( + mission_order_type in dynamic_mission_orders and world.options.maximum_campaign_size < 20 or + mission_order_type == MissionOrder.option_mini_campaign + ) ): excluded_missions = excluded_missions.union( [mission for mission in SC2Mission if @@ -967,6 +959,22 @@ def get_excluded_missions(world: 'SC2World') -> Set[SC2Mission]: MissionOrder.option_mini_campaign ] +static_mission_orders = { + MissionOrder.option_vanilla: vanilla_shuffle_order, + MissionOrder.option_vanilla_shuffled: vanilla_shuffle_order, + MissionOrder.option_mini_campaign: mini_campaign_order +} + +dynamic_mission_orders = [ + MissionOrder.option_golden_path, + MissionOrder.option_grid, + MissionOrder.option_gauntlet, + MissionOrder.option_blitz, + MissionOrder.option_hopscotch +] + +LEGACY_GRID_ORDERS = {3, 4, 8} # Medium Grid, Mini Grid, and Tiny Grid respectively + kerrigan_unit_available = [ KerriganPresence.option_vanilla, ] diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py index 4aa1329cc667..ee7f77873bc6 100644 --- a/worlds/sc2/PoolFilter.py +++ b/worlds/sc2/PoolFilter.py @@ -10,7 +10,8 @@ from .Options import (get_option_value, MissionOrder, get_enabled_campaigns, RequiredTactics, kerrigan_unit_available, GrantStoryTech, TakeOverAIAllies, campaign_depending_orders, - ShuffleCampaigns, get_excluded_missions, ShuffleNoBuild, ExtraLocations, GrantStoryLevels, EnableMorphling + ShuffleCampaigns, get_excluded_missions, ShuffleNoBuild, ExtraLocations, GrantStoryLevels, EnableMorphling, + static_mission_orders, dynamic_mission_orders ) from . import ItemNames, ItemGroups diff --git a/worlds/sc2/Regions.py b/worlds/sc2/Regions.py index db22c7deccfa..2c0a3e601587 100644 --- a/worlds/sc2/Regions.py +++ b/worlds/sc2/Regions.py @@ -4,9 +4,10 @@ from BaseClasses import Region, Entrance, Location, CollectionState from .Locations import LocationData from .Options import get_option_value, MissionOrder, get_enabled_campaigns, campaign_depending_orders, \ - GridTwoStartPositions -from .MissionTables import MissionInfo, mission_orders, vanilla_mission_req_table, \ - MissionPools, SC2Campaign, get_goal_location, SC2Mission, MissionConnection + GridTwoStartPositions, static_mission_orders, dynamic_mission_orders +from .MissionTables import MissionInfo, vanilla_mission_req_table, \ + MissionPools, SC2Campaign, get_goal_location, SC2Mission, MissionConnection, FillMission +from .MissionOrders import make_gauntlet, make_blitz, make_golden_path, make_hopscotch from .PoolFilter import filter_missions @@ -29,7 +30,7 @@ def create_regions( * int The number of missions in the world * str The name of the goal location """ - mission_order_type: int = get_option_value(world, "mission_order") + mission_order_type: MissionOrder = world.options.mission_order if mission_order_type == MissionOrder.option_vanilla: return create_vanilla_regions(world, locations, location_cache) @@ -379,15 +380,39 @@ def make_grid_connect_rule( return lambda state: state.has(f"Beat {missions[connected_coords].mission_name}", player) +def make_dynamic_mission_order( + world: 'SC2World', + mission_order_type: int +) -> Dict[SC2Campaign, List[FillMission]]: + mission_pools = filter_missions(world) + + mission_pool = [mission for mission_pool in mission_pools.values() for mission in mission_pool] + + num_missions = min(len(mission_pool), get_option_value(world, "maximum_campaign_size")) + num_missions = max(2, num_missions) + if mission_order_type == MissionOrder.option_golden_path: + return make_golden_path(world, num_missions) + # Grid handled by dedicated region generator + elif mission_order_type == MissionOrder.option_gauntlet: + return make_gauntlet(num_missions) + elif mission_order_type == MissionOrder.option_blitz: + return make_blitz(num_missions) + elif mission_order_type == MissionOrder.option_hopscotch: + return make_hopscotch(world.options.grid_two_start_positions, num_missions) + + def create_structured_regions( world: 'SC2World', locations: Tuple[LocationData, ...], location_cache: List[Location], - mission_order_type: int, + mission_order_type: MissionOrder, ) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: locations_per_region = get_locations_per_region(locations) - mission_order = mission_orders[mission_order_type]() + if mission_order_type in static_mission_orders: + mission_order = static_mission_orders[mission_order_type]() + else: + mission_order = make_dynamic_mission_order(world, mission_order_type) enabled_campaigns = get_enabled_campaigns(world) shuffle_campaigns = get_option_value(world, "shuffle_campaigns") @@ -596,7 +621,9 @@ def build_connection_rule(mission_names: List[str], missions_req: int) -> Callab mission.slot, connections, mission_order[campaign][i].category, number=mission_order[campaign][i].number, completion_critical=mission_order[campaign][i].completion_critical, - or_requirements=mission_order[campaign][i].or_requirements)}) + or_requirements=mission_order[campaign][i].or_requirements, + ui_vertical_padding=mission_order[campaign][i].ui_vertical_padding), + }) final_mission_id = final_mission.id # Changing the completion condition for alternate final missions into an event diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 5b443a701b28..1b446f855f1c 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -159,7 +159,7 @@ def fill_slot_data(self): slot_data["nova_covert_ops_only"] = (enabled_campaigns == {SC2Campaign.NCO}) slot_data["mission_req"] = slot_req_table slot_data["final_mission"] = self.final_mission_id - slot_data["version"] = 3 + slot_data["version"] = 4 if SC2Campaign.HOTS not in enabled_campaigns: slot_data["kerrigan_presence"] = KerriganPresence.option_not_present @@ -450,8 +450,7 @@ def flag_start_unit(world: SC2World, item_list: List[FilterItem], starter_unit: # If the first mission is a logic-less no-build missions = get_all_missions(world.mission_req_table) build_missions = [mission for mission in missions if MissionFlag.NoBuild not in mission.flags] - races = set(mission.race for mission in build_missions) - races.remove(SC2Race.ANY) + races = {mission.race for mission in build_missions if mission.race != SC2Race.ANY} if races: first_race = world.random.choice(list(races))