SC2: Various bugfixes (#1267)

* SC2: Fixed nondeterminism resulting from early unit placement

* SC2: Renamed "world" arguments to "multiworld"

* SC2: Fixed All-In Ground including anti-air in defense score, fixed error in beats_protoss_deathball

* SC2: Fixed No Logic using logic on Beat events

* SC2: Fixed /unfinished command failing when All-In available
This commit is contained in:
Magnemania 2022-12-11 14:46:24 -05:00 committed by GitHub
parent e3f169b4c3
commit e71ea94fe5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 290 additions and 282 deletions

View File

@ -639,6 +639,13 @@ def request_unfinished_missions(ctx: SC2Context):
_, unfinished_missions = calc_unfinished_missions(ctx, unlocks=unlocks)
# Removing All-In from location pool
final_mission = lookup_id_to_mission[ctx.final_mission]
if final_mission in unfinished_missions.keys():
message = f"Final Mission Available: {final_mission}[{ctx.final_mission}]\n" + message
if unfinished_missions[final_mission] == -1:
unfinished_missions.pop(final_mission)
message += ", ".join(f"{mark_up_mission_name(ctx, mission, unlocks)}[{ctx.mission_req_table[mission].id}] " +
mark_up_objectives(
f"[{len(unfinished_missions[mission])}/"

View File

@ -157,17 +157,17 @@ basic_units = {
'Vulture'
}
advanced_basic_units = {
advanced_basic_units = basic_units.union({
'Reaper',
'Goliath',
'Diamondback',
'Viking'
}
})
def get_basic_units(world: MultiWorld, player: int) -> typing.Set[str]:
if get_option_value(world, player, 'required_tactics') > 0:
return basic_units.union(advanced_basic_units)
def get_basic_units(multiworld: MultiWorld, player: int) -> typing.Set[str]:
if get_option_value(multiworld, player, 'required_tactics') > 0:
return advanced_basic_units
else:
return basic_units
@ -193,7 +193,7 @@ defense_ratings = {
}
zerg_defense_ratings = {
"Perdition Turret": 2,
# Bunker w/ Firebat
# Bunker w/ Firebat: 2
"Hive Mind Emulator": 3,
"Psi Disruptor": 3
}

View File

@ -18,9 +18,9 @@ class LocationData(NamedTuple):
rule: Callable = lambda state: True
def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[LocationData, ...]:
def get_locations(multiworld: 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
logic_level = get_option_value(world, player, 'required_tactics')
logic_level = get_option_value(multiworld, player, 'required_tactics')
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),
@ -30,144 +30,144 @@ 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("The Outlaws", "The Outlaws: Victory", SC2WOL_LOC_ID_OFFSET + 200,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player)),
LocationData("The Outlaws", "The Outlaws: Rebel Base", SC2WOL_LOC_ID_OFFSET + 201,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player)),
LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_defense_rating(world, player, True) >= 2 and
(logic_level > 0 or state._sc2wol_has_anti_air(world, player))),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_defense_rating(multiworld, player, True) >= 2 and
(logic_level > 0 or state._sc2wol_has_anti_air(multiworld, 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,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player)),
LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_defense_rating(world, player, True) >= 2),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_defense_rating(multiworld, player, True) >= 2),
LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400,
lambda state: state._sc2wol_has_common_unit(world, player) and
(logic_level > 0 and state._sc2wol_has_anti_air(world, player)
or state._sc2wol_has_competent_anti_air(world, player))),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
(logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player)
or state._sc2wol_has_competent_anti_air(multiworld, player))),
LocationData("Evacuation", "Evacuation: First Chysalis", SC2WOL_LOC_ID_OFFSET + 401),
LocationData("Evacuation", "Evacuation: Second Chysalis", SC2WOL_LOC_ID_OFFSET + 402,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player)),
LocationData("Evacuation", "Evacuation: Third Chysalis", SC2WOL_LOC_ID_OFFSET + 403,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player)),
LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500,
lambda state: state._sc2wol_defense_rating(world, player, True, False) >= 4 and
(state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player))),
lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 4 and
(state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))),
LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501,
lambda state: state._sc2wol_defense_rating(world, player, True, False) >= 2 and
(state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player))),
lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and
(state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))),
LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502,
lambda state: state._sc2wol_defense_rating(world, player, True, False) >= 2 and
(state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player))),
lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and
(state._sc2wol_has_common_unit(multiworld, 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_competent_anti_air(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_has_competent_anti_air(multiworld, player)),
LocationData("Safe Haven", "Safe Haven: North Nexus", SC2WOL_LOC_ID_OFFSET + 601,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_has_competent_anti_air(multiworld, player)),
LocationData("Safe Haven", "Safe Haven: East Nexus", SC2WOL_LOC_ID_OFFSET + 602,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_has_competent_anti_air(multiworld, player)),
LocationData("Safe Haven", "Safe Haven: South Nexus", SC2WOL_LOC_ID_OFFSET + 603,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_has_competent_anti_air(multiworld, 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_competent_anti_air(world, player) and
state._sc2wol_defense_rating(world, player, True) >= 3),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_has_competent_anti_air(multiworld, player) and
state._sc2wol_defense_rating(multiworld, player, True) >= 3),
LocationData("Haven's Fall", "Haven's Fall: North Hive", SC2WOL_LOC_ID_OFFSET + 701,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player) and
state._sc2wol_defense_rating(world, player, True) >= 3),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_has_competent_anti_air(multiworld, player) and
state._sc2wol_defense_rating(multiworld, player, True) >= 3),
LocationData("Haven's Fall", "Haven's Fall: East Hive", SC2WOL_LOC_ID_OFFSET + 702,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player) and
state._sc2wol_defense_rating(world, player, True) >= 3),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_has_competent_anti_air(multiworld, player) and
state._sc2wol_defense_rating(multiworld, player, True) >= 3),
LocationData("Haven's Fall", "Haven's Fall: South Hive", SC2WOL_LOC_ID_OFFSET + 703,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player) and
state._sc2wol_defense_rating(world, player, True) >= 3),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_has_competent_anti_air(multiworld, player) and
state._sc2wol_defense_rating(multiworld, player, True) >= 3),
LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800,
lambda state: state._sc2wol_has_common_unit(world, player) and
(logic_level > 0 and state._sc2wol_has_anti_air(world, player)
or state._sc2wol_has_competent_anti_air(world, player))),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
(logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player)
or state._sc2wol_has_competent_anti_air(multiworld, 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,
lambda state: state._sc2wol_has_common_unit(world, player) and
(logic_level > 0 and state._sc2wol_has_anti_air(world, player)
or state._sc2wol_has_competent_anti_air(world, player))),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
(logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player)
or state._sc2wol_has_competent_anti_air(multiworld, 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
(logic_level > 0 and state._sc2wol_has_anti_air(world, player)
or state._sc2wol_has_competent_anti_air(world, player))),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
(logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player)
or state._sc2wol_has_competent_anti_air(multiworld, player))),
LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900,
lambda state: state._sc2wol_has_anti_air(world, player) and
state._sc2wol_defense_rating(world, player, False) >= 7),
lambda state: state._sc2wol_has_anti_air(multiworld, player) and
state._sc2wol_defense_rating(multiworld, player, False) >= 7),
LocationData("The Dig", "The Dig: Left Relic", SC2WOL_LOC_ID_OFFSET + 901,
lambda state: state._sc2wol_defense_rating(world, player, False) >= 5),
lambda state: state._sc2wol_defense_rating(multiworld, player, False) >= 5),
LocationData("The Dig", "The Dig: Right Ground Relic", SC2WOL_LOC_ID_OFFSET + 902,
lambda state: state._sc2wol_defense_rating(world, player, False) >= 5),
lambda state: state._sc2wol_defense_rating(multiworld, player, False) >= 5),
LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903,
lambda state: state._sc2wol_defense_rating(world, player, False) >= 5),
lambda state: state._sc2wol_defense_rating(multiworld, player, False) >= 5),
LocationData("The Moebius Factor", "The Moebius Factor: Victory", SC2WOL_LOC_ID_OFFSET + 1000,
lambda state: state._sc2wol_has_anti_air(world, player) and
(state._sc2wol_has_air(world, player)
lambda state: state._sc2wol_has_anti_air(multiworld, player) and
(state._sc2wol_has_air(multiworld, player)
or state.has_any({'Medivac', 'Hercules'}, player)
and state._sc2wol_has_common_unit(world, player))),
and state._sc2wol_has_common_unit(multiworld, player))),
LocationData("The Moebius Factor", "The Moebius Factor: South Rescue", SC2WOL_LOC_ID_OFFSET + 1003,
lambda state: state._sc2wol_able_to_rescue(world, player)),
lambda state: state._sc2wol_able_to_rescue(multiworld, player)),
LocationData("The Moebius Factor", "The Moebius Factor: Wall Rescue", SC2WOL_LOC_ID_OFFSET + 1004,
lambda state: state._sc2wol_able_to_rescue(world, player)),
lambda state: state._sc2wol_able_to_rescue(multiworld, player)),
LocationData("The Moebius Factor", "The Moebius Factor: Mid Rescue", SC2WOL_LOC_ID_OFFSET + 1005,
lambda state: state._sc2wol_able_to_rescue(world, player)),
lambda state: state._sc2wol_able_to_rescue(multiworld, player)),
LocationData("The Moebius Factor", "The Moebius Factor: Nydus Roof Rescue", SC2WOL_LOC_ID_OFFSET + 1006,
lambda state: state._sc2wol_able_to_rescue(world, player)),
lambda state: state._sc2wol_able_to_rescue(multiworld, player)),
LocationData("The Moebius Factor", "The Moebius Factor: Alive Inside Rescue", SC2WOL_LOC_ID_OFFSET + 1007,
lambda state: state._sc2wol_able_to_rescue(world, player)),
lambda state: state._sc2wol_able_to_rescue(multiworld, player)),
LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008,
lambda state: state._sc2wol_has_anti_air(world, player) and
(state._sc2wol_has_air(world, player)
lambda state: state._sc2wol_has_anti_air(multiworld, player) and
(state._sc2wol_has_air(multiworld, player)
or state.has_any({'Medivac', 'Hercules'}, player)
and state._sc2wol_has_common_unit(world, player))),
and state._sc2wol_has_common_unit(multiworld, player))),
LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100,
lambda state: state._sc2wol_beats_protoss_deathball(world, player)),
lambda state: state._sc2wol_beats_protoss_deathball(multiworld, 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,
lambda state: state._sc2wol_beats_protoss_deathball(world, player)),
lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)),
LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104,
lambda state: state._sc2wol_beats_protoss_deathball(world, player)),
lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)),
LocationData("Maw of the Void", "Maw of the Void: Victory", SC2WOL_LOC_ID_OFFSET + 1200,
lambda state: state._sc2wol_survives_rip_field(world, player)),
lambda state: state._sc2wol_survives_rip_field(multiworld, 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,
lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(world, player)),
lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(multiworld, player)),
LocationData("Maw of the Void", "Maw of the Void: South Close Prisoners", SC2WOL_LOC_ID_OFFSET + 1203,
lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(world, player)),
lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(multiworld, player)),
LocationData("Maw of the Void", "Maw of the Void: South Far Prisoners", SC2WOL_LOC_ID_OFFSET + 1204,
lambda state: state._sc2wol_survives_rip_field(world, player)),
lambda state: state._sc2wol_survives_rip_field(multiworld, player)),
LocationData("Maw of the Void", "Maw of the Void: North Prisoners", SC2WOL_LOC_ID_OFFSET + 1205,
lambda state: state._sc2wol_survives_rip_field(world, player)),
lambda state: state._sc2wol_survives_rip_field(multiworld, player)),
LocationData("Devil's Playground", "Devil's Playground: Victory", SC2WOL_LOC_ID_OFFSET + 1300,
lambda state: logic_level > 0 or
state._sc2wol_has_anti_air(world, player) and (
state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player))),
state._sc2wol_has_anti_air(multiworld, player) and (
state._sc2wol_has_common_unit(multiworld, 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: logic_level > 0 or state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
lambda state: logic_level > 0 or state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player)),
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_competent_anti_air(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_has_competent_anti_air(multiworld, 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,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_has_competent_anti_air(multiworld, 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_competent_anti_air(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
state._sc2wol_has_competent_anti_air(multiworld, player)),
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),
@ -178,101 +178,101 @@ 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("The Great Train Robbery", "The Great Train Robbery: Victory", SC2WOL_LOC_ID_OFFSET + 1700,
lambda state: state._sc2wol_has_train_killers(world, player) and
state._sc2wol_has_anti_air(world, player)),
lambda state: state._sc2wol_has_train_killers(multiworld, player) and
state._sc2wol_has_anti_air(multiworld, 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("Cutthroat", "Cutthroat: Victory", SC2WOL_LOC_ID_OFFSET + 1800,
lambda state: state._sc2wol_has_common_unit(world, player) and
lambda state: state._sc2wol_has_common_unit(multiworld, player) and
(logic_level > 0 or state._sc2wol_has_anti_air)),
LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player)),
LocationData("Cutthroat", "Cutthroat: North Relic", SC2WOL_LOC_ID_OFFSET + 1802,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player)),
LocationData("Cutthroat", "Cutthroat: Mid Relic", SC2WOL_LOC_ID_OFFSET + 1803),
LocationData("Cutthroat", "Cutthroat: Southwest Relic", SC2WOL_LOC_ID_OFFSET + 1804,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_has_common_unit(multiworld, player)),
LocationData("Engine of Destruction", "Engine of Destruction: Victory", SC2WOL_LOC_ID_OFFSET + 1900,
lambda state: state._sc2wol_has_competent_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and
state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', 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_competent_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and
state._sc2wol_has_common_unit(multiworld, 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_competent_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and
state._sc2wol_has_common_unit(multiworld, 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_competent_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and
state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)),
LocationData("Media Blitz", "Media Blitz: Victory", SC2WOL_LOC_ID_OFFSET + 2000,
lambda state: state._sc2wol_has_competent_comp(world, player)),
lambda state: state._sc2wol_has_competent_comp(multiworld, player)),
LocationData("Media Blitz", "Media Blitz: Tower 1", SC2WOL_LOC_ID_OFFSET + 2001,
lambda state: state._sc2wol_has_competent_comp(world, player)),
lambda state: state._sc2wol_has_competent_comp(multiworld, player)),
LocationData("Media Blitz", "Media Blitz: Tower 2", SC2WOL_LOC_ID_OFFSET + 2002,
lambda state: state._sc2wol_has_competent_comp(world, player)),
lambda state: state._sc2wol_has_competent_comp(multiworld, player)),
LocationData("Media Blitz", "Media Blitz: Tower 3", SC2WOL_LOC_ID_OFFSET + 2003,
lambda state: state._sc2wol_has_competent_comp(world, player)),
lambda state: state._sc2wol_has_competent_comp(multiworld, player)),
LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004),
LocationData("Piercing the Shroud", "Piercing the Shroud: Victory", SC2WOL_LOC_ID_OFFSET + 2100,
lambda state: state._sc2wol_has_mm_upgrade(world, player)),
lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)),
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,
lambda state: state._sc2wol_has_mm_upgrade(world, player)),
lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)),
LocationData("Piercing the Shroud", "Piercing the Shroud: First Escape Relic", SC2WOL_LOC_ID_OFFSET + 2103,
lambda state: state._sc2wol_has_mm_upgrade(world, player)),
lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)),
LocationData("Piercing the Shroud", "Piercing the Shroud: Second Escape Relic", SC2WOL_LOC_ID_OFFSET + 2104,
lambda state: state._sc2wol_has_mm_upgrade(world, player)),
lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)),
LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk ", SC2WOL_LOC_ID_OFFSET + 2105,
lambda state: state._sc2wol_has_mm_upgrade(world, player)),
lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)),
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("A Sinister Turn", "A Sinister Turn: Victory", SC2WOL_LOC_ID_OFFSET + 2300,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)),
LocationData("A Sinister Turn", "A Sinister Turn: Robotics Facility", SC2WOL_LOC_ID_OFFSET + 2301,
lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(world, player)),
lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)),
LocationData("A Sinister Turn", "A Sinister Turn: Dark Shrine", SC2WOL_LOC_ID_OFFSET + 2302,
lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(world, player)),
lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)),
LocationData("A Sinister Turn", "A Sinister Turn: Templar Archives", SC2WOL_LOC_ID_OFFSET + 2303,
lambda state: state._sc2wol_has_protoss_common_units(world, player)),
lambda state: state._sc2wol_has_protoss_common_units(multiworld, player)),
LocationData("Echoes of the Future", "Echoes of the Future: Victory", SC2WOL_LOC_ID_OFFSET + 2400,
lambda state: logic_level > 0 or state._sc2wol_has_protoss_medium_units(world, player)),
lambda state: logic_level > 0 or state._sc2wol_has_protoss_medium_units(multiworld, 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: logic_level > 0 or state._sc2wol_has_protoss_common_units(world, player)),
lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)),
LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2500),
LocationData("In Utter Darkness", "In Utter Darkness: Protoss Archive", SC2WOL_LOC_ID_OFFSET + 2501,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)),
LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2502,
lambda state: state._sc2wol_has_protoss_common_units(world, player)),
lambda state: state._sc2wol_has_protoss_common_units(multiworld, player)),
LocationData("Gates of Hell", "Gates of Hell: Victory", SC2WOL_LOC_ID_OFFSET + 2600,
lambda state: state._sc2wol_has_competent_comp(world, player) and
state._sc2wol_defense_rating(world, player, True) > 6),
lambda state: state._sc2wol_has_competent_comp(multiworld, player) and
state._sc2wol_defense_rating(multiworld, player, True) > 6),
LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601,
lambda state: state._sc2wol_has_competent_comp(world, player) and
state._sc2wol_defense_rating(world, player, True) > 6),
lambda state: state._sc2wol_has_competent_comp(multiworld, player) and
state._sc2wol_defense_rating(multiworld, player, True) > 6),
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("Shatter the Sky", "Shatter the Sky: Victory", SC2WOL_LOC_ID_OFFSET + 2800,
lambda state: state._sc2wol_has_competent_comp(world, player)),
lambda state: state._sc2wol_has_competent_comp(multiworld, 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)),
lambda state: state._sc2wol_has_competent_comp(multiworld, 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)),
lambda state: state._sc2wol_has_competent_comp(multiworld, 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)),
lambda state: state._sc2wol_has_competent_comp(multiworld, 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)),
lambda state: state._sc2wol_has_competent_comp(multiworld, player)),
LocationData("Shatter the Sky", "Shatter the Sky: Leviathan", SC2WOL_LOC_ID_OFFSET + 2805,
lambda state: state._sc2wol_has_competent_comp(world, player)),
lambda state: state._sc2wol_has_competent_comp(multiworld, player)),
LocationData("All-In", "All-In: Victory", None,
lambda state: state._sc2wol_final_mission_requirements(world, player))
lambda state: state._sc2wol_final_mission_requirements(multiworld, player))
]
beat_events = []
@ -280,7 +280,8 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
for i, location_data in enumerate(location_table):
# Removing all item-based logic on No Logic
if logic_level == 2:
location_table[i] = location_data._replace(rule=Location.access_rule)
location_data = location_data._replace(rule=Location.access_rule)
location_table[i] = location_data
# Generating Beat event locations
if location_data.name.endswith((": Victory", ": Defeat")):
beat_events.append(

View File

@ -5,26 +5,26 @@ from .Items import get_basic_units, defense_ratings, zerg_defense_ratings
class SC2WoLLogic(LogicMixin):
def _sc2wol_has_common_unit(self, world: MultiWorld, player: int) -> bool:
return self.has_any(get_basic_units(world, player), player)
def _sc2wol_has_common_unit(self, multiworld: MultiWorld, player: int) -> bool:
return self.has_any(get_basic_units(multiworld, player), player)
def _sc2wol_has_air(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Viking', 'Wraith', 'Banshee'}, player) or get_option_value(world, player, 'required_tactics') > 0 \
and self.has_any({'Hercules', 'Medivac'}, player) and self._sc2wol_has_common_unit(world, player)
def _sc2wol_has_air(self, multiworld: MultiWorld, player: int) -> bool:
return self.has_any({'Viking', 'Wraith', 'Banshee'}, player) or get_option_value(multiworld, player, 'required_tactics') > 0 \
and self.has_any({'Hercules', 'Medivac'}, player) and self._sc2wol_has_common_unit(multiworld, player)
def _sc2wol_has_air_anti_air(self, world: MultiWorld, player: int) -> bool:
def _sc2wol_has_air_anti_air(self, multiworld: MultiWorld, player: int) -> bool:
return self.has('Viking', player) \
or get_option_value(world, player, 'required_tactics') > 0 and self.has('Wraith', player)
or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has('Wraith', player)
def _sc2wol_has_competent_anti_air(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Marine', 'Goliath'}, player) or self._sc2wol_has_air_anti_air(world, player)
def _sc2wol_has_competent_anti_air(self, multiworld: MultiWorld, player: int) -> bool:
return self.has_any({'Marine', 'Goliath'}, player) or self._sc2wol_has_air_anti_air(multiworld, player)
def _sc2wol_has_anti_air(self, world: MultiWorld, player: int) -> bool:
def _sc2wol_has_anti_air(self, multiworld: MultiWorld, player: int) -> bool:
return self.has_any({'Missile Turret', 'Thor', 'War Pigs', 'Spartan Company', "Hel's Angel", 'Battlecruiser', 'Wraith'}, player) \
or self._sc2wol_has_competent_anti_air(world, player) \
or get_option_value(world, player, 'required_tactics') > 0 and self.has_any({'Ghost', 'Spectre'}, player)
or self._sc2wol_has_competent_anti_air(multiworld, player) \
or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'Ghost', 'Spectre'}, player)
def _sc2wol_defense_rating(self, world: MultiWorld, player: int, zerg_enemy: bool, air_enemy: bool = True) -> bool:
def _sc2wol_defense_rating(self, multiworld: MultiWorld, player: int, zerg_enemy: bool, air_enemy: bool = True) -> bool:
defense_score = sum((defense_ratings[item] for item in defense_ratings if self.has(item, player)))
if self.has_any({'Marine', 'Marauder'}, player) and self.has('Bunker', player):
defense_score += 3
@ -35,62 +35,63 @@ class SC2WoLLogic(LogicMixin):
if not air_enemy and self.has('Missile Turret', player):
defense_score -= defense_ratings['Missile Turret']
# Advanced Tactics bumps defense rating requirements down by 2
if get_option_value(world, player, 'required_tactics') > 0:
if get_option_value(multiworld, player, 'required_tactics') > 0:
defense_score += 2
return defense_score
def _sc2wol_has_competent_comp(self, world: MultiWorld, player: int) -> bool:
def _sc2wol_has_competent_comp(self, multiworld: MultiWorld, player: int) -> bool:
return (self.has('Marine', player) or self.has('Marauder', player) and
self._sc2wol_has_competent_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_competent_anti_air(world, player) or \
self.has('Battlecruiser', player) and self._sc2wol_has_common_unit(world, player) or \
self.has('Siege Tank', player) and self._sc2wol_has_competent_anti_air(world, player)
self._sc2wol_has_competent_anti_air(multiworld, player)) and self.has_any({'Medivac', 'Medic'}, player) or \
self.has('Thor', player) or self.has("Banshee", player) and self._sc2wol_has_competent_anti_air(multiworld, player) or \
self.has('Battlecruiser', player) and self._sc2wol_has_common_unit(multiworld, player) or \
self.has('Siege Tank', player) and self._sc2wol_has_competent_anti_air(multiworld, player)
def _sc2wol_has_train_killers(self, world: MultiWorld, player: int) -> bool:
return (self.has_any({'Siege Tank', 'Diamondback', 'Marauder'}, player) or get_option_value(world, player, 'required_tactics') > 0
def _sc2wol_has_train_killers(self, multiworld: MultiWorld, player: int) -> bool:
return (self.has_any({'Siege Tank', 'Diamondback', 'Marauder'}, player) or get_option_value(multiworld, player, 'required_tactics') > 0
and self.has_all({'Reaper', "G-4 Clusterbomb"}, player) or self.has_all({'Spectre', 'Psionic Lash'}, player))
def _sc2wol_able_to_rescue(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Medivac', 'Hercules', 'Raven', 'Viking'}, player) or get_option_value(world, player, 'required_tactics') > 0
def _sc2wol_able_to_rescue(self, multiworld: MultiWorld, player: int) -> bool:
return self.has_any({'Medivac', 'Hercules', 'Raven', 'Viking'}, player) or get_option_value(multiworld, player, 'required_tactics') > 0
def _sc2wol_has_protoss_common_units(self, world: MultiWorld, player: int) -> bool:
def _sc2wol_has_protoss_common_units(self, multiworld: MultiWorld, player: int) -> bool:
return self.has_any({'Zealot', 'Immortal', 'Stalker', 'Dark Templar'}, player) \
or get_option_value(world, player, 'required_tactics') > 0 and self.has_any({'High Templar', 'Dark Templar'}, player)
or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'High Templar', 'Dark Templar'}, player)
def _sc2wol_has_protoss_medium_units(self, world: MultiWorld, player: int) -> bool:
return self._sc2wol_has_protoss_common_units(world, player) and \
def _sc2wol_has_protoss_medium_units(self, multiworld: MultiWorld, player: int) -> bool:
return self._sc2wol_has_protoss_common_units(multiworld, player) and \
self.has_any({'Stalker', 'Void Ray', 'Phoenix', 'Carrier'}, player) \
or get_option_value(world, player, 'required_tactics') > 0 and self.has_any({'High Templar', 'Dark Templar'}, player)
or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'High Templar', 'Dark Templar'}, player)
def _sc2wol_beats_protoss_deathball(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Banshee', 'Battlecruiser'}, player) and self._sc2wol_has_competent_anti_air or \
self._sc2wol_has_competent_comp(world, player) and self._sc2wol_has_air_anti_air(world, player)
def _sc2wol_beats_protoss_deathball(self, multiworld: MultiWorld, player: int) -> bool:
return self.has_any({'Banshee', 'Battlecruiser'}, player) and self._sc2wol_has_competent_anti_air(multiworld, player) or \
self._sc2wol_has_competent_comp(multiworld, player) and self._sc2wol_has_air_anti_air(multiworld, player)
def _sc2wol_has_mm_upgrade(self, world: MultiWorld, player: int) -> bool:
def _sc2wol_has_mm_upgrade(self, multiworld: MultiWorld, player: int) -> bool:
return self.has_any({"Combat Shield (Marine)", "Stabilizer Medpacks (Medic)"}, player)
def _sc2wol_survives_rip_field(self, world: MultiWorld, player: int) -> bool:
def _sc2wol_survives_rip_field(self, multiworld: MultiWorld, player: int) -> bool:
return self.has("Battlecruiser", player) or \
self._sc2wol_has_air(world, player) and \
self._sc2wol_has_competent_anti_air(world, player) and \
self._sc2wol_has_air(multiworld, player) and \
self._sc2wol_has_competent_anti_air(multiworld, player) and \
self.has("Science Vessel", player)
def _sc2wol_has_nukes(self, world: MultiWorld, player: int) -> bool:
return get_option_value(world, player, 'required_tactics') > 0 and self.has_any({'Ghost', 'Spectre'}, player)
def _sc2wol_has_nukes(self, multiworld: MultiWorld, player: int) -> bool:
return get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'Ghost', 'Spectre'}, player)
def _sc2wol_final_mission_requirements(self, world: MultiWorld, player: int):
defense_rating = self._sc2wol_defense_rating(world, player, True)
beats_kerrigan = self.has_any({'Marine', 'Banshee', 'Ghost'}, player) or get_option_value(world, player, 'required_tactics') > 0
if get_option_value(world, player, 'all_in_map') == 0:
def _sc2wol_final_mission_requirements(self, multiworld: MultiWorld, player: int):
beats_kerrigan = self.has_any({'Marine', 'Banshee', 'Ghost'}, player) or get_option_value(multiworld, player, 'required_tactics') > 0
if get_option_value(multiworld, player, 'all_in_map') == 0:
# Ground
defense_rating = self._sc2wol_defense_rating(multiworld, player, True, False)
if self.has_any({'Battlecruiser', 'Banshee'}, player):
defense_rating += 3
return defense_rating >= 12 and beats_kerrigan
else:
# Air
defense_rating = self._sc2wol_defense_rating(multiworld, player, True, True)
return defense_rating >= 8 and beats_kerrigan \
and self.has_any({'Viking', 'Battlecruiser'}, player) \
and self.has_any({'Hive Mind Emulator', 'Psi Disruptor', 'Missile Turret'}, player)
def _sc2wol_cleared_missions(self, world: MultiWorld, player: int, mission_count: int) -> bool:
def _sc2wol_cleared_missions(self, multiworld: MultiWorld, player: int, mission_count: int) -> bool:
return self.has_group("Missions", player, mission_count)

