SC2: New Settings, Logic improvements (#1110)
* Switched mission item group to a list comprehension to fix missile shuffle errors * Logic for reducing mission and item counts * SC2: Piercing the Shroud/Maw of the Void requirements now DRY * SC2: Logic for All-In, may need further refinement * SC2: Additional mission orders and starting locations * SC2: New Mission Order options for shorter campaigns and smaller item pools * Using location table for hardcoded starter unit * SC2: Options to curate random item pool and control early unit placement * SC2: Proper All-In logic * SC2: Grid, Mini Grid and Blitz mission orders * SC2: Required Tactics and Unit Upgrade options, better connected item handling * SC2: Client compatibility with Grid settings * SC2: Mission rando now uses world random * SC2: Alternate final missions, new logic, fixes * SC2: Handling alternate final missions, identifying final mission on client * SC2: Minor changes to handle edge-case generation failures * SC2: Removed invalid type hints for Python 3.8 * Revert "SC2: Removed invalid type hints for Python 3.8" This reverts commit 7851b9f7a39396c8ee1d85d4e4e46e61e8dc80f6. * SC2: Removed invalid type hints for Python 3.8 * SC2: Removed invalid type hints for Python 3.8 * SC2: Removed invalid type hints for Python 3.8 * SC2: Removed invalid type hints for Python 3.8 * SC2: Changed location loop to enumerate * SC2: Passing category names through slot data * SC2: Cleaned up unnecessary _create_items method * SC2: Removed vestigial extra_locations field from MissionInfo * SC2: Client backwards compatibility * SC2: Fixed item generation issue where item is present in both locked and unlocked inventories * SC2: Removed Missile Turret from defense rating on maps without air * SC2: No logic locations point to same access rule Co-authored-by: michaelasantiago <michael.alec.santiago@gmail.com> Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
This commit is contained in:
parent
d5efc71344
commit
700fe8b75e
|
@ -155,7 +155,9 @@ class SC2Context(CommonContext):
|
||||||
items_handling = 0b111
|
items_handling = 0b111
|
||||||
difficulty = -1
|
difficulty = -1
|
||||||
all_in_choice = 0
|
all_in_choice = 0
|
||||||
|
mission_order = 0
|
||||||
mission_req_table: typing.Dict[str, MissionInfo] = {}
|
mission_req_table: typing.Dict[str, MissionInfo] = {}
|
||||||
|
final_mission: int = 29
|
||||||
announcements = queue.Queue()
|
announcements = queue.Queue()
|
||||||
sc2_run_task: typing.Optional[asyncio.Task] = None
|
sc2_run_task: typing.Optional[asyncio.Task] = None
|
||||||
missions_unlocked: bool = False # allow launching missions ignoring requirements
|
missions_unlocked: bool = False # allow launching missions ignoring requirements
|
||||||
|
@ -180,9 +182,15 @@ class SC2Context(CommonContext):
|
||||||
self.difficulty = args["slot_data"]["game_difficulty"]
|
self.difficulty = args["slot_data"]["game_difficulty"]
|
||||||
self.all_in_choice = args["slot_data"]["all_in_map"]
|
self.all_in_choice = args["slot_data"]["all_in_map"]
|
||||||
slot_req_table = args["slot_data"]["mission_req"]
|
slot_req_table = args["slot_data"]["mission_req"]
|
||||||
|
# Maintaining backwards compatibility with older slot data
|
||||||
self.mission_req_table = {
|
self.mission_req_table = {
|
||||||
mission: MissionInfo(**slot_req_table[mission]) for mission in slot_req_table
|
mission: MissionInfo(
|
||||||
|
**{field: value for field, value in mission_info.items() if field in MissionInfo._fields}
|
||||||
|
)
|
||||||
|
for mission, mission_info in slot_req_table.items()
|
||||||
}
|
}
|
||||||
|
self.mission_order = args["slot_data"].get("mission_order", 0)
|
||||||
|
self.final_mission = args["slot_data"].get("final_mission", 29)
|
||||||
|
|
||||||
self.build_location_to_mission_mapping()
|
self.build_location_to_mission_mapping()
|
||||||
|
|
||||||
|
@ -304,7 +312,6 @@ class SC2Context(CommonContext):
|
||||||
self.refresh_from_launching = True
|
self.refresh_from_launching = True
|
||||||
|
|
||||||
self.mission_panel.clear_widgets()
|
self.mission_panel.clear_widgets()
|
||||||
|
|
||||||
if self.ctx.mission_req_table:
|
if self.ctx.mission_req_table:
|
||||||
self.last_checked_locations = self.ctx.checked_locations.copy()
|
self.last_checked_locations = self.ctx.checked_locations.copy()
|
||||||
self.first_check = False
|
self.first_check = False
|
||||||
|
@ -322,17 +329,20 @@ class SC2Context(CommonContext):
|
||||||
|
|
||||||
for category in categories:
|
for category in categories:
|
||||||
category_panel = MissionCategory()
|
category_panel = MissionCategory()
|
||||||
|
if category.startswith('_'):
|
||||||
|
category_display_name = ''
|
||||||
|
else:
|
||||||
|
category_display_name = category
|
||||||
category_panel.add_widget(
|
category_panel.add_widget(
|
||||||
Label(text=category, size_hint_y=None, height=50, outline_width=1))
|
Label(text=category_display_name, size_hint_y=None, height=50, outline_width=1))
|
||||||
|
|
||||||
for mission in categories[category]:
|
for mission in categories[category]:
|
||||||
text: str = mission
|
text: str = mission
|
||||||
tooltip: str = ""
|
tooltip: str = ""
|
||||||
|
mission_id: int = self.ctx.mission_req_table[mission].id
|
||||||
# Map has uncollected locations
|
# Map has uncollected locations
|
||||||
if mission in unfinished_missions:
|
if mission in unfinished_missions:
|
||||||
text = f"[color=6495ED]{text}[/color]"
|
text = f"[color=6495ED]{text}[/color]"
|
||||||
|
|
||||||
elif mission in available_missions:
|
elif mission in available_missions:
|
||||||
text = f"[color=FFFFFF]{text}[/color]"
|
text = f"[color=FFFFFF]{text}[/color]"
|
||||||
# Map requirements not met
|
# Map requirements not met
|
||||||
|
@ -351,6 +361,16 @@ class SC2Context(CommonContext):
|
||||||
remaining_location_names: typing.List[str] = [
|
remaining_location_names: typing.List[str] = [
|
||||||
self.ctx.location_names[loc] for loc in self.ctx.locations_for_mission(mission)
|
self.ctx.location_names[loc] for loc in self.ctx.locations_for_mission(mission)
|
||||||
if loc in self.ctx.missing_locations]
|
if loc in self.ctx.missing_locations]
|
||||||
|
|
||||||
|
if mission_id == self.ctx.final_mission:
|
||||||
|
if mission in available_missions:
|
||||||
|
text = f"[color=FFBC95]{mission}[/color]"
|
||||||
|
else:
|
||||||
|
text = f"[color=D0C0BE]{mission}[/color]"
|
||||||
|
if tooltip:
|
||||||
|
tooltip += "\n"
|
||||||
|
tooltip += "Final Mission"
|
||||||
|
|
||||||
if remaining_location_names:
|
if remaining_location_names:
|
||||||
if tooltip:
|
if tooltip:
|
||||||
tooltip += "\n"
|
tooltip += "\n"
|
||||||
|
@ -360,7 +380,7 @@ class SC2Context(CommonContext):
|
||||||
mission_button = MissionButton(text=text, size_hint_y=None, height=50)
|
mission_button = MissionButton(text=text, size_hint_y=None, height=50)
|
||||||
mission_button.tooltip_text = tooltip
|
mission_button.tooltip_text = tooltip
|
||||||
mission_button.bind(on_press=self.mission_callback)
|
mission_button.bind(on_press=self.mission_callback)
|
||||||
self.mission_id_to_button[self.ctx.mission_req_table[mission].id] = mission_button
|
self.mission_id_to_button[mission_id] = mission_button
|
||||||
category_panel.add_widget(mission_button)
|
category_panel.add_widget(mission_button)
|
||||||
|
|
||||||
category_panel.add_widget(Label(text=""))
|
category_panel.add_widget(Label(text=""))
|
||||||
|
@ -469,6 +489,9 @@ wol_default_categories = [
|
||||||
"Rebellion", "Rebellion", "Rebellion", "Rebellion", "Rebellion", "Prophecy", "Prophecy", "Prophecy", "Prophecy",
|
"Rebellion", "Rebellion", "Rebellion", "Rebellion", "Rebellion", "Prophecy", "Prophecy", "Prophecy", "Prophecy",
|
||||||
"Char", "Char", "Char", "Char"
|
"Char", "Char", "Char", "Char"
|
||||||
]
|
]
|
||||||
|
wol_default_category_names = [
|
||||||
|
"Mar Sara", "Colonist", "Artifact", "Covert", "Rebellion", "Prophecy", "Char"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def calculate_items(items: typing.List[NetworkItem]) -> typing.List[int]:
|
def calculate_items(items: typing.List[NetworkItem]) -> typing.List[int]:
|
||||||
|
@ -586,7 +609,7 @@ class ArchipelagoBot(sc2.bot_ai.BotAI):
|
||||||
|
|
||||||
if self.can_read_game:
|
if self.can_read_game:
|
||||||
if game_state & (1 << 1) and not self.mission_completed:
|
if game_state & (1 << 1) and not self.mission_completed:
|
||||||
if self.mission_id != 29:
|
if self.mission_id != self.ctx.final_mission:
|
||||||
print("Mission Completed")
|
print("Mission Completed")
|
||||||
await self.ctx.send_msgs(
|
await self.ctx.send_msgs(
|
||||||
[{"cmd": 'LocationChecks',
|
[{"cmd": 'LocationChecks',
|
||||||
|
@ -742,13 +765,14 @@ def calc_available_missions(ctx: SC2Context, unlocks=None):
|
||||||
return available_missions
|
return available_missions
|
||||||
|
|
||||||
|
|
||||||
def mission_reqs_completed(ctx: SC2Context, mission_name: str, missions_complete):
|
def mission_reqs_completed(ctx: SC2Context, mission_name: str, missions_complete: int):
|
||||||
"""Returns a bool signifying if the mission has all requirements complete and can be done
|
"""Returns a bool signifying if the mission has all requirements complete and can be done
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
ctx -- instance of SC2Context
|
ctx -- instance of SC2Context
|
||||||
locations_to_check -- the mission string name to check
|
locations_to_check -- the mission string name to check
|
||||||
missions_complete -- an int of how many missions have been completed
|
missions_complete -- an int of how many missions have been completed
|
||||||
|
mission_path -- a list of missions that have already been checked
|
||||||
"""
|
"""
|
||||||
if len(ctx.mission_req_table[mission_name].required_world) >= 1:
|
if len(ctx.mission_req_table[mission_name].required_world) >= 1:
|
||||||
# A check for when the requirements are being or'd
|
# A check for when the requirements are being or'd
|
||||||
|
@ -766,7 +790,18 @@ def mission_reqs_completed(ctx: SC2Context, mission_name: str, missions_complete
|
||||||
else:
|
else:
|
||||||
req_success = False
|
req_success = False
|
||||||
|
|
||||||
|
# Grid-specific logic (to avoid long path checks and infinite recursion)
|
||||||
|
if ctx.mission_order in (3, 4):
|
||||||
|
if req_success:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if req_mission is ctx.mission_req_table[mission_name].required_world[-1]:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
# Recursively check required mission to see if it's requirements are met, in case !collect has been done
|
# Recursively check required mission to see if it's requirements are met, in case !collect has been done
|
||||||
|
# Skipping recursive check on Grid settings to speed up checks and avoid infinite recursion
|
||||||
if not mission_reqs_completed(ctx, list(ctx.mission_req_table)[req_mission - 1], missions_complete):
|
if not mission_reqs_completed(ctx, list(ctx.mission_req_table)[req_mission - 1], missions_complete):
|
||||||
if not ctx.mission_req_table[mission_name].or_requirements:
|
if not ctx.mission_req_table[mission_name].or_requirements:
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from BaseClasses import Item, ItemClassification
|
from BaseClasses import Item, ItemClassification, MultiWorld
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
from .Options import get_option_value
|
||||||
from .MissionTables import vanilla_mission_req_table
|
from .MissionTables import vanilla_mission_req_table
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +11,7 @@ class ItemData(typing.NamedTuple):
|
||||||
number: typing.Optional[int]
|
number: typing.Optional[int]
|
||||||
classification: ItemClassification = ItemClassification.useful
|
classification: ItemClassification = ItemClassification.useful
|
||||||
quantity: int = 1
|
quantity: int = 1
|
||||||
|
parent_item: str = None
|
||||||
|
|
||||||
|
|
||||||
class StarcraftWoLItem(Item):
|
class StarcraftWoLItem(Item):
|
||||||
|
@ -48,51 +51,51 @@ item_table = {
|
||||||
"Progressive Ship Weapon": ItemData(105 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 8, quantity=3),
|
"Progressive Ship Weapon": ItemData(105 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 8, quantity=3),
|
||||||
"Progressive Ship Armor": ItemData(106 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 10, quantity=3),
|
"Progressive Ship Armor": ItemData(106 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 10, quantity=3),
|
||||||
|
|
||||||
"Projectile Accelerator (Bunker)": ItemData(200 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 0),
|
"Projectile Accelerator (Bunker)": ItemData(200 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 0, parent_item="Bunker"),
|
||||||
"Neosteel Bunker (Bunker)": ItemData(201 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 1),
|
"Neosteel Bunker (Bunker)": ItemData(201 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 1, parent_item="Bunker"),
|
||||||
"Titanium Housing (Missile Turret)": ItemData(202 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 2, classification=ItemClassification.filler),
|
"Titanium Housing (Missile Turret)": ItemData(202 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 2, classification=ItemClassification.filler, parent_item="Missile Turret"),
|
||||||
"Hellstorm Batteries (Missile Turret)": ItemData(203 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 3),
|
"Hellstorm Batteries (Missile Turret)": ItemData(203 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 3, parent_item="Missile Turret"),
|
||||||
"Advanced Construction (SCV)": ItemData(204 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 4),
|
"Advanced Construction (SCV)": ItemData(204 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 4, parent_item="SCV"),
|
||||||
"Dual-Fusion Welders (SCV)": ItemData(205 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 5),
|
"Dual-Fusion Welders (SCV)": ItemData(205 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 5, parent_item="SCV"),
|
||||||
"Fire-Suppression System (Building)": ItemData(206 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 6, classification=ItemClassification.filler),
|
"Fire-Suppression System (Building)": ItemData(206 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 6, classification=ItemClassification.filler),
|
||||||
"Orbital Command (Building)": ItemData(207 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 7),
|
"Orbital Command (Building)": ItemData(207 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 7),
|
||||||
"Stimpack (Marine)": ItemData(208 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 8),
|
"Stimpack (Marine)": ItemData(208 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 8, parent_item="Marine"),
|
||||||
"Combat Shield (Marine)": ItemData(209 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 9, classification=ItemClassification.progression),
|
"Combat Shield (Marine)": ItemData(209 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 9, classification=ItemClassification.progression, parent_item="Marine"),
|
||||||
"Advanced Medic Facilities (Medic)": ItemData(210 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 10, classification=ItemClassification.progression),
|
"Advanced Medic Facilities (Medic)": ItemData(210 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 10, classification=ItemClassification.progression, parent_item="Medic"),
|
||||||
"Stabilizer Medpacks (Medic)": ItemData(211 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 11, classification=ItemClassification.progression),
|
"Stabilizer Medpacks (Medic)": ItemData(211 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 11, classification=ItemClassification.progression, parent_item="Medic"),
|
||||||
"Incinerator Gauntlets (Firebat)": ItemData(212 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 12, classification=ItemClassification.filler),
|
"Incinerator Gauntlets (Firebat)": ItemData(212 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 12, classification=ItemClassification.filler, parent_item="Firebat"),
|
||||||
"Juggernaut Plating (Firebat)": ItemData(213 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 13),
|
"Juggernaut Plating (Firebat)": ItemData(213 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 13, parent_item="Firebat"),
|
||||||
"Concussive Shells (Marauder)": ItemData(214 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 14),
|
"Concussive Shells (Marauder)": ItemData(214 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 14, parent_item="Marauder"),
|
||||||
"Kinetic Foam (Marauder)": ItemData(215 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 15),
|
"Kinetic Foam (Marauder)": ItemData(215 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 15, parent_item="Marauder"),
|
||||||
"U-238 Rounds (Reaper)": ItemData(216 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 16),
|
"U-238 Rounds (Reaper)": ItemData(216 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 16, parent_item="Reaper"),
|
||||||
"G-4 Clusterbomb (Reaper)": ItemData(217 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17, classification=ItemClassification.progression),
|
"G-4 Clusterbomb (Reaper)": ItemData(217 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17, classification=ItemClassification.progression, parent_item="Reaper"),
|
||||||
|
|
||||||
"Twin-Linked Flamethrower (Hellion)": ItemData(300 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 0, classification=ItemClassification.filler),
|
"Twin-Linked Flamethrower (Hellion)": ItemData(300 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 0, classification=ItemClassification.filler, parent_item="Hellion"),
|
||||||
"Thermite Filaments (Hellion)": ItemData(301 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 1),
|
"Thermite Filaments (Hellion)": ItemData(301 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 1, parent_item="Hellion"),
|
||||||
"Cerberus Mine (Vulture)": ItemData(302 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 2, classification=ItemClassification.filler),
|
"Cerberus Mine (Vulture)": ItemData(302 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 2, classification=ItemClassification.filler, parent_item="Vulture"),
|
||||||
"Replenishable Magazine (Vulture)": ItemData(303 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 3, classification=ItemClassification.filler),
|
"Replenishable Magazine (Vulture)": ItemData(303 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 3, classification=ItemClassification.filler, parent_item="Vulture"),
|
||||||
"Multi-Lock Weapons System (Goliath)": ItemData(304 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 4),
|
"Multi-Lock Weapons System (Goliath)": ItemData(304 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 4, parent_item="Goliath"),
|
||||||
"Ares-Class Targeting System (Goliath)": ItemData(305 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 5),
|
"Ares-Class Targeting System (Goliath)": ItemData(305 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 5, parent_item="Goliath"),
|
||||||
"Tri-Lithium Power Cell (Diamondback)": ItemData(306 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 6, classification=ItemClassification.filler),
|
"Tri-Lithium Power Cell (Diamondback)": ItemData(306 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 6, classification=ItemClassification.filler, parent_item="Diamondback"),
|
||||||
"Shaped Hull (Diamondback)": ItemData(307 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 7, classification=ItemClassification.filler),
|
"Shaped Hull (Diamondback)": ItemData(307 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 7, classification=ItemClassification.filler, parent_item="Diamondback"),
|
||||||
"Maelstrom Rounds (Siege Tank)": ItemData(308 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 8),
|
"Maelstrom Rounds (Siege Tank)": ItemData(308 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 8, classification=ItemClassification.progression, parent_item="Siege Tank"),
|
||||||
"Shaped Blast (Siege Tank)": ItemData(309 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 9),
|
"Shaped Blast (Siege Tank)": ItemData(309 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 9, parent_item="Siege Tank"),
|
||||||
"Rapid Deployment Tube (Medivac)": ItemData(310 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 10, classification=ItemClassification.filler),
|
"Rapid Deployment Tube (Medivac)": ItemData(310 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 10, classification=ItemClassification.filler, parent_item="Medivac"),
|
||||||
"Advanced Healing AI (Medivac)": ItemData(311 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 11, classification=ItemClassification.filler),
|
"Advanced Healing AI (Medivac)": ItemData(311 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 11, classification=ItemClassification.filler, parent_item="Medivac"),
|
||||||
"Tomahawk Power Cells (Wraith)": ItemData(312 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 12, classification=ItemClassification.filler),
|
"Tomahawk Power Cells (Wraith)": ItemData(312 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 12, classification=ItemClassification.filler, parent_item="Wraith"),
|
||||||
"Displacement Field (Wraith)": ItemData(313 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 13, classification=ItemClassification.filler),
|
"Displacement Field (Wraith)": ItemData(313 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 13, classification=ItemClassification.filler, parent_item="Wraith"),
|
||||||
"Ripwave Missiles (Viking)": ItemData(314 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 14),
|
"Ripwave Missiles (Viking)": ItemData(314 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 14, parent_item="Viking"),
|
||||||
"Phobos-Class Weapons System (Viking)": ItemData(315 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 15),
|
"Phobos-Class Weapons System (Viking)": ItemData(315 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 15, parent_item="Viking"),
|
||||||
"Cross-Spectrum Dampeners (Banshee)": ItemData(316 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 16, classification=ItemClassification.filler),
|
"Cross-Spectrum Dampeners (Banshee)": ItemData(316 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 16, classification=ItemClassification.filler, parent_item="Banshee"),
|
||||||
"Shockwave Missile Battery (Banshee)": ItemData(317 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 17),
|
"Shockwave Missile Battery (Banshee)": ItemData(317 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 17, parent_item="Banshee"),
|
||||||
"Missile Pods (Battlecruiser)": ItemData(318 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 18, classification=ItemClassification.filler),
|
"Missile Pods (Battlecruiser)": ItemData(318 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 18, classification=ItemClassification.filler, parent_item="Battlecruiser"),
|
||||||
"Defensive Matrix (Battlecruiser)": ItemData(319 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 19, classification=ItemClassification.filler),
|
"Defensive Matrix (Battlecruiser)": ItemData(319 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 19, classification=ItemClassification.filler, parent_item="Battlecruiser"),
|
||||||
"Ocular Implants (Ghost)": ItemData(320 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 20),
|
"Ocular Implants (Ghost)": ItemData(320 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 20, parent_item="Ghost"),
|
||||||
"Crius Suit (Ghost)": ItemData(321 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 21),
|
"Crius Suit (Ghost)": ItemData(321 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 21, parent_item="Ghost"),
|
||||||
"Psionic Lash (Spectre)": ItemData(322 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 22, classification=ItemClassification.progression),
|
"Psionic Lash (Spectre)": ItemData(322 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 22, classification=ItemClassification.progression, parent_item="Spectre"),
|
||||||
"Nyx-Class Cloaking Module (Spectre)": ItemData(323 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 23),
|
"Nyx-Class Cloaking Module (Spectre)": ItemData(323 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 23, parent_item="Spectre"),
|
||||||
"330mm Barrage Cannon (Thor)": ItemData(324 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 24, classification=ItemClassification.filler),
|
"330mm Barrage Cannon (Thor)": ItemData(324 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 24, classification=ItemClassification.filler, parent_item="Thor"),
|
||||||
"Immortality Protocol (Thor)": ItemData(325 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 25, classification=ItemClassification.filler),
|
"Immortality Protocol (Thor)": ItemData(325 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 25, classification=ItemClassification.filler, parent_item="Thor"),
|
||||||
|
|
||||||
"Bunker": ItemData(400 + SC2WOL_ITEM_ID_OFFSET, "Building", 0, classification=ItemClassification.progression),
|
"Bunker": ItemData(400 + SC2WOL_ITEM_ID_OFFSET, "Building", 0, classification=ItemClassification.progression),
|
||||||
"Missile Turret": ItemData(401 + SC2WOL_ITEM_ID_OFFSET, "Building", 1, classification=ItemClassification.progression),
|
"Missile Turret": ItemData(401 + SC2WOL_ITEM_ID_OFFSET, "Building", 1, classification=ItemClassification.progression),
|
||||||
|
@ -117,16 +120,16 @@ item_table = {
|
||||||
"Science Vessel": ItemData(607 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 7, classification=ItemClassification.progression),
|
"Science Vessel": ItemData(607 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 7, classification=ItemClassification.progression),
|
||||||
"Tech Reactor": ItemData(608 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 8),
|
"Tech Reactor": ItemData(608 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 8),
|
||||||
"Orbital Strike": ItemData(609 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 9),
|
"Orbital Strike": ItemData(609 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 9),
|
||||||
"Shrike Turret": ItemData(610 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 10),
|
"Shrike Turret": ItemData(610 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 10, parent_item="Bunker"),
|
||||||
"Fortified Bunker": ItemData(611 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 11),
|
"Fortified Bunker": ItemData(611 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 11, parent_item="Bunker"),
|
||||||
"Planetary Fortress": ItemData(612 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 12),
|
"Planetary Fortress": ItemData(612 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 12, classification=ItemClassification.progression),
|
||||||
"Perdition Turret": ItemData(613 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 13),
|
"Perdition Turret": ItemData(613 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 13, classification=ItemClassification.progression),
|
||||||
"Predator": ItemData(614 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 14, classification=ItemClassification.filler),
|
"Predator": ItemData(614 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 14, classification=ItemClassification.filler),
|
||||||
"Hercules": ItemData(615 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 15, classification=ItemClassification.progression),
|
"Hercules": ItemData(615 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 15, classification=ItemClassification.progression),
|
||||||
"Cellular Reactor": ItemData(616 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 16, classification=ItemClassification.filler),
|
"Cellular Reactor": ItemData(616 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 16, classification=ItemClassification.filler),
|
||||||
"Regenerative Bio-Steel": ItemData(617 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 17, classification=ItemClassification.filler),
|
"Regenerative Bio-Steel": ItemData(617 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 17, classification=ItemClassification.filler),
|
||||||
"Hive Mind Emulator": ItemData(618 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 18),
|
"Hive Mind Emulator": ItemData(618 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 18, ItemClassification.progression),
|
||||||
"Psi Disrupter": ItemData(619 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 19, classification=ItemClassification.filler),
|
"Psi Disrupter": ItemData(619 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 19, classification=ItemClassification.progression),
|
||||||
|
|
||||||
"Zealot": ItemData(700 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 0, classification=ItemClassification.progression),
|
"Zealot": ItemData(700 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 0, classification=ItemClassification.progression),
|
||||||
"Stalker": ItemData(701 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 1, classification=ItemClassification.progression),
|
"Stalker": ItemData(701 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 1, classification=ItemClassification.progression),
|
||||||
|
@ -141,15 +144,33 @@ item_table = {
|
||||||
"+15 Starting Minerals": ItemData(800 + SC2WOL_ITEM_ID_OFFSET, "Minerals", 15, quantity=0, classification=ItemClassification.filler),
|
"+15 Starting Minerals": ItemData(800 + SC2WOL_ITEM_ID_OFFSET, "Minerals", 15, quantity=0, classification=ItemClassification.filler),
|
||||||
"+15 Starting Vespene": ItemData(801 + SC2WOL_ITEM_ID_OFFSET, "Vespene", 15, quantity=0, classification=ItemClassification.filler),
|
"+15 Starting Vespene": ItemData(801 + SC2WOL_ITEM_ID_OFFSET, "Vespene", 15, quantity=0, classification=ItemClassification.filler),
|
||||||
"+2 Starting Supply": ItemData(802 + SC2WOL_ITEM_ID_OFFSET, "Supply", 2, quantity=0, classification=ItemClassification.filler),
|
"+2 Starting Supply": ItemData(802 + SC2WOL_ITEM_ID_OFFSET, "Supply", 2, quantity=0, classification=ItemClassification.filler),
|
||||||
|
|
||||||
|
# "Keystone Piece": ItemData(850 + SC2WOL_ITEM_ID_OFFSET, "Goal", 0, quantity=0, classification=ItemClassification.progression_skip_balancing)
|
||||||
}
|
}
|
||||||
|
|
||||||
basic_unit: typing.Tuple[str, ...] = (
|
|
||||||
|
basic_units = {
|
||||||
'Marine',
|
'Marine',
|
||||||
'Marauder',
|
'Marauder',
|
||||||
'Firebat',
|
'Firebat',
|
||||||
'Hellion',
|
'Hellion',
|
||||||
'Vulture'
|
'Vulture'
|
||||||
)
|
}
|
||||||
|
|
||||||
|
advanced_basic_units = {
|
||||||
|
'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)
|
||||||
|
else:
|
||||||
|
return basic_units
|
||||||
|
|
||||||
|
|
||||||
item_name_groups = {}
|
item_name_groups = {}
|
||||||
for item, data in item_table.items():
|
for item, data in item_table.items():
|
||||||
|
@ -161,6 +182,22 @@ filler_items: typing.Tuple[str, ...] = (
|
||||||
'+15 Starting Vespene'
|
'+15 Starting Vespene'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
defense_ratings = {
|
||||||
|
"Siege Tank": 5,
|
||||||
|
"Maelstrom Rounds": 2,
|
||||||
|
"Planetary Fortress": 3,
|
||||||
|
# Bunker w/ Marine/Marauder: 3,
|
||||||
|
"Perdition Turret": 2,
|
||||||
|
"Missile Turret": 2,
|
||||||
|
"Vulture": 2
|
||||||
|
}
|
||||||
|
zerg_defense_ratings = {
|
||||||
|
"Perdition Turret": 2,
|
||||||
|
# Bunker w/ Firebat
|
||||||
|
"Hive Mind Emulator": 3,
|
||||||
|
"Psi Disruptor": 3
|
||||||
|
}
|
||||||
|
|
||||||
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in get_full_item_list().items() if
|
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in get_full_item_list().items() if
|
||||||
data.code}
|
data.code}
|
||||||
# Map type to expected int
|
# Map type to expected int
|
||||||
|
@ -176,4 +213,5 @@ type_flaggroups: typing.Dict[str, int] = {
|
||||||
"Minerals": 8,
|
"Minerals": 8,
|
||||||
"Vespene": 9,
|
"Vespene": 9,
|
||||||
"Supply": 10,
|
"Supply": 10,
|
||||||
|
"Goal": 11
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from typing import List, Tuple, Optional, Callable, NamedTuple
|
from typing import List, Tuple, Optional, Callable, NamedTuple
|
||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld
|
||||||
|
from .Options import get_option_value
|
||||||
|
|
||||||
from BaseClasses import Location
|
from BaseClasses import Location
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ class LocationData(NamedTuple):
|
||||||
|
|
||||||
def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[LocationData, ...]:
|
def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[LocationData, ...]:
|
||||||
# Note: rules which are ended with or True are rules identified as needed later when restricted units is an option
|
# 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')
|
||||||
location_table: List[LocationData] = [
|
location_table: List[LocationData] = [
|
||||||
LocationData("Liberation Day", "Liberation Day: Victory", SC2WOL_LOC_ID_OFFSET + 100),
|
LocationData("Liberation Day", "Liberation Day: Victory", SC2WOL_LOC_ID_OFFSET + 100),
|
||||||
LocationData("Liberation Day", "Liberation Day: First Statue", SC2WOL_LOC_ID_OFFSET + 101),
|
LocationData("Liberation Day", "Liberation Day: First Statue", SC2WOL_LOC_ID_OFFSET + 101),
|
||||||
|
@ -32,26 +34,33 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
||||||
LocationData("The Outlaws", "The Outlaws: Rebel Base", SC2WOL_LOC_ID_OFFSET + 201,
|
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(world, player)),
|
||||||
LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300,
|
LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player)),
|
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))),
|
||||||
LocationData("Zero Hour", "Zero Hour: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 301),
|
LocationData("Zero Hour", "Zero Hour: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 301),
|
||||||
LocationData("Zero Hour", "Zero Hour: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 302,
|
LocationData("Zero Hour", "Zero Hour: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 302,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player)),
|
lambda state: state._sc2wol_has_common_unit(world, player)),
|
||||||
LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303,
|
LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player)),
|
lambda state: state._sc2wol_has_common_unit(world, player) and
|
||||||
|
state._sc2wol_defense_rating(world, player, True) >= 2),
|
||||||
LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400,
|
LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) and
|
lambda state: state._sc2wol_has_common_unit(world, player) and
|
||||||
state._sc2wol_has_competent_anti_air(world, player)),
|
(logic_level > 0 and state._sc2wol_has_anti_air(world, player)
|
||||||
|
or state._sc2wol_has_competent_anti_air(world, player))),
|
||||||
LocationData("Evacuation", "Evacuation: First Chysalis", SC2WOL_LOC_ID_OFFSET + 401),
|
LocationData("Evacuation", "Evacuation: First Chysalis", SC2WOL_LOC_ID_OFFSET + 401),
|
||||||
LocationData("Evacuation", "Evacuation: Second Chysalis", SC2WOL_LOC_ID_OFFSET + 402,
|
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(world, player)),
|
||||||
LocationData("Evacuation", "Evacuation: Third Chysalis", SC2WOL_LOC_ID_OFFSET + 403,
|
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(world, player)),
|
||||||
LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500,
|
LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
|
lambda state: state._sc2wol_defense_rating(world, player, True, False) >= 4 and
|
||||||
|
(state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player))),
|
||||||
LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501,
|
LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
|
lambda state: state._sc2wol_defense_rating(world, player, True, False) >= 2 and
|
||||||
|
(state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player))),
|
||||||
LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502,
|
LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
|
lambda state: state._sc2wol_defense_rating(world, player, True, False) >= 2 and
|
||||||
|
(state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player))),
|
||||||
LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600,
|
LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) and
|
lambda state: state._sc2wol_has_common_unit(world, player) and
|
||||||
state._sc2wol_has_competent_anti_air(world, player)),
|
state._sc2wol_has_competent_anti_air(world, player)),
|
||||||
|
@ -66,38 +75,48 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
||||||
state._sc2wol_has_competent_anti_air(world, player)),
|
state._sc2wol_has_competent_anti_air(world, player)),
|
||||||
LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700,
|
LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) and
|
lambda state: state._sc2wol_has_common_unit(world, player) and
|
||||||
state._sc2wol_has_competent_anti_air(world, player)),
|
state._sc2wol_has_competent_anti_air(world, player) and
|
||||||
|
state._sc2wol_defense_rating(world, player, True) >= 3),
|
||||||
LocationData("Haven's Fall", "Haven's Fall: North Hive", SC2WOL_LOC_ID_OFFSET + 701,
|
LocationData("Haven's Fall", "Haven's Fall: North Hive", SC2WOL_LOC_ID_OFFSET + 701,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) and
|
lambda state: state._sc2wol_has_common_unit(world, player) and
|
||||||
state._sc2wol_has_competent_anti_air(world, player)),
|
state._sc2wol_has_competent_anti_air(world, player) and
|
||||||
|
state._sc2wol_defense_rating(world, player, True) >= 3),
|
||||||
LocationData("Haven's Fall", "Haven's Fall: East Hive", SC2WOL_LOC_ID_OFFSET + 702,
|
LocationData("Haven's Fall", "Haven's Fall: East Hive", SC2WOL_LOC_ID_OFFSET + 702,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) and
|
lambda state: state._sc2wol_has_common_unit(world, player) and
|
||||||
state._sc2wol_has_competent_anti_air(world, player)),
|
state._sc2wol_has_competent_anti_air(world, player) and
|
||||||
|
state._sc2wol_defense_rating(world, player, True) >= 3),
|
||||||
LocationData("Haven's Fall", "Haven's Fall: South Hive", SC2WOL_LOC_ID_OFFSET + 703,
|
LocationData("Haven's Fall", "Haven's Fall: South Hive", SC2WOL_LOC_ID_OFFSET + 703,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) and
|
lambda state: state._sc2wol_has_common_unit(world, player) and
|
||||||
state._sc2wol_has_competent_anti_air(world, player)),
|
state._sc2wol_has_competent_anti_air(world, player) and
|
||||||
|
state._sc2wol_defense_rating(world, player, True) >= 3),
|
||||||
LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800,
|
LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) and
|
lambda state: state._sc2wol_has_common_unit(world, player) and
|
||||||
state._sc2wol_has_competent_anti_air(world, player)),
|
(logic_level > 0 and state._sc2wol_has_anti_air(world, player)
|
||||||
|
or state._sc2wol_has_competent_anti_air(world, player))),
|
||||||
LocationData("Smash and Grab", "Smash and Grab: First Relic", SC2WOL_LOC_ID_OFFSET + 801),
|
LocationData("Smash and Grab", "Smash and Grab: 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: Second Relic", SC2WOL_LOC_ID_OFFSET + 802),
|
||||||
LocationData("Smash and Grab", "Smash and Grab: Third Relic", SC2WOL_LOC_ID_OFFSET + 803,
|
LocationData("Smash and Grab", "Smash and Grab: Third Relic", SC2WOL_LOC_ID_OFFSET + 803,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player)),
|
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))),
|
||||||
LocationData("Smash and Grab", "Smash and Grab: Fourth Relic", SC2WOL_LOC_ID_OFFSET + 804,
|
LocationData("Smash and Grab", "Smash and Grab: Fourth Relic", SC2WOL_LOC_ID_OFFSET + 804,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) and
|
lambda state: state._sc2wol_has_common_unit(world, player) and
|
||||||
state._sc2wol_has_anti_air(world, player)),
|
(logic_level > 0 and state._sc2wol_has_anti_air(world, player)
|
||||||
|
or state._sc2wol_has_competent_anti_air(world, player))),
|
||||||
LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900,
|
LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) and
|
lambda state: state._sc2wol_has_anti_air(world, player) and
|
||||||
state._sc2wol_has_anti_air(world, player) and
|
state._sc2wol_defense_rating(world, player, False) >= 7),
|
||||||
state._sc2wol_has_heavy_defense(world, player)),
|
|
||||||
LocationData("The Dig", "The Dig: Left Relic", SC2WOL_LOC_ID_OFFSET + 901,
|
LocationData("The Dig", "The Dig: Left Relic", SC2WOL_LOC_ID_OFFSET + 901,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player)),
|
lambda state: state._sc2wol_defense_rating(world, player, False) >= 5),
|
||||||
LocationData("The Dig", "The Dig: Right Ground Relic", SC2WOL_LOC_ID_OFFSET + 902,
|
LocationData("The Dig", "The Dig: Right Ground Relic", SC2WOL_LOC_ID_OFFSET + 902,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player)),
|
lambda state: state._sc2wol_defense_rating(world, player, False) >= 5),
|
||||||
LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903,
|
LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player)),
|
lambda state: state._sc2wol_defense_rating(world, player, False) >= 5),
|
||||||
LocationData("The Moebius Factor", "The Moebius Factor: Victory", 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) and state._sc2wol_has_anti_air(world, player)),
|
lambda state: state._sc2wol_has_anti_air(world, player) and
|
||||||
|
(state._sc2wol_has_air(world, player)
|
||||||
|
or state.has_any({'Medivac', 'Hercules'}, player)
|
||||||
|
and state._sc2wol_has_common_unit(world, player))),
|
||||||
LocationData("The Moebius Factor", "The Moebius Factor: South Rescue", SC2WOL_LOC_ID_OFFSET + 1003,
|
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(world, player)),
|
||||||
LocationData("The Moebius Factor", "The Moebius Factor: Wall Rescue", SC2WOL_LOC_ID_OFFSET + 1004,
|
LocationData("The Moebius Factor", "The Moebius Factor: Wall Rescue", SC2WOL_LOC_ID_OFFSET + 1004,
|
||||||
|
@ -109,7 +128,10 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
||||||
LocationData("The Moebius Factor", "The Moebius Factor: Alive Inside Rescue", SC2WOL_LOC_ID_OFFSET + 1007,
|
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(world, player)),
|
||||||
LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008,
|
LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008,
|
||||||
lambda state: state._sc2wol_has_air(world, player)),
|
lambda state: state._sc2wol_has_anti_air(world, player) and
|
||||||
|
(state._sc2wol_has_air(world, player)
|
||||||
|
or state.has_any({'Medivac', 'Hercules'}, player)
|
||||||
|
and state._sc2wol_has_common_unit(world, player))),
|
||||||
LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100,
|
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(world, player)),
|
||||||
LocationData("Supernova", "Supernova: West Relic", SC2WOL_LOC_ID_OFFSET + 1101),
|
LocationData("Supernova", "Supernova: West Relic", SC2WOL_LOC_ID_OFFSET + 1101),
|
||||||
|
@ -119,37 +141,23 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
||||||
LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104,
|
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(world, player)),
|
||||||
LocationData("Maw of the Void", "Maw of the Void: Victory", 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
|
lambda state: state._sc2wol_survives_rip_field(world, player)),
|
||||||
state._sc2wol_has_air(world, player) and
|
|
||||||
state._sc2wol_has_competent_anti_air(world, player) and
|
|
||||||
state.has('Science Vessel', 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: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1201),
|
||||||
LocationData("Maw of the Void", "Maw of the Void: Expansion Prisoners", SC2WOL_LOC_ID_OFFSET + 1202,
|
LocationData("Maw of the Void", "Maw of the Void: Expansion Prisoners", SC2WOL_LOC_ID_OFFSET + 1202,
|
||||||
lambda state: state.has('Battlecruiser', player) or
|
lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(world, player)),
|
||||||
state._sc2wol_has_air(world, player) and
|
|
||||||
state._sc2wol_has_competent_anti_air(world, player) and
|
|
||||||
state.has('Science Vessel', player)),
|
|
||||||
LocationData("Maw of the Void", "Maw of the Void: South Close Prisoners", SC2WOL_LOC_ID_OFFSET + 1203,
|
LocationData("Maw of the Void", "Maw of the Void: South Close Prisoners", SC2WOL_LOC_ID_OFFSET + 1203,
|
||||||
lambda state: state.has('Battlecruiser', player) or
|
lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(world, player)),
|
||||||
state._sc2wol_has_air(world, player) and
|
|
||||||
state._sc2wol_has_competent_anti_air(world, player) and
|
|
||||||
state.has('Science Vessel', player)),
|
|
||||||
LocationData("Maw of the Void", "Maw of the Void: South Far Prisoners", SC2WOL_LOC_ID_OFFSET + 1204,
|
LocationData("Maw of the Void", "Maw of the Void: South Far Prisoners", SC2WOL_LOC_ID_OFFSET + 1204,
|
||||||
lambda state: state.has('Battlecruiser', player) or
|
lambda state: state._sc2wol_survives_rip_field(world, player)),
|
||||||
state._sc2wol_has_air(world, player) and
|
|
||||||
state._sc2wol_has_competent_anti_air(world, player) and
|
|
||||||
state.has('Science Vessel', player)),
|
|
||||||
LocationData("Maw of the Void", "Maw of the Void: North Prisoners", SC2WOL_LOC_ID_OFFSET + 1205,
|
LocationData("Maw of the Void", "Maw of the Void: North Prisoners", SC2WOL_LOC_ID_OFFSET + 1205,
|
||||||
lambda state: state.has('Battlecruiser', player) or
|
lambda state: state._sc2wol_survives_rip_field(world, player)),
|
||||||
state._sc2wol_has_air(world, player) and
|
|
||||||
state._sc2wol_has_competent_anti_air(world, player) and
|
|
||||||
state.has('Science Vessel', player)),
|
|
||||||
LocationData("Devil's Playground", "Devil's Playground: Victory", SC2WOL_LOC_ID_OFFSET + 1300,
|
LocationData("Devil's Playground", "Devil's Playground: Victory", SC2WOL_LOC_ID_OFFSET + 1300,
|
||||||
lambda state: state._sc2wol_has_anti_air(world, player) and (
|
lambda state: logic_level > 0 or
|
||||||
state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player))),
|
state._sc2wol_has_anti_air(world, player) and (
|
||||||
|
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: Tosh's Miners", SC2WOL_LOC_ID_OFFSET + 1301),
|
||||||
LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302,
|
LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
|
lambda state: logic_level > 0 or state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
|
||||||
LocationData("Welcome to the Jungle", "Welcome to the Jungle: Victory", 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
|
lambda state: state._sc2wol_has_common_unit(world, player) and
|
||||||
state._sc2wol_has_competent_anti_air(world, player)),
|
state._sc2wol_has_competent_anti_air(world, player)),
|
||||||
|
@ -176,7 +184,8 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
||||||
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: 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", "The Great Train Robbery: South Defiler", SC2WOL_LOC_ID_OFFSET + 1703),
|
||||||
LocationData("Cutthroat", "Cutthroat: Victory", SC2WOL_LOC_ID_OFFSET + 1800,
|
LocationData("Cutthroat", "Cutthroat: Victory", SC2WOL_LOC_ID_OFFSET + 1800,
|
||||||
lambda state: state._sc2wol_has_common_unit(world, player)),
|
lambda state: state._sc2wol_has_common_unit(world, player) and
|
||||||
|
(logic_level > 0 or state._sc2wol_has_anti_air)),
|
||||||
LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801,
|
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(world, player)),
|
||||||
LocationData("Cutthroat", "Cutthroat: North Relic", SC2WOL_LOC_ID_OFFSET + 1802,
|
LocationData("Cutthroat", "Cutthroat: North Relic", SC2WOL_LOC_ID_OFFSET + 1802,
|
||||||
|
@ -208,40 +217,44 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
||||||
lambda state: state._sc2wol_has_competent_comp(world, player)),
|
lambda state: state._sc2wol_has_competent_comp(world, player)),
|
||||||
LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004),
|
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,
|
LocationData("Piercing the Shroud", "Piercing the Shroud: Victory", SC2WOL_LOC_ID_OFFSET + 2100,
|
||||||
lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)),
|
lambda state: state._sc2wol_has_mm_upgrade(world, player)),
|
||||||
LocationData("Piercing the Shroud", "Piercing the Shroud: Holding Cell Relic", SC2WOL_LOC_ID_OFFSET + 2101),
|
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: Brutalisk Relic", SC2WOL_LOC_ID_OFFSET + 2102,
|
||||||
lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)),
|
lambda state: state._sc2wol_has_mm_upgrade(world, player)),
|
||||||
LocationData("Piercing the Shroud", "Piercing the Shroud: First Escape Relic", SC2WOL_LOC_ID_OFFSET + 2103,
|
LocationData("Piercing the Shroud", "Piercing the Shroud: First Escape Relic", SC2WOL_LOC_ID_OFFSET + 2103,
|
||||||
lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)),
|
lambda state: state._sc2wol_has_mm_upgrade(world, player)),
|
||||||
LocationData("Piercing the Shroud", "Piercing the Shroud: Second Escape Relic", SC2WOL_LOC_ID_OFFSET + 2104,
|
LocationData("Piercing the Shroud", "Piercing the Shroud: Second Escape Relic", SC2WOL_LOC_ID_OFFSET + 2104,
|
||||||
lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)),
|
lambda state: state._sc2wol_has_mm_upgrade(world, player)),
|
||||||
LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk ", SC2WOL_LOC_ID_OFFSET + 2105,
|
LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk ", SC2WOL_LOC_ID_OFFSET + 2105,
|
||||||
lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)),
|
lambda state: state._sc2wol_has_mm_upgrade(world, player)),
|
||||||
LocationData("Whispers of Doom", "Whispers of Doom: Victory", 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: 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: 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", "Whispers of Doom: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 2203),
|
||||||
LocationData("A Sinister Turn", "A Sinister Turn: Victory", 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)),
|
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: Robotics Facility", SC2WOL_LOC_ID_OFFSET + 2301,
|
||||||
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)),
|
||||||
|
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)),
|
||||||
LocationData("A Sinister Turn", "A Sinister Turn: Templar Archives", SC2WOL_LOC_ID_OFFSET + 2303,
|
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(world, player)),
|
||||||
LocationData("Echoes of the Future", "Echoes of the Future: Victory", 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)),
|
lambda state: logic_level > 0 or 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: Close Obelisk", SC2WOL_LOC_ID_OFFSET + 2401),
|
||||||
LocationData("Echoes of the Future", "Echoes of the Future: West Obelisk", SC2WOL_LOC_ID_OFFSET + 2402,
|
LocationData("Echoes of the Future", "Echoes of the Future: West Obelisk", SC2WOL_LOC_ID_OFFSET + 2402,
|
||||||
lambda state: state._sc2wol_has_protoss_common_units(world, player)),
|
lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(world, player)),
|
||||||
LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2500),
|
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,
|
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(world, player)),
|
||||||
LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2502,
|
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(world, player)),
|
||||||
LocationData("Gates of Hell", "Gates of Hell: Victory", 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)),
|
lambda state: state._sc2wol_has_competent_comp(world, player) and
|
||||||
|
state._sc2wol_defense_rating(world, player, True) > 6),
|
||||||
LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601,
|
LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601,
|
||||||
lambda state: state._sc2wol_has_competent_comp(world, player)),
|
lambda state: state._sc2wol_has_competent_comp(world, player) and
|
||||||
|
state._sc2wol_defense_rating(world, 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: 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: 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: Second Charge", SC2WOL_LOC_ID_OFFSET + 2702),
|
||||||
|
@ -258,15 +271,19 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
|
||||||
lambda state: state._sc2wol_has_competent_comp(world, player)),
|
lambda state: state._sc2wol_has_competent_comp(world, player)),
|
||||||
LocationData("Shatter the Sky", "Shatter the Sky: Leviathan", SC2WOL_LOC_ID_OFFSET + 2805,
|
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(world, player)),
|
||||||
LocationData("All-In", "All-In: Victory", None)
|
LocationData("All-In", "All-In: Victory", None,
|
||||||
|
lambda state: state._sc2wol_final_mission_requirements(world, player))
|
||||||
]
|
]
|
||||||
|
|
||||||
beat_events = []
|
beat_events = []
|
||||||
|
|
||||||
for location_data in location_table:
|
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)
|
||||||
|
# Generating Beat event locations
|
||||||
if location_data.name.endswith((": Victory", ": Defeat")):
|
if location_data.name.endswith((": Victory", ": Defeat")):
|
||||||
beat_events.append(
|
beat_events.append(
|
||||||
location_data._replace(name="Beat " + location_data.name.rsplit(": ", 1)[0], code=None)
|
location_data._replace(name="Beat " + location_data.name.rsplit(": ", 1)[0], code=None)
|
||||||
)
|
)
|
||||||
|
|
||||||
return tuple(location_table + beat_events)
|
return tuple(location_table + beat_events)
|
||||||
|
|
|
@ -1,31 +1,43 @@
|
||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld
|
||||||
from worlds.AutoWorld import LogicMixin
|
from worlds.AutoWorld import LogicMixin
|
||||||
|
from .Options import get_option_value
|
||||||
|
from .Items import get_basic_units, defense_ratings, zerg_defense_ratings
|
||||||
|
|
||||||
|
|
||||||
class SC2WoLLogic(LogicMixin):
|
class SC2WoLLogic(LogicMixin):
|
||||||
def _sc2wol_has_common_unit(self, world: MultiWorld, player: int) -> bool:
|
def _sc2wol_has_common_unit(self, world: MultiWorld, player: int) -> bool:
|
||||||
return self.has_any({'Marine', 'Marauder', 'Firebat', 'Hellion', 'Vulture'}, player)
|
return self.has_any(get_basic_units(world, player), player)
|
||||||
|
|
||||||
def _sc2wol_has_bunker_unit(self, world: MultiWorld, player: int) -> bool:
|
|
||||||
return self.has_any({'Marine', 'Marauder'}, player)
|
|
||||||
|
|
||||||
def _sc2wol_has_air(self, world: MultiWorld, player: int) -> bool:
|
def _sc2wol_has_air(self, world: MultiWorld, player: int) -> bool:
|
||||||
return self.has_any({'Viking', 'Wraith', 'Banshee'}, player) or \
|
return self.has_any({'Viking', 'Wraith', 'Banshee'}, player) or get_option_value(world, player, 'required_tactics') > 0 \
|
||||||
self.has_any({'Hercules', 'Medivac'}, player) and self._sc2wol_has_common_unit(world, player)
|
and self.has_any({'Hercules', 'Medivac'}, player) and self._sc2wol_has_common_unit(world, player)
|
||||||
|
|
||||||
def _sc2wol_has_air_anti_air(self, world: MultiWorld, player: int) -> bool:
|
def _sc2wol_has_air_anti_air(self, world: MultiWorld, player: int) -> bool:
|
||||||
return self.has_any({'Viking', 'Wraith'}, player)
|
return self.has('Viking', player) \
|
||||||
|
or get_option_value(world, player, 'required_tactics') > 0 and self.has('Wraith', player)
|
||||||
|
|
||||||
def _sc2wol_has_competent_anti_air(self, world: MultiWorld, player: int) -> bool:
|
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)
|
return self.has_any({'Marine', 'Goliath'}, player) or self._sc2wol_has_air_anti_air(world, player)
|
||||||
|
|
||||||
def _sc2wol_has_anti_air(self, world: MultiWorld, player: int) -> bool:
|
def _sc2wol_has_anti_air(self, world: MultiWorld, player: int) -> bool:
|
||||||
return self.has_any({'Missile Turret', 'Thor', 'War Pigs', 'Spartan Company', "Hel's Angel", 'Battlecruiser'}, player) or self._sc2wol_has_competent_anti_air(world, player)
|
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)
|
||||||
|
|
||||||
def _sc2wol_has_heavy_defense(self, world: MultiWorld, player: int) -> bool:
|
def _sc2wol_defense_rating(self, world: MultiWorld, player: int, zerg_enemy: bool, air_enemy: bool = True) -> bool:
|
||||||
return (self.has_any({'Siege Tank', 'Vulture'}, player) or
|
defense_score = sum((defense_ratings[item] for item in defense_ratings if self.has(item, player)))
|
||||||
self.has('Bunker', player) and self._sc2wol_has_bunker_unit(world, player)) and \
|
if self.has_any({'Marine', 'Marauder'}, player) and self.has('Bunker', player):
|
||||||
self._sc2wol_has_anti_air(world, player)
|
defense_score += 3
|
||||||
|
if zerg_enemy:
|
||||||
|
defense_score += sum((zerg_defense_ratings[item] for item in zerg_defense_ratings if self.has(item, player)))
|
||||||
|
if self.has('Firebat', player) and self.has('Bunker', player):
|
||||||
|
defense_score += 2
|
||||||
|
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:
|
||||||
|
defense_score += 2
|
||||||
|
return defense_score
|
||||||
|
|
||||||
def _sc2wol_has_competent_comp(self, world: MultiWorld, player: int) -> bool:
|
def _sc2wol_has_competent_comp(self, world: MultiWorld, player: int) -> bool:
|
||||||
return (self.has('Marine', player) or self.has('Marauder', player) and
|
return (self.has('Marine', player) or self.has('Marauder', player) and
|
||||||
|
@ -35,25 +47,50 @@ class SC2WoLLogic(LogicMixin):
|
||||||
self.has('Siege Tank', player) and self._sc2wol_has_competent_anti_air(world, player)
|
self.has('Siege Tank', player) and self._sc2wol_has_competent_anti_air(world, player)
|
||||||
|
|
||||||
def _sc2wol_has_train_killers(self, world: MultiWorld, player: int) -> bool:
|
def _sc2wol_has_train_killers(self, world: MultiWorld, player: int) -> bool:
|
||||||
return (self.has_any({'Siege Tank', 'Diamondback'}, player) or
|
return (self.has_any({'Siege Tank', 'Diamondback', 'Marauder'}, player) or get_option_value(world, player, 'required_tactics') > 0
|
||||||
self.has_all({'Reaper', "G-4 Clusterbomb"}, player) or self.has_all({'Spectre', 'Psionic Lash'}, player)
|
and self.has_all({'Reaper', "G-4 Clusterbomb"}, player) or self.has_all({'Spectre', 'Psionic Lash'}, player))
|
||||||
or self.has('Marauders', player))
|
|
||||||
|
|
||||||
def _sc2wol_able_to_rescue(self, world: MultiWorld, player: int) -> bool:
|
def _sc2wol_able_to_rescue(self, world: MultiWorld, player: int) -> bool:
|
||||||
return self.has_any({'Medivac', 'Hercules', 'Raven', 'Viking'}, player)
|
return self.has_any({'Medivac', 'Hercules', 'Raven', 'Viking'}, player) or get_option_value(world, player, 'required_tactics') > 0
|
||||||
|
|
||||||
def _sc2wol_has_protoss_common_units(self, world: MultiWorld, player: int) -> bool:
|
def _sc2wol_has_protoss_common_units(self, world: MultiWorld, player: int) -> bool:
|
||||||
return self.has_any({'Zealot', 'Immortal', 'Stalker', 'Dark Templar'}, player)
|
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)
|
||||||
|
|
||||||
def _sc2wol_has_protoss_medium_units(self, world: MultiWorld, player: int) -> bool:
|
def _sc2wol_has_protoss_medium_units(self, world: MultiWorld, player: int) -> bool:
|
||||||
return self._sc2wol_has_protoss_common_units(world, player) and \
|
return self._sc2wol_has_protoss_common_units(world, player) and \
|
||||||
self.has_any({'Stalker', 'Void Ray', 'Phoenix', 'Carrier'}, player)
|
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)
|
||||||
|
|
||||||
def _sc2wol_beats_protoss_deathball(self, world: MultiWorld, player: int) -> bool:
|
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 \
|
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)
|
self._sc2wol_has_competent_comp(world, player) and self._sc2wol_has_air_anti_air(world, player)
|
||||||
|
|
||||||
|
def _sc2wol_has_mm_upgrade(self, world: 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:
|
||||||
|
return self.has("Battlecruiser", player) or \
|
||||||
|
self._sc2wol_has_air(world, player) and \
|
||||||
|
self._sc2wol_has_competent_anti_air(world, 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_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:
|
||||||
|
# Ground
|
||||||
|
if self.has_any({'Battlecruiser', 'Banshee'}, player):
|
||||||
|
defense_rating += 3
|
||||||
|
return defense_rating >= 12 and beats_kerrigan
|
||||||
|
else:
|
||||||
|
# Air
|
||||||
|
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, world: MultiWorld, player: int, mission_count: int) -> bool:
|
||||||
return self.has_group("Missions", player, mission_count)
|
return self.has_group("Missions", player, mission_count)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
from typing import NamedTuple, Dict, List
|
from typing import NamedTuple, Dict, List, Set
|
||||||
|
|
||||||
|
from BaseClasses import MultiWorld
|
||||||
|
from .Options import get_option_value
|
||||||
|
|
||||||
no_build_regions_list = ["Liberation Day", "Breakout", "Ghost of a Chance", "Piercing the Shroud", "Whispers of Doom",
|
no_build_regions_list = ["Liberation Day", "Breakout", "Ghost of a Chance", "Piercing the Shroud", "Whispers of Doom",
|
||||||
"Belly of the Beast"]
|
"Belly of the Beast"]
|
||||||
|
@ -12,7 +15,6 @@ hard_regions_list = ["Maw of the Void", "Engine of Destruction", "In Utter Darkn
|
||||||
|
|
||||||
class MissionInfo(NamedTuple):
|
class MissionInfo(NamedTuple):
|
||||||
id: int
|
id: int
|
||||||
extra_locations: int
|
|
||||||
required_world: List[int]
|
required_world: List[int]
|
||||||
category: str
|
category: str
|
||||||
number: int = 0 # number of worlds need beaten
|
number: int = 0 # number of worlds need beaten
|
||||||
|
@ -62,38 +64,156 @@ vanilla_shuffle_order = [
|
||||||
FillMission("all_in", [26, 27], "Char", completion_critical=True, or_requirements=True)
|
FillMission("all_in", [26, 27], "Char", completion_critical=True, or_requirements=True)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
mini_campaign_order = [
|
||||||
|
FillMission("no_build", [-1], "Mar Sara", completion_critical=True),
|
||||||
|
FillMission("easy", [0], "Colonist"),
|
||||||
|
FillMission("medium", [1], "Colonist"),
|
||||||
|
FillMission("medium", [0], "Artifact", completion_critical=True),
|
||||||
|
FillMission("medium", [3], "Artifact", number=4, completion_critical=True),
|
||||||
|
FillMission("hard", [4], "Artifact", number=8, completion_critical=True),
|
||||||
|
FillMission("medium", [0], "Covert", number=2),
|
||||||
|
FillMission("hard", [6], "Covert"),
|
||||||
|
FillMission("medium", [0], "Rebellion", number=3),
|
||||||
|
FillMission("hard", [8], "Rebellion"),
|
||||||
|
FillMission("medium", [4], "Prophecy"),
|
||||||
|
FillMission("hard", [10], "Prophecy"),
|
||||||
|
FillMission("hard", [5], "Char", completion_critical=True),
|
||||||
|
FillMission("hard", [5], "Char", completion_critical=True),
|
||||||
|
FillMission("all_in", [12, 13], "Char", completion_critical=True, or_requirements=True)
|
||||||
|
]
|
||||||
|
|
||||||
|
gauntlet_order = [
|
||||||
|
FillMission("no_build", [-1], "I", completion_critical=True),
|
||||||
|
FillMission("easy", [0], "II", completion_critical=True),
|
||||||
|
FillMission("medium", [1], "III", completion_critical=True),
|
||||||
|
FillMission("medium", [2], "IV", completion_critical=True),
|
||||||
|
FillMission("hard", [3], "V", completion_critical=True),
|
||||||
|
FillMission("hard", [4], "VI", completion_critical=True),
|
||||||
|
FillMission("all_in", [5], "Final", completion_critical=True)
|
||||||
|
]
|
||||||
|
|
||||||
|
grid_order = [
|
||||||
|
FillMission("no_build", [-1], "_1"),
|
||||||
|
FillMission("medium", [0], "_1"),
|
||||||
|
FillMission("medium", [1, 6, 3], "_1", or_requirements=True),
|
||||||
|
FillMission("hard", [2, 7], "_1", or_requirements=True),
|
||||||
|
FillMission("easy", [0], "_2"),
|
||||||
|
FillMission("medium", [1, 4], "_2", or_requirements=True),
|
||||||
|
FillMission("hard", [2, 5, 10, 7], "_2", or_requirements=True),
|
||||||
|
FillMission("hard", [3, 6, 11], "_2", or_requirements=True),
|
||||||
|
FillMission("medium", [4, 9, 12], "_3", or_requirements=True),
|
||||||
|
FillMission("hard", [5, 8, 10, 13], "_3", or_requirements=True),
|
||||||
|
FillMission("hard", [6, 9, 11, 14], "_3", or_requirements=True),
|
||||||
|
FillMission("hard", [7, 10], "_3", or_requirements=True),
|
||||||
|
FillMission("hard", [8, 13], "_4", or_requirements=True),
|
||||||
|
FillMission("hard", [9, 12, 14], "_4", or_requirements=True),
|
||||||
|
FillMission("hard", [10, 13], "_4", or_requirements=True),
|
||||||
|
FillMission("all_in", [11, 14], "_4", or_requirements=True)
|
||||||
|
]
|
||||||
|
|
||||||
|
mini_grid_order = [
|
||||||
|
FillMission("no_build", [-1], "_1"),
|
||||||
|
FillMission("medium", [0], "_1"),
|
||||||
|
FillMission("medium", [1, 5], "_1", or_requirements=True),
|
||||||
|
FillMission("easy", [0], "_2"),
|
||||||
|
FillMission("medium", [1, 3], "_2", or_requirements=True),
|
||||||
|
FillMission("hard", [2, 4], "_2", or_requirements=True),
|
||||||
|
FillMission("medium", [3, 7], "_3", or_requirements=True),
|
||||||
|
FillMission("hard", [4, 6], "_3", or_requirements=True),
|
||||||
|
FillMission("all_in", [5, 7], "_3", or_requirements=True)
|
||||||
|
]
|
||||||
|
|
||||||
|
blitz_order = [
|
||||||
|
FillMission("no_build", [-1], "I"),
|
||||||
|
FillMission("easy", [-1], "I"),
|
||||||
|
FillMission("medium", [0, 1], "II", number=1, or_requirements=True),
|
||||||
|
FillMission("medium", [0, 1], "II", number=1, or_requirements=True),
|
||||||
|
FillMission("medium", [0, 1], "III", number=2, or_requirements=True),
|
||||||
|
FillMission("medium", [0, 1], "III", number=2, or_requirements=True),
|
||||||
|
FillMission("hard", [0, 1], "IV", number=3, or_requirements=True),
|
||||||
|
FillMission("hard", [0, 1], "IV", number=3, or_requirements=True),
|
||||||
|
FillMission("hard", [0, 1], "V", number=4, or_requirements=True),
|
||||||
|
FillMission("hard", [0, 1], "V", number=4, or_requirements=True),
|
||||||
|
FillMission("hard", [0, 1], "Final", number=5, or_requirements=True),
|
||||||
|
FillMission("all_in", [0, 1], "Final", number=5, or_requirements=True)
|
||||||
|
]
|
||||||
|
|
||||||
|
mission_orders = [vanilla_shuffle_order, vanilla_shuffle_order, mini_campaign_order, grid_order, mini_grid_order, blitz_order, gauntlet_order]
|
||||||
|
|
||||||
|
|
||||||
vanilla_mission_req_table = {
|
vanilla_mission_req_table = {
|
||||||
"Liberation Day": MissionInfo(1, 7, [], "Mar Sara", completion_critical=True),
|
"Liberation Day": MissionInfo(1, [], "Mar Sara", completion_critical=True),
|
||||||
"The Outlaws": MissionInfo(2, 2, [1], "Mar Sara", completion_critical=True),
|
"The Outlaws": MissionInfo(2, [1], "Mar Sara", completion_critical=True),
|
||||||
"Zero Hour": MissionInfo(3, 4, [2], "Mar Sara", completion_critical=True),
|
"Zero Hour": MissionInfo(3, [2], "Mar Sara", completion_critical=True),
|
||||||
"Evacuation": MissionInfo(4, 4, [3], "Colonist"),
|
"Evacuation": MissionInfo(4, [3], "Colonist"),
|
||||||
"Outbreak": MissionInfo(5, 3, [4], "Colonist"),
|
"Outbreak": MissionInfo(5, [4], "Colonist"),
|
||||||
"Safe Haven": MissionInfo(6, 4, [5], "Colonist", number=7),
|
"Safe Haven": MissionInfo(6, [5], "Colonist", number=7),
|
||||||
"Haven's Fall": MissionInfo(7, 4, [5], "Colonist", number=7),
|
"Haven's Fall": MissionInfo(7, [5], "Colonist", number=7),
|
||||||
"Smash and Grab": MissionInfo(8, 5, [3], "Artifact", completion_critical=True),
|
"Smash and Grab": MissionInfo(8, [3], "Artifact", completion_critical=True),
|
||||||
"The Dig": MissionInfo(9, 4, [8], "Artifact", number=8, completion_critical=True),
|
"The Dig": MissionInfo(9, [8], "Artifact", number=8, completion_critical=True),
|
||||||
"The Moebius Factor": MissionInfo(10, 9, [9], "Artifact", number=11, completion_critical=True),
|
"The Moebius Factor": MissionInfo(10, [9], "Artifact", number=11, completion_critical=True),
|
||||||
"Supernova": MissionInfo(11, 5, [10], "Artifact", number=14, completion_critical=True),
|
"Supernova": MissionInfo(11, [10], "Artifact", number=14, completion_critical=True),
|
||||||
"Maw of the Void": MissionInfo(12, 6, [11], "Artifact", completion_critical=True),
|
"Maw of the Void": MissionInfo(12, [11], "Artifact", completion_critical=True),
|
||||||
"Devil's Playground": MissionInfo(13, 3, [3], "Covert", number=4),
|
"Devil's Playground": MissionInfo(13, [3], "Covert", number=4),
|
||||||
"Welcome to the Jungle": MissionInfo(14, 4, [13], "Covert"),
|
"Welcome to the Jungle": MissionInfo(14, [13], "Covert"),
|
||||||
"Breakout": MissionInfo(15, 3, [14], "Covert", number=8),
|
"Breakout": MissionInfo(15, [14], "Covert", number=8),
|
||||||
"Ghost of a Chance": MissionInfo(16, 6, [14], "Covert", number=8),
|
"Ghost of a Chance": MissionInfo(16, [14], "Covert", number=8),
|
||||||
"The Great Train Robbery": MissionInfo(17, 4, [3], "Rebellion", number=6),
|
"The Great Train Robbery": MissionInfo(17, [3], "Rebellion", number=6),
|
||||||
"Cutthroat": MissionInfo(18, 5, [17], "Rebellion"),
|
"Cutthroat": MissionInfo(18, [17], "Rebellion"),
|
||||||
"Engine of Destruction": MissionInfo(19, 6, [18], "Rebellion"),
|
"Engine of Destruction": MissionInfo(19, [18], "Rebellion"),
|
||||||
"Media Blitz": MissionInfo(20, 5, [19], "Rebellion"),
|
"Media Blitz": MissionInfo(20, [19], "Rebellion"),
|
||||||
"Piercing the Shroud": MissionInfo(21, 6, [20], "Rebellion"),
|
"Piercing the Shroud": MissionInfo(21, [20], "Rebellion"),
|
||||||
"Whispers of Doom": MissionInfo(22, 4, [9], "Prophecy"),
|
"Whispers of Doom": MissionInfo(22, [9], "Prophecy"),
|
||||||
"A Sinister Turn": MissionInfo(23, 4, [22], "Prophecy"),
|
"A Sinister Turn": MissionInfo(23, [22], "Prophecy"),
|
||||||
"Echoes of the Future": MissionInfo(24, 3, [23], "Prophecy"),
|
"Echoes of the Future": MissionInfo(24, [23], "Prophecy"),
|
||||||
"In Utter Darkness": MissionInfo(25, 3, [24], "Prophecy"),
|
"In Utter Darkness": MissionInfo(25, [24], "Prophecy"),
|
||||||
"Gates of Hell": MissionInfo(26, 2, [12], "Char", completion_critical=True),
|
"Gates of Hell": MissionInfo(26, [12], "Char", completion_critical=True),
|
||||||
"Belly of the Beast": MissionInfo(27, 4, [26], "Char", completion_critical=True),
|
"Belly of the Beast": MissionInfo(27, [26], "Char", completion_critical=True),
|
||||||
"Shatter the Sky": MissionInfo(28, 5, [26], "Char", completion_critical=True),
|
"Shatter the Sky": MissionInfo(28, [26], "Char", completion_critical=True),
|
||||||
"All-In": MissionInfo(29, -1, [27, 28], "Char", completion_critical=True, or_requirements=True)
|
"All-In": MissionInfo(29, [27, 28], "Char", completion_critical=True, or_requirements=True)
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup_id_to_mission: Dict[int, str] = {
|
lookup_id_to_mission: Dict[int, str] = {
|
||||||
data.id: mission_name for mission_name, data in vanilla_mission_req_table.items() if data.id}
|
data.id: mission_name for mission_name, data in vanilla_mission_req_table.items() if data.id}
|
||||||
|
|
||||||
|
no_build_starting_mission_locations = {
|
||||||
|
"Liberation Day": "Liberation Day: Victory",
|
||||||
|
"Breakout": "Breakout: Victory",
|
||||||
|
"Ghost of a Chance": "Ghost of a Chance: Victory",
|
||||||
|
"Piercing the Shroud": "Piercing the Shroud: Victory",
|
||||||
|
"Whispers of Doom": "Whispers of Doom: Victory",
|
||||||
|
"Belly of the Beast": "Belly of the Beast: Victory",
|
||||||
|
}
|
||||||
|
|
||||||
|
build_starting_mission_locations = {
|
||||||
|
"Zero Hour": "Zero Hour: First Group Rescued",
|
||||||
|
"Evacuation": "Evacuation: First Chysalis",
|
||||||
|
"Devil's Playground": "Devil's Playground: Tosh's Miners"
|
||||||
|
}
|
||||||
|
|
||||||
|
advanced_starting_mission_locations = {
|
||||||
|
"Smash and Grab": "Smash and Grab: First Relic",
|
||||||
|
"The Great Train Robbery": "The Great Train Robbery: North Defiler"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
# 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:
|
||||||
|
# Advanced Tactics/No Logic add more starting missions to the pool
|
||||||
|
return {**build_starting_mission_locations, **advanced_starting_mission_locations}
|
||||||
|
else:
|
||||||
|
# Standard starting missions when relegate is on
|
||||||
|
return build_starting_mission_locations
|
||||||
|
|
||||||
|
|
||||||
|
alt_final_mission_locations = {
|
||||||
|
"Maw of the Void": "Maw of the Void: Victory",
|
||||||
|
"Engine of Destruction": "Engine of Destruction: Victory",
|
||||||
|
"Supernova": "Supernova: Victory",
|
||||||
|
"Gates of Hell": "Gates of Hell: Victory",
|
||||||
|
"Shatter the Sky": "Shatter the Sky: Victory"
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld
|
||||||
from Options import Choice, Option, DefaultOnToggle
|
from Options import Choice, Option, Toggle, DefaultOnToggle, ItemSet, OptionSet, Range
|
||||||
|
|
||||||
|
|
||||||
class GameDifficulty(Choice):
|
class GameDifficulty(Choice):
|
||||||
|
@ -36,25 +36,75 @@ class AllInMap(Choice):
|
||||||
|
|
||||||
|
|
||||||
class MissionOrder(Choice):
|
class MissionOrder(Choice):
|
||||||
"""Determines the order the missions are played in.
|
"""Determines the order the missions are played in. The last three mission orders end in a random mission.
|
||||||
Vanilla: Keeps the standard mission order and branching from the WoL Campaign.
|
Vanilla (29): 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."""
|
Vanilla Shuffled (29): Keeps same branching paths from the WoL Campaign but randomizes the order of missions within.
|
||||||
|
Mini Campaign (15): Shorter version of the campaign with randomized missions and optional branches.
|
||||||
|
Grid (16): A 4x4 grid of random missions. Start at the top-left and forge a path towards All-In.
|
||||||
|
Mini Grid (9): A 3x3 version of Grid. Complete the bottom-right mission to win.
|
||||||
|
Blitz (12): 12 random missions that open up very quickly. Complete the bottom-right mission to win.
|
||||||
|
Gauntlet (7): Linear series of 7 random missions to complete the campaign."""
|
||||||
display_name = "Mission Order"
|
display_name = "Mission Order"
|
||||||
option_vanilla = 0
|
option_vanilla = 0
|
||||||
option_vanilla_shuffled = 1
|
option_vanilla_shuffled = 1
|
||||||
|
option_mini_campaign = 2
|
||||||
|
option_grid = 3
|
||||||
|
option_mini_grid = 4
|
||||||
|
option_blitz = 5
|
||||||
|
option_gauntlet = 6
|
||||||
|
|
||||||
|
|
||||||
class ShuffleProtoss(DefaultOnToggle):
|
class ShuffleProtoss(DefaultOnToggle):
|
||||||
"""Determines if the 3 protoss missions are included in the shuffle if Vanilla Shuffled is enabled. If this is
|
"""Determines if the 3 protoss missions are included in the shuffle if Vanilla mission order is not enabled.
|
||||||
not the 3 protoss missions will stay in their vanilla order in the mission order making them optional to complete
|
If turned off with Vanilla Shuffled, the 3 protoss missions will be in their normal position on the Prophecy chain if not shuffled.
|
||||||
the game."""
|
If turned off with reduced mission settings, the 3 protoss missions will not appear and Protoss units are removed from the pool."""
|
||||||
display_name = "Shuffle Protoss Missions"
|
display_name = "Shuffle Protoss Missions"
|
||||||
|
|
||||||
|
|
||||||
class RelegateNoBuildMissions(DefaultOnToggle):
|
class ShuffleNoBuild(DefaultOnToggle):
|
||||||
"""If enabled, all no build missions besides the needed first one will be placed at the end of optional routes so
|
"""Determines if the 5 no-build missions are included in the shuffle if Vanilla mission order is not enabled.
|
||||||
that none of them become required to complete the game. Only takes effect if mission order is not set to vanilla."""
|
If turned off with Vanilla Shuffled, one no-build mission will be placed as the first mission and the rest will be placed at the end of optional routes.
|
||||||
display_name = "Relegate No-Build Missions"
|
If turned off with reduced mission settings, the 5 no-build missions will not appear."""
|
||||||
|
display_name = "Shuffle No-Build Missions"
|
||||||
|
|
||||||
|
|
||||||
|
class EarlyUnit(DefaultOnToggle):
|
||||||
|
"""Guarantees that the first mission will contain a unit."""
|
||||||
|
display_name = "Early Unit"
|
||||||
|
|
||||||
|
|
||||||
|
class RequiredTactics(Choice):
|
||||||
|
"""Determines the maximum tactical difficulty of the seed (separate from mission difficulty). Higher settings increase randomness.
|
||||||
|
Standard: All missions can be completed with good micro and macro.
|
||||||
|
Advanced: Completing missions may require relying on starting units and micro-heavy units.
|
||||||
|
No Logic: Units and upgrades may be placed anywhere. LIKELY TO RENDER THE RUN IMPOSSIBLE ON HARDER DIFFICULTIES!"""
|
||||||
|
display_name = "Required Tactics"
|
||||||
|
option_standard = 0
|
||||||
|
option_advanced = 1
|
||||||
|
option_no_logic = 2
|
||||||
|
|
||||||
|
|
||||||
|
class UnitsAlwaysHaveUpgrades(DefaultOnToggle):
|
||||||
|
"""If turned on, both upgrades will be present for each unit and structure in the seed.
|
||||||
|
This usually results in fewer units."""
|
||||||
|
display_name = "Units Always Have Upgrades"
|
||||||
|
|
||||||
|
|
||||||
|
class LockedItems(ItemSet):
|
||||||
|
"""Guarantees that these items will be unlockable"""
|
||||||
|
display_name = "Locked Items"
|
||||||
|
|
||||||
|
|
||||||
|
class ExcludedItems(ItemSet):
|
||||||
|
"""Guarantees that these items will not be unlockable"""
|
||||||
|
display_name = "Excluded Items"
|
||||||
|
|
||||||
|
|
||||||
|
class ExcludedMissions(OptionSet):
|
||||||
|
"""Guarantees that these missions will not appear in the campaign
|
||||||
|
Only applies on shortened mission orders.
|
||||||
|
It may be impossible to build a valid campaign if too many missions are excluded."""
|
||||||
|
display_name = "Excluded Missions"
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
|
@ -65,14 +115,29 @@ sc2wol_options: Dict[str, Option] = {
|
||||||
"all_in_map": AllInMap,
|
"all_in_map": AllInMap,
|
||||||
"mission_order": MissionOrder,
|
"mission_order": MissionOrder,
|
||||||
"shuffle_protoss": ShuffleProtoss,
|
"shuffle_protoss": ShuffleProtoss,
|
||||||
"relegate_no_build": RelegateNoBuildMissions
|
"shuffle_no_build": ShuffleNoBuild,
|
||||||
|
"early_unit": EarlyUnit,
|
||||||
|
"required_tactics": RequiredTactics,
|
||||||
|
"units_always_have_upgrades": UnitsAlwaysHaveUpgrades,
|
||||||
|
"locked_items": LockedItems,
|
||||||
|
"excluded_items": ExcludedItems,
|
||||||
|
"excluded_missions": ExcludedMissions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_option_value(world: MultiWorld, player: int, name: str) -> int:
|
def get_option_value(world: MultiWorld, player: int, name: str) -> int:
|
||||||
option = getattr(world, name, None)
|
option = getattr(world, name, None)
|
||||||
|
|
||||||
if option == None:
|
if option is None:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return int(option[player].value)
|
return int(option[player].value)
|
||||||
|
|
||||||
|
|
||||||
|
def get_option_set_value(world: MultiWorld, player: int, name: str) -> set:
|
||||||
|
option = getattr(world, name, None)
|
||||||
|
|
||||||
|
if option is None:
|
||||||
|
return set()
|
||||||
|
|
||||||
|
return option[player].value
|
||||||
|
|
|
@ -0,0 +1,257 @@
|
||||||
|
from typing import Callable, Dict, List, Set
|
||||||
|
from BaseClasses import MultiWorld, ItemClassification, Item, Location
|
||||||
|
from .Items import item_table
|
||||||
|
from .MissionTables import no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list,\
|
||||||
|
mission_orders, get_starting_mission_locations, MissionInfo, vanilla_mission_req_table, alt_final_mission_locations
|
||||||
|
from .Options import get_option_value, get_option_set_value
|
||||||
|
from .LogicMixin import SC2WoLLogic
|
||||||
|
|
||||||
|
# Items with associated upgrades
|
||||||
|
UPGRADABLE_ITEMS = [
|
||||||
|
"Marine", "Medic", "Firebat", "Marauder", "Reaper", "Ghost", "Spectre",
|
||||||
|
"Hellion", "Vulture", "Goliath", "Diamondback", "Siege Tank", "Thor",
|
||||||
|
"Medivac", "Wraith", "Viking", "Banshee", "Battlecruiser",
|
||||||
|
"Bunker", "Missile Turret"
|
||||||
|
]
|
||||||
|
|
||||||
|
BARRACKS_UNITS = {"Marine", "Medic", "Firebat", "Marauder", "Reaper", "Ghost", "Spectre"}
|
||||||
|
FACTORY_UNITS = {"Hellion", "Vulture", "Goliath", "Diamondback", "Siege Tank", "Thor", "Predator"}
|
||||||
|
STARPORT_UNITS = {"Medivac", "Wraith", "Viking", "Banshee", "Battlecruiser", "Hercules", "Science Vessel", "Raven"}
|
||||||
|
|
||||||
|
PROTOSS_REGIONS = {"A Sinister Turn", "Echoes of the Future", "In Utter Darkness"}
|
||||||
|
|
||||||
|
|
||||||
|
def filter_missions(world: 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"))
|
||||||
|
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))
|
||||||
|
mission_count = len(mission_orders[mission_order_type]) - 1
|
||||||
|
# Vanilla and Vanilla Shuffled use the entire mission pool
|
||||||
|
if mission_count == 28:
|
||||||
|
return {
|
||||||
|
"no_build": no_build_regions_list[:],
|
||||||
|
"easy": easy_regions_list[:],
|
||||||
|
"medium": medium_regions_list[:],
|
||||||
|
"hard": hard_regions_list[:],
|
||||||
|
"all_in": ["All-In"]
|
||||||
|
}
|
||||||
|
|
||||||
|
mission_pools = [
|
||||||
|
[],
|
||||||
|
easy_regions_list,
|
||||||
|
medium_regions_list,
|
||||||
|
hard_regions_list
|
||||||
|
]
|
||||||
|
# Omitting Protoss missions if not shuffling protoss
|
||||||
|
if not shuffle_protoss:
|
||||||
|
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])
|
||||||
|
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]
|
||||||
|
# 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'):
|
||||||
|
# Swapping Outbreak and The Great Train Robbery
|
||||||
|
if "Outbreak" in mission_pools[1]:
|
||||||
|
mission_pools[1].remove("Outbreak")
|
||||||
|
mission_pools[2].append("Outbreak")
|
||||||
|
if "The Great Train Robbery" in mission_pools[2]:
|
||||||
|
mission_pools[2].remove("The Great Train Robbery")
|
||||||
|
mission_pools[1].append("The Great Train Robbery")
|
||||||
|
# Removing random missions from each difficulty set in a cycle
|
||||||
|
set_cycle = 0
|
||||||
|
current_count = sum(len(mission_pool) for mission_pool in mission_pools)
|
||||||
|
|
||||||
|
if current_count < mission_count:
|
||||||
|
raise Exception("Not enough missions available to fill the campaign on current settings. Please exclude fewer missions.")
|
||||||
|
while current_count > mission_count:
|
||||||
|
if set_cycle == 4:
|
||||||
|
set_cycle = 0
|
||||||
|
# Must contain at least one mission per set
|
||||||
|
mission_pool = mission_pools[set_cycle]
|
||||||
|
if len(mission_pool) <= 1:
|
||||||
|
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))
|
||||||
|
current_count -= 1
|
||||||
|
set_cycle += 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
"no_build": mission_pools[0],
|
||||||
|
"easy": mission_pools[1],
|
||||||
|
"medium": mission_pools[2],
|
||||||
|
"hard": mission_pools[3],
|
||||||
|
"all_in": [final_mission]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_item_upgrades(inventory: List[Item], parent_item: Item or str):
|
||||||
|
item_name = parent_item.name if isinstance(parent_item, Item) else parent_item
|
||||||
|
return [
|
||||||
|
inv_item for inv_item in inventory
|
||||||
|
if item_table[inv_item.name].parent_item == item_name
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ValidInventory:
|
||||||
|
|
||||||
|
def has(self, item: str, player: int):
|
||||||
|
return item in self.logical_inventory
|
||||||
|
|
||||||
|
def has_any(self, items: Set[str], player: int):
|
||||||
|
return any(item in self.logical_inventory for item in items)
|
||||||
|
|
||||||
|
def has_all(self, items: Set[str], player: int):
|
||||||
|
return all(item in self.logical_inventory for item in items)
|
||||||
|
|
||||||
|
def has_units_per_structure(self) -> bool:
|
||||||
|
return len(BARRACKS_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \
|
||||||
|
len(FACTORY_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \
|
||||||
|
len(STARPORT_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure
|
||||||
|
|
||||||
|
def generate_reduced_inventory(self, inventory_size: int, mission_requirements: List[Callable]) -> List[Item]:
|
||||||
|
"""Attempts to generate a reduced inventory that can fulfill the mission requirements."""
|
||||||
|
inventory = list(self.item_pool)
|
||||||
|
locked_items = list(self.locked_items)
|
||||||
|
self.logical_inventory = {
|
||||||
|
item.name for item in inventory + locked_items + self.existing_items
|
||||||
|
if item.classification in (ItemClassification.progression, ItemClassification.progression_skip_balancing)
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
if self.min_units_per_structure > 0:
|
||||||
|
requirements.append(lambda state: state.has_units_per_structure())
|
||||||
|
|
||||||
|
def attempt_removal(item: Item) -> bool:
|
||||||
|
# If item can be removed and has associated items, remove them as well
|
||||||
|
inventory.remove(item)
|
||||||
|
# Only run logic checks when removing logic items
|
||||||
|
if item.name in self.logical_inventory:
|
||||||
|
self.logical_inventory.remove(item.name)
|
||||||
|
if not all(requirement(self) for requirement in requirements):
|
||||||
|
# If item cannot be removed, lock or revert
|
||||||
|
self.logical_inventory.add(item.name)
|
||||||
|
locked_items.append(item)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
while len(inventory) + len(locked_items) > inventory_size:
|
||||||
|
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)
|
||||||
|
# Cascade removals to associated items
|
||||||
|
if item in cascade_keys:
|
||||||
|
items_to_remove = self.cascade_removal_map[item]
|
||||||
|
transient_items = []
|
||||||
|
while len(items_to_remove) > 0:
|
||||||
|
item_to_remove = items_to_remove.pop()
|
||||||
|
if item_to_remove not in inventory:
|
||||||
|
continue
|
||||||
|
success = attempt_removal(item_to_remove)
|
||||||
|
if success:
|
||||||
|
transient_items.append(item_to_remove)
|
||||||
|
elif units_always_have_upgrades:
|
||||||
|
# Lock all associated items if any of them cannot be removed
|
||||||
|
transient_items += items_to_remove
|
||||||
|
for transient_item in transient_items:
|
||||||
|
if transient_item not in inventory and transient_item not in locked_items:
|
||||||
|
locked_items += transient_item
|
||||||
|
if transient_item.classification in (ItemClassification.progression, ItemClassification.progression_skip_balancing):
|
||||||
|
self.logical_inventory.add(transient_item.name)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
attempt_removal(item)
|
||||||
|
|
||||||
|
return inventory + locked_items
|
||||||
|
|
||||||
|
def _read_logic(self):
|
||||||
|
self._sc2wol_has_common_unit = lambda world, player: SC2WoLLogic._sc2wol_has_common_unit(self, world, player)
|
||||||
|
self._sc2wol_has_air = lambda world, player: SC2WoLLogic._sc2wol_has_air(self, world, player)
|
||||||
|
self._sc2wol_has_air_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_air_anti_air(self, world, player)
|
||||||
|
self._sc2wol_has_competent_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_competent_anti_air(self, world, player)
|
||||||
|
self._sc2wol_has_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_anti_air(self, world, player)
|
||||||
|
self._sc2wol_defense_rating = lambda world, player, zerg_enemy, air_enemy=False: SC2WoLLogic._sc2wol_defense_rating(self, world, player, zerg_enemy, air_enemy)
|
||||||
|
self._sc2wol_has_competent_comp = lambda world, player: SC2WoLLogic._sc2wol_has_competent_comp(self, world, player)
|
||||||
|
self._sc2wol_has_train_killers = lambda world, player: SC2WoLLogic._sc2wol_has_train_killers(self, world, player)
|
||||||
|
self._sc2wol_able_to_rescue = lambda world, player: SC2WoLLogic._sc2wol_able_to_rescue(self, world, player)
|
||||||
|
self._sc2wol_beats_protoss_deathball = lambda world, player: SC2WoLLogic._sc2wol_beats_protoss_deathball(self, world, player)
|
||||||
|
self._sc2wol_survives_rip_field = lambda world, player: SC2WoLLogic._sc2wol_survives_rip_field(self, world, player)
|
||||||
|
self._sc2wol_has_protoss_common_units = lambda world, player: SC2WoLLogic._sc2wol_has_protoss_common_units(self, world, player)
|
||||||
|
self._sc2wol_has_protoss_medium_units = lambda world, player: SC2WoLLogic._sc2wol_has_protoss_medium_units(self, world, player)
|
||||||
|
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,
|
||||||
|
item_pool: List[Item], existing_items: List[Item], locked_items: List[Item],
|
||||||
|
has_protoss: bool):
|
||||||
|
self.world = world
|
||||||
|
self.player = player
|
||||||
|
self.logical_inventory = set()
|
||||||
|
self.locked_items = locked_items[:]
|
||||||
|
self.existing_items = existing_items
|
||||||
|
self._read_logic()
|
||||||
|
# Initial filter of item pool
|
||||||
|
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_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
|
||||||
|
for item in item_pool:
|
||||||
|
item_info = item_table[item.name]
|
||||||
|
if item_info.type == "Upgrade":
|
||||||
|
# Locking upgrades based on mission duration
|
||||||
|
if item.name not in item_quantities:
|
||||||
|
item_quantities[item.name] = 0
|
||||||
|
item_quantities[item.name] += 1
|
||||||
|
if item_quantities[item.name] < min_upgrades:
|
||||||
|
self.locked_items.append(item)
|
||||||
|
else:
|
||||||
|
self.item_pool.append(item)
|
||||||
|
elif item_info.type == "Goal":
|
||||||
|
locked_items.append(item)
|
||||||
|
elif item_info.type != "Protoss" or has_protoss:
|
||||||
|
self.item_pool.append(item)
|
||||||
|
self.cascade_removal_map: Dict[Item, List[Item]] = dict()
|
||||||
|
for item in self.item_pool + locked_items + existing_items:
|
||||||
|
if item.name in UPGRADABLE_ITEMS:
|
||||||
|
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"):
|
||||||
|
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],
|
||||||
|
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.
|
||||||
|
The returned inventory must be capable of logically accessing every location in the world.
|
||||||
|
"""
|
||||||
|
open_locations = [location for location in location_cache if location.item is None]
|
||||||
|
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_items = valid_inventory.generate_reduced_inventory(inventory_size, mission_requirements)
|
||||||
|
return valid_items
|
|
@ -2,55 +2,47 @@ from typing import List, Set, Dict, Tuple, Optional, Callable
|
||||||
from BaseClasses import MultiWorld, Region, Entrance, Location, RegionType
|
from BaseClasses import MultiWorld, Region, Entrance, Location, RegionType
|
||||||
from .Locations import LocationData
|
from .Locations import LocationData
|
||||||
from .Options import get_option_value
|
from .Options import get_option_value
|
||||||
from .MissionTables import MissionInfo, vanilla_shuffle_order, vanilla_mission_req_table, \
|
from .MissionTables import MissionInfo, mission_orders, vanilla_mission_req_table, alt_final_mission_locations
|
||||||
no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list
|
from .PoolFilter import filter_missions
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location]):
|
def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location])\
|
||||||
|
-> Tuple[Dict[str, MissionInfo], int, str]:
|
||||||
locations_per_region = get_locations_per_region(locations)
|
locations_per_region = get_locations_per_region(locations)
|
||||||
|
|
||||||
regions = [
|
mission_order_type = get_option_value(world, player, "mission_order")
|
||||||
create_region(world, player, locations_per_region, location_cache, "Menu"),
|
mission_order = mission_orders[mission_order_type]
|
||||||
create_region(world, player, locations_per_region, location_cache, "Liberation Day"),
|
|
||||||
create_region(world, player, locations_per_region, location_cache, "The Outlaws"),
|
mission_pools = filter_missions(world, player)
|
||||||
create_region(world, player, locations_per_region, location_cache, "Zero Hour"),
|
final_mission = mission_pools['all_in'][0]
|
||||||
create_region(world, player, locations_per_region, location_cache, "Evacuation"),
|
|
||||||
create_region(world, player, locations_per_region, location_cache, "Outbreak"),
|
used_regions = [mission for mission_pool in mission_pools.values() for mission in mission_pool]
|
||||||
create_region(world, player, locations_per_region, location_cache, "Safe Haven"),
|
regions = [create_region(world, player, locations_per_region, location_cache, "Menu")]
|
||||||
create_region(world, player, locations_per_region, location_cache, "Haven's Fall"),
|
for region_name in used_regions:
|
||||||
create_region(world, player, locations_per_region, location_cache, "Smash and Grab"),
|
regions.append(create_region(world, player, locations_per_region, location_cache, region_name))
|
||||||
create_region(world, player, locations_per_region, location_cache, "The Dig"),
|
# Changing the completion condition for alternate final missions into an event
|
||||||
create_region(world, player, locations_per_region, location_cache, "The Moebius Factor"),
|
if final_mission != 'All-In':
|
||||||
create_region(world, player, locations_per_region, location_cache, "Supernova"),
|
final_location = alt_final_mission_locations[final_mission]
|
||||||
create_region(world, player, locations_per_region, location_cache, "Maw of the Void"),
|
# Final location should be near the end of the cache
|
||||||
create_region(world, player, locations_per_region, location_cache, "Devil's Playground"),
|
for i in range(len(location_cache) - 1, -1, -1):
|
||||||
create_region(world, player, locations_per_region, location_cache, "Welcome to the Jungle"),
|
if location_cache[i].name == final_location:
|
||||||
create_region(world, player, locations_per_region, location_cache, "Breakout"),
|
location_cache[i].locked = True
|
||||||
create_region(world, player, locations_per_region, location_cache, "Ghost of a Chance"),
|
location_cache[i].event = True
|
||||||
create_region(world, player, locations_per_region, location_cache, "The Great Train Robbery"),
|
location_cache[i].address = None
|
||||||
create_region(world, player, locations_per_region, location_cache, "Cutthroat"),
|
break
|
||||||
create_region(world, player, locations_per_region, location_cache, "Engine of Destruction"),
|
else:
|
||||||
create_region(world, player, locations_per_region, location_cache, "Media Blitz"),
|
final_location = 'All-In: Victory'
|
||||||
create_region(world, player, locations_per_region, location_cache, "Piercing the Shroud"),
|
|
||||||
create_region(world, player, locations_per_region, location_cache, "Whispers of Doom"),
|
|
||||||
create_region(world, player, locations_per_region, location_cache, "A Sinister Turn"),
|
|
||||||
create_region(world, player, locations_per_region, location_cache, "Echoes of the Future"),
|
|
||||||
create_region(world, player, locations_per_region, location_cache, "In Utter Darkness"),
|
|
||||||
create_region(world, player, locations_per_region, location_cache, "Gates of Hell"),
|
|
||||||
create_region(world, player, locations_per_region, location_cache, "Belly of the Beast"),
|
|
||||||
create_region(world, player, locations_per_region, location_cache, "Shatter the Sky"),
|
|
||||||
create_region(world, player, locations_per_region, location_cache, "All-In")
|
|
||||||
]
|
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys())
|
if mission_order_type in (0, 1):
|
||||||
|
throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys())
|
||||||
|
|
||||||
world.regions += regions
|
world.regions += regions
|
||||||
|
|
||||||
names: Dict[str, int] = {}
|
names: Dict[str, int] = {}
|
||||||
|
|
||||||
if get_option_value(world, player, "mission_order") == 0:
|
if mission_order_type == 0:
|
||||||
connect(world, player, names, 'Menu', 'Liberation Day'),
|
connect(world, player, names, 'Menu', 'Liberation Day'),
|
||||||
connect(world, player, names, 'Liberation Day', 'The Outlaws',
|
connect(world, player, names, 'Liberation Day', 'The Outlaws',
|
||||||
lambda state: state.has("Beat Liberation Day", player)),
|
lambda state: state.has("Beat Liberation Day", player)),
|
||||||
|
@ -119,32 +111,30 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
||||||
lambda state: state.has('Beat Gates of Hell', player) and (
|
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)))
|
state.has('Beat Shatter the Sky', player) or state.has('Beat Belly of the Beast', player)))
|
||||||
|
|
||||||
return vanilla_mission_req_table
|
return vanilla_mission_req_table, 29, final_location
|
||||||
|
|
||||||
elif get_option_value(world, player, "mission_order") == 1:
|
else:
|
||||||
missions = []
|
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
|
# Initial fill out of mission list and marking all-in mission
|
||||||
for mission in vanilla_shuffle_order:
|
for mission in mission_order:
|
||||||
if mission.type == "all_in":
|
if mission is None:
|
||||||
missions.append("All-In")
|
missions.append(None)
|
||||||
elif get_option_value(world, player, "relegate_no_build") and mission.relegate:
|
elif mission.type == "all_in":
|
||||||
|
missions.append(final_mission)
|
||||||
|
elif mission.relegate and not get_option_value(world, player, "shuffle_no_build"):
|
||||||
missions.append("no_build")
|
missions.append("no_build")
|
||||||
else:
|
else:
|
||||||
missions.append(mission.type)
|
missions.append(mission.type)
|
||||||
|
|
||||||
# Place Protoss Missions if we are not using ShuffleProtoss
|
# Place Protoss Missions if we are not using ShuffleProtoss and are in Vanilla Shuffled
|
||||||
if get_option_value(world, player, "shuffle_protoss") == 0:
|
if get_option_value(world, player, "shuffle_protoss") == 0 and mission_order_type == 1:
|
||||||
missions[22] = "A Sinister Turn"
|
missions[22] = "A Sinister Turn"
|
||||||
medium_pool.remove("A Sinister Turn")
|
mission_pools['medium'].remove("A Sinister Turn")
|
||||||
missions[23] = "Echoes of the Future"
|
missions[23] = "Echoes of the Future"
|
||||||
medium_pool.remove("Echoes of the Future")
|
mission_pools['medium'].remove("Echoes of the Future")
|
||||||
missions[24] = "In Utter Darkness"
|
missions[24] = "In Utter Darkness"
|
||||||
hard_pool.remove("In Utter Darkness")
|
mission_pools['hard'].remove("In Utter Darkness")
|
||||||
|
|
||||||
no_build_slots = []
|
no_build_slots = []
|
||||||
easy_slots = []
|
easy_slots = []
|
||||||
|
@ -153,6 +143,8 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
||||||
|
|
||||||
# Search through missions to find slots needed to fill
|
# Search through missions to find slots needed to fill
|
||||||
for i in range(len(missions)):
|
for i in range(len(missions)):
|
||||||
|
if missions[i] is None:
|
||||||
|
continue
|
||||||
if missions[i] == "no_build":
|
if missions[i] == "no_build":
|
||||||
no_build_slots.append(i)
|
no_build_slots.append(i)
|
||||||
elif missions[i] == "easy":
|
elif missions[i] == "easy":
|
||||||
|
@ -163,30 +155,30 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
||||||
hard_slots.append(i)
|
hard_slots.append(i)
|
||||||
|
|
||||||
# Add no_build missions to the pool and fill in no_build slots
|
# Add no_build missions to the pool and fill in no_build slots
|
||||||
missions_to_add = no_build_pool
|
missions_to_add = mission_pools['no_build']
|
||||||
for slot in no_build_slots:
|
for slot in no_build_slots:
|
||||||
filler = random.randint(0, len(missions_to_add)-1)
|
filler = world.random.randint(0, len(missions_to_add)-1)
|
||||||
|
|
||||||
missions[slot] = missions_to_add.pop(filler)
|
missions[slot] = missions_to_add.pop(filler)
|
||||||
|
|
||||||
# Add easy missions into pool and fill in easy slots
|
# Add easy missions into pool and fill in easy slots
|
||||||
missions_to_add = missions_to_add + easy_pool
|
missions_to_add = missions_to_add + mission_pools['easy']
|
||||||
for slot in easy_slots:
|
for slot in easy_slots:
|
||||||
filler = random.randint(0, len(missions_to_add) - 1)
|
filler = world.random.randint(0, len(missions_to_add) - 1)
|
||||||
|
|
||||||
missions[slot] = missions_to_add.pop(filler)
|
missions[slot] = missions_to_add.pop(filler)
|
||||||
|
|
||||||
# Add medium missions into pool and fill in medium slots
|
# Add medium missions into pool and fill in medium slots
|
||||||
missions_to_add = missions_to_add + medium_pool
|
missions_to_add = missions_to_add + mission_pools['medium']
|
||||||
for slot in medium_slots:
|
for slot in medium_slots:
|
||||||
filler = random.randint(0, len(missions_to_add) - 1)
|
filler = world.random.randint(0, len(missions_to_add) - 1)
|
||||||
|
|
||||||
missions[slot] = missions_to_add.pop(filler)
|
missions[slot] = missions_to_add.pop(filler)
|
||||||
|
|
||||||
# Add hard missions into pool and fill in hard slots
|
# Add hard missions into pool and fill in hard slots
|
||||||
missions_to_add = missions_to_add + hard_pool
|
missions_to_add = missions_to_add + mission_pools['hard']
|
||||||
for slot in hard_slots:
|
for slot in hard_slots:
|
||||||
filler = random.randint(0, len(missions_to_add) - 1)
|
filler = world.random.randint(0, len(missions_to_add) - 1)
|
||||||
|
|
||||||
missions[slot] = missions_to_add.pop(filler)
|
missions[slot] = missions_to_add.pop(filler)
|
||||||
|
|
||||||
|
@ -195,7 +187,7 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
||||||
mission_req_table = {}
|
mission_req_table = {}
|
||||||
for i in range(len(missions)):
|
for i in range(len(missions)):
|
||||||
connections = []
|
connections = []
|
||||||
for connection in vanilla_shuffle_order[i].connect_to:
|
for connection in mission_order[i].connect_to:
|
||||||
if connection == -1:
|
if connection == -1:
|
||||||
connect(world, player, names, "Menu", missions[i])
|
connect(world, player, names, "Menu", missions[i])
|
||||||
else:
|
else:
|
||||||
|
@ -203,16 +195,17 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
||||||
(lambda name, missions_req: (lambda state: state.has(f"Beat {name}", player) and
|
(lambda name, missions_req: (lambda state: state.has(f"Beat {name}", player) and
|
||||||
state._sc2wol_cleared_missions(world, player,
|
state._sc2wol_cleared_missions(world, player,
|
||||||
missions_req)))
|
missions_req)))
|
||||||
(missions[connection], vanilla_shuffle_order[i].number))
|
(missions[connection], mission_order[i].number))
|
||||||
connections.append(connection + 1)
|
connections.append(connection + 1)
|
||||||
|
|
||||||
mission_req_table.update({missions[i]: MissionInfo(
|
mission_req_table.update({missions[i]: MissionInfo(
|
||||||
vanilla_mission_req_table[missions[i]].id, vanilla_mission_req_table[missions[i]].extra_locations,
|
vanilla_mission_req_table[missions[i]].id, connections, mission_order[i].category,
|
||||||
connections, vanilla_shuffle_order[i].category, number=vanilla_shuffle_order[i].number,
|
number=mission_order[i].number,
|
||||||
completion_critical=vanilla_shuffle_order[i].completion_critical,
|
completion_critical=mission_order[i].completion_critical,
|
||||||
or_requirements=vanilla_shuffle_order[i].or_requirements)})
|
or_requirements=mission_order[i].or_requirements)})
|
||||||
|
|
||||||
return mission_req_table
|
final_mission_id = vanilla_mission_req_table[final_mission].id
|
||||||
|
return mission_req_table, final_mission_id, final_mission + ': Victory'
|
||||||
|
|
||||||
|
|
||||||
def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: Set[str]):
|
def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: Set[str]):
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from typing import List, Set, Tuple
|
from typing import List, Set, Tuple, Dict
|
||||||
from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification
|
from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification
|
||||||
from worlds.AutoWorld import WebWorld, World
|
from worlds.AutoWorld import WebWorld, World
|
||||||
from .Items import StarcraftWoLItem, item_table, filler_items, item_name_groups, get_full_item_list, \
|
from .Items import StarcraftWoLItem, item_table, filler_items, item_name_groups, get_full_item_list, \
|
||||||
basic_unit
|
get_basic_units
|
||||||
from .Locations import get_locations
|
from .Locations import get_locations
|
||||||
from .Regions import create_regions
|
from .Regions import create_regions
|
||||||
from .Options import sc2wol_options, get_option_value
|
from .Options import sc2wol_options, get_option_value, get_option_set_value
|
||||||
from .LogicMixin import SC2WoLLogic
|
from .LogicMixin import SC2WoLLogic
|
||||||
|
from .PoolFilter import filter_missions, filter_items, get_item_upgrades
|
||||||
|
from .MissionTables import get_starting_mission_locations, MissionInfo
|
||||||
|
|
||||||
|
|
||||||
class Starcraft2WoLWebWorld(WebWorld):
|
class Starcraft2WoLWebWorld(WebWorld):
|
||||||
|
@ -42,6 +44,8 @@ class SC2WoLWorld(World):
|
||||||
locked_locations: typing.List[str]
|
locked_locations: typing.List[str]
|
||||||
location_cache: typing.List[Location]
|
location_cache: typing.List[Location]
|
||||||
mission_req_table = {}
|
mission_req_table = {}
|
||||||
|
final_mission_id: int
|
||||||
|
victory_item: str
|
||||||
required_client_version = 0, 3, 5
|
required_client_version = 0, 3, 5
|
||||||
|
|
||||||
def __init__(self, world: MultiWorld, player: int):
|
def __init__(self, world: MultiWorld, player: int):
|
||||||
|
@ -49,24 +53,21 @@ class SC2WoLWorld(World):
|
||||||
self.location_cache = []
|
self.location_cache = []
|
||||||
self.locked_locations = []
|
self.locked_locations = []
|
||||||
|
|
||||||
def _create_items(self, name: str):
|
|
||||||
data = get_full_item_list()[name]
|
|
||||||
return [self.create_item(name) for _ in range(data.quantity)]
|
|
||||||
|
|
||||||
def create_item(self, name: str) -> Item:
|
def create_item(self, name: str) -> Item:
|
||||||
data = get_full_item_list()[name]
|
data = get_full_item_list()[name]
|
||||||
return StarcraftWoLItem(name, data.classification, data.code, self.player)
|
return StarcraftWoLItem(name, data.classification, data.code, self.player)
|
||||||
|
|
||||||
def create_regions(self):
|
def create_regions(self):
|
||||||
self.mission_req_table = create_regions(self.world, self.player, get_locations(self.world, self.player),
|
self.mission_req_table, self.final_mission_id, self.victory_item = create_regions(
|
||||||
self.location_cache)
|
self.world, self.player, get_locations(self.world, self.player), self.location_cache
|
||||||
|
)
|
||||||
|
|
||||||
def generate_basic(self):
|
def generate_basic(self):
|
||||||
excluded_items = get_excluded_items(self, self.world, self.player)
|
excluded_items = get_excluded_items(self, self.world, self.player)
|
||||||
|
|
||||||
assign_starter_items(self.world, self.player, excluded_items, self.locked_locations)
|
starter_items = assign_starter_items(self.world, self.player, excluded_items, self.locked_locations)
|
||||||
|
|
||||||
pool = get_item_pool(self.world, self.player, excluded_items)
|
pool = get_item_pool(self.world, self.player, self.mission_req_table, starter_items, excluded_items, self.location_cache)
|
||||||
|
|
||||||
fill_item_pool_with_dummy_items(self, self.world, self.player, self.locked_locations, self.location_cache, pool)
|
fill_item_pool_with_dummy_items(self, self.world, self.player, self.locked_locations, self.location_cache, pool)
|
||||||
|
|
||||||
|
@ -74,8 +75,7 @@ class SC2WoLWorld(World):
|
||||||
|
|
||||||
def set_rules(self):
|
def set_rules(self):
|
||||||
setup_events(self.world, self.player, self.locked_locations, self.location_cache)
|
setup_events(self.world, self.player, self.locked_locations, self.location_cache)
|
||||||
|
self.world.completion_condition[self.player] = lambda state: state.has(self.victory_item, self.player)
|
||||||
self.world.completion_condition[self.player] = lambda state: state.has('All-In: Victory', self.player)
|
|
||||||
|
|
||||||
def get_filler_item_name(self) -> str:
|
def get_filler_item_name(self) -> str:
|
||||||
return self.world.random.choice(filler_items)
|
return self.world.random.choice(filler_items)
|
||||||
|
@ -91,6 +91,7 @@ class SC2WoLWorld(World):
|
||||||
slot_req_table[mission] = self.mission_req_table[mission]._asdict()
|
slot_req_table[mission] = self.mission_req_table[mission]._asdict()
|
||||||
|
|
||||||
slot_data["mission_req"] = slot_req_table
|
slot_data["mission_req"] = slot_req_table
|
||||||
|
slot_data["final_mission"] = self.final_mission_id
|
||||||
return slot_data
|
return slot_data
|
||||||
|
|
||||||
|
|
||||||
|
@ -120,30 +121,37 @@ def get_excluded_items(self: SC2WoLWorld, world: MultiWorld, player: int) -> Set
|
||||||
for item in world.precollected_items[player]:
|
for item in world.precollected_items[player]:
|
||||||
excluded_items.add(item.name)
|
excluded_items.add(item.name)
|
||||||
|
|
||||||
|
excluded_items_option = getattr(world, 'excluded_items', [])
|
||||||
|
|
||||||
|
excluded_items.update(excluded_items_option[player].value)
|
||||||
|
|
||||||
return excluded_items
|
return excluded_items
|
||||||
|
|
||||||
|
|
||||||
def assign_starter_items(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]):
|
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
|
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)
|
||||||
|
if not local_basic_unit:
|
||||||
|
raise Exception("At least one basic unit must be local")
|
||||||
|
|
||||||
local_basic_unit = tuple(item for item in basic_unit if item not in non_local_items)
|
# The first world should also be the starting world
|
||||||
if not local_basic_unit:
|
first_mission = list(world.worlds[player].mission_req_table)[0]
|
||||||
raise Exception("At least one basic unit must be local")
|
starting_mission_locations = get_starting_mission_locations(world, player)
|
||||||
|
if first_mission in starting_mission_locations:
|
||||||
|
first_location = starting_mission_locations[first_mission]
|
||||||
|
elif first_mission == "In Utter Darkness":
|
||||||
|
first_location = first_mission + ": Defeat"
|
||||||
|
else:
|
||||||
|
first_location = first_mission + ": Victory"
|
||||||
|
|
||||||
# The first world should also be the starting world
|
return [assign_starter_item(world, player, excluded_items, locked_locations, first_location, local_basic_unit)]
|
||||||
first_location = list(world.worlds[player].mission_req_table)[0]
|
|
||||||
|
|
||||||
if first_location == "In Utter Darkness":
|
|
||||||
first_location = first_location + ": Defeat"
|
|
||||||
else:
|
else:
|
||||||
first_location = first_location + ": Victory"
|
return []
|
||||||
|
|
||||||
assign_starter_item(world, player, excluded_items, locked_locations, first_location,
|
|
||||||
local_basic_unit)
|
|
||||||
|
|
||||||
|
|
||||||
def assign_starter_item(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str],
|
def assign_starter_item(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str],
|
||||||
location: str, item_list: Tuple[str, ...]):
|
location: str, item_list: Tuple[str, ...]) -> Item:
|
||||||
|
|
||||||
item_name = world.random.choice(item_list)
|
item_name = world.random.choice(item_list)
|
||||||
|
|
||||||
|
@ -155,17 +163,40 @@ def assign_starter_item(world: MultiWorld, player: int, excluded_items: Set[str]
|
||||||
|
|
||||||
locked_locations.append(location)
|
locked_locations.append(location)
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
def get_item_pool(world: MultiWorld, player: int, excluded_items: Set[str]) -> List[Item]:
|
|
||||||
|
def get_item_pool(world: 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] = []
|
pool: List[Item] = []
|
||||||
|
|
||||||
|
# For the future: goal items like Artifact Shards go here
|
||||||
|
locked_items = []
|
||||||
|
|
||||||
|
# YAML items
|
||||||
|
yaml_locked_items = get_option_set_value(world, player, 'locked_items')
|
||||||
|
|
||||||
for name, data in item_table.items():
|
for name, data in item_table.items():
|
||||||
if name not in excluded_items:
|
if name not in excluded_items:
|
||||||
for _ in range(data.quantity):
|
for _ in range(data.quantity):
|
||||||
item = create_item_with_correct_settings(world, player, name)
|
item = create_item_with_correct_settings(world, player, name)
|
||||||
pool.append(item)
|
if name in yaml_locked_items:
|
||||||
|
locked_items.append(item)
|
||||||
|
else:
|
||||||
|
pool.append(item)
|
||||||
|
|
||||||
return pool
|
existing_items = starter_items + [item for item in world.precollected_items[player]]
|
||||||
|
existing_names = [item.name for item in existing_items]
|
||||||
|
# Removing upgrades for excluded items
|
||||||
|
for item_name in excluded_items:
|
||||||
|
if item_name in existing_names:
|
||||||
|
continue
|
||||||
|
invalid_upgrades = get_item_upgrades(pool, item_name)
|
||||||
|
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)
|
||||||
|
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, world: MultiWorld, player: int, locked_locations: List[str],
|
||||||
|
|
Loading…
Reference in New Issue