SC2: Quality of Life Changes/Fixes to Prepare For Future Feature (#550)

This commit is contained in:
TheCondor07 2022-05-20 20:47:16 -04:00 committed by GitHub
parent cb9db5dff1
commit bb15485965
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 284 additions and 137 deletions

View File

@ -57,13 +57,13 @@ class StarcraftClientProcessor(ClientCommandProcessor):
def _cmd_available(self) -> bool:
"""Get what missions are currently available to play"""
request_available_missions(self.ctx.checked_locations, mission_req_table)
request_available_missions(self.ctx.checked_locations, mission_req_table, self.ctx.ui)
return True
def _cmd_unfinished(self) -> bool:
"""Get what missions are currently available to play and have not had all locations checked"""
request_unfinished_missions(self.ctx.checked_locations, mission_req_table)
request_unfinished_missions(self.ctx.checked_locations, mission_req_table, self.ctx.ui)
return True
@ -378,22 +378,23 @@ class MissionInfo(typing.NamedTuple):
extra_locations: int
required_world: list[int]
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
mission_req_table = {
"Liberation Day": MissionInfo(1, 7, []),
"The Outlaws": MissionInfo(2, 2, [1]),
"Zero Hour": MissionInfo(3, 4, [2]),
"Liberation Day": MissionInfo(1, 7, [], completion_critical=True),
"The Outlaws": MissionInfo(2, 2, [1], completion_critical=True),
"Zero Hour": MissionInfo(3, 4, [2], completion_critical=True),
"Evacuation": MissionInfo(4, 4, [3]),
"Outbreak": MissionInfo(5, 3, [4]),
"Safe Haven": MissionInfo(6, 1, [5], number=7),
"Haven's Fall": MissionInfo(7, 1, [5], number=7),
"Smash and Grab": MissionInfo(8, 5, [3]),
"The Dig": MissionInfo(9, 4, [8], number=8),
"The Moebius Factor": MissionInfo(10, 9, [9], number=11),
"Supernova": MissionInfo(11, 5, [10], number=14),
"Maw of the Void": MissionInfo(12, 6, [11]),
"Smash and Grab": MissionInfo(8, 5, [3], completion_critical=True),
"The Dig": MissionInfo(9, 4, [8], number=8, completion_critical=True),
"The Moebius Factor": MissionInfo(10, 9, [9], number=11, completion_critical=True),
"Supernova": MissionInfo(11, 5, [10], number=14, completion_critical=True),
"Maw of the Void": MissionInfo(12, 6, [11], completion_critical=True),
"Devil's Playground": MissionInfo(13, 3, [3], number=4),
"Welcome to the Jungle": MissionInfo(14, 4, [13]),
"Breakout": MissionInfo(15, 3, [14], number=8),
@ -407,10 +408,10 @@ mission_req_table = {
"A Sinister Turn": MissionInfo(23, 4, [22]),
"Echoes of the Future": MissionInfo(24, 3, [23]),
"In Utter Darkness": MissionInfo(25, 3, [24]),
"Gates of Hell": MissionInfo(26, 2, [12]),
"Belly of the Beast": MissionInfo(27, 4, [26]),
"Shatter the Sky": MissionInfo(28, 5, [26]),
"All-In": MissionInfo(29, -1, [27, 28], or_requirements=True)
"Gates of Hell": MissionInfo(26, 2, [12], completion_critical=True),
"Belly of the Beast": MissionInfo(27, 4, [26], completion_critical=True),
"Shatter the Sky": MissionInfo(28, 5, [26], completion_critical=True),
"All-In": MissionInfo(29, -1, [27, 28], completion_critical=True, or_requirements=True)
}
lookup_id_to_mission: typing.Dict[int, str] = {
@ -431,16 +432,21 @@ def calc_objectives_completed(mission, missions_info, locations_done):
return -1
def request_unfinished_missions(locations_done, location_table):
def request_unfinished_missions(locations_done, location_table, ui):
message = "Unfinished Missions: "
unfinished_missions = calc_unfinished_missions(locations_done, location_table)
message += ", ".join(f"{mission}[{location_table[mission].id}] "
message += ", ".join(f"{mark_critical(mission,location_table, ui)}[{location_table[mission].id}] "
f"({unfinished_missions[mission]}/{location_table[mission].extra_locations})"
for mission in unfinished_missions)
sc2_logger.info(message)
if ui:
ui.log_panels['All'].on_message_markup(message)
ui.log_panels['Starcraft2'].on_message_markup(message)
else:
sc2_logger.info(message)
def calc_unfinished_missions(locations_done, locations):
@ -469,13 +475,28 @@ def is_mission_available(mission_id_to_check, locations_done, locations):
return any(mission_id_to_check == locations[mission].id for mission in unfinished_missions)
def request_available_missions(locations_done, location_table):
def mark_critical(mission, location_table, ui):
"""Checks if the mission is required for game completion and adds '*' to the name to mark that."""
if location_table[mission].completion_critical:
if ui:
return "[color=AF99EF]" + mission + "[/color]"
else:
return "*" + mission + "*"
else:
return mission
def request_available_missions(locations_done, location_table, ui):
message = "Available Missions: "
missions = calc_available_missions(locations_done, location_table)
message += ", ".join(f"{mission}[{location_table[mission].id}]" for mission in missions)
message += ", ".join(f"{mark_critical(mission,location_table, ui)}[{location_table[mission].id}]" for mission in missions)
sc2_logger.info(message)
if ui:
ui.log_panels['All'].on_message_markup(message)
ui.log_panels['Starcraft2'].on_message_markup(message)
else:
sc2_logger.info(message)
def calc_available_missions(locations_done, locations):

View File

@ -116,7 +116,7 @@ item_table = {
"Automated Refinery": ItemData(604 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 4),
"Command Center Reactor": ItemData(605 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 5),
"Raven": ItemData(606 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 6),
"Science Vessel": ItemData(607 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 7),
"Science Vessel": ItemData(607 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 7, progression=True),
"Tech Reactor": ItemData(608 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 8),
"Orbital Strike": ItemData(609 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 9),
"Shrike Turret": ItemData(610 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 10),

View File

@ -5,6 +5,7 @@ from BaseClasses import Location
SC2WOL_LOC_ID_OFFSET = 1000
class SC2WoLLocation(Location):
game: str = "Starcraft2WoL"
@ -17,6 +18,7 @@ class LocationData(NamedTuple):
def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[LocationData, ...]:
# Note: rules which are ended with or True are rules identified as needed later when restricted units is an option
location_table: List[LocationData] = [
LocationData("Liberation Day", "Liberation Day: Victory", SC2WOL_LOC_ID_OFFSET + 100),
LocationData("Liberation Day", "Liberation Day: First Statue", SC2WOL_LOC_ID_OFFSET + 101),
@ -26,41 +28,84 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData("Liberation Day", "Liberation Day: Fifth Statue", SC2WOL_LOC_ID_OFFSET + 105),
LocationData("Liberation Day", "Liberation Day: Sixth Statue", SC2WOL_LOC_ID_OFFSET + 106),
LocationData("Liberation Day", "Beat Liberation Day", None),
LocationData("The Outlaws", "The Outlaws: Victory", SC2WOL_LOC_ID_OFFSET + 200),
LocationData("The Outlaws", "The Outlaws: Rebel Base", SC2WOL_LOC_ID_OFFSET + 201),
LocationData("The Outlaws", "Beat The Outlaws", None),
LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300),
LocationData("The Outlaws", "The Outlaws: Victory", SC2WOL_LOC_ID_OFFSET + 200,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("The Outlaws", "The Outlaws: Rebel Base", SC2WOL_LOC_ID_OFFSET + 201,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("The Outlaws", "Beat The Outlaws", None,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Zero Hour", "Zero Hour: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 301),
LocationData("Zero Hour", "Zero Hour: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 302),
LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303),
LocationData("Zero Hour", "Beat Zero Hour", None),
LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400),
LocationData("Zero Hour", "Zero Hour: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 302,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Zero Hour", "Beat Zero Hour", None,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Evacuation", "Evacuation: First Chysalis", SC2WOL_LOC_ID_OFFSET + 401),
LocationData("Evacuation", "Evacuation: Second Chysalis", SC2WOL_LOC_ID_OFFSET + 402),
LocationData("Evacuation", "Evacuation: Third Chysalis", SC2WOL_LOC_ID_OFFSET + 403),
LocationData("Evacuation", "Beat Evacuation", None),
LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500),
LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501),
LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502),
LocationData("Outbreak", "Beat Outbreak", None),
LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600),
LocationData("Safe Haven", "Beat Safe Haven", None),
LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700),
LocationData("Haven's Fall", "Beat Haven's Fall", None),
LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800),
LocationData("Evacuation", "Evacuation: Second Chysalis", SC2WOL_LOC_ID_OFFSET + 402,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Evacuation", "Evacuation: Third Chysalis", SC2WOL_LOC_ID_OFFSET + 403,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Evacuation", "Beat Evacuation", None,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500,
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501,
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502,
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
LocationData("Outbreak", "Beat Outbreak", None,
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Safe Haven", "Beat Safe Haven", None,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Haven's Fall", "Beat Haven's Fall", None,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Smash and Grab", "Smash and Grab: First Relic", SC2WOL_LOC_ID_OFFSET + 801),
LocationData("Smash and Grab", "Smash and Grab: Second Relic", SC2WOL_LOC_ID_OFFSET + 802),
LocationData("Smash and Grab", "Smash and Grab: Third Relic", SC2WOL_LOC_ID_OFFSET + 803),
LocationData("Smash and Grab", "Smash and Grab: Fourth Relic", SC2WOL_LOC_ID_OFFSET + 804),
LocationData("Smash and Grab", "Beat Smash and Grab", None),
LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900),
LocationData("The Dig", "The Dig: Left Relic", SC2WOL_LOC_ID_OFFSET + 901),
LocationData("The Dig", "The Dig: Right Ground Relic", SC2WOL_LOC_ID_OFFSET + 902),
LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903),
LocationData("The Dig", "Beat The Dig", None),
LocationData("The Moebius Factor", "The Moebius Factor: 3rd Data Core", SC2WOL_LOC_ID_OFFSET + 1000),
LocationData("The Moebius Factor", "The Moebius Factor: 1st Data Core ", SC2WOL_LOC_ID_OFFSET + 1001),
LocationData("The Moebius Factor", "The Moebius Factor: 2nd Data Core", SC2WOL_LOC_ID_OFFSET + 1002),
LocationData("Smash and Grab", "Smash and Grab: Third Relic", SC2WOL_LOC_ID_OFFSET + 803,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Smash and Grab", "Smash and Grab: Fourth Relic", SC2WOL_LOC_ID_OFFSET + 804,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_anti_air(world, player)),
LocationData("Smash and Grab", "Beat Smash and Grab", None,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_anti_air(world, player)),
LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_anti_air(world, player) and
state._sc2wol_has_heavy_defense(world, player)),
LocationData("The Dig", "The Dig: Left Relic", SC2WOL_LOC_ID_OFFSET + 901,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("The Dig", "The Dig: Right Ground Relic", SC2WOL_LOC_ID_OFFSET + 902,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("The Dig", "Beat The Dig", None,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_anti_air(world, player) and
state._sc2wol_has_heavy_defense(world, player)),
LocationData("The Moebius Factor", "The Moebius Factor: 3rd Data Core", SC2WOL_LOC_ID_OFFSET + 1000,
lambda state: state._sc2wol_has_air(world, player)),
LocationData("The Moebius Factor", "The Moebius Factor: 1st Data Core ", SC2WOL_LOC_ID_OFFSET + 1001,
lambda state: state._sc2wol_has_air(world, player) or True),
LocationData("The Moebius Factor", "The Moebius Factor: 2nd Data Core", SC2WOL_LOC_ID_OFFSET + 1002,
lambda state: state._sc2wol_has_air(world, player)),
LocationData("The Moebius Factor", "The Moebius Factor: South Rescue", SC2WOL_LOC_ID_OFFSET + 1003,
lambda state: state._sc2wol_able_to_rescue(world, player) or True),
LocationData("The Moebius Factor", "The Moebius Factor: Wall Rescue", SC2WOL_LOC_ID_OFFSET + 1004,
@ -71,30 +116,58 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
lambda state: state._sc2wol_able_to_rescue(world, player) or True),
LocationData("The Moebius Factor", "The Moebius Factor: Alive Inside Rescue", SC2WOL_LOC_ID_OFFSET + 1007,
lambda state: state._sc2wol_able_to_rescue(world, player) or True),
LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008),
LocationData("The Moebius Factor", "Beat The Moebius Factor", None),
LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100),
LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008,
lambda state: state._sc2wol_has_air(world, player)),
LocationData("The Moebius Factor", "Beat The Moebius Factor", None,
lambda state: state._sc2wol_has_air(world, player)),
LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Supernova", "Supernova: West Relic", SC2WOL_LOC_ID_OFFSET + 1101),
LocationData("Supernova", "Supernova: North Relic", SC2WOL_LOC_ID_OFFSET + 1102),
LocationData("Supernova", "Supernova: South Relic", SC2WOL_LOC_ID_OFFSET + 1103),
LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104),
LocationData("Supernova", "Beat Supernova", None),
LocationData("Maw of the Void", "Maw of the Void: Xel'Naga Vault", SC2WOL_LOC_ID_OFFSET + 1200),
LocationData("Supernova", "Supernova: North Relic", SC2WOL_LOC_ID_OFFSET + 1102,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Supernova", "Supernova: South Relic", SC2WOL_LOC_ID_OFFSET + 1103,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Supernova", "Beat Supernova", None,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Maw of the Void", "Maw of the Void: Xel'Naga Vault", SC2WOL_LOC_ID_OFFSET + 1200,
lambda state: state.has('Battlecruiser', player) or state.has('Science Vessel', player) and
state._sc2wol_has_air(world, player)),
LocationData("Maw of the Void", "Maw of the Void: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1201),
LocationData("Maw of the Void", "Maw of the Void: Expansion Prisoners", SC2WOL_LOC_ID_OFFSET + 1202),
LocationData("Maw of the Void", "Maw of the Void: South Close Prisoners", SC2WOL_LOC_ID_OFFSET + 1203),
LocationData("Maw of the Void", "Maw of the Void: South Far Prisoners", SC2WOL_LOC_ID_OFFSET + 1204),
LocationData("Maw of the Void", "Maw of the Void: North Prisoners", SC2WOL_LOC_ID_OFFSET + 1205),
LocationData("Maw of the Void", "Beat Maw of the Void", None),
LocationData("Devil's Playground", "Devil's Playground: 8000 Minerals", SC2WOL_LOC_ID_OFFSET + 1300),
LocationData("Maw of the Void", "Maw of the Void: South Close Prisoners", SC2WOL_LOC_ID_OFFSET + 1203,
lambda state: state.has('Battlecruiser', player) or state.has('Science Vessel', player) and
state._sc2wol_has_air(world, player)),
LocationData("Maw of the Void", "Maw of the Void: South Far Prisoners", SC2WOL_LOC_ID_OFFSET + 1204,
lambda state: state.has('Battlecruiser', player) or state.has('Science Vessel', player) and
state._sc2wol_has_air(world, player)),
LocationData("Maw of the Void", "Maw of the Void: North Prisoners", SC2WOL_LOC_ID_OFFSET + 1205,
lambda state: state.has('Battlecruiser', player) or state.has('Science Vessel', player) and
state._sc2wol_has_air(world, player)),
LocationData("Maw of the Void", "Beat Maw of the Void", None,
lambda state: state.has('Battlecruiser', player) or state.has('Science Vessel', player) and
state._sc2wol_has_air(world, player)),
LocationData("Devil's Playground", "Devil's Playground: 8000 Minerals", SC2WOL_LOC_ID_OFFSET + 1300,
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
LocationData("Devil's Playground", "Devil's Playground: Tosh's Miners", SC2WOL_LOC_ID_OFFSET + 1301),
LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302),
LocationData("Devil's Playground", "Beat Devil's Playground", None),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: 7 Canisters", SC2WOL_LOC_ID_OFFSET + 1400),
LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302,
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
LocationData("Devil's Playground", "Beat Devil's Playground", None,
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: 7 Canisters", SC2WOL_LOC_ID_OFFSET + 1400,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: Close Relic", SC2WOL_LOC_ID_OFFSET + 1401),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: West Relic", SC2WOL_LOC_ID_OFFSET + 1402),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: North-East Relic", SC2WOL_LOC_ID_OFFSET + 1403),
LocationData("Welcome to the Jungle", "Beat Welcome to the Jungle", None),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: West Relic", SC2WOL_LOC_ID_OFFSET + 1402,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: North-East Relic", SC2WOL_LOC_ID_OFFSET + 1403,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Welcome to the Jungle", "Beat Welcome to the Jungle", None,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Breakout", "Breakout: Main Prison", SC2WOL_LOC_ID_OFFSET + 1500),
LocationData("Breakout", "Breakout: Diamondback Prison", SC2WOL_LOC_ID_OFFSET + 1501),
LocationData("Breakout", "Breakout: Siegetank Prison", SC2WOL_LOC_ID_OFFSET + 1502),
@ -106,36 +179,51 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData("Ghost of a Chance", "Ghost of a Chance: Second Island Spectres", SC2WOL_LOC_ID_OFFSET + 1604),
LocationData("Ghost of a Chance", "Ghost of a Chance: Third Island Spectres", SC2WOL_LOC_ID_OFFSET + 1605),
LocationData("Ghost of a Chance", "Beat Ghost of a Chance", None),
LocationData("The Great Train Robbery", "The Great Train Robbery: 8 Trains", SC2WOL_LOC_ID_OFFSET + 1700, lambda state: state._sc2wol_has_train_killers(world, player)),
LocationData("The Great Train Robbery", "The Great Train Robbery: 8 Trains", SC2WOL_LOC_ID_OFFSET + 1700,
lambda state: state._sc2wol_has_train_killers(world, player)),
LocationData("The Great Train Robbery", "The Great Train Robbery: North Defiler", SC2WOL_LOC_ID_OFFSET + 1701),
LocationData("The Great Train Robbery", "The Great Train Robbery: Mid Defiler", SC2WOL_LOC_ID_OFFSET + 1702),
LocationData("The Great Train Robbery", "The Great Train Robbery: South Defiler", SC2WOL_LOC_ID_OFFSET + 1703),
LocationData("The Great Train Robbery", "Beat The Great Train Robbery", None,
lambda state: state._sc2wol_has_train_killers(world, player)),
LocationData("Cutthroat", "Cutthroat: Orlan's Planetary", SC2WOL_LOC_ID_OFFSET + 1800),
LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801),
LocationData("Cutthroat", "Cutthroat: North Relic", SC2WOL_LOC_ID_OFFSET + 1802),
LocationData("Cutthroat", "Cutthroat: Orlan's Planetary", SC2WOL_LOC_ID_OFFSET + 1800,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Cutthroat", "Cutthroat: North Relic", SC2WOL_LOC_ID_OFFSET + 1802,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Cutthroat", "Cutthroat: Mid Relic", SC2WOL_LOC_ID_OFFSET + 1803),
LocationData("Cutthroat", "Cutthroat: Southwest Relic", SC2WOL_LOC_ID_OFFSET + 1804),
LocationData("Cutthroat", "Beat Cutthroat", None),
LocationData("Cutthroat", "Cutthroat: Southwest Relic", SC2WOL_LOC_ID_OFFSET + 1804,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Cutthroat", "Beat Cutthroat", None,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Engine of Destruction", "Engine of Destruction: Dominion Bases", SC2WOL_LOC_ID_OFFSET + 1900,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Engine of Destruction", "Engine of Destruction: Odin", SC2WOL_LOC_ID_OFFSET + 1901),
LocationData("Engine of Destruction", "Engine of Destruction: Loki", SC2WOL_LOC_ID_OFFSET + 1902,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
LocationData("Engine of Destruction", "Engine of Destruction: Lab Devourer", SC2WOL_LOC_ID_OFFSET + 1903),
LocationData("Engine of Destruction", "Engine of Destruction: North Devourer", SC2WOL_LOC_ID_OFFSET + 1904,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
LocationData("Engine of Destruction", "Engine of Destruction: Southeast Devourer", SC2WOL_LOC_ID_OFFSET + 1905,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
LocationData("Engine of Destruction", "Beat Engine of Destruction", None,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Media Blitz", "Media Blitz: Full Upload", SC2WOL_LOC_ID_OFFSET + 2000),
LocationData("Media Blitz", "Media Blitz: Tower 1", SC2WOL_LOC_ID_OFFSET + 2001),
LocationData("Media Blitz", "Media Blitz: Tower 2", SC2WOL_LOC_ID_OFFSET + 2002),
LocationData("Media Blitz", "Media Blitz: Tower 3", SC2WOL_LOC_ID_OFFSET + 2003),
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
LocationData("Media Blitz", "Media Blitz: Full Upload", SC2WOL_LOC_ID_OFFSET + 2000,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Media Blitz", "Media Blitz: Tower 1", SC2WOL_LOC_ID_OFFSET + 2001,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Media Blitz", "Media Blitz: Tower 2", SC2WOL_LOC_ID_OFFSET + 2002,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Media Blitz", "Media Blitz: Tower 3", SC2WOL_LOC_ID_OFFSET + 2003,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004),
LocationData("Media Blitz", "Beat Media Blitz", None),
LocationData("Media Blitz", "Beat Media Blitz", None,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Piercing the Shroud", "Piercing the Shroud: Facility Escape", SC2WOL_LOC_ID_OFFSET + 2100),
LocationData("Piercing the Shroud", "Piercing the Shroud: Holding Cell Relic", SC2WOL_LOC_ID_OFFSET + 2101),
LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk Relic", SC2WOL_LOC_ID_OFFSET + 2102),
@ -156,31 +244,45 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
lambda state: state._sc2wol_has_protoss_common_units(world, player)),
LocationData("A Sinister Turn", "Beat A Sinister Turn", None,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("Echoes of the Future", "Echoes of the Future: Overmind", SC2WOL_LOC_ID_OFFSET + 2400),
LocationData("Echoes of the Future", "Echoes of the Future: Overmind", SC2WOL_LOC_ID_OFFSET + 2400,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("Echoes of the Future", "Echoes of the Future: Close Obelisk", SC2WOL_LOC_ID_OFFSET + 2401),
LocationData("Echoes of the Future", "Echoes of the Future: West Obelisk", SC2WOL_LOC_ID_OFFSET + 2402),
LocationData("Echoes of the Future", "Beat Echoes of the Future", None),
LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2500),
LocationData("In Utter Darkness", "In Utter Darkness: Protoss Archive", SC2WOL_LOC_ID_OFFSET + 2501),
LocationData("Echoes of the Future", "Echoes of the Future: West Obelisk", SC2WOL_LOC_ID_OFFSET + 2402,
lambda state: state._sc2wol_has_protoss_common_units(world, player)),
LocationData("Echoes of the Future", "Beat Echoes of the Future", None,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2500,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("In Utter Darkness", "In Utter Darkness: Protoss Archive", SC2WOL_LOC_ID_OFFSET + 2501,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2502),
LocationData("In Utter Darkness", "Beat In Utter Darkness", None),
LocationData("Gates of Hell", "Gates of Hell: Nydus Worms", SC2WOL_LOC_ID_OFFSET + 2600),
LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601),
LocationData("In Utter Darkness", "Beat In Utter Darkness", None,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("Gates of Hell", "Gates of Hell: Nydus Worms", SC2WOL_LOC_ID_OFFSET + 2600,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Gates of Hell", "Beat Gates of Hell", None),
LocationData("Belly of the Beast", "Belly of the Beast: Extract", SC2WOL_LOC_ID_OFFSET + 2700),
LocationData("Belly of the Beast", "Belly of the Beast: First Charge", SC2WOL_LOC_ID_OFFSET + 2701),
LocationData("Belly of the Beast", "Belly of the Beast: Second Charge", SC2WOL_LOC_ID_OFFSET + 2702),
LocationData("Belly of the Beast", "Belly of the Beast: Third Charge", SC2WOL_LOC_ID_OFFSET + 2703),
LocationData("Belly of the Beast", "Beat Belly of the Beast", None),
LocationData("Shatter the Sky", "Shatter the Sky: Platform Destroyed", SC2WOL_LOC_ID_OFFSET + 2800),
LocationData("Shatter the Sky", "Shatter the Sky: Close Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2801),
LocationData("Shatter the Sky", "Shatter the Sky: Northwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2802),
LocationData("Shatter the Sky", "Shatter the Sky: Southeast Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2803),
LocationData("Shatter the Sky", "Shatter the Sky: Southwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2804),
LocationData("Shatter the Sky", "Shatter the Sky: Leviathan", SC2WOL_LOC_ID_OFFSET + 2805),
LocationData("Shatter the Sky", "Beat Shatter the Sky", None),
LocationData("Shatter the Sky", "Shatter the Sky: Platform Destroyed", SC2WOL_LOC_ID_OFFSET + 2800,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Shatter the Sky", "Shatter the Sky: Close Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2801,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Shatter the Sky", "Shatter the Sky: Northwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2802,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Shatter the Sky", "Shatter the Sky: Southeast Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2803,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Shatter the Sky", "Shatter the Sky: Southwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2804,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Shatter the Sky", "Shatter the Sky: Leviathan", SC2WOL_LOC_ID_OFFSET + 2805,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Shatter the Sky", "Beat Shatter the Sky", None,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("All-In", "All-In: Victory", None)
]
return tuple(location_table)

View File

@ -12,9 +12,6 @@ class SC2WoLLogic(LogicMixin):
def _sc2wol_has_air(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Viking', 'Wraith', 'Medivac', 'Banshee', 'Hercules'}, player)
def _sc2wol_has_battlecruiser(self, world: MultiWorld, player: int) -> bool:
return self.has('Battlecruiser', player)
def _sc2wol_has_air_anti_air(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Viking', 'Wraith'}, player)
@ -29,6 +26,12 @@ class SC2WoLLogic(LogicMixin):
self.has('Bunker', player) and self._sc2wol_has_bunker_unit(world, player)) and \
self._sc2wol_has_anti_air(world, player)
def _sc2wol_has_competent_comp(self, world: MultiWorld, player: int) -> bool:
return (self.has('Marine', player) or self.has('Marauder', player) and
self._sc2wol_has_mobile_anti_air(world, player)) and self.has_any({'Medivac', 'Medic'}, player) or \
self.has('Thor', player) or self.has("Banshee", player) and self._sc2wol_has_mobile_anti_air(world, player) or \
self.has('Battlecruiser', player) and self._sc2wol_has_common_unit(world, player)
def _sc2wol_has_train_killers(self, world: MultiWorld, player: int) -> bool:
return (self.has_any({'Siege Tank', 'Diamondback'}, player) or
self.has_all({'Reaper', "G-4 Clusterbomb"}, player) or self.has_all({'Spectre', 'Psionic Lash'}, player))

View File

@ -48,54 +48,71 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
connect(world, player, names, 'Menu', 'Liberation Day'),
connect(world, player, names, 'Liberation Day', 'The Outlaws',
lambda state: state._sc2wol_has_common_unit(world, player)),
connect(world, player, names, 'The Outlaws', 'Zero Hour'),
lambda state: state.has("Beat Liberation Day", player)),
connect(world, player, names, 'The Outlaws', 'Zero Hour',
lambda state: state.has("Beat The Outlaws", player)),
connect(world, player, names, 'Zero Hour', 'Evacuation',
lambda state: state._sc2wol_has_anti_air(world, player)),
connect(world, player, names, 'Evacuation', 'Outbreak'),
lambda state: state.has("Beat Zero Hour", player)),
connect(world, player, names, 'Evacuation', 'Outbreak',
lambda state: state.has("Beat Evacuation", player)),
connect(world, player, names, "Outbreak", "Safe Haven",
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
state._sc2wol_cleared_missions(world, player, 7)),
lambda state: state._sc2wol_cleared_missions(world, player, 7) and
state.has("Beat Outbreak", player)),
connect(world, player, names, "Outbreak", "Haven's Fall",
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
state._sc2wol_cleared_missions(world, player, 7)),
lambda state: state._sc2wol_cleared_missions(world, player, 7) and
state.has("Beat Outbreak", player)),
connect(world, player, names, 'Zero Hour', 'Smash and Grab',
lambda state: state._sc2wol_has_anti_air(world, player)),
lambda state: state.has("Beat Zero Hour", player)),
connect(world, player, names, 'Smash and Grab', 'The Dig',
lambda state: state._sc2wol_cleared_missions(world, player, 8) and
state._sc2wol_has_heavy_defense(world, player)),
state.has("Beat Smash and Grab", player)),
connect(world, player, names, 'The Dig', 'The Moebius Factor',
lambda state: state._sc2wol_cleared_missions(world, player, 11) and
state._sc2wol_has_air(world, player)),
state.has("Beat The Dig", player)),
connect(world, player, names, 'The Moebius Factor', 'Supernova',
lambda state: state._sc2wol_cleared_missions(world, player, 14)),
connect(world, player, names, 'Supernova', 'Maw of the Void'),
lambda state: state._sc2wol_cleared_missions(world, player, 14) and
state.has("Beat The Moebius Factor", player)),
connect(world, player, names, 'Supernova', 'Maw of the Void',
lambda state: state.has("Beat Supernova", player)),
connect(world, player, names, 'Zero Hour', "Devil's Playground",
lambda state: state._sc2wol_cleared_missions(world, player, 4)),
connect(world, player, names, "Devil's Playground", 'Welcome to the Jungle'),
lambda state: state._sc2wol_cleared_missions(world, player, 4) and
state.has("Beat Zero Hour", player)),
connect(world, player, names, "Devil's Playground", 'Welcome to the Jungle',
lambda state: state.has("Beat Devil's Playground", player)),
connect(world, player, names, "Welcome to the Jungle", 'Breakout',
lambda state: state._sc2wol_cleared_missions(world, player, 8)),
lambda state: state._sc2wol_cleared_missions(world, player, 8) and
state.has("Beat Welcome to the Jungle", player)),
connect(world, player, names, "Welcome to the Jungle", 'Ghost of a Chance',
lambda state: state._sc2wol_cleared_missions(world, player, 8)),
lambda state: state._sc2wol_cleared_missions(world, player, 8) and
state.has("Beat Welcome to the Jungle", player)),
connect(world, player, names, "Zero Hour", 'The Great Train Robbery',
lambda state: state._sc2wol_cleared_missions(world, player, 6)),
lambda state: state._sc2wol_cleared_missions(world, player, 6) and
state.has("Beat Zero Hour", player)),
connect(world, player, names, 'The Great Train Robbery', 'Cutthroat',
lambda state: state.has("Beat The Great Train Robbery", player)),
connect(world, player, names, 'Cutthroat', 'Engine of Destruction',
lambda state: state.has("Beat The Great Train Robbery", player)),
lambda state: state.has("Beat Cutthroat", player)),
connect(world, player, names, 'Engine of Destruction', 'Media Blitz',
lambda state: state.has("Beat Engine of Destruction", player)),
connect(world, player, names, 'Media Blitz', 'Piercing the Shroud'),
connect(world, player, names, 'The Dig', 'Whispers of Doom',),
connect(world, player, names, 'Whispers of Doom', 'A Sinister Turn'),
connect(world, player, names, 'Media Blitz', 'Piercing the Shroud',
lambda state: state.has("Beat Media Blitz", player)),
connect(world, player, names, 'The Dig', 'Whispers of Doom',
lambda state: state.has("Beat The Dig", player)),
connect(world, player, names, 'Whispers of Doom', 'A Sinister Turn',
lambda state: state.has("Beat Whispers of Doom", player)),
connect(world, player, names, 'A Sinister Turn', 'Echoes of the Future',
lambda state: state.has("Beat A Sinister Turn", player)),
connect(world, player, names, 'Echoes of the Future', 'In Utter Darkness'),
connect(world, player, names, 'Maw of the Void', 'Gates of Hell'),
connect(world, player, names, 'Gates of Hell', 'Belly of the Beast'),
connect(world, player, names, 'Gates of Hell', 'Shatter the Sky'),
connect(world, player, names, 'Echoes of the Future', 'In Utter Darkness',
lambda state: state.has("Beat Echoes of the Future", player)),
connect(world, player, names, 'Maw of the Void', 'Gates of Hell',
lambda state: state.has("Beat Maw of the Void", player)),
connect(world, player, names, 'Gates of Hell', 'Belly of the Beast',
lambda state: state.has("Beat Gates of Hell", player)),
connect(world, player, names, 'Gates of Hell', 'Shatter the Sky',
lambda state: state.has("Beat Gates of Hell", player)),
connect(world, player, names, 'Gates of Hell', 'All-In',
lambda state: state.has('Beat Gates of Hell', player) or state.has('Beat Shatter the Sky', player))
lambda state: state.has('Beat Gates of Hell', player) and (
state.has('Beat Shatter the Sky', player) or state.has('Beat Belly of the Beast', player)))
def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: Set[str]):
@ -105,10 +122,12 @@ def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames:
existingRegions.add(region.name)
if (regionNames - existingRegions):
raise Exception("Starcraft: the following regions are used in locations: {}, but no such region exists".format(regionNames - existingRegions))
raise Exception("Starcraft: the following regions are used in locations: {}, but no such region exists".format(
regionNames - existingRegions))
def create_location(player: int, location_data: LocationData, region: Region, location_cache: List[Location]) -> Location:
def create_location(player: int, location_data: LocationData, region: Region,
location_cache: List[Location]) -> Location:
location = Location(player, location_data.name, location_data.code, region)
location.access_rule = location_data.rule
@ -121,7 +140,8 @@ def create_location(player: int, location_data: LocationData, region: Region, lo
return location
def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]], location_cache: List[Location], name: str) -> Region:
def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]],
location_cache: List[Location], name: str) -> Region:
region = Region(name, RegionType.Generic, name, player)
region.world = world
@ -133,7 +153,8 @@ def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str
return region
def connect(world: MultiWorld, player: int, used_names: Dict[str, int], source: str, target: str, rule: Optional[Callable] = None):
def connect(world: MultiWorld, player: int, used_names: Dict[str, int], source: str, target: str,
rule: Optional[Callable] = None):
sourceRegion = world.get_region(source, player)
targetRegion = world.get_region(target, player)
@ -154,7 +175,7 @@ def connect(world: MultiWorld, player: int, used_names: Dict[str, int], source:
def get_locations_per_region(locations: Tuple[LocationData, ...]) -> Dict[str, List[LocationData]]:
per_region: Dict[str, List[LocationData]] = {}
per_region: Dict[str, List[LocationData]] = {}
for location in locations:
per_region.setdefault(location.region, []).append(location)