Starcraft 2 Wings of Liberty AP Implementation ()

This commit is contained in:
TheCondor07 2022-05-18 17:27:38 -04:00 committed by GitHub
parent 90d506ee7c
commit 551cf8442f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1384 additions and 1 deletions

View File

@ -152,6 +152,8 @@ components: Iterable[Component] = (
Component('FF1 Client', 'FF1Client'),
# ChecksFinder
Component('ChecksFinder Client', 'ChecksFinderClient'),
# Starcraft 2
Component('Starcraft 2 Client', 'StarcraftClient'),
# Functions
Component('Open host.yaml', func=open_host_yaml),
Component('Open Patch', func=open_patch),

View File

@ -25,6 +25,7 @@ Currently, the following games are supported:
* Hollow Knight
* The Witness
* Sonic Adventure 2: Battle
* Starcraft 2: Wings of Liberty
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled

520
StarcraftClient.py Normal file
View File

@ -0,0 +1,520 @@
from __future__ import annotations
import sys
import multiprocessing
import logging
import asyncio
import nest_asyncio
import sc2
from sc2.main import run_game
from sc2.data import Race
from sc2.bot_ai import BotAI
from sc2.player import Bot
from worlds.sc2wol.Items import lookup_id_to_name, item_table
from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET
from Utils import init_logging
if __name__ == "__main__":
init_logging("SC2Client", exception_logger="Client")
logger = logging.getLogger("Client")
sc2_logger = logging.getLogger("Starcraft2")
import colorama
from NetUtils import *
from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor, gui_enabled, get_base_parser
nest_asyncio.apply()
class StarcraftClientProcessor(ClientCommandProcessor):
ctx: Context
def _cmd_play(self, mission_id: str = "") -> bool:
"""Start a Starcraft 2 mission"""
options = mission_id.split()
num_options = len(options)
if num_options > 0:
mission_number = int(options[0])
if is_mission_available(mission_number, self.ctx.checked_locations, mission_req_table):
asyncio.create_task(starcraft_launch(self.ctx, mission_number), name="Starcraft Launch")
else:
sc2_logger.info(
"This mission is not currently unlocked. Use /unfinished or /available to see what is available.")
else:
sc2_logger.info("Mission ID needs to be specified. Use /unfinished or /available to view ids for available missions.")
return True
def _cmd_available(self) -> bool:
"""Get what missions are currently available to play"""
request_available_missions(self.ctx.checked_locations, mission_req_table)
return True
def _cmd_unfinished(self) -> bool:
"""Get what missions are currently available to play and have not had all locations checked"""
request_unfinished_missions(self.ctx.checked_locations, mission_req_table)
return True
class Context(CommonContext):
command_processor = StarcraftClientProcessor
game = "Starcraft 2 Wings of Liberty"
items_handling = 0b111
difficulty = -1
all_in_choice = 0
items_rec_to_announce = []
rec_announce_pos = 0
items_sent_to_announce = []
sent_announce_pos = 0
announcements = []
announcement_pos = 0
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(Context, self).server_auth(password_requested)
if not self.auth:
logger.info('Enter slot name:')
self.auth = await self.console_input()
await self.send_connect()
def on_package(self, cmd: str, args: dict):
if cmd in {"Connected"}:
self.difficulty = args["slot_data"]["game_difficulty"]
self.all_in_choice = args["slot_data"]["all_in_map"]
if cmd in {"PrintJSON"}:
noted = False
if "receiving" in args:
if args["receiving"] == self.slot:
self.announcements.append(args["data"])
noted = True
if not noted and "item" in args:
if args["item"].player == self.slot:
self.announcements.append(args["data"])
def run_gui(self):
from kvui import GameManager
class SC2Manager(GameManager):
logging_pairs = [
("Client", "Archipelago"),
("Starcraft2", "Starcraft2"),
]
base_title = "Archipelago Starcraft 2 Client"
self.ui = SC2Manager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
async def main():
multiprocessing.freeze_support()
parser = get_base_parser()
parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical'])
args = parser.parse_args()
ctx = Context(args.connect, args.password)
if ctx.server_task is None:
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
input_task = None
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
if sys.stdin:
input_task = asyncio.create_task(console_loop(ctx), name="Input")
await ctx.exit_event.wait()
ctx.server_address = None
ctx.snes_reconnect_address = None
await ctx.shutdown()
if ui_task:
await ui_task
if input_task:
input_task.cancel()
maps_table = ["ap_traynor01", "ap_traynor02", "ap_traynor03", "ap_thanson01", "ap_thanson02", "ap_thanson03a", "ap_thanson03b", "ap_ttychus01",
"ap_ttychus02", "ap_ttychus03", "ap_ttychus04", "ap_ttychus05", "ap_ttosh01", "ap_ttosh02", "ap_ttosh03a", "ap_ttosh03b",
"ap_thorner01", "ap_thorner02", "ap_thorner03", "ap_thorner04", "ap_thorner05s", "ap_tzeratul01", "ap_tzeratul02",
"ap_tzeratul03", "ap_tzeratul04", "ap_tvalerian01", "ap_tvalerian02a", "ap_tvalerian02b", "ap_tvalerian03"]
def calculate_items(items):
unit_unlocks = 0
armory1_unlocks = 0
armory2_unlocks = 0
upgrade_unlocks = 0
building_unlocks = 0
merc_unlocks = 0
lab_unlocks = 0
protoss_unlock = 0
minerals = 0
vespene = 0
for item in items:
data = lookup_id_to_name[item.item]
if item_table[data].type == "Unit":
unit_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Upgrade":
upgrade_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Armory 1":
armory1_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Armory 2":
armory2_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Building":
building_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Mercenary":
merc_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Laboratory":
lab_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Protoss":
protoss_unlock += (1 << item_table[data].number)
elif item_table[data].type == "Minerals":
minerals += item_table[data].number
elif item_table[data].type == "Vespene":
vespene += item_table[data].number
return [unit_unlocks, upgrade_unlocks, armory1_unlocks, armory2_unlocks, building_unlocks, merc_unlocks,
lab_unlocks, protoss_unlock, minerals, vespene]
def calc_difficulty(difficulty):
if difficulty == 0:
return 'C'
elif difficulty == 1:
return 'N'
elif difficulty == 2:
return 'H'
elif difficulty == 3:
return 'B'
return 'X'
async def starcraft_launch(ctx: Context, mission_id):
ctx.rec_announce_pos = len(ctx.items_rec_to_announce)
ctx.sent_announce_pos = len(ctx.items_sent_to_announce)
ctx.announcements_pos = len(ctx.announcements)
run_game(sc2.maps.get(maps_table[mission_id-1]), [
Bot(Race.Terran, ArchipelagoBot(ctx, mission_id), name="Archipelago")], realtime=True)
class ArchipelagoBot(sc2.bot_ai.BotAI):
game_running = False
mission_completed = False
first_bonus = False
second_bonus = False
third_bonus = False
fourth_bonus = False
fifth_bonus = False
sixth_bonus = False
seventh_bonus = False
eight_bonus = False
ctx: Context = None
mission_id = 0
can_read_game = False
last_received_update = 0
def __init__(self, ctx: Context, mission_id):
self.ctx = ctx
self.mission_id = mission_id
super(ArchipelagoBot, self).__init__()
async def on_step(self, iteration: int):
game_state = 0
if iteration == 0:
start_items = calculate_items(self.ctx.items_received)
difficulty = calc_difficulty(self.ctx.difficulty)
await self.chat_send("ArchipelagoLoad {} {} {} {} {} {} {} {} {} {} {} {}".format(
difficulty, start_items[0], start_items[1], start_items[2], start_items[3], start_items[4], start_items[5],
start_items[6], start_items[7], start_items[8], start_items[9], self.ctx.all_in_choice))
self.last_received_update = len(self.ctx.items_received)
else:
if self.ctx.announcement_pos < len(self.ctx.announcements):
index = 0
message = ""
while index < len(self.ctx.announcements[self.ctx.announcement_pos]):
message += self.ctx.announcements[self.ctx.announcement_pos][index]["text"]
index += 1
index = 0
start_rem_pos = -1
# Remove unneeded [Color] tags
while index < len(message):
if message[index] == '[':
start_rem_pos = index
index += 1
elif message[index] == ']' and start_rem_pos > -1:
temp_msg = ""
if start_rem_pos > 0:
temp_msg = message[:start_rem_pos]
if index < len(message) - 1:
temp_msg += message[index+1:]
message = temp_msg
index += start_rem_pos - index
start_rem_pos = -1
else:
index += 1
await self.chat_send("SendMessage " + message)
self.ctx.announcement_pos += 1
# Archipelago reads the health
for unit in self.all_own_units():
if unit.health_max == 38281:
game_state = int(38281 - unit.health)
can_read_game = True
if iteration == 80 and not game_state & 1:
await self.chat_send("SendMessage Warning: Archipelago unable to connect or has lost connection to " +
"Starcraft 2 (This is likely a map issue)")
if self.last_received_update < len(self.ctx.items_received):
current_items = calculate_items(self.ctx.items_received)
await self.chat_send("UpdateTech {} {} {} {} {} {} {} {}".format(
current_items[0], current_items[1], current_items[2], current_items[3], current_items[4], current_items[5],
current_items[6], current_items[7]))
self.last_received_update = len(self.ctx.items_received)
if game_state & 1:
if not self.game_running:
print("Archipelago Connected")
self.game_running = True
if can_read_game:
if game_state & (1 << 1) and not self.mission_completed:
if self.mission_id != 29:
print("Mission Completed")
await self.ctx.send_msgs([
{"cmd": 'LocationChecks', "locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id]}])
self.mission_completed = True
else:
print("Game Complete")
await self.ctx.send_msgs([{"cmd": 'StatusUpdate', "status": ClientStatus.CLIENT_GOAL}])
self.mission_completed = True
if game_state & (1 << 2) and not self.first_bonus:
print("1st Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks', "locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 1]}])
self.first_bonus = True
if not self.second_bonus and game_state & (1 << 3):
print("2nd Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks', "locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 2]}])
self.second_bonus = True
if not self.third_bonus and game_state & (1 << 4):
print("3rd Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks', "locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 3]}])
self.third_bonus = True
if not self.fourth_bonus and game_state & (1 << 5):
print("4th Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks', "locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 4]}])
self.fourth_bonus = True
if not self.fifth_bonus and game_state & (1 << 6):
print("5th Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks', "locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 5]}])
self.fifth_bonus = True
if not self.sixth_bonus and game_state & (1 << 7):
print("6th Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks', "locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 6]}])
self.sixth_bonus = True
if not self.seventh_bonus and game_state & (1 << 8):
print("6th Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks', "locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 7]}])
self.seventh_bonus = True
if not self.eight_bonus and game_state & (1 << 9):
print("6th Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks', "locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 8]}])
self.eight_bonus = True
else:
await self.chat_send("LostConnection - Lost connection to game.")
class MissionInfo(typing.NamedTuple):
id: int
extra_locations: int
required_world: list[int]
number: int = 0 # number of worlds need beaten
mission_req_table = {
"Liberation Day": MissionInfo(1, 7, []),
"The Outlaws": MissionInfo(2, 2, [1]),
"Zero Hour": MissionInfo(3, 4, [2]),
"Evacuation": MissionInfo(4, 4, [3]),
"Outbreak": MissionInfo(5, 3, [4]),
"Safe Haven": MissionInfo(6, 1, [5], number=7),
"Haven's Fall": MissionInfo(7, 1, [5], number=7),
"Smash and Grab": MissionInfo(8, 5, [3]),
"The Dig": MissionInfo(9, 4, [8], number=8),
"The Moebius Factor": MissionInfo(10, 9, [9], number=11),
"Supernova": MissionInfo(11, 5, [10], number=14),
"Maw of the Void": MissionInfo(12, 6, [11]),
"Devil's Playground": MissionInfo(13, 3, [3], number=4),
"Welcome to the Jungle": MissionInfo(14, 4, [13]),
"Breakout": MissionInfo(15, 3, [14], number=8),
"Ghost of a Chance": MissionInfo(16, 6, [14], number=8),
"The Great Train Robbery": MissionInfo(17, 4, [3], number=6),
"Cutthroat": MissionInfo(18, 5, [17]),
"Engine of Destruction": MissionInfo(19, 6, [18]),
"Media Blitz": MissionInfo(20, 5, [19]),
"Piercing the Shroud": MissionInfo(21, 6, [20]),
"Whispers of Doom": MissionInfo(22, 4, [9]),
"A Sinister Turn": MissionInfo(23, 4, [22]),
"Echoes of the Future": MissionInfo(24, 3, [23]),
"In Utter Darkness": MissionInfo(25, 3, [24]),
"Gates of Hell": MissionInfo(26, 2, [12]),
"Belly of the Beast": MissionInfo(27, 4, [26]),
"Shatter the Sky": MissionInfo(28, 5, [26]),
"All-In": MissionInfo(29, -1, [27, 28])
}
def calc_objectives_completed(mission, missions_info, locations_done):
objectives_complete = 0
if missions_info[mission].extra_locations > 0:
for i in range(missions_info[mission].extra_locations):
if (missions_info[mission].id * 100 + SC2WOL_LOC_ID_OFFSET + i) in locations_done:
objectives_complete += 1
return objectives_complete
else:
return -1
def request_unfinished_missions(locations_done, location_table):
message = "Unfinished Missions:"
first_item = True
unfinished_missions = calc_unfinished_missions(locations_done, location_table)
for mission in unfinished_missions:
if first_item:
message += " {}[{}] ({}/{})".format(mission, location_table[mission].id, unfinished_missions[mission],
location_table[mission].extra_locations)
first_item = False
else:
message += ", {}[{}] ({}/{})".format(mission, location_table[mission].id, unfinished_missions[mission],
location_table[mission].extra_locations)
sc2_logger.info(message)
def calc_unfinished_missions(locations_done, locations):
unfinished_missions = []
locations_completed = []
available_missions = calc_available_missions(locations_done, locations)
for name in available_missions:
if not locations[name].extra_locations == -1:
objectives_completed = calc_objectives_completed(name, locations, locations_done)
if objectives_completed < locations[name].extra_locations:
unfinished_missions.append(name)
locations_completed.append(objectives_completed)
else:
unfinished_missions.append(name)
locations_completed.append(-1)
return {unfinished_missions[i]: locations_completed[i] for i in range(len(unfinished_missions))}
def is_mission_available(mission_id_to_check, locations_done, locations):
unfinished_missions = calc_available_missions(locations_done, locations)
for mission in unfinished_missions:
if locations[mission].id == mission_id_to_check:
return True
return False
def request_available_missions(locations_done, location_table):
message = "Available Missions:"
first_item = True
missions = calc_available_missions(locations_done, location_table)
for mission in missions:
if first_item:
message += " {}[{}]".format(mission, location_table[mission].id)
first_item = False
else:
message += ", {}[{}]".format(mission, location_table[mission].id)
sc2_logger.info(message)
def calc_available_missions(locations_done, locations):
available_missions = []
mission_complete = 0
# Get number of missions completed
for loc in locations_done:
if loc % 100 == 0:
mission_complete += 1
for name in locations:
if len(locations[name].required_world) >= 1:
reqs_complete = True
for req_mission in locations[name].required_world:
if not(req_mission * 100 + SC2WOL_LOC_ID_OFFSET) in locations_done:
reqs_complete = False
break
if reqs_complete and mission_complete >= locations[name].number:
available_missions.append(name)
else:
available_missions.append(name)
return available_missions
if __name__ == '__main__':
colorama.init()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
colorama.deinit()

