SC2: GUI Mission Launcher (#586)
* SC2: Functioning Starcraft 2 Mission Launcher UI * AutoWorld: add .__file__ attribute to AutoWorlds This tries to help with a recurring easy to make mistake, where ./worlds/myworld does not exist in frozen form and is instead ./lib/worlds/myworld * SC2: get .kv file path correctly when frozen too Co-authored-by: TheCondor07 <TheCondorian07@gmail.com> Co-authored-by: Fabian Dill <fabian.dill@web.de>
This commit is contained in:
parent
3dd3f045e6
commit
20be691f36
|
@ -3,18 +3,21 @@ from __future__ import annotations
|
|||
import multiprocessing
|
||||
import logging
|
||||
import asyncio
|
||||
import nest_asyncio
|
||||
import os.path
|
||||
|
||||
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.Regions import MissionInfo
|
||||
from worlds.sc2wol.MissionTables import lookup_id_to_mission
|
||||
from worlds.sc2wol.Items import lookup_id_to_name, item_table
|
||||
from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET
|
||||
from worlds.sc2wol import SC2WoLWorld
|
||||
|
||||
from Utils import init_logging
|
||||
|
||||
|
@ -34,12 +37,11 @@ nest_asyncio.apply()
|
|||
|
||||
class StarcraftClientProcessor(ClientCommandProcessor):
|
||||
ctx: Context
|
||||
missions_unlocked = False
|
||||
|
||||
def _cmd_disable_mission_check(self) -> bool:
|
||||
"""Disables the check to see if a mission is available to play. Meant for co-op runs where one player can play
|
||||
the next mission in a chain the other player is doing."""
|
||||
self.missions_unlocked = True
|
||||
self.ctx.missions_unlocked = True
|
||||
sc2_logger.info("Mission check has been disabled")
|
||||
|
||||
def _cmd_play(self, mission_id: str = "") -> bool:
|
||||
|
@ -51,20 +53,7 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
|||
if num_options > 0:
|
||||
mission_number = int(options[0])
|
||||
|
||||
if self.missions_unlocked or \
|
||||
is_mission_available(mission_number, self.ctx.checked_locations, self.ctx.mission_req_table):
|
||||
if self.ctx.sc2_run_task:
|
||||
if not self.ctx.sc2_run_task.done():
|
||||
sc2_logger.warning("Starcraft 2 Client is still running!")
|
||||
self.ctx.sc2_run_task.cancel() # doesn't actually close the game, just stops the python task
|
||||
if self.ctx.slot is None:
|
||||
sc2_logger.warning("Launching Mission without Archipelago authentication, "
|
||||
"checks will not be registered to server.")
|
||||
self.ctx.sc2_run_task = asyncio.create_task(starcraft_launch(self.ctx, mission_number),
|
||||
name="Starcraft 2 Launch")
|
||||
else:
|
||||
sc2_logger.info(
|
||||
"This mission is not currently unlocked. Use /unfinished or /available to see what is available.")
|
||||
self.ctx.play_mission(mission_number)
|
||||
|
||||
else:
|
||||
sc2_logger.info(
|
||||
|
@ -99,6 +88,7 @@ class Context(CommonContext):
|
|||
announcements = []
|
||||
announcement_pos = 0
|
||||
sc2_run_task: typing.Optional[asyncio.Task] = None
|
||||
missions_unlocked = False
|
||||
|
||||
async def server_auth(self, password_requested: bool = False):
|
||||
if password_requested and not self.password:
|
||||
|
@ -130,6 +120,23 @@ class Context(CommonContext):
|
|||
|
||||
def run_gui(self):
|
||||
from kvui import GameManager
|
||||
from kivy.base import Clock
|
||||
from kivy.uix.tabbedpanel import TabbedPanelItem
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.button import Button
|
||||
|
||||
import Utils
|
||||
|
||||
class MissionButton(Button):
|
||||
pass
|
||||
|
||||
class MissionLayout(GridLayout):
|
||||
pass
|
||||
|
||||
class MissionCategory(GridLayout):
|
||||
pass
|
||||
|
||||
class SC2Manager(GameManager):
|
||||
logging_pairs = [
|
||||
|
@ -138,14 +145,97 @@ class Context(CommonContext):
|
|||
]
|
||||
base_title = "Archipelago Starcraft 2 Client"
|
||||
|
||||
mission_panel = None
|
||||
last_checked_locations = {}
|
||||
mission_id_to_button = {}
|
||||
|
||||
def __init__(self, ctx):
|
||||
super().__init__(ctx)
|
||||
|
||||
def build(self):
|
||||
container = super().build()
|
||||
|
||||
panel = TabbedPanelItem(text="Starcraft 2 Launcher")
|
||||
self.mission_panel = panel.content = MissionLayout()
|
||||
|
||||
self.tabs.add_widget(panel)
|
||||
|
||||
Clock.schedule_interval(self.build_mission_table, 0.5)
|
||||
|
||||
return container
|
||||
|
||||
def build_mission_table(self, dt):
|
||||
self.mission_panel.clear_widgets()
|
||||
|
||||
if self.ctx.mission_req_table:
|
||||
self.mission_id_to_button = {}
|
||||
categories = {}
|
||||
available_missions = []
|
||||
unfinished_missions = calc_unfinished_missions(self.ctx.checked_locations, self.ctx.mission_req_table,
|
||||
self.ctx, available_missions=available_missions)
|
||||
|
||||
self.last_checked_locations = self.ctx.checked_locations
|
||||
|
||||
# separate missions into categories
|
||||
for mission in self.ctx.mission_req_table:
|
||||
if not self.ctx.mission_req_table[mission].category in categories:
|
||||
categories[self.ctx.mission_req_table[mission].category] = []
|
||||
|
||||
categories[self.ctx.mission_req_table[mission].category].append(mission)
|
||||
|
||||
for category in categories:
|
||||
category_panel = MissionCategory()
|
||||
category_panel.add_widget(Label(text=category, size_hint_y=None, height=50, outline_width=1))
|
||||
|
||||
for mission in categories[category]:
|
||||
text = mission
|
||||
|
||||
if mission in unfinished_missions:
|
||||
text = f"[color=6495ED]{text}[/color]"
|
||||
elif mission in available_missions:
|
||||
text = f"[color=FFFFFF]{text}[/color]"
|
||||
else:
|
||||
text = f"[color=a9a9a9]{text}[/color]"
|
||||
|
||||
mission_button = MissionButton(text=text, size_hint_y=None, height=50)
|
||||
mission_button.bind(on_press=self.mission_callback)
|
||||
self.mission_id_to_button[self.ctx.mission_req_table[mission].id] = mission_button
|
||||
category_panel.add_widget(mission_button)
|
||||
|
||||
category_panel.add_widget(Label(text=""))
|
||||
self.mission_panel.add_widget(category_panel)
|
||||
|
||||
def mission_callback(self, button):
|
||||
self.ctx.play_mission(list(self.mission_id_to_button.keys())
|
||||
[list(self.mission_id_to_button.values()).index(button)])
|
||||
|
||||
self.ui = SC2Manager(self)
|
||||
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
|
||||
|
||||
Builder.load_file(Utils.local_path(os.path.dirname(SC2WoLWorld.__file__), "Starcraft2.kv"))
|
||||
|
||||
async def shutdown(self):
|
||||
await super(Context, self).shutdown()
|
||||
if self.sc2_run_task:
|
||||
self.sc2_run_task.cancel()
|
||||
|
||||
def play_mission(self, mission_id):
|
||||
if self.missions_unlocked or \
|
||||
is_mission_available(mission_id, self.checked_locations, self.mission_req_table):
|
||||
if self.sc2_run_task:
|
||||
if not self.sc2_run_task.done():
|
||||
sc2_logger.warning("Starcraft 2 Client is still running!")
|
||||
self.sc2_run_task.cancel() # doesn't actually close the game, just stops the python task
|
||||
if self.slot is None:
|
||||
sc2_logger.warning("Launching Mission without Archipelago authentication, "
|
||||
"checks will not be registered to server.")
|
||||
self.sc2_run_task = asyncio.create_task(starcraft_launch(self, mission_id),
|
||||
name="Starcraft 2 Launch")
|
||||
else:
|
||||
sc2_logger.info(
|
||||
f"{lookup_id_to_mission[mission_id]} is not currently unlocked. "
|
||||
f"Use /unfinished or /available to see what is available.")
|
||||
|
||||
|
||||
async def main():
|
||||
multiprocessing.freeze_support()
|
||||
|
@ -404,39 +494,6 @@ class ArchipelagoBot(sc2.bot_ai.BotAI):
|
|||
await self.chat_send("LostConnection - Lost connection to game.")
|
||||
|
||||
|
||||
mission_req_table = {
|
||||
"Liberation Day": MissionInfo(1, 7, [], completion_critical=True),
|
||||
"The Outlaws": MissionInfo(2, 2, [1], completion_critical=True),
|
||||
"Zero Hour": MissionInfo(3, 4, [2], completion_critical=True),
|
||||
"Evacuation": MissionInfo(4, 4, [3]),
|
||||
"Outbreak": MissionInfo(5, 3, [4]),
|
||||
"Safe Haven": MissionInfo(6, 1, [5], number=7),
|
||||
"Haven's Fall": MissionInfo(7, 1, [5], number=7),
|
||||
"Smash and Grab": MissionInfo(8, 5, [3], completion_critical=True),
|
||||
"The Dig": MissionInfo(9, 4, [8], number=8, completion_critical=True),
|
||||
"The Moebius Factor": MissionInfo(10, 9, [9], number=11, completion_critical=True),
|
||||
"Supernova": MissionInfo(11, 5, [10], number=14, completion_critical=True),
|
||||
"Maw of the Void": MissionInfo(12, 6, [11], completion_critical=True),
|
||||
"Devil's Playground": MissionInfo(13, 3, [3], number=4),
|
||||
"Welcome to the Jungle": MissionInfo(14, 4, [13]),
|
||||
"Breakout": MissionInfo(15, 3, [14], number=8),
|
||||
"Ghost of a Chance": MissionInfo(16, 6, [14], number=8),
|
||||
"The Great Train Robbery": MissionInfo(17, 4, [3], number=6),
|
||||
"Cutthroat": MissionInfo(18, 5, [17]),
|
||||
"Engine of Destruction": MissionInfo(19, 6, [18]),
|
||||
"Media Blitz": MissionInfo(20, 5, [19]),
|
||||
"Piercing the Shroud": MissionInfo(21, 6, [20]),
|
||||
"Whispers of Doom": MissionInfo(22, 4, [9]),
|
||||
"A Sinister Turn": MissionInfo(23, 4, [22]),
|
||||
"Echoes of the Future": MissionInfo(24, 3, [23]),
|
||||
"In Utter Darkness": MissionInfo(25, 3, [24]),
|
||||
"Gates of Hell": MissionInfo(26, 2, [12], completion_critical=True),
|
||||
"Belly of the Beast": MissionInfo(27, 4, [26], completion_critical=True),
|
||||
"Shatter the Sky": MissionInfo(28, 5, [26], completion_critical=True),
|
||||
"All-In": MissionInfo(29, -1, [27, 28], completion_critical=True, or_requirements=True)
|
||||
}
|
||||
|
||||
|
||||
def calc_objectives_completed(mission, missions_info, locations_done, unfinished_locations, ctx):
|
||||
objectives_complete = 0
|
||||
|
||||
|
@ -460,7 +517,8 @@ def request_unfinished_missions(locations_done, location_table, ui, ctx):
|
|||
unlocks = initialize_blank_mission_dict(location_table)
|
||||
unfinished_locations = initialize_blank_mission_dict(location_table)
|
||||
|
||||
unfinished_missions = calc_unfinished_missions(locations_done, location_table, unlocks, unfinished_locations, ctx)
|
||||
unfinished_missions = calc_unfinished_missions(locations_done, location_table, ctx, unlocks=unlocks,
|
||||
unfinished_locations=unfinished_locations)
|
||||
|
||||
message += ", ".join(f"{mark_up_mission_name(mission, location_table, ui,unlocks)}[{location_table[mission].id}] " +
|
||||
mark_up_objectives(
|
||||
|
@ -477,10 +535,21 @@ def request_unfinished_missions(locations_done, location_table, ui, ctx):
|
|||
sc2_logger.warning("No mission table found, you are likely not connected to a server.")
|
||||
|
||||
|
||||
def calc_unfinished_missions(locations_done, locations, unlocks, unfinished_locations, ctx):
|
||||
def calc_unfinished_missions(locations_done, locations, ctx, unlocks=None, unfinished_locations=None,
|
||||
available_missions=[]):
|
||||
unfinished_missions = []
|
||||
locations_completed = []
|
||||
available_missions = calc_available_missions(locations_done, locations, unlocks)
|
||||
|
||||
if not unlocks:
|
||||
unlocks = initialize_blank_mission_dict(locations)
|
||||
|
||||
if not unfinished_locations:
|
||||
unfinished_locations = initialize_blank_mission_dict(locations)
|
||||
|
||||
if len(available_missions) > 0:
|
||||
available_missions = []
|
||||
|
||||
available_missions.extend(calc_available_missions(locations_done, locations, unlocks))
|
||||
|
||||
for name in available_missions:
|
||||
if not locations[name].extra_locations == -1:
|
||||
|
|
3
kvui.py
3
kvui.py
|
@ -363,7 +363,8 @@ class GameManager(App):
|
|||
return self.container
|
||||
|
||||
def update_texts(self, dt):
|
||||
self.tabs.content.children[0].fix_heights() # TODO: remove this when Kivy fixes this upstream
|
||||
if hasattr(self.tabs.content.children[0], 'fix_heights'):
|
||||
self.tabs.content.children[0].fix_heights() # TODO: remove this when Kivy fixes this upstream
|
||||
if self.ctx.server:
|
||||
self.title = self.base_title + " " + Utils.__version__ + \
|
||||
f" | Connected to: {self.ctx.server_address} " \
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from typing import Dict, FrozenSet, Set, Tuple, List, Optional, TextIO, Any, Callable, Union, NamedTuple
|
||||
|
||||
from BaseClasses import MultiWorld, Item, CollectionState, Location, Tutorial
|
||||
|
@ -41,6 +42,7 @@ class AutoWorldRegister(type):
|
|||
new_class = super().__new__(mcs, name, bases, dct)
|
||||
if "game" in dct:
|
||||
AutoWorldRegister.world_types[dct["game"]] = new_class
|
||||
new_class.__file__ = sys.modules[new_class.__module__].__file__
|
||||
return new_class
|
||||
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ class MissionInfo(NamedTuple):
|
|||
id: int
|
||||
extra_locations: int
|
||||
required_world: List[int]
|
||||
category: str
|
||||
number: int = 0 # number of worlds need beaten
|
||||
completion_critical: bool = False # missions needed to beat game
|
||||
or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed
|
||||
|
@ -22,74 +23,76 @@ class MissionInfo(NamedTuple):
|
|||
class FillMission(NamedTuple):
|
||||
type: str
|
||||
connect_to: List[int] # -1 connects to Menu
|
||||
category: str
|
||||
number: int = 0 # number of worlds need beaten
|
||||
completion_critical: bool = False # missions needed to beat game
|
||||
or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed
|
||||
|
||||
|
||||
|
||||
vanilla_shuffle_order = [
|
||||
FillMission("no_build", [-1], completion_critical=True),
|
||||
FillMission("easy", [0], completion_critical=True),
|
||||
FillMission("easy", [1], completion_critical=True),
|
||||
FillMission("easy", [2]),
|
||||
FillMission("medium", [3]),
|
||||
FillMission("hard", [4], number=7),
|
||||
FillMission("hard", [4], number=7),
|
||||
FillMission("easy", [2], completion_critical=True),
|
||||
FillMission("medium", [7], number=8, completion_critical=True),
|
||||
FillMission("hard", [8], number=11, completion_critical=True),
|
||||
FillMission("hard", [9], number=14, completion_critical=True),
|
||||
FillMission("hard", [10], completion_critical=True),
|
||||
FillMission("medium", [2], number=4),
|
||||
FillMission("medium", [12]),
|
||||
FillMission("hard", [13], number=8),
|
||||
FillMission("hard", [13], number=8),
|
||||
FillMission("medium", [2], number=6),
|
||||
FillMission("hard", [16]),
|
||||
FillMission("hard", [17]),
|
||||
FillMission("hard", [18]),
|
||||
FillMission("hard", [19]),
|
||||
FillMission("medium", [8]),
|
||||
FillMission("hard", [21]),
|
||||
FillMission("hard", [22]),
|
||||
FillMission("hard", [23]),
|
||||
FillMission("hard", [11], completion_critical=True),
|
||||
FillMission("hard", [25], completion_critical=True),
|
||||
FillMission("hard", [25], completion_critical=True),
|
||||
FillMission("all_in", [26, 27], completion_critical=True, or_requirements=True)
|
||||
FillMission("no_build", [-1], "Mar Sara", completion_critical=True),
|
||||
FillMission("easy", [0], "Mar Sara", completion_critical=True),
|
||||
FillMission("easy", [1], "Mar Sara", completion_critical=True),
|
||||
FillMission("easy", [2], "Colonist"),
|
||||
FillMission("medium", [3], "Colonist"),
|
||||
FillMission("hard", [4], "Colonist", number=7),
|
||||
FillMission("hard", [4], "Colonist", number=7),
|
||||
FillMission("easy", [2], "Artifact", completion_critical=True),
|
||||
FillMission("medium", [7], "Artifact", number=8, completion_critical=True),
|
||||
FillMission("hard", [8], "Artifact", number=11, completion_critical=True),
|
||||
FillMission("hard", [9], "Artifact", number=14, completion_critical=True),
|
||||
FillMission("hard", [10], "Artifact", completion_critical=True),
|
||||
FillMission("medium", [2], "Covert", number=4),
|
||||
FillMission("medium", [12], "Covert"),
|
||||
FillMission("hard", [13], "Covert", number=8),
|
||||
FillMission("hard", [13], "Covert", number=8),
|
||||
FillMission("medium", [2], "Rebellion", number=6),
|
||||
FillMission("hard", [16], "Rebellion"),
|
||||
FillMission("hard", [17], "Rebellion"),
|
||||
FillMission("hard", [18], "Rebellion"),
|
||||
FillMission("hard", [19], "Rebellion"),
|
||||
FillMission("medium", [8], "Prophecy"),
|
||||
FillMission("hard", [21], "Prophecy"),
|
||||
FillMission("hard", [22], "Prophecy"),
|
||||
FillMission("hard", [23], "Prophecy"),
|
||||
FillMission("hard", [11], "Char", completion_critical=True),
|
||||
FillMission("hard", [25], "Char", completion_critical=True),
|
||||
FillMission("hard", [25], "Char", completion_critical=True),
|
||||
FillMission("all_in", [26, 27], "Char", completion_critical=True, or_requirements=True)
|
||||
]
|
||||
|
||||
|
||||
vanilla_mission_req_table = {
|
||||
"Liberation Day": MissionInfo(1, 7, [], completion_critical=True),
|
||||
"The Outlaws": MissionInfo(2, 2, [1], completion_critical=True),
|
||||
"Zero Hour": MissionInfo(3, 4, [2], completion_critical=True),
|
||||
"Evacuation": MissionInfo(4, 4, [3]),
|
||||
"Outbreak": MissionInfo(5, 3, [4]),
|
||||
"Safe Haven": MissionInfo(6, 1, [5], number=7),
|
||||
"Haven's Fall": MissionInfo(7, 1, [5], number=7),
|
||||
"Smash and Grab": MissionInfo(8, 5, [3], completion_critical=True),
|
||||
"The Dig": MissionInfo(9, 4, [8], number=8, completion_critical=True),
|
||||
"The Moebius Factor": MissionInfo(10, 9, [9], number=11, completion_critical=True),
|
||||
"Supernova": MissionInfo(11, 5, [10], number=14, completion_critical=True),
|
||||
"Maw of the Void": MissionInfo(12, 6, [11], completion_critical=True),
|
||||
"Devil's Playground": MissionInfo(13, 3, [3], number=4),
|
||||
"Welcome to the Jungle": MissionInfo(14, 4, [13]),
|
||||
"Breakout": MissionInfo(15, 3, [14], number=8),
|
||||
"Ghost of a Chance": MissionInfo(16, 6, [14], number=8),
|
||||
"The Great Train Robbery": MissionInfo(17, 4, [3], number=6),
|
||||
"Cutthroat": MissionInfo(18, 5, [17]),
|
||||
"Engine of Destruction": MissionInfo(19, 6, [18]),
|
||||
"Media Blitz": MissionInfo(20, 5, [19]),
|
||||
"Piercing the Shroud": MissionInfo(21, 6, [20]),
|
||||
"Whispers of Doom": MissionInfo(22, 4, [9]),
|
||||
"A Sinister Turn": MissionInfo(23, 4, [22]),
|
||||
"Echoes of the Future": MissionInfo(24, 3, [23]),
|
||||
"In Utter Darkness": MissionInfo(25, 3, [24]),
|
||||
"Gates of Hell": MissionInfo(26, 2, [12], completion_critical=True),
|
||||
"Belly of the Beast": MissionInfo(27, 4, [26], completion_critical=True),
|
||||
"Shatter the Sky": MissionInfo(28, 5, [26], completion_critical=True),
|
||||
"All-In": MissionInfo(29, -1, [27, 28], completion_critical=True, or_requirements=True)
|
||||
"Liberation Day": MissionInfo(1, 7, [], "Mar Sara", completion_critical=True),
|
||||
"The Outlaws": MissionInfo(2, 2, [1], "Mar Sara", completion_critical=True),
|
||||
"Zero Hour": MissionInfo(3, 4, [2], "Mar Sara", completion_critical=True),
|
||||
"Evacuation": MissionInfo(4, 4, [3], "Colonist"),
|
||||
"Outbreak": MissionInfo(5, 3, [4], "Colonist"),
|
||||
"Safe Haven": MissionInfo(6, 1, [5], "Colonist", number=7),
|
||||
"Haven's Fall": MissionInfo(7, 1, [5], "Colonist", number=7),
|
||||
"Smash and Grab": MissionInfo(8, 5, [3], "Artifact", completion_critical=True),
|
||||
"The Dig": MissionInfo(9, 4, [8], "Artifact", number=8, completion_critical=True),
|
||||
"The Moebius Factor": MissionInfo(10, 9, [9], "Artifact", number=11, completion_critical=True),
|
||||
"Supernova": MissionInfo(11, 5, [10], "Artifact", number=14, completion_critical=True),
|
||||
"Maw of the Void": MissionInfo(12, 6, [11], "Artifact", completion_critical=True),
|
||||
"Devil's Playground": MissionInfo(13, 3, [3], "Covert", number=4),
|
||||
"Welcome to the Jungle": MissionInfo(14, 4, [13], "Covert"),
|
||||
"Breakout": MissionInfo(15, 3, [14], "Covert", number=8),
|
||||
"Ghost of a Chance": MissionInfo(16, 6, [14], "Covert", number=8),
|
||||
"The Great Train Robbery": MissionInfo(17, 4, [3], "Rebellion", number=6),
|
||||
"Cutthroat": MissionInfo(18, 5, [17], "Rebellion"),
|
||||
"Engine of Destruction": MissionInfo(19, 6, [18], "Rebellion"),
|
||||
"Media Blitz": MissionInfo(20, 5, [19], "Rebellion"),
|
||||
"Piercing the Shroud": MissionInfo(21, 6, [20], "Rebellion"),
|
||||
"Whispers of Doom": MissionInfo(22, 4, [9], "Prophecy"),
|
||||
"A Sinister Turn": MissionInfo(23, 4, [22], "Prophecy"),
|
||||
"Echoes of the Future": MissionInfo(24, 3, [23], "Prophecy"),
|
||||
"In Utter Darkness": MissionInfo(25, 3, [24], "Prophecy"),
|
||||
"Gates of Hell": MissionInfo(26, 2, [12], "Char", completion_critical=True),
|
||||
"Belly of the Beast": MissionInfo(27, 4, [26], "Char", completion_critical=True),
|
||||
"Shatter the Sky": MissionInfo(28, 5, [26], "Char", completion_critical=True),
|
||||
"All-In": MissionInfo(29, -1, [27, 28], "Char", completion_critical=True, or_requirements=True)
|
||||
}
|
||||
|
||||
lookup_id_to_mission: Dict[int, str] = {
|
||||
|
|
|
@ -203,8 +203,9 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
|||
|
||||
mission_req_table.update({missions[i]: MissionInfo(
|
||||
vanilla_mission_req_table[missions[i]].id, vanilla_mission_req_table[missions[i]].extra_locations,
|
||||
connections, completion_critical=vanilla_shuffle_order[i].completion_critical,
|
||||
number=vanilla_shuffle_order[i].number, or_requirements=vanilla_shuffle_order[i].or_requirements)})
|
||||
connections, vanilla_shuffle_order[i].category, number=vanilla_shuffle_order[i].number,
|
||||
completion_critical=vanilla_shuffle_order[i].completion_critical,
|
||||
or_requirements=vanilla_shuffle_order[i].or_requirements)})
|
||||
|
||||
return mission_req_table
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<MissionLayout>:
|
||||
rows: 1
|
||||
|
||||
<MissionCategory>:
|
||||
cols: 1
|
||||
padding: [10,5,10,5]
|
||||
spacing: [0,5]
|
||||
|
||||
<MissionButton>:
|
||||
text_size: self.size
|
||||
markup: True
|
||||
halign: 'center'
|
||||
valign: 'middle'
|
||||
padding_x: 5
|
||||
markup: True
|
||||
outline_width: 1
|
Loading…
Reference in New Issue