View File

@ -197,12 +197,12 @@ advanced_starting_mission_locations = {
}
def get_starting_mission_locations(world: MultiWorld, player: int) -> Set[str]:
if get_option_value(world, player, 'shuffle_no_build') or get_option_value(world, player, 'mission_order') < 2:
def get_starting_mission_locations(multiworld: MultiWorld, player: int) -> Set[str]:
if get_option_value(multiworld, player, 'shuffle_no_build') or get_option_value(multiworld, player, 'mission_order') < 2:
# Always start with a no-build mission unless explicitly relegating them
# Vanilla and Vanilla Shuffled always start with a no-build even when relegated
return no_build_starting_mission_locations
elif get_option_value(world, player, 'required_tactics') > 0:
elif get_option_value(multiworld, player, 'required_tactics') > 0:
# Advanced Tactics/No Logic add more starting missions to the pool
return {**build_starting_mission_locations, **advanced_starting_mission_locations}
else:

View File

@ -130,8 +130,8 @@ sc2wol_options: Dict[str, Option] = {
}
def get_option_value(world: MultiWorld, player: int, name: str) -> int:
option = getattr(world, name, None)
def get_option_value(multiworld: MultiWorld, player: int, name: str) -> int:
option = getattr(multiworld, name, None)
if option is None:
return 0
@ -139,8 +139,8 @@ def get_option_value(world: MultiWorld, player: int, name: str) -> int:
return int(option[player].value)
def get_option_set_value(world: MultiWorld, player: int, name: str) -> set:
option = getattr(world, name, None)
def get_option_set_value(multiworld: MultiWorld, player: int, name: str) -> set:
option = getattr(multiworld, name, None)
if option is None:
return set()