View File

@ -67,6 +67,7 @@ Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDi
Name: "client/oot"; Description: "Ocarina of Time"; Types: full playing
Name: "client/ff1"; Description: "Final Fantasy 1"; Types: full playing
Name: "client/cf"; Description: "ChecksFinder"; Types: full playing
Name: "client/sc2"; Description: "Starcraft 2"; Types: full playing
Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing
[Dirs]
@ -92,6 +93,7 @@ Source: "{#source_path}\ArchipelagoOoTClient.exe"; DestDir: "{app}"; Flags: igno
Source: "{#source_path}\ArchipelagoOoTAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/oot
Source: "{#source_path}\ArchipelagoFF1Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/ff1
Source: "{#source_path}\ArchipelagoChecksFinderClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/cf
Source: "{#source_path}\ArchipelagoStarcraft2Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sc2
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
[Icons]
@ -104,6 +106,7 @@ Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinec
Name: "{group}\{#MyAppName} Ocarina of Time Client"; Filename: "{app}\ArchipelagoOoTClient.exe"; Components: client/oot
Name: "{group}\{#MyAppName} Final Fantasy 1 Client"; Filename: "{app}\ArchipelagoFF1Client.exe"; Components: client/ff1
Name: "{group}\{#MyAppName} ChecksFinder Client"; Filename: "{app}\ArchipelagoChecksFinderClient.exe"; Components: client/cf
Name: "{group}\{#MyAppName} Starcraft 2 Client"; Filename: "{app}\ArchipelagoStarcraft2Client.exe"; Components: client/sc2
Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopicon
Name: "{commondesktop}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; Components: server
@ -113,6 +116,7 @@ Name: "{commondesktop}\{#MyAppName} Minecraft Client"; Filename: "{app}\Archipel
Name: "{commondesktop}\{#MyAppName} Ocarina of Time Client"; Filename: "{app}\ArchipelagoOoTClient.exe"; Tasks: desktopicon; Components: client/oot
Name: "{commondesktop}\{#MyAppName} Final Fantasy 1 Client"; Filename: "{app}\ArchipelagoFF1Client.exe"; Tasks: desktopicon; Components: client/ff1
Name: "{commondesktop}\{#MyAppName} ChecksFinder Client"; Filename: "{app}\ArchipelagoChecksFinderClient.exe"; Tasks: desktopicon; Components: client/cf
Name: "{commondesktop}\{#MyAppName} Starcraft 2 Client"; Filename: "{app}\ArchipelagoStarcraft2Client.exe"; Tasks: desktopicon; Components: client/sc2
[Run]

