SC2: Option for random mission order (#569)
This commit is contained in:
parent
cec0e2cbfb
commit
e786243738
|
@ -11,6 +11,8 @@ from sc2.main import run_game
|
|||
from sc2.data import Race
|
||||
from sc2.bot_ai import BotAI
|
||||
from sc2.player import Bot
|
||||
from worlds.sc2wol.Regions import MissionInfo
|
||||
from worlds.sc2wol.MissionTables import lookup_id_to_mission
|
||||
from worlds.sc2wol.Items import lookup_id_to_name, item_table
|
||||
from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET
|
||||
|
||||
|
@ -32,6 +34,13 @@ nest_asyncio.apply()
|
|||
|
||||
class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
ctx: Context
|
||||
missions_unlocked = False
|
||||
|
||||
def _cmd_disable_mission_check(self) -> bool:
|
||||
"""Disables the check to see if a mission is available to play. Meant for co-op runs where one player can play
|
||||
the next mission in a chain the other player is doing."""
|
||||
self.missions_unlocked = True
|
||||
sc2_logger.info("Mission check has been disabled")
|
||||
|
||||
def _cmd_play(self, mission_id: str = "") -> bool:
|
||||
"""Start a Starcraft 2 mission"""
|
||||
|
@ -42,7 +51,8 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
|||
if num_options > 0:
|
||||
mission_number = int(options[0])
|
||||
|
||||
if is_mission_available(mission_number, self.ctx.checked_locations, mission_req_table):
|
||||
if self.missions_unlocked or \
|
||||
is_mission_available(mission_number, self.ctx.checked_locations, self.ctx.mission_req_table):
|
||||
if self.ctx.sc2_run_task:
|
||||
if not self.ctx.sc2_run_task.done():
|
||||
sc2_logger.warning("Starcraft 2 Client is still running!")
|
||||
|
@ -65,13 +75,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, self.ctx.ui)
|
||||
request_available_missions(self.ctx.checked_locations, self.ctx.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, self.ctx.ui)
|
||||
request_unfinished_missions(self.ctx.checked_locations, self.ctx.mission_req_table, self.ctx.ui, self.ctx)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -81,6 +91,7 @@ class Context(CommonContext):
|
|||
items_handling = 0b111
|
||||
difficulty = -1
|
||||
all_in_choice = 0
|
||||
mission_req_table = None
|
||||
items_rec_to_announce = []
|
||||
rec_announce_pos = 0
|
||||
items_sent_to_announce = []
|
||||
|
@ -102,6 +113,11 @@ class Context(CommonContext):
|
|||
if cmd in {"Connected"}:
|
||||
self.difficulty = args["slot_data"]["game_difficulty"]
|
||||
self.all_in_choice = args["slot_data"]["all_in_map"]
|
||||
slot_req_table = args["slot_data"]["mission_req"]
|
||||
self.mission_req_table = {}
|
||||
for mission in slot_req_table:
|
||||
self.mission_req_table[mission] = MissionInfo(**slot_req_table[mission])
|
||||
|
||||
if cmd in {"PrintJSON"}:
|
||||
noted = False
|
||||
if "receiving" in args:
|
||||
|
@ -224,8 +240,8 @@ async def starcraft_launch(ctx: Context, mission_id):
|
|||
|
||||
sc2_logger.info(f"Launching {lookup_id_to_mission[mission_id]}. If game does not launch check log file for errors.")
|
||||
|
||||
run_game(sc2.maps.get(maps_table[mission_id - 1]), [
|
||||
Bot(Race.Terran, ArchipelagoBot(ctx, mission_id), name="Archipelago", fullscreen=True)], realtime=True)
|
||||
run_game(sc2.maps.get(maps_table[mission_id - 1]), [Bot(Race.Terran, ArchipelagoBot(ctx, mission_id),
|
||||
name="Archipelago", fullscreen=True)], realtime=True)
|
||||
|
||||
|
||||
class ArchipelagoBot(sc2.bot_ai.BotAI):
|
||||
|
@ -302,7 +318,7 @@ class ArchipelagoBot(sc2.bot_ai.BotAI):
|
|||
game_state = int(38281 - unit.health)
|
||||
self.can_read_game = True
|
||||
|
||||
if iteration == 80 and not game_state & 1:
|
||||
if iteration == 160 and not game_state & 1:
|
||||
await self.chat_send("SendMessage Warning: Archipelago unable to connect or has lost connection to " +
|
||||
"Starcraft 2 (This is likely a map issue)")
|
||||
|
||||
|
@ -390,15 +406,6 @@ class ArchipelagoBot(sc2.bot_ai.BotAI):
|
|||
await self.chat_send("LostConnection - Lost connection to game.")
|
||||
|
||||
|
||||
class MissionInfo(typing.NamedTuple):
|
||||
id: int
|
||||
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, [], completion_critical=True),
|
||||
"The Outlaws": MissionInfo(2, 2, [1], completion_critical=True),
|
||||
|
@ -431,17 +438,17 @@ mission_req_table = {
|
|||
"All-In": MissionInfo(29, -1, [27, 28], completion_critical=True, or_requirements=True)
|
||||
}
|
||||
|
||||
lookup_id_to_mission: typing.Dict[int, str] = {
|
||||
data.id: mission_name for mission_name, data in mission_req_table.items() if data.id}
|
||||
|
||||
|
||||
def calc_objectives_completed(mission, missions_info, locations_done):
|
||||
def calc_objectives_completed(mission, missions_info, locations_done, unfinished_locations, ctx):
|
||||
objectives_complete = 0
|
||||
|
||||
if missions_info[mission].extra_locations > 0:
|
||||
for i in range(missions_info[mission].extra_locations):
|
||||
if (missions_info[mission].id * 100 + SC2WOL_LOC_ID_OFFSET + i) in locations_done:
|
||||
objectives_complete += 1
|
||||
else:
|
||||
unfinished_locations[mission].append(ctx.location_name_getter(
|
||||
missions_info[mission].id * 100 + SC2WOL_LOC_ID_OFFSET + i))
|
||||
|
||||
return objectives_complete
|
||||
|
||||
|
@ -449,31 +456,37 @@ def calc_objectives_completed(mission, missions_info, locations_done):
|
|||
return -1
|
||||
|
||||
|
||||
def request_unfinished_missions(locations_done, location_table, ui):
|
||||
message = "Unfinished Missions: "
|
||||
def request_unfinished_missions(locations_done, location_table, ui, ctx):
|
||||
if location_table:
|
||||
message = "Unfinished Missions: "
|
||||
unlocks = initialize_blank_mission_dict(location_table)
|
||||
unfinished_locations = initialize_blank_mission_dict(location_table)
|
||||
|
||||
unfinished_missions = calc_unfinished_missions(locations_done, location_table)
|
||||
unfinished_missions = calc_unfinished_missions(locations_done, location_table, unlocks, unfinished_locations, ctx)
|
||||
|
||||
message += ", ".join(f"{mark_up_mission_name(mission, location_table, ui,unlocks)}[{location_table[mission].id}] " +
|
||||
mark_up_objectives(
|
||||
f"[{unfinished_missions[mission]}/{location_table[mission].extra_locations}]",
|
||||
ctx, unfinished_locations, mission)
|
||||
for mission in unfinished_missions)
|
||||
|
||||
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)
|
||||
|
||||
if ui:
|
||||
ui.log_panels['All'].on_message_markup(message)
|
||||
ui.log_panels['Starcraft2'].on_message_markup(message)
|
||||
if ui:
|
||||
ui.log_panels['All'].on_message_markup(message)
|
||||
ui.log_panels['Starcraft2'].on_message_markup(message)
|
||||
else:
|
||||
sc2_logger.info(message)
|
||||
else:
|
||||
sc2_logger.info(message)
|
||||
sc2_logger.warning("No mission table found, you are likely not connected to a server.")
|
||||
|
||||
|
||||
def calc_unfinished_missions(locations_done, locations):
|
||||
def calc_unfinished_missions(locations_done, locations, unlocks, unfinished_locations, ctx):
|
||||
unfinished_missions = []
|
||||
locations_completed = []
|
||||
available_missions = calc_available_missions(locations_done, locations)
|
||||
available_missions = calc_available_missions(locations_done, locations, unlocks)
|
||||
|
||||
for name in available_missions:
|
||||
if not locations[name].extra_locations == -1:
|
||||
objectives_completed = calc_objectives_completed(name, locations, locations_done)
|
||||
objectives_completed = calc_objectives_completed(name, locations, locations_done, unfinished_locations, ctx)
|
||||
|
||||
if objectives_completed < locations[name].extra_locations:
|
||||
unfinished_missions.append(name)
|
||||
|
@ -492,31 +505,65 @@ 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 mark_critical(mission, location_table, ui):
|
||||
def mark_up_mission_name(mission, location_table, ui, unlock_table):
|
||||
"""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]"
|
||||
message = "[color=AF99EF]" + mission + "[/color]"
|
||||
else:
|
||||
return "*" + mission + "*"
|
||||
message = "*" + mission + "*"
|
||||
else:
|
||||
return mission
|
||||
message = mission
|
||||
|
||||
if ui:
|
||||
unlocks = unlock_table[mission]
|
||||
|
||||
if len(unlocks) > 0:
|
||||
pre_message = f"[ref={list(location_table).index(mission)}|Unlocks: "
|
||||
pre_message += ", ".join(f"{unlock}({location_table[unlock].id})" for unlock in unlocks)
|
||||
pre_message += f"]"
|
||||
message = pre_message + message + "[/ref]"
|
||||
|
||||
return message
|
||||
|
||||
|
||||
def mark_up_objectives(message, ctx, unfinished_locations, mission):
|
||||
formatted_message = message
|
||||
|
||||
if ctx.ui:
|
||||
locations = unfinished_locations[mission]
|
||||
|
||||
pre_message = f"[ref={list(ctx.mission_req_table).index(mission)+30}|"
|
||||
pre_message += "<br>".join(location for location in locations)
|
||||
pre_message += f"]"
|
||||
formatted_message = pre_message + message + "[/ref]"
|
||||
|
||||
return formatted_message
|
||||
|
||||
|
||||
def request_available_missions(locations_done, location_table, ui):
|
||||
message = "Available Missions: "
|
||||
if location_table:
|
||||
message = "Available Missions: "
|
||||
|
||||
missions = calc_available_missions(locations_done, location_table)
|
||||
message += ", ".join(f"{mark_critical(mission,location_table, ui)}[{location_table[mission].id}]" for mission in missions)
|
||||
# Initialize mission unlock table
|
||||
unlocks = initialize_blank_mission_dict(location_table)
|
||||
|
||||
if ui:
|
||||
ui.log_panels['All'].on_message_markup(message)
|
||||
ui.log_panels['Starcraft2'].on_message_markup(message)
|
||||
missions = calc_available_missions(locations_done, location_table, unlocks)
|
||||
message += \
|
||||
", ".join(f"{mark_up_mission_name(mission, location_table, ui, unlocks)}[{location_table[mission].id}]"
|
||||
for mission in missions)
|
||||
|
||||
if ui:
|
||||
ui.log_panels['All'].on_message_markup(message)
|
||||
ui.log_panels['Starcraft2'].on_message_markup(message)
|
||||
else:
|
||||
sc2_logger.info(message)
|
||||
else:
|
||||
sc2_logger.info(message)
|
||||
sc2_logger.warning("No mission table found, you are likely not connected to a server.")
|
||||
|
||||
|
||||
def calc_available_missions(locations_done, locations):
|
||||
def calc_available_missions(locations_done, locations, unlocks=None):
|
||||
available_missions = []
|
||||
missions_complete = 0
|
||||
|
||||
|
@ -526,6 +573,11 @@ def calc_available_missions(locations_done, locations):
|
|||
missions_complete += 1
|
||||
|
||||
for name in locations:
|
||||
# Go through the required missions for each mission and fill up unlock table used later for hover-over tooltips
|
||||
if unlocks:
|
||||
for unlock in locations[name].required_world:
|
||||
unlocks[list(locations)[unlock-1]].append(name)
|
||||
|
||||
if mission_reqs_completed(name, missions_complete, locations_done, locations):
|
||||
available_missions.append(name)
|
||||
|
||||
|
@ -549,14 +601,14 @@ def mission_reqs_completed(location_to_check, missions_complete, locations_done,
|
|||
req_success = True
|
||||
|
||||
# Check if required mission has been completed
|
||||
if not (req_mission * 100 + SC2WOL_LOC_ID_OFFSET) in locations_done:
|
||||
if not (locations[list(locations)[req_mission-1]].id * 100 + SC2WOL_LOC_ID_OFFSET) in locations_done:
|
||||
if not locations[location_to_check].or_requirements:
|
||||
return False
|
||||
else:
|
||||
req_success = False
|
||||
|
||||
# Recursively check required mission to see if it's requirements are met, in case !collect has been done
|
||||
if not mission_reqs_completed(lookup_id_to_mission[req_mission], missions_complete, locations_done,
|
||||
if not mission_reqs_completed(list(locations)[req_mission-1], missions_complete, locations_done,
|
||||
locations):
|
||||
if not locations[location_to_check].or_requirements:
|
||||
return False
|
||||
|
@ -581,6 +633,15 @@ def mission_reqs_completed(location_to_check, missions_complete, locations_done,
|
|||
return True
|
||||
|
||||
|
||||
def initialize_blank_mission_dict(location_table):
|
||||
unlocks = {}
|
||||
|
||||
for mission in list(location_table):
|
||||
unlocks[mission] = []
|
||||
|
||||
return unlocks
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
colorama.init()
|
||||
asyncio.run(main())
|
||||
|
|
|
@ -100,7 +100,7 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
|||
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,
|
||||
LocationData("The Moebius Factor", "The Moebius Factor: Victory", 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),
|
||||
|
@ -131,7 +131,7 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
|||
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,
|
||||
LocationData("Maw of the Void", "Maw of the Void: Victory", 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),
|
||||
|
@ -148,14 +148,14 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
|||
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,
|
||||
LocationData("Devil's Playground", "Devil's Playground: Victory", 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,
|
||||
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,
|
||||
LocationData("Welcome to the Jungle", "Welcome to the Jungle: Victory", 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),
|
||||
|
@ -168,25 +168,25 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
|||
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: Victory", 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),
|
||||
LocationData("Breakout", "Beat Breakout", None),
|
||||
LocationData("Ghost of a Chance", "Ghost of a Chance: Psi-Indoctrinator", SC2WOL_LOC_ID_OFFSET + 1600),
|
||||
LocationData("Ghost of a Chance", "Ghost of a Chance: Victory", SC2WOL_LOC_ID_OFFSET + 1600),
|
||||
LocationData("Ghost of a Chance", "Ghost of a Chance: Terrazine Tank", SC2WOL_LOC_ID_OFFSET + 1601),
|
||||
LocationData("Ghost of a Chance", "Ghost of a Chance: Jorium Stockpile", SC2WOL_LOC_ID_OFFSET + 1602),
|
||||
LocationData("Ghost of a Chance", "Ghost of a Chance: First Island Spectres", SC2WOL_LOC_ID_OFFSET + 1603),
|
||||
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,
|
||||
LocationData("The Great Train Robbery", "The Great Train Robbery: Victory", 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: Victory", 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)),
|
||||
|
@ -197,7 +197,7 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
|||
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,
|
||||
LocationData("Engine of Destruction", "Engine of Destruction: Victory", 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,
|
||||
|
@ -213,7 +213,7 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
|||
LocationData("Engine of Destruction", "Beat Engine of Destruction", None,
|
||||
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,
|
||||
LocationData("Media Blitz", "Media Blitz: Victory", 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)),
|
||||
|
@ -224,19 +224,19 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
|||
LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004),
|
||||
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: Victory", 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),
|
||||
LocationData("Piercing the Shroud", "Piercing the Shroud: First Escape Relic", SC2WOL_LOC_ID_OFFSET + 2103),
|
||||
LocationData("Piercing the Shroud", "Piercing the Shroud: Second Escape Relic", SC2WOL_LOC_ID_OFFSET + 2104),
|
||||
LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk ", SC2WOL_LOC_ID_OFFSET + 2105),
|
||||
LocationData("Piercing the Shroud", "Beat Piercing the Shroud", None),
|
||||
LocationData("Whispers of Doom", "Whispers of Doom: Void Seeker Escape", SC2WOL_LOC_ID_OFFSET + 2200),
|
||||
LocationData("Whispers of Doom", "Whispers of Doom: Victory", SC2WOL_LOC_ID_OFFSET + 2200),
|
||||
LocationData("Whispers of Doom", "Whispers of Doom: First Hatchery", SC2WOL_LOC_ID_OFFSET + 2201),
|
||||
LocationData("Whispers of Doom", "Whispers of Doom: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 2202),
|
||||
LocationData("Whispers of Doom", "Whispers of Doom: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 2203),
|
||||
LocationData("Whispers of Doom", "Beat Whispers of Doom", None),
|
||||
LocationData("A Sinister Turn", "A Sinister Turn: Preservers Freed", SC2WOL_LOC_ID_OFFSET + 2300,
|
||||
LocationData("A Sinister Turn", "A Sinister Turn: Victory", SC2WOL_LOC_ID_OFFSET + 2300,
|
||||
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
|
||||
LocationData("A Sinister Turn", "A Sinister Turn: Robotics Facility", SC2WOL_LOC_ID_OFFSET + 2301),
|
||||
LocationData("A Sinister Turn", "A Sinister Turn: Dark Shrine", SC2WOL_LOC_ID_OFFSET + 2302),
|
||||
|
@ -244,31 +244,31 @@ 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: Victory", 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,
|
||||
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,
|
||||
LocationData("In Utter Darkness", "In Utter Darkness: Defeat", 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", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2502),
|
||||
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,
|
||||
LocationData("Gates of Hell", "Gates of Hell: Victory", 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: Victory", 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: Victory", 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)),
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
from typing import NamedTuple, Dict, List
|
||||
|
||||
no_build_regions_list = ["Liberation Day", "Breakout", "Ghost of a Chance", "Piercing the Shroud", "Whispers of Doom",
|
||||
"Belly of the Beast"]
|
||||
easy_regions_list = ["The Outlaws", "Zero Hour", "Evacuation", "Outbreak", "Smash and Grab", "Devil's Playground"]
|
||||
medium_regions_list = ["Safe Haven", "Haven's Fall", "The Dig", "The Moebius Factor", "Supernova",
|
||||
"Welcome to the Jungle", "The Great Train Robbery", "Cutthroat", "Media Blitz",
|
||||
"A Sinister Turn", "Echoes of the Future"]
|
||||
hard_regions_list = ["Maw of the Void", "Engine of Destruction", "In Utter Darkness", "Gates of Hell",
|
||||
"Shatter the Sky"]
|
||||
|
||||
|
||||
class MissionInfo(NamedTuple):
|
||||
id: int
|
||||
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
|
||||
|
||||
|
||||
class FillMission(NamedTuple):
|
||||
type: str
|
||||
connect_to: List[int] # -1 connects to Menu
|
||||
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
|
||||
|
||||
|
||||
vanilla_shuffle_order = [
|
||||
FillMission("no_build", [-1], completion_critical=True),
|
||||
FillMission("easy", [0], completion_critical=True),
|
||||
FillMission("easy", [1], completion_critical=True),
|
||||
FillMission("easy", [2]),
|
||||
FillMission("medium", [3]),
|
||||
FillMission("hard", [4], number=7),
|
||||
FillMission("hard", [4], number=7),
|
||||
FillMission("easy", [2], completion_critical=True),
|
||||
FillMission("medium", [7], number=8, completion_critical=True),
|
||||
FillMission("hard", [8], number=11, completion_critical=True),
|
||||
FillMission("hard", [9], number=14, completion_critical=True),
|
||||
FillMission("hard", [10], completion_critical=True),
|
||||
FillMission("medium", [2], number=4),
|
||||
FillMission("medium", [12]),
|
||||
FillMission("hard", [13], number=8),
|
||||
FillMission("hard", [13], number=8),
|
||||
FillMission("medium", [2], number=6),
|
||||
FillMission("hard", [16]),
|
||||
FillMission("hard", [17]),
|
||||
FillMission("hard", [18]),
|
||||
FillMission("hard", [19]),
|
||||
FillMission("medium", [8]),
|
||||
FillMission("hard", [21]),
|
||||
FillMission("hard", [22]),
|
||||
FillMission("hard", [23]),
|
||||
FillMission("hard", [11], completion_critical=True),
|
||||
FillMission("hard", [25], completion_critical=True),
|
||||
FillMission("hard", [25], completion_critical=True),
|
||||
FillMission("all_in", [26, 27], completion_critical=True, or_requirements=True)
|
||||
]
|
||||
|
||||
|
||||
vanilla_mission_req_table = {
|
||||
"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], 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),
|
||||
"Ghost of a Chance": MissionInfo(16, 6, [14], number=8),
|
||||
"The Great Train Robbery": MissionInfo(17, 4, [3], number=6),
|
||||
"Cutthroat": MissionInfo(18, 5, [17]),
|
||||
"Engine of Destruction": MissionInfo(19, 6, [18]),
|
||||
"Media Blitz": MissionInfo(20, 5, [19]),
|
||||
"Piercing the Shroud": MissionInfo(21, 6, [20]),
|
||||
"Whispers of Doom": MissionInfo(22, 4, [9]),
|
||||
"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], 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: Dict[int, str] = {
|
||||
data.id: mission_name for mission_name, data in vanilla_mission_req_table.items() if data.id}
|
|
@ -1,6 +1,6 @@
|
|||
from typing import Dict
|
||||
from BaseClasses import MultiWorld
|
||||
from Options import Choice, Option
|
||||
from Options import Choice, Option, DefaultOnToggle
|
||||
|
||||
|
||||
class GameDifficulty(Choice):
|
||||
|
@ -35,12 +35,28 @@ class AllInMap(Choice):
|
|||
option_air = 1
|
||||
|
||||
|
||||
class MissionOrder(Choice):
|
||||
"""Determines the order the missions are played in.
|
||||
Vanilla: Keeps the standard mission order and branching from the WoL Campaign.
|
||||
Vanilla Shuffled: Keeps same branching paths from the WoL Campaign but randomizes the order of missions within"""
|
||||
display_name = "Mission Order"
|
||||
option_vanilla = 0
|
||||
option_vanilla_shuffled = 1
|
||||
|
||||
class ShuffleProtoss(DefaultOnToggle):
|
||||
"""Determines if the 3 protoss missions are included in the shuffle if Vanilla Shuffled is enabled. If this is
|
||||
not the 3 protoss missions will stay in their vanilla order in the mission order making them optional to complete
|
||||
the game."""
|
||||
display_name = "Shuffle Protoss Missions"
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
sc2wol_options: Dict[str, Option] = {
|
||||
"game_difficulty": GameDifficulty,
|
||||
"upgrade_bonus": UpgradeBonus,
|
||||
"bunker_upgrade": BunkerUpgrade,
|
||||
"all_in_map": AllInMap,
|
||||
"mission_order": MissionOrder,
|
||||
"shuffle_protoss": ShuffleProtoss
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
from typing import List, Set, Dict, Tuple, Optional, Callable
|
||||
from typing import List, Set, Dict, Tuple, Optional, Callable, NamedTuple
|
||||
from BaseClasses import MultiWorld, Region, Entrance, Location, RegionType
|
||||
from .Locations import LocationData
|
||||
from .Options import get_option_value
|
||||
from worlds.sc2wol.MissionTables import MissionInfo, vanilla_shuffle_order, vanilla_mission_req_table, \
|
||||
no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list
|
||||
import random
|
||||
|
||||
|
||||
def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location]):
|
||||
|
@ -46,73 +50,163 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
|||
|
||||
names: Dict[str, int] = {}
|
||||
|
||||
connect(world, player, names, 'Menu', 'Liberation Day'),
|
||||
connect(world, player, names, 'Liberation Day', 'The Outlaws',
|
||||
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.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_cleared_missions(world, player, 7) and
|
||||
state.has("Beat Outbreak", player)),
|
||||
connect(world, player, names, "Outbreak", "Haven's Fall",
|
||||
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.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.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.has("Beat The Dig", player)),
|
||||
connect(world, player, names, 'The Moebius Factor', 'Supernova',
|
||||
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) 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) 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) 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) 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 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',
|
||||
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',
|
||||
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) and (
|
||||
state.has('Beat Shatter the Sky', player) or state.has('Beat Belly of the Beast', player)))
|
||||
if get_option_value(world, player, "mission_order") == 0:
|
||||
connect(world, player, names, 'Menu', 'Liberation Day'),
|
||||
connect(world, player, names, 'Liberation Day', 'The Outlaws',
|
||||
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.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_cleared_missions(world, player, 7) and
|
||||
state.has("Beat Outbreak", player)),
|
||||
connect(world, player, names, "Outbreak", "Haven's Fall",
|
||||
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.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.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.has("Beat The Dig", player)),
|
||||
connect(world, player, names, 'The Moebius Factor', 'Supernova',
|
||||
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) 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) 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) 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) 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 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',
|
||||
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',
|
||||
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) and (
|
||||
state.has('Beat Shatter the Sky', player) or state.has('Beat Belly of the Beast', player)))
|
||||
|
||||
return vanilla_mission_req_table
|
||||
|
||||
elif get_option_value(world, player, "mission_order") == 1:
|
||||
missions = []
|
||||
no_build_pool = no_build_regions_list[:]
|
||||
easy_pool = easy_regions_list[:]
|
||||
medium_pool = medium_regions_list[:]
|
||||
hard_pool = hard_regions_list[:]
|
||||
|
||||
# Initial fill out of mission list and marking all-in mission
|
||||
for mission in vanilla_shuffle_order:
|
||||
if mission.type == "all_in":
|
||||
missions.append("All-In")
|
||||
else:
|
||||
missions.append(mission.type)
|
||||
|
||||
# Place Protoss Missions if we are not using ShuffleProtoss
|
||||
if get_option_value(world, player, "shuffle_protoss") == 0:
|
||||
missions[22] = "A Sinister Turn"
|
||||
medium_pool.remove("A Sinister Turn")
|
||||
missions[23] = "Echoes of the Future"
|
||||
medium_pool.remove("Echoes of the Future")
|
||||
missions[24] = "In Utter Darkness"
|
||||
hard_pool.remove("In Utter Darkness")
|
||||
|
||||
no_build_slots = []
|
||||
easy_slots = []
|
||||
medium_slots = []
|
||||
hard_slots = []
|
||||
|
||||
# Search through missions to find slots needed to fill
|
||||
for i in range(len(missions)):
|
||||
if missions[i] == "no_build":
|
||||
no_build_slots.append(i)
|
||||
elif missions[i] == "easy":
|
||||
easy_slots.append(i)
|
||||
elif missions[i] == "medium":
|
||||
medium_slots.append(i)
|
||||
elif missions[i] == "hard":
|
||||
hard_slots.append(i)
|
||||
|
||||
# Add no_build missions to the pool and fill in no_build slots
|
||||
missions_to_add = no_build_pool
|
||||
for slot in no_build_slots:
|
||||
filler = random.randint(0, len(missions_to_add)-1)
|
||||
|
||||
missions[slot] = missions_to_add.pop(filler)
|
||||
|
||||
# Add easy missions into pool and fill in easy slots
|
||||
missions_to_add = missions_to_add + easy_pool
|
||||
for slot in easy_slots:
|
||||
filler = random.randint(0, len(missions_to_add) - 1)
|
||||
|
||||
missions[slot] = missions_to_add.pop(filler)
|
||||
|
||||
# Add medium missions into pool and fill in medium slots
|
||||
missions_to_add = missions_to_add + medium_pool
|
||||
for slot in medium_slots:
|
||||
filler = random.randint(0, len(missions_to_add) - 1)
|
||||
|
||||
missions[slot] = missions_to_add.pop(filler)
|
||||
|
||||
# Add hard missions into pool and fill in hard slots
|
||||
missions_to_add = missions_to_add + hard_pool
|
||||
for slot in hard_slots:
|
||||
filler = random.randint(0, len(missions_to_add) - 1)
|
||||
|
||||
missions[slot] = missions_to_add.pop(filler)
|
||||
|
||||
# Loop through missions to create requirements table and connect regions
|
||||
# TODO: Handle 'and' connections
|
||||
mission_req_table = {}
|
||||
for i in range(len(missions)):
|
||||
connections = []
|
||||
for connection in vanilla_shuffle_order[i].connect_to:
|
||||
if connection == -1:
|
||||
connect(world, player, names, "Menu", missions[i])
|
||||
else:
|
||||
connect(world, player, names, missions[connection], missions[i],
|
||||
(lambda name: (lambda state: state.has(f"Beat {name}", player)))(missions[connection]))
|
||||
connections.append(connection + 1)
|
||||
|
||||
mission_req_table.update({missions[i]: MissionInfo(
|
||||
vanilla_mission_req_table[missions[i]].id, vanilla_mission_req_table[missions[i]].extra_locations,
|
||||
connections, completion_critical=vanilla_shuffle_order[i].completion_critical,
|
||||
or_requirements=vanilla_shuffle_order[i].or_requirements)})
|
||||
|
||||
return mission_req_table
|
||||
|
||||
|
||||
def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: Set[str]):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import typing
|
||||
|
||||
from typing import List, Set, Tuple
|
||||
from typing import List, Set, Tuple, NamedTuple
|
||||
from BaseClasses import Item, MultiWorld, Location, Tutorial
|
||||
from ..AutoWorld import World, WebWorld
|
||||
from .Items import StarcraftWoLItem, item_table, filler_items, item_name_groups, get_full_item_list, \
|
||||
|
@ -24,6 +24,7 @@ class Starcraft2WoLWebWorld(WebWorld):
|
|||
|
||||
tutorials = [setup]
|
||||
|
||||
|
||||
class SC2WoLWorld(World):
|
||||
"""
|
||||
StarCraft II: Wings of Liberty is a science fiction real-time strategy video game developed and published by Blizzard Entertainment.
|
||||
|
@ -40,6 +41,7 @@ class SC2WoLWorld(World):
|
|||
item_name_groups = item_name_groups
|
||||
locked_locations: typing.List[str]
|
||||
location_cache: typing.List[Location]
|
||||
mission_req_table = {}
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super(SC2WoLWorld, self).__init__(world, player)
|
||||
|
@ -55,8 +57,8 @@ class SC2WoLWorld(World):
|
|||
return StarcraftWoLItem(name, data.progression, data.code, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world, self.player, get_locations(self.world, self.player),
|
||||
self.location_cache)
|
||||
self.mission_req_table = create_regions(self.world, self.player, get_locations(self.world, self.player),
|
||||
self.location_cache)
|
||||
|
||||
def generate_basic(self):
|
||||
excluded_items = get_excluded_items(self, self.world, self.player)
|
||||
|
@ -83,6 +85,11 @@ class SC2WoLWorld(World):
|
|||
option = getattr(self.world, option_name)[self.player]
|
||||
if type(option.value) in {str, int}:
|
||||
slot_data[option_name] = int(option.value)
|
||||
slot_req_table = {}
|
||||
for mission in self.mission_req_table:
|
||||
slot_req_table[mission] = self.mission_req_table[mission]._asdict()
|
||||
|
||||
slot_data["mission_req"] = slot_req_table
|
||||
return slot_data
|
||||
|
||||
|
||||
|
@ -122,7 +129,15 @@ def assign_starter_items(world: MultiWorld, player: int, excluded_items: Set[str
|
|||
if not local_basic_unit:
|
||||
raise Exception("At least one basic unit must be local")
|
||||
|
||||
assign_starter_item(world, player, excluded_items, locked_locations, 'Liberation Day: First Statue',
|
||||
# The first world should also be the starting world
|
||||
first_location = list(world.worlds[player].mission_req_table)[0]
|
||||
|
||||
if first_location == "In Utter Darkness":
|
||||
first_location = first_location + ": Defeat"
|
||||
else:
|
||||
first_location = first_location + ": Victory"
|
||||
|
||||
assign_starter_item(world, player, excluded_items, locked_locations, first_location,
|
||||
local_basic_unit)
|
||||
|
||||
|
||||
|
@ -168,4 +183,4 @@ def create_item_with_correct_settings(world: MultiWorld, player: int, name: str)
|
|||
if not item.advancement:
|
||||
return item
|
||||
|
||||
return item
|
||||
return item
|
||||
|
|
Loading…
Reference in New Issue