View File

@ -21,14 +21,14 @@ STARPORT_UNITS = {"Medivac", "Wraith", "Viking", "Banshee", "Battlecruiser", "He
PROTOSS_REGIONS = {"A Sinister Turn", "Echoes of the Future", "In Utter Darkness"}
def filter_missions(world: MultiWorld, player: int) -> Dict[str, List[str]]:
def filter_missions(multiworld: MultiWorld, player: int) -> Dict[str, List[str]]:
"""
Returns a semi-randomly pruned tuple of no-build, easy, medium, and hard mission sets
"""
mission_order_type = get_option_value(world, player, "mission_order")
shuffle_protoss = get_option_value(world, player, "shuffle_protoss")
excluded_missions = set(get_option_set_value(world, player, "excluded_missions"))
mission_order_type = get_option_value(multiworld, player, "mission_order")
shuffle_protoss = get_option_value(multiworld, player, "shuffle_protoss")
excluded_missions = set(get_option_set_value(multiworld, player, "excluded_missions"))
invalid_mission_names = excluded_missions.difference(vanilla_mission_req_table.keys())
if invalid_mission_names:
raise Exception("Error in locked_missions - the following are not valid mission names: " + ", ".join(invalid_mission_names))
@ -54,17 +54,17 @@ def filter_missions(world: MultiWorld, player: int) -> Dict[str, List[str]]:
excluded_missions = excluded_missions.union(PROTOSS_REGIONS)
# Replacing All-In on low mission counts
if mission_count < 14:
final_mission = world.random.choice([mission for mission in alt_final_mission_locations.keys() if mission not in excluded_missions])
final_mission = multiworld.random.choice([mission for mission in alt_final_mission_locations.keys() if mission not in excluded_missions])
excluded_missions.add(final_mission)
else:
final_mission = 'All-In'
# Yaml settings determine which missions can be placed in the first slot
mission_pools[0] = [mission for mission in get_starting_mission_locations(world, player).keys() if mission not in excluded_missions]
mission_pools[0] = [mission for mission in get_starting_mission_locations(multiworld, player).keys() if mission not in excluded_missions]
# Removing the new no-build missions from their original sets
for i in range(1, len(mission_pools)):
mission_pools[i] = [mission for mission in mission_pools[i] if mission not in excluded_missions.union(mission_pools[0])]
# If the first mission is a build mission, there may not be enough locations to reach Outbreak as a second mission
if not get_option_value(world, player, 'shuffle_no_build'):
if not get_option_value(multiworld, player, 'shuffle_no_build'):
# Swapping Outbreak and The Great Train Robbery
if "Outbreak" in mission_pools[1]:
mission_pools[1].remove("Outbreak")
@ -87,7 +87,7 @@ def filter_missions(world: MultiWorld, player: int) -> Dict[str, List[str]]:
if all(len(mission_pool) <= 1 for mission_pool in mission_pools):
raise Exception("Not enough missions available to fill the campaign on current settings. Please exclude fewer missions.")
else:
mission_pool.remove(world.random.choice(mission_pool))
mission_pool.remove(multiworld.random.choice(mission_pool))
current_count -= 1
set_cycle += 1
@ -134,7 +134,7 @@ class ValidInventory:
}
requirements = mission_requirements
cascade_keys = self.cascade_removal_map.keys()
units_always_have_upgrades = get_option_value(self.world, self.player, "units_always_have_upgrades")
units_always_have_upgrades = get_option_value(self.multiworld, self.player, "units_always_have_upgrades")
if self.min_units_per_structure > 0:
requirements.append(lambda state: state.has_units_per_structure())
@ -155,7 +155,7 @@ class ValidInventory:
if len(inventory) == 0:
raise Exception("Reduced item pool generation failed - not enough locations available to place items.")
# Select random item from removable items
item = self.world.random.choice(inventory)
item = self.multiworld.random.choice(inventory)
# Cascade removals to associated items
if item in cascade_keys:
items_to_remove = self.cascade_removal_map[item]
@ -206,10 +206,10 @@ class ValidInventory:
self._sc2wol_has_mm_upgrade = lambda world, player: SC2WoLLogic._sc2wol_has_mm_upgrade(self, world, player)
self._sc2wol_final_mission_requirements = lambda world, player: SC2WoLLogic._sc2wol_final_mission_requirements(self, world, player)
def __init__(self, world: MultiWorld, player: int,
def __init__(self, multiworld: MultiWorld, player: int,
item_pool: List[Item], existing_items: List[Item], locked_items: List[Item],
has_protoss: bool):
self.world = world
self.multiworld = multiworld
self.player = player
self.logical_inventory = set()
self.locked_items = locked_items[:]
@ -219,7 +219,7 @@ class ValidInventory:
self.item_pool = []
item_quantities: dict[str, int] = dict()
# Inventory restrictiveness based on number of missions with checks
mission_order_type = get_option_value(self.world, self.player, "mission_order")
mission_order_type = get_option_value(self.multiworld, self.player, "mission_order")
mission_count = len(mission_orders[mission_order_type]) - 1
self.min_units_per_structure = int(mission_count / 7)
min_upgrades = 1 if mission_count < 10 else 2
@ -244,12 +244,12 @@ class ValidInventory:
upgrades = get_item_upgrades(self.item_pool, item)
associated_items = [*upgrades, item]
self.cascade_removal_map[item] = associated_items
if get_option_value(world, player, "units_always_have_upgrades"):
if get_option_value(multiworld, player, "units_always_have_upgrades"):
for upgrade in upgrades:
self.cascade_removal_map[upgrade] = associated_items
def filter_items(world: MultiWorld, player: int, mission_req_table: Dict[str, MissionInfo], location_cache: List[Location],
def filter_items(multiworld: MultiWorld, player: int, mission_req_table: Dict[str, MissionInfo], location_cache: List[Location],
item_pool: List[Item], existing_items: List[Item], locked_items: List[Item]) -> List[Item]:
"""
Returns a semi-randomly pruned set of items based on number of available locations.
@ -259,7 +259,7 @@ def filter_items(world: MultiWorld, player: int, mission_req_table: Dict[str, Mi
inventory_size = len(open_locations)
has_protoss = bool(PROTOSS_REGIONS.intersection(mission_req_table.keys()))
mission_requirements = [location.access_rule for location in location_cache]
valid_inventory = ValidInventory(world, player, item_pool, existing_items, locked_items, has_protoss)
valid_inventory = ValidInventory(multiworld, player, item_pool, existing_items, locked_items, has_protoss)
valid_items = valid_inventory.generate_reduced_inventory(inventory_size, mission_requirements)
return valid_items

View File

@ -4,23 +4,22 @@ from .Locations import LocationData
from .Options import get_option_value
from .MissionTables import MissionInfo, mission_orders, vanilla_mission_req_table, alt_final_mission_locations
from .PoolFilter import filter_missions
import random
def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location])\
def create_regions(multiworld: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location])\
-> Tuple[Dict[str, MissionInfo], int, str]:
locations_per_region = get_locations_per_region(locations)
mission_order_type = get_option_value(world, player, "mission_order")
mission_order_type = get_option_value(multiworld, player, "mission_order")
mission_order = mission_orders[mission_order_type]
mission_pools = filter_missions(world, player)
mission_pools = filter_missions(multiworld, player)
final_mission = mission_pools['all_in'][0]
used_regions = [mission for mission_pool in mission_pools.values() for mission in mission_pool]
regions = [create_region(world, player, locations_per_region, location_cache, "Menu")]
regions = [create_region(multiworld, player, locations_per_region, location_cache, "Menu")]
for region_name in used_regions:
regions.append(create_region(world, player, locations_per_region, location_cache, region_name))
regions.append(create_region(multiworld, player, locations_per_region, location_cache, region_name))
# Changing the completion condition for alternate final missions into an event
if final_mission != 'All-In':
final_location = alt_final_mission_locations[final_mission]
@ -38,76 +37,76 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
if mission_order_type in (0, 1):
throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys())
world.regions += regions
multiworld.regions += regions
names: Dict[str, int] = {}
if mission_order_type == 0:
connect(world, player, names, 'Menu', 'Liberation Day'),
connect(world, player, names, 'Liberation Day', 'The Outlaws',
connect(multiworld, player, names, 'Menu', 'Liberation Day'),
connect(multiworld, player, names, 'Liberation Day', 'The Outlaws',
lambda state: state.has("Beat Liberation Day", player)),
connect(world, player, names, 'The Outlaws', 'Zero Hour',
connect(multiworld, player, names, 'The Outlaws', 'Zero Hour',
lambda state: state.has("Beat The Outlaws", player)),
connect(world, player, names, 'Zero Hour', 'Evacuation',
connect(multiworld, player, names, 'Zero Hour', 'Evacuation',
lambda state: state.has("Beat Zero Hour", player)),
connect(world, player, names, 'Evacuation', 'Outbreak',
connect(multiworld, 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
connect(multiworld, player, names, "Outbreak", "Safe Haven",
lambda state: state._sc2wol_cleared_missions(multiworld, 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
connect(multiworld, player, names, "Outbreak", "Haven's Fall",
lambda state: state._sc2wol_cleared_missions(multiworld, player, 7) and
state.has("Beat Outbreak", player)),
connect(world, player, names, 'Zero Hour', 'Smash and Grab',
connect(multiworld, 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
connect(multiworld, player, names, 'Smash and Grab', 'The Dig',
lambda state: state._sc2wol_cleared_missions(multiworld, 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
connect(multiworld, player, names, 'The Dig', 'The Moebius Factor',
lambda state: state._sc2wol_cleared_missions(multiworld, 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
connect(multiworld, player, names, 'The Moebius Factor', 'Supernova',
lambda state: state._sc2wol_cleared_missions(multiworld, player, 14) and
state.has("Beat The Moebius Factor", player)),
connect(world, player, names, 'Supernova', 'Maw of the Void',
connect(multiworld, 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
connect(multiworld, player, names, 'Zero Hour', "Devil's Playground",
lambda state: state._sc2wol_cleared_missions(multiworld, player, 4) and
state.has("Beat Zero Hour", player)),
connect(world, player, names, "Devil's Playground", 'Welcome to the Jungle',
connect(multiworld, 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
connect(multiworld, player, names, "Welcome to the Jungle", 'Breakout',
lambda state: state._sc2wol_cleared_missions(multiworld, 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
connect(multiworld, player, names, "Welcome to the Jungle", 'Ghost of a Chance',
lambda state: state._sc2wol_cleared_missions(multiworld, 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
connect(multiworld, player, names, "Zero Hour", 'The Great Train Robbery',
lambda state: state._sc2wol_cleared_missions(multiworld, player, 6) and
state.has("Beat Zero Hour", player)),
connect(world, player, names, 'The Great Train Robbery', 'Cutthroat',
connect(multiworld, 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',
connect(multiworld, player, names, 'Cutthroat', 'Engine of Destruction',
lambda state: state.has("Beat Cutthroat", player)),
connect(world, player, names, 'Engine of Destruction', 'Media Blitz',
connect(multiworld, 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(multiworld, player, names, 'Media Blitz', 'Piercing the Shroud',
lambda state: state.has("Beat Media Blitz", player)),
connect(world, player, names, 'The Dig', 'Whispers of Doom',
connect(multiworld, 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',
connect(multiworld, 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',
connect(multiworld, 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(multiworld, 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',
connect(multiworld, 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',
connect(multiworld, 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',
connect(multiworld, 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',
connect(multiworld, 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)))
@ -122,13 +121,13 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
missions.append(None)
elif mission.type == "all_in":
missions.append(final_mission)
elif mission.relegate and not get_option_value(world, player, "shuffle_no_build"):
elif mission.relegate and not get_option_value(multiworld, player, "shuffle_no_build"):
missions.append("no_build")
else:
missions.append(mission.type)
# Place Protoss Missions if we are not using ShuffleProtoss and are in Vanilla Shuffled
if get_option_value(world, player, "shuffle_protoss") == 0 and mission_order_type == 1:
if get_option_value(multiworld, player, "shuffle_protoss") == 0 and mission_order_type == 1:
missions[22] = "A Sinister Turn"
mission_pools['medium'].remove("A Sinister Turn")
missions[23] = "Echoes of the Future"
@ -157,28 +156,28 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
# Add no_build missions to the pool and fill in no_build slots
missions_to_add = mission_pools['no_build']
for slot in no_build_slots:
filler = world.random.randint(0, len(missions_to_add)-1)
filler = multiworld.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 + mission_pools['easy']
for slot in easy_slots:
filler = world.random.randint(0, len(missions_to_add) - 1)
filler = multiworld.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 + mission_pools['medium']
for slot in medium_slots:
filler = world.random.randint(0, len(missions_to_add) - 1)
filler = multiworld.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 + mission_pools['hard']
for slot in hard_slots:
filler = world.random.randint(0, len(missions_to_add) - 1)
filler = multiworld.random.randint(0, len(missions_to_add) - 1)
missions[slot] = missions_to_add.pop(filler)
@ -189,11 +188,11 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
connections = []
for connection in mission_order[i].connect_to:
if connection == -1:
connect(world, player, names, "Menu", missions[i])
connect(multiworld, player, names, "Menu", missions[i])
else:
connect(world, player, names, missions[connection], missions[i],
connect(multiworld, player, names, missions[connection], missions[i],
(lambda name, missions_req: (lambda state: state.has(f"Beat {name}", player) and
state._sc2wol_cleared_missions(world, player,
state._sc2wol_cleared_missions(multiworld, player,
missions_req)))
(missions[connection], mission_order[i].number))
connections.append(connection + 1)
@ -233,10 +232,10 @@ def create_location(player: int, location_data: LocationData, region: Region,
return location
def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]],
def create_region(multiworld: 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.multiworld = world
region.multiworld = multiworld
if name in locations_per_region:
for location_data in locations_per_region[name]:

View File

@ -48,8 +48,8 @@ class SC2WoLWorld(World):
victory_item: str
required_client_version = 0, 3, 6
def __init__(self, world: MultiWorld, player: int):
super(SC2WoLWorld, self).__init__(world, player)
def __init__(self, multiworld: MultiWorld, player: int):
super(SC2WoLWorld, self).__init__(multiworld, player)
self.location_cache = []
self.locked_locations = []
@ -63,7 +63,7 @@ class SC2WoLWorld(World):
)
def generate_basic(self):
excluded_items = get_excluded_items(self, self.multiworld, self.player)
excluded_items = get_excluded_items(self.multiworld, self.player)
starter_items = assign_starter_items(self.multiworld, self.player, excluded_items, self.locked_locations)
@ -74,7 +74,7 @@ class SC2WoLWorld(World):
self.multiworld.itempool += pool
def set_rules(self):
setup_events(self.multiworld, self.player, self.locked_locations, self.location_cache)
setup_events(self.player, self.locked_locations, self.location_cache)
self.multiworld.completion_condition[self.player] = lambda state: state.has(self.victory_item, self.player)
def get_filler_item_name(self) -> str:
@ -95,7 +95,7 @@ class SC2WoLWorld(World):
return slot_data
def setup_events(world: MultiWorld, player: int, locked_locations: typing.List[str], location_cache: typing.List[Location]):
def setup_events(player: int, locked_locations: typing.List[str], location_cache: typing.List[Location]):
for location in location_cache:
if location.address is None:
item = Item(location.name, ItemClassification.progression, None, player)
@ -105,39 +105,39 @@ def setup_events(world: MultiWorld, player: int, locked_locations: typing.List[s
location.place_locked_item(item)
def get_excluded_items(self: SC2WoLWorld, world: MultiWorld, player: int) -> Set[str]:
def get_excluded_items(multiworld: MultiWorld, player: int) -> Set[str]:
excluded_items: Set[str] = set()
if get_option_value(world, player, "upgrade_bonus") == 1:
if get_option_value(multiworld, player, "upgrade_bonus") == 1:
excluded_items.add("Ultra-Capacitors")
else:
excluded_items.add("Vanadium Plating")
if get_option_value(world, player, "bunker_upgrade") == 1:
if get_option_value(multiworld, player, "bunker_upgrade") == 1:
excluded_items.add("Shrike Turret")
else:
excluded_items.add("Fortified Bunker")
for item in world.precollected_items[player]:
for item in multiworld.precollected_items[player]:
excluded_items.add(item.name)
excluded_items_option = getattr(world, 'excluded_items', [])
excluded_items_option = getattr(multiworld, 'excluded_items', [])
excluded_items.update(excluded_items_option[player].value)
return excluded_items
def assign_starter_items(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]) -> List[Item]:
non_local_items = world.non_local_items[player].value
if get_option_value(world, player, "early_unit"):
local_basic_unit = tuple(item for item in get_basic_units(world, player) if item not in non_local_items and item not in excluded_items)
def assign_starter_items(multiworld: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]) -> List[Item]:
non_local_items = multiworld.non_local_items[player].value
if get_option_value(multiworld, player, "early_unit"):
local_basic_unit = sorted(item for item in get_basic_units(multiworld, player) if item not in non_local_items and item not in excluded_items)
if not local_basic_unit:
raise Exception("At least one basic unit must be local")
# The first world should also be the starting world
first_mission = list(world.worlds[player].mission_req_table)[0]
starting_mission_locations = get_starting_mission_locations(world, player)
first_mission = list(multiworld.worlds[player].mission_req_table)[0]
starting_mission_locations = get_starting_mission_locations(multiworld, player)
if first_mission in starting_mission_locations:
first_location = starting_mission_locations[first_mission]
elif first_mission == "In Utter Darkness":
@ -145,28 +145,28 @@ def assign_starter_items(world: MultiWorld, player: int, excluded_items: Set[str
else:
first_location = first_mission + ": Victory"
return [assign_starter_item(world, player, excluded_items, locked_locations, first_location, local_basic_unit)]
return [assign_starter_item(multiworld, player, excluded_items, locked_locations, first_location, local_basic_unit)]
else:
return []
def assign_starter_item(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str],
def assign_starter_item(multiworld: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str],
location: str, item_list: Tuple[str, ...]) -> Item:
item_name = world.random.choice(item_list)
item_name = multiworld.random.choice(item_list)
excluded_items.add(item_name)
item = create_item_with_correct_settings(world, player, item_name)
item = create_item_with_correct_settings(player, item_name)
world.get_location(location, player).place_locked_item(item)
multiworld.get_location(location, player).place_locked_item(item)
locked_locations.append(location)
return item
def get_item_pool(world: MultiWorld, player: int, mission_req_table: Dict[str, MissionInfo],
def get_item_pool(multiworld: MultiWorld, player: int, mission_req_table: Dict[str, MissionInfo],
starter_items: List[str], excluded_items: Set[str], location_cache: List[Location]) -> List[Item]:
pool: List[Item] = []
@ -174,18 +174,18 @@ def get_item_pool(world: MultiWorld, player: int, mission_req_table: Dict[str, M
locked_items = []
# YAML items
yaml_locked_items = get_option_set_value(world, player, 'locked_items')
yaml_locked_items = get_option_set_value(multiworld, player, 'locked_items')
for name, data in item_table.items():
if name not in excluded_items:
for _ in range(data.quantity):
item = create_item_with_correct_settings(world, player, name)
item = create_item_with_correct_settings(player, name)
if name in yaml_locked_items:
locked_items.append(item)
else:
pool.append(item)
existing_items = starter_items + [item for item in world.precollected_items[player]]
existing_items = starter_items + [item for item in multiworld.precollected_items[player]]
existing_names = [item.name for item in existing_items]
# Removing upgrades for excluded items
for item_name in excluded_items:
@ -195,18 +195,18 @@ def get_item_pool(world: MultiWorld, player: int, mission_req_table: Dict[str, M
for invalid_upgrade in invalid_upgrades:
pool.remove(invalid_upgrade)
filtered_pool = filter_items(world, player, mission_req_table, location_cache, pool, existing_items, locked_items)
filtered_pool = filter_items(multiworld, player, mission_req_table, location_cache, pool, existing_items, locked_items)
return filtered_pool
def fill_item_pool_with_dummy_items(self: SC2WoLWorld, world: MultiWorld, player: int, locked_locations: List[str],
def fill_item_pool_with_dummy_items(self: SC2WoLWorld, multiworld: MultiWorld, player: int, locked_locations: List[str],
location_cache: List[Location], pool: List[Item]):
for _ in range(len(location_cache) - len(locked_locations) - len(pool)):
item = create_item_with_correct_settings(world, player, self.get_filler_item_name())
item = create_item_with_correct_settings(player, self.get_filler_item_name())
pool.append(item)
def create_item_with_correct_settings(world: MultiWorld, player: int, name: str) -> Item:
def create_item_with_correct_settings(player: int, name: str) -> Item:
data = item_table[name]
item = Item(name, data.classification, data.code, player)