View File

@ -348,7 +348,7 @@ cx_Freeze.setup(
"excludes": ["numpy", "Cython", "PySide2", "PIL",
"pandas"],
"zip_include_packages": ["*"],
"zip_exclude_packages": ["worlds", "kivy"],
"zip_exclude_packages": ["worlds", "kivy", "sc2"],
"include_files": [],
"include_msvcr": False,
"replace_paths": [("*", "")],

169
worlds/sc2wol/Items.py Normal file
View File

@ -0,0 +1,169 @@
from BaseClasses import Item
import typing
class ItemData(typing.NamedTuple):
code: typing.Optional[int]
type: typing.Optional[str]
number: typing.Optional[int]
progression: bool = False
never_exclude: bool = True
quantity: int = 1
class StarcraftWoLItem(Item):
game: str = "Starcraft2WoL"
def __init__(self, name, advancement: bool = False, code: int = None, player: int = None):
super(StarcraftWoLItem, self).__init__(name, advancement, code, player)
def get_full_item_list():
return item_table
SC2WOL_ITEM_ID_OFFSET = 1000
item_table = {
"Marine": ItemData(0+SC2WOL_ITEM_ID_OFFSET, "Unit", 0, progression=True),
"Medic": ItemData(1+SC2WOL_ITEM_ID_OFFSET, "Unit", 1, progression=True),
"Firebat": ItemData(2+SC2WOL_ITEM_ID_OFFSET, "Unit", 2, progression=True),
"Marauder": ItemData(3+SC2WOL_ITEM_ID_OFFSET, "Unit", 3, progression=True),
"Reaper": ItemData(4+SC2WOL_ITEM_ID_OFFSET, "Unit", 4, progression=True),
"Hellion": ItemData(5+SC2WOL_ITEM_ID_OFFSET, "Unit", 5, progression=True),
"Vulture": ItemData(6+SC2WOL_ITEM_ID_OFFSET, "Unit", 6, progression=True),
"Goliath": ItemData(7+SC2WOL_ITEM_ID_OFFSET, "Unit", 7, progression=True),
"Diamondback": ItemData(8+SC2WOL_ITEM_ID_OFFSET, "Unit", 8, progression=True),
"Siege Tank": ItemData(9+SC2WOL_ITEM_ID_OFFSET, "Unit", 9, progression=True),
"Medivac": ItemData(10+SC2WOL_ITEM_ID_OFFSET, "Unit", 10, progression=True),
"Wraith": ItemData(11+SC2WOL_ITEM_ID_OFFSET, "Unit", 11, progression=True),
"Viking": ItemData(12+SC2WOL_ITEM_ID_OFFSET, "Unit", 12, progression=True),
"Banshee": ItemData(13+SC2WOL_ITEM_ID_OFFSET, "Unit", 13, progression=True),
"Battlecruiser": ItemData(14+SC2WOL_ITEM_ID_OFFSET, "Unit", 14, progression=True),
"Ghost": ItemData(15+SC2WOL_ITEM_ID_OFFSET, "Unit", 15, progression=True),
"Spectre": ItemData(16+SC2WOL_ITEM_ID_OFFSET, "Unit", 16, progression=True),
"Thor": ItemData(17+SC2WOL_ITEM_ID_OFFSET, "Unit", 17, progression=True),
"Progressive Infantry Weapon": ItemData (100+SC2WOL_ITEM_ID_OFFSET, "Upgrade", 0, quantity=3),
"Progressive Infantry Armor": ItemData (102+SC2WOL_ITEM_ID_OFFSET, "Upgrade", 2, quantity=3),
"Progressive Vehicle Weapon": ItemData (103+SC2WOL_ITEM_ID_OFFSET, "Upgrade", 4, quantity=3),
"Progressive Vehicle Armor": ItemData (104+SC2WOL_ITEM_ID_OFFSET, "Upgrade", 6, 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),
"Projectile Accelerator (Bunker)": ItemData (200+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 0),
"Neosteel Bunker (Bunker)": ItemData (201+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 1),
"Titanium Housing (Missile Turret)": ItemData (202+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 2),
"Hellstorm Batteries (Missile Turret)": ItemData (203+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 3),
"Advanced Construction (SCV)": ItemData (204+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 4),
"Dual-Fusion Welders (SCV)": ItemData (205+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 5),
"Fire-Suppression System (Building)": ItemData (206+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 6),
"Orbital Command (Building)": ItemData (207+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 7),
"Stimpack (Marine)": ItemData (208+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 8),
"Combat Shield (Marine)": ItemData (209+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 9),
"Advanced Medic Facilities (Medic)": ItemData (210+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 10),
"Stabilizer Medpacks (Medic)": ItemData (211+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 11),
"Incinerator Gauntlets (Firebat)": ItemData (212+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 12),
"Juggernaut Plating (Firebat)": ItemData (213+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 13),
"Concussive Shells (Marauder)": ItemData (214+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 14),
"Kinetic Foam (Marauder)": ItemData (215+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 15),
"U-238 Rounds (Reaper)": ItemData (216+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 16),
"G-4 Clusterbomb (Reaper)": ItemData (217+SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17),
"Twin-Linked Flamethrower (Hellion)": ItemData(300+SC2WOL_ITEM_ID_OFFSET, "Armory 2", 0),
"Thermite Filaments (Hellion)": ItemData(301+SC2WOL_ITEM_ID_OFFSET, "Armory 2", 1),
"Cerberus Mine (Vulture)": ItemData(302 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 2),
"Replenishable Magazine (Vulture)": ItemData(303 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 3),
"Multi-Lock Weapons System (Goliath)": ItemData(304 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 4),
"Ares-Class Targeting System (Goliath)": ItemData(305 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 5),
"Tri-Lithium Power Cell (Diamondback)": ItemData(306 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 6),
"Shaped Hull (Diamondback)": ItemData(307 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 7),
"Maelstrom Rounds (Siege Tank)": ItemData(308 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 8),
"Shaped Blast (Siege Tank)": ItemData(309 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 9),
"Rapid Deployment Tube (Medivac)": ItemData(310 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 10),
"Advanced Healing AI (Medivac)": ItemData(311 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 11),
"Tomahawk Power Cells (Wraith)": ItemData(312 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 12),
"Displacement Field (Wraith)": ItemData(313 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 13),
"Ripwave Missiles (Viking)": ItemData(314 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 14),
"Phobos-Class Weapons System (Viking)": ItemData(315 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 15),
"Cross-Spectrum Dampeners (Banshee)": ItemData(316 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 16),
"Shockwave Missile Battery (Banshee)": ItemData(317 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 17),
"Missile Pods (Battlecruiser)": ItemData(318 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 18),
"Defensive Matrix (Battlecruiser)": ItemData(319 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 19),
"Ocular Implants (Ghost)": ItemData(320 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 20),
"Crius Suit (Ghost)": ItemData(321 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 21),
"Psionic Lash (Spectre)": ItemData(322 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 22),
"Nyx-Class Cloaking Module (Spectre)": ItemData(323 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 23),
"330mm Barrage Cannon (Thor)": ItemData(324 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 24),
"Immortality Protocol (Thor)": ItemData(325 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 25),
"Bunker": ItemData (400+SC2WOL_ITEM_ID_OFFSET, "Building", 0, progression=True),
"Missile Turret": ItemData (401+SC2WOL_ITEM_ID_OFFSET, "Building", 1, progression=True),
"Sensor Tower": ItemData (402+SC2WOL_ITEM_ID_OFFSET, "Building", 2),
"War Pigs": ItemData (500 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 0),
"Devil Dogs": ItemData(501 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 1),
"Hammer Securities": ItemData(502 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 2),
"Spartan Company": ItemData(503 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 3),
"Siege Breakers": ItemData(504 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 4),
"Hel's Angel": ItemData(505 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 5),
"Dusk Wings": ItemData(506 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 6),
"Jackson's Revenge": ItemData(507 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 7),
"Ultra-Capacitors": ItemData(600 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 0),
"Vanadium Plating": ItemData(601 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 1),
"Orbital Depots": ItemData(602 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 2),
"Micro-Filtering": ItemData(603 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 3),
"Automated Refinery": ItemData(604 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 4),
"Command Center Reactor": ItemData(605 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 5),
"Raven": ItemData(606 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 6),
"Science Vessel": ItemData(607 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 7),
"Tech Reactor": ItemData(608 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 8),
"Orbital Strike": ItemData(609 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 9),
"Shrike Turret": ItemData(610 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 10),
"Fortified Bunker": ItemData(611 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 11),
"Planetary Fortress": ItemData(612 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 12),
"Perdition Turret": ItemData(613 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 13),
"Predator": ItemData(614 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 14),
"Hercules": ItemData(615 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 15, progression=True),
"Cellular Reactor": ItemData(616 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 16),
"Regenerative Bio-Steel": ItemData(617 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 17),
"Hive Mind Emulator": ItemData(618 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 18),
"Psi Disrupter": ItemData(619 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 19),
"Zealot": ItemData (700 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 0, progression=True),
"Stalker": ItemData (701 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 1, progression=True),
"High Templar": ItemData (702 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 2, progression=True),
"Dark Templar": ItemData (703 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 3, progression=True),
"Immortal": ItemData (704 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 4, progression=True),
"Colossus": ItemData (705 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 5, progression=True),
"Phoenix": ItemData (706 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 6, progression=True),
"Void Ray": ItemData (707 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 7, progression=True),
"Carrier": ItemData (708 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 8, progression=True),
"+5 Starting Minerals": ItemData(800+SC2WOL_ITEM_ID_OFFSET, "Minerals", 5, quantity=0),
"+5 Starting Vespene": ItemData(801+SC2WOL_ITEM_ID_OFFSET, "Vespene", 5, quantity=0)
}
basic_unit: typing.Tuple[str, ...] = (
'Marine',
'Marauder',
'Firebat',
'Hellion',
'Vulture'
)
item_name_groups = {"Missions":
{"Beat Liberation Day", "Beat The Outlaws", "Beat Zero Hour", "Beat Evacuation",
"None Outbreak", "Beat Safe Haven", "Beat Haven's Fall", "Beat Smash and Grab", "Beat The Dig",
"Beat The Moebius Factor", "Beat Supernova", "Beat Maw of the Void", "Beat Devil's Playground",
"Beat Welcome to the Jungle", "Beat Breakout", "Beat Ghost of a Chance",
"Beat The Great Train Robbery", "Beat Cutthroat", "Beat Engine of Destruction",
"Beat Media Blitz", "Beat Piercing the Shroud"}}
filler_items: typing.Tuple[str, ...] = (
'+5 Starting Minerals',
'+5 Starting Vespene'
)
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}

186
worlds/sc2wol/Locations.py Normal file
View File

@ -0,0 +1,186 @@
from typing import List, Tuple, Optional, Callable, NamedTuple
from BaseClasses import MultiWorld
from BaseClasses import Location
SC2WOL_LOC_ID_OFFSET = 1000
class SC2WoLLocation(Location):
game: str = "Starcraft2WoL"
class LocationData(NamedTuple):
region: str
name: str
code: Optional[int]
rule: Callable = lambda state: True
def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[LocationData, ...]:
location_table: List[LocationData] = [
LocationData("Liberation Day", "Liberation Day: Victory", SC2WOL_LOC_ID_OFFSET + 100),
LocationData("Liberation Day", "Liberation Day: First Statue", SC2WOL_LOC_ID_OFFSET + 101),
LocationData("Liberation Day", "Liberation Day: Second Statue", SC2WOL_LOC_ID_OFFSET + 102),
LocationData("Liberation Day", "Liberation Day: Third Statue", SC2WOL_LOC_ID_OFFSET + 103),
LocationData("Liberation Day", "Liberation Day: Fourth Statue", SC2WOL_LOC_ID_OFFSET + 104),
LocationData("Liberation Day", "Liberation Day: Fifth Statue", SC2WOL_LOC_ID_OFFSET + 105),
LocationData("Liberation Day", "Liberation Day: Sixth Statue", SC2WOL_LOC_ID_OFFSET + 106),
LocationData("Liberation Day", "Beat Liberation Day", None),
LocationData("The Outlaws", "The Outlaws: Victory", SC2WOL_LOC_ID_OFFSET + 200),
LocationData("The Outlaws", "The Outlaws: Rebel Base", SC2WOL_LOC_ID_OFFSET + 201),
LocationData("The Outlaws", "Beat The Outlaws", None),
LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300),
LocationData("Zero Hour", "Zero Hour: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 301),
LocationData("Zero Hour", "Zero Hour: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 302),
LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303),
LocationData("Zero Hour", "Beat Zero Hour", None),
LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400),
LocationData("Evacuation", "Evacuation: First Chysalis", SC2WOL_LOC_ID_OFFSET + 401),
LocationData("Evacuation", "Evacuation: Second Chysalis", SC2WOL_LOC_ID_OFFSET + 402),
LocationData("Evacuation", "Evacuation: Third Chysalis", SC2WOL_LOC_ID_OFFSET + 403),
LocationData("Evacuation", "Beat Evacuation", None),
LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500),
LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501),
LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502),
LocationData("Outbreak", "Beat Outbreak", None),
LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600),
LocationData("Safe Haven", "Beat Safe Haven", None),
LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700),
LocationData("Haven's Fall", "Beat Haven's Fall", None),
LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800),
LocationData("Smash and Grab", "Smash and Grab: First Relic", SC2WOL_LOC_ID_OFFSET + 801),
LocationData("Smash and Grab", "Smash and Grab: Second Relic", SC2WOL_LOC_ID_OFFSET + 802),
LocationData("Smash and Grab", "Smash and Grab: Third Relic", SC2WOL_LOC_ID_OFFSET + 803),
LocationData("Smash and Grab", "Smash and Grab: Fourth Relic", SC2WOL_LOC_ID_OFFSET + 804),
LocationData("Smash and Grab", "Beat Smash and Grab", None),
LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900),
LocationData("The Dig", "The Dig: Left Relic", SC2WOL_LOC_ID_OFFSET + 901),
LocationData("The Dig", "The Dig: Right Ground Relic", SC2WOL_LOC_ID_OFFSET + 902),
LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903),
LocationData("The Dig", "Beat The Dig", None),
LocationData("The Moebius Factor", "The Moebius Factor: 3rd Data Core", SC2WOL_LOC_ID_OFFSET + 1000),
LocationData("The Moebius Factor", "The Moebius Factor: 1st Data Core ", SC2WOL_LOC_ID_OFFSET + 1001),
LocationData("The Moebius Factor", "The Moebius Factor: 2nd Data Core", SC2WOL_LOC_ID_OFFSET + 1002),
LocationData("The Moebius Factor", "The Moebius Factor: South Rescue", SC2WOL_LOC_ID_OFFSET + 1003,
lambda state: state._sc2wol_able_to_rescue(world, player) or True),
LocationData("The Moebius Factor", "The Moebius Factor: Wall Rescue", SC2WOL_LOC_ID_OFFSET + 1004,
lambda state: state._sc2wol_able_to_rescue(world, player) or True),
LocationData("The Moebius Factor", "The Moebius Factor: Mid Rescue", SC2WOL_LOC_ID_OFFSET + 1005,
lambda state: state._sc2wol_able_to_rescue(world, player) or True),
LocationData("The Moebius Factor", "The Moebius Factor: Nydus Roof Rescue", SC2WOL_LOC_ID_OFFSET + 1006,
lambda state: state._sc2wol_able_to_rescue(world, player) or True),
LocationData("The Moebius Factor", "The Moebius Factor: Alive Inside Rescue", SC2WOL_LOC_ID_OFFSET + 1007,
lambda state: state._sc2wol_able_to_rescue(world, player) or True),
LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008),
LocationData("The Moebius Factor", "Beat The Moebius Factor", None),
LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100),
LocationData("Supernova", "Supernova: West Relic", SC2WOL_LOC_ID_OFFSET + 1101),
LocationData("Supernova", "Supernova: North Relic", SC2WOL_LOC_ID_OFFSET + 1102),
LocationData("Supernova", "Supernova: South Relic", SC2WOL_LOC_ID_OFFSET + 1103),
LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104),
LocationData("Supernova", "Beat Supernova", None),
LocationData("Maw of the Void", "Maw of the Void: Xel'Naga Vault", SC2WOL_LOC_ID_OFFSET + 1200),
LocationData("Maw of the Void", "Maw of the Void: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1201),
LocationData("Maw of the Void", "Maw of the Void: Expansion Prisoners", SC2WOL_LOC_ID_OFFSET + 1202),
LocationData("Maw of the Void", "Maw of the Void: South Close Prisoners", SC2WOL_LOC_ID_OFFSET + 1203),
LocationData("Maw of the Void", "Maw of the Void: South Far Prisoners", SC2WOL_LOC_ID_OFFSET + 1204),
LocationData("Maw of the Void", "Maw of the Void: North Prisoners", SC2WOL_LOC_ID_OFFSET + 1205),
LocationData("Maw of the Void", "Beat Maw of the Void", None),
LocationData("Devil's Playground", "Devil's Playground: 8000 Minerals", SC2WOL_LOC_ID_OFFSET + 1300),
LocationData("Devil's Playground", "Devil's Playground: Tosh's Miners", SC2WOL_LOC_ID_OFFSET + 1301),
LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302),
LocationData("Devil's Playground", "Beat Devil's Playground", None),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: 7 Canisters", SC2WOL_LOC_ID_OFFSET + 1400),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: Close Relic", SC2WOL_LOC_ID_OFFSET + 1401),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: West Relic", SC2WOL_LOC_ID_OFFSET + 1402),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: North-East Relic", SC2WOL_LOC_ID_OFFSET + 1403),
LocationData("Welcome to the Jungle", "Beat Welcome to the Jungle", None),
LocationData("Breakout", "Breakout: Main Prison", SC2WOL_LOC_ID_OFFSET + 1500),
LocationData("Breakout", "Breakout: Diamondback Prison", SC2WOL_LOC_ID_OFFSET + 1501),
LocationData("Breakout", "Breakout: Siegetank Prison", SC2WOL_LOC_ID_OFFSET + 1502),
LocationData("Breakout", "Beat Breakout", None),
LocationData("Ghost of a Chance", "Ghost of a Chance: Psi-Indoctrinator", SC2WOL_LOC_ID_OFFSET + 1600),
LocationData("Ghost of a Chance", "Ghost of a Chance: Terrazine Tank", SC2WOL_LOC_ID_OFFSET + 1601),
LocationData("Ghost of a Chance", "Ghost of a Chance: Jorium Stockpile", SC2WOL_LOC_ID_OFFSET + 1602),
LocationData("Ghost of a Chance", "Ghost of a Chance: First Island Spectres", SC2WOL_LOC_ID_OFFSET + 1603),
LocationData("Ghost of a Chance", "Ghost of a Chance: Second Island Spectres", SC2WOL_LOC_ID_OFFSET + 1604),
LocationData("Ghost of a Chance", "Ghost of a Chance: Third Island Spectres", SC2WOL_LOC_ID_OFFSET + 1605),
LocationData("Ghost of a Chance", "Beat Ghost of a Chance", None),
LocationData("The Great Train Robbery", "The Great Train Robbery: 8 Trains", SC2WOL_LOC_ID_OFFSET + 1700, lambda state: state._sc2wol_has_train_killers(world, player)),
LocationData("The Great Train Robbery", "The Great Train Robbery: North Defiler", SC2WOL_LOC_ID_OFFSET + 1701),
LocationData("The Great Train Robbery", "The Great Train Robbery: Mid Defiler", SC2WOL_LOC_ID_OFFSET + 1702),
LocationData("The Great Train Robbery", "The Great Train Robbery: South Defiler", SC2WOL_LOC_ID_OFFSET + 1703),
LocationData("The Great Train Robbery", "Beat The Great Train Robbery", None,
lambda state: state._sc2wol_has_train_killers(world, player)),
LocationData("Cutthroat", "Cutthroat: Orlan's Planetary", SC2WOL_LOC_ID_OFFSET + 1800),
LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801),
LocationData("Cutthroat", "Cutthroat: North Relic", SC2WOL_LOC_ID_OFFSET + 1802),
LocationData("Cutthroat", "Cutthroat: Mid Relic", SC2WOL_LOC_ID_OFFSET + 1803),
LocationData("Cutthroat", "Cutthroat: Southwest Relic", SC2WOL_LOC_ID_OFFSET + 1804),
LocationData("Cutthroat", "Beat Cutthroat", None),
LocationData("Engine of Destruction", "Engine of Destruction: Dominion Bases", SC2WOL_LOC_ID_OFFSET + 1900,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Engine of Destruction", "Engine of Destruction: Odin", SC2WOL_LOC_ID_OFFSET + 1901),
LocationData("Engine of Destruction", "Engine of Destruction: Loki", SC2WOL_LOC_ID_OFFSET + 1902,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Engine of Destruction", "Engine of Destruction: Lab Devourer", SC2WOL_LOC_ID_OFFSET + 1903),
LocationData("Engine of Destruction", "Engine of Destruction: North Devourer", SC2WOL_LOC_ID_OFFSET + 1904,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Engine of Destruction", "Engine of Destruction: Southeast Devourer", SC2WOL_LOC_ID_OFFSET + 1905,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Engine of Destruction", "Beat Engine of Destruction", None,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
LocationData("Media Blitz", "Media Blitz: Full Upload", SC2WOL_LOC_ID_OFFSET + 2000),
LocationData("Media Blitz", "Media Blitz: Tower 1", SC2WOL_LOC_ID_OFFSET + 2001),
LocationData("Media Blitz", "Media Blitz: Tower 2", SC2WOL_LOC_ID_OFFSET + 2002),
LocationData("Media Blitz", "Media Blitz: Tower 3", SC2WOL_LOC_ID_OFFSET + 2003),
LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004),
LocationData("Media Blitz", "Beat Media Blitz", None),
LocationData("Piercing the Shroud", "Piercing the Shroud: Facility Escape", SC2WOL_LOC_ID_OFFSET + 2100),
LocationData("Piercing the Shroud", "Piercing the Shroud: Holding Cell Relic", SC2WOL_LOC_ID_OFFSET + 2101),
LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk Relic", SC2WOL_LOC_ID_OFFSET + 2102),
LocationData("Piercing the Shroud", "Piercing the Shroud: First Escape Relic", SC2WOL_LOC_ID_OFFSET + 2103),
LocationData("Piercing the Shroud", "Piercing the Shroud: Second Escape Relic", SC2WOL_LOC_ID_OFFSET + 2104),
LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk ", SC2WOL_LOC_ID_OFFSET + 2105),
LocationData("Piercing the Shroud", "Beat Piercing the Shroud", None),
LocationData("Whispers of Doom", "Whispers of Doom: Void Seeker Escape", SC2WOL_LOC_ID_OFFSET + 2200),
LocationData("Whispers of Doom", "Whispers of Doom: First Hatchery", SC2WOL_LOC_ID_OFFSET + 2201),
LocationData("Whispers of Doom", "Whispers of Doom: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 2202),
LocationData("Whispers of Doom", "Whispers of Doom: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 2203),
LocationData("Whispers of Doom", "Beat Whispers of Doom", None),
LocationData("A Sinister Turn", "A Sinister Turn: Preservers Freed", SC2WOL_LOC_ID_OFFSET + 2300,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("A Sinister Turn", "A Sinister Turn: Robotics Facility", SC2WOL_LOC_ID_OFFSET + 2301),
LocationData("A Sinister Turn", "A Sinister Turn: Dark Shrine", SC2WOL_LOC_ID_OFFSET + 2302),
LocationData("A Sinister Turn", "A Sinister Turn: Templar Archives", SC2WOL_LOC_ID_OFFSET + 2303,
lambda state: state._sc2wol_has_protoss_common_units(world, player)),
LocationData("A Sinister Turn", "Beat A Sinister Turn", None,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("Echoes of the Future", "Echoes of the Future: Overmind", SC2WOL_LOC_ID_OFFSET + 2400),
LocationData("Echoes of the Future", "Echoes of the Future: Close Obelisk", SC2WOL_LOC_ID_OFFSET + 2401),
LocationData("Echoes of the Future", "Echoes of the Future: West Obelisk", SC2WOL_LOC_ID_OFFSET + 2402),
LocationData("Echoes of the Future", "Beat Echoes of the Future", None),
LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2500),
LocationData("In Utter Darkness", "In Utter Darkness: Protoss Archive", SC2WOL_LOC_ID_OFFSET + 2501),
LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2502),
LocationData("In Utter Darkness", "Beat In Utter Darkness", None),
LocationData("Gates of Hell", "Gates of Hell: Nydus Worms", SC2WOL_LOC_ID_OFFSET + 2600),
LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601),
LocationData("Gates of Hell", "Beat Gates of Hell", None),
LocationData("Belly of the Beast", "Belly of the Beast: Extract", SC2WOL_LOC_ID_OFFSET + 2700),
LocationData("Belly of the Beast", "Belly of the Beast: First Charge", SC2WOL_LOC_ID_OFFSET + 2701),
LocationData("Belly of the Beast", "Belly of the Beast: Second Charge", SC2WOL_LOC_ID_OFFSET + 2702),
LocationData("Belly of the Beast", "Belly of the Beast: Third Charge", SC2WOL_LOC_ID_OFFSET + 2703),
LocationData("Belly of the Beast", "Beat Belly of the Beast", None),
LocationData("Shatter the Sky", "Shatter the Sky: Platform Destroyed", SC2WOL_LOC_ID_OFFSET + 2800),
LocationData("Shatter the Sky", "Shatter the Sky: Close Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2801),
LocationData("Shatter the Sky", "Shatter the Sky: Northwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2802),
LocationData("Shatter the Sky", "Shatter the Sky: Southeast Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2803),
LocationData("Shatter the Sky", "Shatter the Sky: Southwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2804),
LocationData("Shatter the Sky", "Shatter the Sky: Leviathan", SC2WOL_LOC_ID_OFFSET + 2805),
LocationData("Shatter the Sky", "Beat Shatter the Sky", None),
LocationData("All-In", "All-In: Victory", None)
]
return tuple(location_table)

View File

@ -0,0 +1,49 @@
from BaseClasses import MultiWorld
from ..AutoWorld import LogicMixin
class SC2WoLLogic(LogicMixin):
def _sc2wol_has_common_unit(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Marine', 'Marauder', 'Firebat', 'Hellion', 'Vulture'}, 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:
return self.has_any({'Viking', 'Wraith', 'Medivac', 'Banshee', 'Hercules'}, player)
def _sc2wol_has_battlecruiser(self, world: MultiWorld, player: int) -> bool:
return self.has('Battlecruiser', player)
def _sc2wol_has_air_anti_air(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Viking', 'Wraith'}, player)
def _sc2wol_has_mobile_anti_air(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Marine', 'Goliath'}, player) or self._sc2wol_has_air_anti_air(world, player)
def _sc2wol_has_anti_air(self, world: MultiWorld, player: int) -> bool:
return self.has('Missile Turret', player) or self._sc2wol_has_mobile_anti_air(world, player)
def _sc2wol_has_heavy_defense(self, world: MultiWorld, player: int) -> bool:
return (self.has_any({'Siege Tank', 'Vulture'}, player) or
self.has('Bunker', player) and self._sc2wol_has_bunker_unit(world, player)) and \
self._sc2wol_has_anti_air(world, player)
def _sc2wol_has_train_killers(self, world: MultiWorld, player: int) -> bool:
return (self.has_any({'Siege Tank', 'Diamondback'}, player) or
self.has_all({'Reaper', "G-4 Clusterbomb"}, player) or self.has_all({'Spectre', 'Psionic Lash'}, player))
def _sc2wol_able_to_rescue(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Medivac', 'Hercules', 'Raven', 'Orbital Strike'}, player)
def _sc2wol_has_protoss_common_units(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Zealot', 'Immortal', 'Stalker', 'Dark Templar'}, player)
def _sc2wol_has_protoss_medium_units(self, world: MultiWorld, player: int) -> bool:
return self._sc2wol_has_protoss_common_units(world, player) and \
self.has_any({'Stalker', 'Void Ray', 'Phoenix', 'Carrier'}, player)
def _sc2wol_cleared_missions(self, world: MultiWorld, player: int, mission_count: int) -> bool:
return self.has_group("Missions", player, mission_count)

48
worlds/sc2wol/Options.py Normal file
View File

@ -0,0 +1,48 @@
from typing import Dict
from BaseClasses import MultiWorld
from Options import Choice, Option
class GameDifficulty(Choice):
"""The difficulty of the campaign, affects enemy AI, starting units, and game speed."""
display_name = "Game Difficulty"
option_casual = 0
option_normal = 1
option_hard = 2
option_brutal = 3
class UpgradeBonus(Choice):
"""Determines what lab upgrade to use, whether it is Ultra-Capacitors which boost attack speed with every weapon upgrade
or Vanadium Plating which boosts life with every armor upgrade."""
display_name = "Upgrade Bonus"
option_ultra_capacitors = 0
option_vanadium_plating = 1
class BunkerUpgrade(Choice):
"""Determines what bunker lab upgrade to use, whether it is Shrike Turret which outfits bunkers with an automated turret or
Fortified Bunker which boosts the life of bunkers."""
display_name = "Bunker Upgrade"
option_shrike_turret = 0
option_fortified_bunker = 1
class AllInMap(Choice):
"""Determines what verion of All-In (final map) that will be generated for the campaign."""
display_name = "All In Map"
option_ground = 0
option_air = 1
# noinspection PyTypeChecker
sc2wol_options: Dict[str, Option] = {
"game_difficulty": GameDifficulty,
"upgrade_bonus": UpgradeBonus,
"bunker_upgrade": BunkerUpgrade,
"all_in_map": AllInMap,
}
def get_option_value(world: MultiWorld, player: int, name: str) -> int:
option = getattr(world, name, None)
if option == None:
return 0
return int(option[player].value)

162
worlds/sc2wol/Regions.py Normal file
View File

@ -0,0 +1,162 @@
from typing import List, Set, Dict, Tuple, Optional, Callable
from BaseClasses import MultiWorld, Region, Entrance, Location, RegionType
from .Locations import LocationData
def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location]):
locations_per_region = get_locations_per_region(locations)
regions = [
create_region(world, player, locations_per_region, location_cache, "Menu"),
create_region(world, player, locations_per_region, location_cache, "Liberation Day"),
create_region(world, player, locations_per_region, location_cache, "The Outlaws"),
create_region(world, player, locations_per_region, location_cache, "Zero Hour"),
create_region(world, player, locations_per_region, location_cache, "Evacuation"),
create_region(world, player, locations_per_region, location_cache, "Outbreak"),
create_region(world, player, locations_per_region, location_cache, "Safe Haven"),
create_region(world, player, locations_per_region, location_cache, "Haven's Fall"),
create_region(world, player, locations_per_region, location_cache, "Smash and Grab"),
create_region(world, player, locations_per_region, location_cache, "The Dig"),
create_region(world, player, locations_per_region, location_cache, "The Moebius Factor"),
create_region(world, player, locations_per_region, location_cache, "Supernova"),
create_region(world, player, locations_per_region, location_cache, "Maw of the Void"),
create_region(world, player, locations_per_region, location_cache, "Devil's Playground"),
create_region(world, player, locations_per_region, location_cache, "Welcome to the Jungle"),
create_region(world, player, locations_per_region, location_cache, "Breakout"),
create_region(world, player, locations_per_region, location_cache, "Ghost of a Chance"),
create_region(world, player, locations_per_region, location_cache, "The Great Train Robbery"),
create_region(world, player, locations_per_region, location_cache, "Cutthroat"),
create_region(world, player, locations_per_region, location_cache, "Engine of Destruction"),
create_region(world, player, locations_per_region, location_cache, "Media Blitz"),
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__:
throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys())
world.regions += regions
names: Dict[str, int] = {}
connect(world, player, names, 'Menu', 'Liberation Day'),
connect(world, player, names, 'Liberation Day', 'The Outlaws',
lambda state: state._sc2wol_has_common_unit(world, player)),
connect(world, player, names, 'The Outlaws', 'Zero Hour'),
connect(world, player, names, 'Zero Hour', 'Evacuation',
lambda state: state._sc2wol_has_anti_air(world, player)),
connect(world, player, names, 'Evacuation', 'Outbreak'),
connect(world, player, names, "Outbreak", "Safe Haven",
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
state._sc2wol_cleared_missions(world, player, 7)),
connect(world, player, names, "Outbreak", "Haven's Fall",
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
state._sc2wol_cleared_missions(world, player, 7)),
connect(world, player, names, 'Zero Hour', 'Smash and Grab',
lambda state: state._sc2wol_has_anti_air(world, player)),
connect(world, player, names, 'Smash and Grab', 'The Dig',
lambda state: state._sc2wol_cleared_missions(world, player, 8) and
state._sc2wol_has_heavy_defense(world, player)),
connect(world, player, names, 'The Dig', 'The Moebius Factor',
lambda state: state._sc2wol_cleared_missions(world, player, 11) and
state._sc2wol_has_air(world, player)),
connect(world, player, names, 'The Moebius Factor', 'Supernova',
lambda state: state._sc2wol_cleared_missions(world, player, 14)),
connect(world, player, names, 'Supernova', 'Maw of the Void'),
connect(world, player, names, 'Zero Hour', "Devil's Playground",
lambda state: state._sc2wol_cleared_missions(world, player, 4)),
connect(world, player, names, "Devil's Playground", 'Welcome to the Jungle'),
connect(world, player, names, "Welcome to the Jungle", 'Breakout',
lambda state: state._sc2wol_cleared_missions(world, player, 8)),
connect(world, player, names, "Welcome to the Jungle", 'Ghost of a Chance',
lambda state: state._sc2wol_cleared_missions(world, player, 8)),
connect(world, player, names, "Zero Hour", 'The Great Train Robbery',
lambda state: state._sc2wol_cleared_missions(world, player, 6)),
connect(world, player, names, 'The Great Train Robbery', 'Cutthroat',
lambda state: state.has("Beat The Great Train Robbery", player)),
connect(world, player, names, 'Cutthroat', 'Engine of Destruction',
lambda state: state.has("Beat The Great Train Robbery", player)),
connect(world, player, names, 'Engine of Destruction', 'Media Blitz',
lambda state: state.has("Beat Engine of Destruction", player)),
connect(world, player, names, 'Media Blitz', 'Piercing the Shroud'),
connect(world, player, names, 'The Dig', 'Whispers of Doom',),
connect(world, player, names, 'Whispers of Doom', 'A Sinister Turn'),
connect(world, player, names, 'A Sinister Turn', 'Echoes of the Future',
lambda state: state.has("Beat A Sinister Turn", player)),
connect(world, player, names, 'Echoes of the Future', 'In Utter Darkness'),
connect(world, player, names, 'Maw of the Void', 'Gates of Hell'),
connect(world, player, names, 'Gates of Hell', 'Belly of the Beast'),
connect(world, player, names, 'Gates of Hell', 'Shatter the Sky'),
connect(world, player, names, 'Gates of Hell', 'All-In',
lambda state: state.has('Beat Gates of Hell', player) or state.has('Beat Shatter the Sky', player))
def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: Set[str]):
existingRegions = set()
for region in regions:
existingRegions.add(region.name)
if (regionNames - existingRegions):
raise Exception("Starcraft: the following regions are used in locations: {}, but no such region exists".format(regionNames - existingRegions))
def create_location(player: int, location_data: LocationData, region: Region, location_cache: List[Location]) -> Location:
location = Location(player, location_data.name, location_data.code, region)
location.access_rule = location_data.rule
if id is None:
location.event = True
location.locked = True
location_cache.append(location)
return location
def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]], location_cache: List[Location], name: str) -> Region:
region = Region(name, RegionType.Generic, name, player)
region.world = world
if name in locations_per_region:
for location_data in locations_per_region[name]:
location = create_location(player, location_data, region, location_cache)
region.locations.append(location)
return region
def connect(world: MultiWorld, player: int, used_names: Dict[str, int], source: str, target: str, rule: Optional[Callable] = None):
sourceRegion = world.get_region(source, player)
targetRegion = world.get_region(target, player)
if target not in used_names:
used_names[target] = 1
name = target
else:
used_names[target] += 1
name = target + (' ' * used_names[target])
connection = Entrance(player, name, sourceRegion)
if rule:
connection.access_rule = rule
sourceRegion.exits.append(connection)
connection.connect(targetRegion)
def get_locations_per_region(locations: Tuple[LocationData, ...]) -> Dict[str, List[LocationData]]:
per_region: Dict[str, List[LocationData]] = {}
for location in locations:
per_region.setdefault(location.region, []).append(location)
return per_region

173
worlds/sc2wol/__init__.py Normal file
View File

@ -0,0 +1,173 @@
import typing
from typing import List, Set, Tuple
from BaseClasses import Item, MultiWorld, Location, Tutorial
from ..AutoWorld import World, WebWorld
from .Items import StarcraftWoLItem, item_table, filler_items, item_name_groups, get_full_item_list, \
basic_unit
from .Locations import get_locations
from .Regions import create_regions
from .Options import sc2wol_options, get_option_value
from .LogicMixin import SC2WoLLogic
from ..AutoWorld import World
class Starcraft2WoLWebWorld(WebWorld):
setup = Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Starcraft 2 randomizer connected to an Archipelago Multiworld",
"English",
"setup_en.md",
"setup/en",
["TheCondor"]
)
tutorials = [setup]
class SC2WoLWorld(World):
"""
StarCraft II: Wings of Liberty is a science fiction real-time strategy video game developed and published by Blizzard Entertainment.
Command Raynor's Raiders in collecting pieces of the Keystone in order to stop the zerg threat posed by the Queen of Blades.
"""
game = "Starcraft 2 Wings of Liberty"
web = Starcraft2WoLWebWorld()
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {location.name: location.code for location in get_locations(None, None)}
options = sc2wol_options
item_name_groups = item_name_groups
locked_locations: typing.List[str]
location_cache: typing.List[Location]
def _get_sc2wol_data(self):
return {}
def __init__(self, world: MultiWorld, player: int):
super(SC2WoLWorld, self).__init__(world, player)
self.location_cache = []
self.locked_locations = []
def _create_items(self, name: str):
data = get_full_item_list()[name]
return [self.create_item(name)] * data.quantity
def create_item(self, name: str) -> Item:
data = get_full_item_list()[name]
return StarcraftWoLItem(name, data.progression, data.code, self.player)
def create_regions(self):
create_regions(self.world, self.player, get_locations(self.world, self.player),
self.location_cache)
def generate_basic(self):
excluded_items = get_excluded_items(self, self.world, self.player)
assign_starter_items(self.world, self.player, excluded_items, self.locked_locations)
pool = get_item_pool(self.world, self.player, excluded_items)
fill_item_pool_with_dummy_items(self, self.world, self.player, self.locked_locations, self.location_cache, pool)
self.world.itempool += pool
def set_rules(self):
setup_events(self.world, self.player, self.locked_locations, self.location_cache)
self.world.completion_condition[self.player] = lambda state: state.has('All-In: Victory', self.player)
def get_filler_item_name(self) -> str:
return self.world.random.choice(filler_items)
def fill_slot_data(self):
slot_data = self._get_sc2wol_data()
for option_name in sc2wol_options:
option = getattr(self.world, option_name)[self.player]
if slot_data.get(option_name, None) is None and type(option.value) in {str, int}:
slot_data[option_name] = int(option.value)
return slot_data
def setup_events(world: MultiWorld, player: int, locked_locations: typing.List[str], location_cache: typing.List[Location]):
for location in location_cache:
if location.address == None:
item = Item(location.name, True, None, player)
locked_locations.append(location.name)
location.place_locked_item(item)
def get_excluded_items(self: SC2WoLWorld, world: MultiWorld, player: int) -> Set[str]:
excluded_items: Set[str] = set()
if get_option_value(world, player, "upgrade_bonus") == 1:
excluded_items.add("Ultra-Capacitors")
else:
excluded_items.add("Vanadium Plating")
if get_option_value(world, player, "bunker_upgrade") == 1:
excluded_items.add("Shrike Turret")
else:
excluded_items.add("Fortified Bunker")
for item in world.precollected_items[player]:
excluded_items.add(item.name)
return excluded_items
def assign_starter_items(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]):
non_local_items = world.non_local_items[player].value
local_basic_unit = tuple(item for item in basic_unit if item not in non_local_items)
if not local_basic_unit:
raise Exception("At least one basic unit must be local")
assign_starter_item(world, player, excluded_items, locked_locations, 'Liberation Day: First Statue',
local_basic_unit)
def assign_starter_item(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str],
location: str, item_list: Tuple[str, ...]):
item_name = world.random.choice(item_list)
excluded_items.add(item_name)
item = create_item_with_correct_settings(world, player, item_name)
world.get_location(location, player).place_locked_item(item)
locked_locations.append(location)
def get_item_pool(world: MultiWorld, player: int, excluded_items: Set[str]) -> List[Item]:
pool: List[Item] = []
for name, data in item_table.items():
if name not in excluded_items:
for _ in range(data.quantity):
item = create_item_with_correct_settings(world, player, name)
pool.append(item)
return pool
def fill_item_pool_with_dummy_items(self: SC2WoLWorld, world: MultiWorld, player: int, locked_locations: List[str],
location_cache: List[Location], pool: List[Item]):
for _ in range(len(location_cache) - len(locked_locations) - len(pool)):
item = create_item_with_correct_settings(world, player, self.get_filler_item_name())
pool.append(item)
def create_item_with_correct_settings(world: MultiWorld, player: int, name: str) -> Item:
data = item_table[name]
item = Item(name, data.progression, data.code, player)
item.never_exclude = data.never_exclude
if not item.advancement:
return item
return item

View File

@ -0,0 +1,33 @@
# Starcraft 2 Wings of Liberty
## Where is the settings page?
The [player settings page for this game](../player-settings) contains all the options you need to configure and export a
config file.
## What does randomization do to this game?
Items which the player would normally acquire throughout the game have been moved around. Logic remains, so the game is
always able to be completed, but because of the item shuffle the player may need to access certain areas before they
would in the vanilla game. All rings and spells are also randomized into those item locations, therefore you can no
longer craft them at the alchemist
## What is the goal of Starcraft 2 when randomized?
The goal remains unchanged. Beat the final mission All In.
## What items and locations get shuffled?
Unit unlocks, upgrade unlocks, armory upgrades, laboratory researches, and mercenary unlocks can be shuffled, and all
bonus objectives, side missions, mission completions are now locations that can contain these items.
## Which items can be in another player's world?
Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to limit
certain items to your own world.
## When the player receives an item, what happens?
When the player receives an item, they will receive a message through their text client and in game if currently playing
a mission. They will immediately be able to use that unlock/upgrade.

View File

@ -0,0 +1,33 @@
# Starcraft 2 Wings of Liberty Randomizer Setup Guide
## Required Software
- [Starcraft 2](https://starcraft2.com/en-us/)
- [Starcraft 2 AP Client](https://github.com/ArchipelagoMW/Archipelago)
- [Starcraft 2 AP Maps and Data](https://github.com/TheCondor07/Starcraft2ArchipelagoData)
## General Concept
Starcraft 2 AP Client launches a custom version of Starcraft 2 running modified Wings of Liberty campaign maps
to allow for randomization of the items
## Installation Procedures
Download latest release on [Starcraft 2 Archipelago Data Releases](https://github.com/TheCondor07/Starcraft2ArchipelagoData/releases) you
can find the .zip files on the releases page. Download the zip then extract the zip to the
folder where your Starcraft 2 game is installed. The just run ArchipelagoStarcraftClient.exe to start the client to
connect to a Multiworld Game.
## Joining a MultiWorld Game
1. Run ArchipelagoStarcraftClient.exe
2. Type in /connect [sever ip]
3. Insert slot name and password as prompted
4. Once connected, use /unfinished to find what missions you can play and '/play [mission id]' to launch a mission. For
new games under default settings the first mission available will always be Liberation Day[1] playable using the command
'/play 1'
## Where do I get a config file?
The [Player Settings](https://archipelago.gg/games/Starcraft%202%20Wings%20of%20Liberty/player-settings) page on the website allows you to
configure your personal settings and export them into a config file

View File

@ -0,0 +1,3 @@
nest-asyncio >= 1.5.5
six >= 1.16.0
apsc2 >= 5.5