SC2: Greater variety on short generations (#1367)
Originally, short generations used an artificial cull to create balanced mission distributions. This resulted in campaigns that were somewhat too consistent, and on some standard settings combinations, this resulted in campaigns having The Outlaws as the second mission 100% of the time. It also caused generation to fail a bit too easily if the player excluded too many missions. This removes the cull and adds an additional early Easy mission slot to all of the reduced sized campaigns. When playing on No Build settings, this also pushes many of the missions down a difficulty level to ensure greater variety, and pushes additional missions down on Advanced Tactics. Additional small fixes: The in-world Excluded Missions validation check is replaced by the core OptionSet check. Fixed issue with Existing Items not getting their upgrades locked with Units Always Have Upgrades on.
This commit is contained in:
parent
016157a0eb
commit
17e90ce12c
|
@ -52,9 +52,9 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||||
"""Overrides the current difficulty set for the seed. Takes the argument casual, normal, hard, or brutal"""
|
"""Overrides the current difficulty set for the seed. Takes the argument casual, normal, hard, or brutal"""
|
||||||
options = difficulty.split()
|
options = difficulty.split()
|
||||||
num_options = len(options)
|
num_options = len(options)
|
||||||
difficulty_choice = options[0].lower()
|
|
||||||
|
|
||||||
if num_options > 0:
|
if num_options > 0:
|
||||||
|
difficulty_choice = options[0].lower()
|
||||||
if difficulty_choice == "casual":
|
if difficulty_choice == "casual":
|
||||||
self.ctx.difficulty_override = 0
|
self.ctx.difficulty_override = 0
|
||||||
elif difficulty_choice == "normal":
|
elif difficulty_choice == "normal":
|
||||||
|
@ -71,7 +71,11 @@ class StarcraftClientProcessor(ClientCommandProcessor):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.output("Difficulty needs to be specified in the command.")
|
if self.ctx.difficulty == -1:
|
||||||
|
self.output("Please connect to a seed before checking difficulty.")
|
||||||
|
else:
|
||||||
|
self.output("Current difficulty: " + ["Casual", "Normal", "Hard", "Brutal"][self.ctx.difficulty])
|
||||||
|
self.output("To change the difficulty, add the name of the difficulty after the command.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _cmd_disable_mission_check(self) -> bool:
|
def _cmd_disable_mission_check(self) -> bool:
|
||||||
|
|
|
@ -182,9 +182,11 @@ filler_items: typing.Tuple[str, ...] = (
|
||||||
'+15 Starting Vespene'
|
'+15 Starting Vespene'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Defense rating table
|
||||||
|
# Commented defense ratings are handled in LogicMixin
|
||||||
defense_ratings = {
|
defense_ratings = {
|
||||||
"Siege Tank": 5,
|
"Siege Tank": 5,
|
||||||
"Maelstrom Rounds": 2,
|
# "Maelstrom Rounds": 2,
|
||||||
"Planetary Fortress": 3,
|
"Planetary Fortress": 3,
|
||||||
# Bunker w/ Marine/Marauder: 3,
|
# Bunker w/ Marine/Marauder: 3,
|
||||||
"Perdition Turret": 2,
|
"Perdition Turret": 2,
|
||||||
|
@ -193,7 +195,7 @@ defense_ratings = {
|
||||||
}
|
}
|
||||||
zerg_defense_ratings = {
|
zerg_defense_ratings = {
|
||||||
"Perdition Turret": 2,
|
"Perdition Turret": 2,
|
||||||
# Bunker w/ Firebat: 2
|
# Bunker w/ Firebat: 2,
|
||||||
"Hive Mind Emulator": 3,
|
"Hive Mind Emulator": 3,
|
||||||
"Psi Disruptor": 3
|
"Psi Disruptor": 3
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,12 @@ class SC2WoLLogic(LogicMixin):
|
||||||
or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has('Wraith', player)
|
or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has('Wraith', player)
|
||||||
|
|
||||||
def _sc2wol_has_competent_anti_air(self, multiworld: MultiWorld, player: int) -> bool:
|
def _sc2wol_has_competent_anti_air(self, multiworld: MultiWorld, player: int) -> bool:
|
||||||
return self.has_any({'Marine', 'Goliath'}, player) or self._sc2wol_has_air_anti_air(multiworld, player)
|
return self.has('Goliath', player) \
|
||||||
|
or self.has('Marine', player) and self.has_any({'Medic', 'Medivac'}, player) \
|
||||||
|
or self._sc2wol_has_air_anti_air(multiworld, player)
|
||||||
|
|
||||||
def _sc2wol_has_anti_air(self, multiworld: MultiWorld, player: int) -> bool:
|
def _sc2wol_has_anti_air(self, multiworld: MultiWorld, player: int) -> bool:
|
||||||
return self.has_any({'Missile Turret', 'Thor', 'War Pigs', 'Spartan Company', "Hel's Angel", 'Battlecruiser', 'Wraith'}, player) \
|
return self.has_any({'Missile Turret', 'Thor', 'War Pigs', 'Spartan Company', "Hel's Angel", 'Battlecruiser', 'Marine', 'Wraith'}, player) \
|
||||||
or self._sc2wol_has_competent_anti_air(multiworld, player) \
|
or self._sc2wol_has_competent_anti_air(multiworld, player) \
|
||||||
or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'Ghost', 'Spectre'}, player)
|
or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'Ghost', 'Spectre'}, player)
|
||||||
|
|
||||||
|
@ -28,6 +30,8 @@ class SC2WoLLogic(LogicMixin):
|
||||||
defense_score = sum((defense_ratings[item] for item in defense_ratings if self.has(item, player)))
|
defense_score = sum((defense_ratings[item] for item in defense_ratings if self.has(item, player)))
|
||||||
if self.has_any({'Marine', 'Marauder'}, player) and self.has('Bunker', player):
|
if self.has_any({'Marine', 'Marauder'}, player) and self.has('Bunker', player):
|
||||||
defense_score += 3
|
defense_score += 3
|
||||||
|
if self.has_all({'Siege Tank', 'Maelstrom Rounds'}, player):
|
||||||
|
defense_score += 2
|
||||||
if zerg_enemy:
|
if zerg_enemy:
|
||||||
defense_score += sum((zerg_defense_ratings[item] for item in zerg_defense_ratings if self.has(item, player)))
|
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):
|
if self.has('Firebat', player) and self.has('Bunker', player):
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
from typing import NamedTuple, Dict, List, Set
|
from typing import NamedTuple, Dict, List
|
||||||
|
from enum import IntEnum
|
||||||
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"]
|
||||||
|
@ -13,6 +11,14 @@ hard_regions_list = ["Maw of the Void", "Engine of Destruction", "In Utter Darkn
|
||||||
"Shatter the Sky"]
|
"Shatter the Sky"]
|
||||||
|
|
||||||
|
|
||||||
|
class MissionPools(IntEnum):
|
||||||
|
STARTER = 0
|
||||||
|
EASY = 1
|
||||||
|
MEDIUM = 2
|
||||||
|
HARD = 3
|
||||||
|
FINAL = 4
|
||||||
|
|
||||||
|
|
||||||
class MissionInfo(NamedTuple):
|
class MissionInfo(NamedTuple):
|
||||||
id: int
|
id: int
|
||||||
required_world: List[int]
|
required_world: List[int]
|
||||||
|
@ -23,119 +29,119 @@ class MissionInfo(NamedTuple):
|
||||||
|
|
||||||
|
|
||||||
class FillMission(NamedTuple):
|
class FillMission(NamedTuple):
|
||||||
type: str
|
type: int
|
||||||
connect_to: List[int] # -1 connects to Menu
|
connect_to: List[int] # -1 connects to Menu
|
||||||
category: str
|
category: str
|
||||||
number: int = 0 # number of worlds need beaten
|
number: int = 0 # number of worlds need beaten
|
||||||
completion_critical: bool = False # missions needed to beat game
|
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
|
or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed
|
||||||
relegate: bool = False # true if this is a slot no build missions should be relegated to.
|
removal_priority: int = 0 # how many missions missing from the pool required to remove this mission
|
||||||
|
|
||||||
|
|
||||||
vanilla_shuffle_order = [
|
vanilla_shuffle_order = [
|
||||||
FillMission("no_build", [-1], "Mar Sara", completion_critical=True),
|
FillMission(MissionPools.STARTER, [-1], "Mar Sara", completion_critical=True),
|
||||||
FillMission("easy", [0], "Mar Sara", completion_critical=True),
|
FillMission(MissionPools.EASY, [0], "Mar Sara", completion_critical=True),
|
||||||
FillMission("easy", [1], "Mar Sara", completion_critical=True),
|
FillMission(MissionPools.EASY, [1], "Mar Sara", completion_critical=True),
|
||||||
FillMission("easy", [2], "Colonist"),
|
FillMission(MissionPools.EASY, [2], "Colonist"),
|
||||||
FillMission("medium", [3], "Colonist"),
|
FillMission(MissionPools.MEDIUM, [3], "Colonist"),
|
||||||
FillMission("hard", [4], "Colonist", number=7),
|
FillMission(MissionPools.HARD, [4], "Colonist", number=7),
|
||||||
FillMission("hard", [4], "Colonist", number=7, relegate=True),
|
FillMission(MissionPools.HARD, [4], "Colonist", number=7, removal_priority=1),
|
||||||
FillMission("easy", [2], "Artifact", completion_critical=True),
|
FillMission(MissionPools.EASY, [2], "Artifact", completion_critical=True),
|
||||||
FillMission("medium", [7], "Artifact", number=8, completion_critical=True),
|
FillMission(MissionPools.MEDIUM, [7], "Artifact", number=8, completion_critical=True),
|
||||||
FillMission("hard", [8], "Artifact", number=11, completion_critical=True),
|
FillMission(MissionPools.HARD, [8], "Artifact", number=11, completion_critical=True),
|
||||||
FillMission("hard", [9], "Artifact", number=14, completion_critical=True),
|
FillMission(MissionPools.HARD, [9], "Artifact", number=14, completion_critical=True),
|
||||||
FillMission("hard", [10], "Artifact", completion_critical=True),
|
FillMission(MissionPools.HARD, [10], "Artifact", completion_critical=True),
|
||||||
FillMission("medium", [2], "Covert", number=4),
|
FillMission(MissionPools.MEDIUM, [2], "Covert", number=4),
|
||||||
FillMission("medium", [12], "Covert"),
|
FillMission(MissionPools.MEDIUM, [12], "Covert"),
|
||||||
FillMission("hard", [13], "Covert", number=8, relegate=True),
|
FillMission(MissionPools.HARD, [13], "Covert", number=8, removal_priority=3),
|
||||||
FillMission("hard", [13], "Covert", number=8, relegate=True),
|
FillMission(MissionPools.HARD, [13], "Covert", number=8, removal_priority=2),
|
||||||
FillMission("medium", [2], "Rebellion", number=6),
|
FillMission(MissionPools.MEDIUM, [2], "Rebellion", number=6),
|
||||||
FillMission("hard", [16], "Rebellion"),
|
FillMission(MissionPools.HARD, [16], "Rebellion"),
|
||||||
FillMission("hard", [17], "Rebellion"),
|
FillMission(MissionPools.HARD, [17], "Rebellion"),
|
||||||
FillMission("hard", [18], "Rebellion"),
|
FillMission(MissionPools.HARD, [18], "Rebellion"),
|
||||||
FillMission("hard", [19], "Rebellion", relegate=True),
|
FillMission(MissionPools.HARD, [19], "Rebellion", removal_priority=5),
|
||||||
FillMission("medium", [8], "Prophecy"),
|
FillMission(MissionPools.MEDIUM, [8], "Prophecy", removal_priority=9),
|
||||||
FillMission("hard", [21], "Prophecy"),
|
FillMission(MissionPools.HARD, [21], "Prophecy", removal_priority=8),
|
||||||
FillMission("hard", [22], "Prophecy"),
|
FillMission(MissionPools.HARD, [22], "Prophecy", removal_priority=7),
|
||||||
FillMission("hard", [23], "Prophecy", relegate=True),
|
FillMission(MissionPools.HARD, [23], "Prophecy", removal_priority=6),
|
||||||
FillMission("hard", [11], "Char", completion_critical=True),
|
FillMission(MissionPools.HARD, [11], "Char", completion_critical=True),
|
||||||
FillMission("hard", [25], "Char", completion_critical=True),
|
FillMission(MissionPools.HARD, [25], "Char", completion_critical=True, removal_priority=4),
|
||||||
FillMission("hard", [25], "Char", completion_critical=True),
|
FillMission(MissionPools.HARD, [25], "Char", completion_critical=True),
|
||||||
FillMission("all_in", [26, 27], "Char", completion_critical=True, or_requirements=True)
|
FillMission(MissionPools.FINAL, [26, 27], "Char", completion_critical=True, or_requirements=True)
|
||||||
]
|
]
|
||||||
|
|
||||||
mini_campaign_order = [
|
mini_campaign_order = [
|
||||||
FillMission("no_build", [-1], "Mar Sara", completion_critical=True),
|
FillMission(MissionPools.STARTER, [-1], "Mar Sara", completion_critical=True),
|
||||||
FillMission("easy", [0], "Colonist"),
|
FillMission(MissionPools.EASY, [0], "Colonist"),
|
||||||
FillMission("medium", [1], "Colonist"),
|
FillMission(MissionPools.MEDIUM, [1], "Colonist"),
|
||||||
FillMission("medium", [0], "Artifact", completion_critical=True),
|
FillMission(MissionPools.EASY, [0], "Artifact", completion_critical=True),
|
||||||
FillMission("medium", [3], "Artifact", number=4, completion_critical=True),
|
FillMission(MissionPools.MEDIUM, [3], "Artifact", number=4, completion_critical=True),
|
||||||
FillMission("hard", [4], "Artifact", number=8, completion_critical=True),
|
FillMission(MissionPools.HARD, [4], "Artifact", number=8, completion_critical=True),
|
||||||
FillMission("medium", [0], "Covert", number=2),
|
FillMission(MissionPools.MEDIUM, [0], "Covert", number=2),
|
||||||
FillMission("hard", [6], "Covert"),
|
FillMission(MissionPools.HARD, [6], "Covert"),
|
||||||
FillMission("medium", [0], "Rebellion", number=3),
|
FillMission(MissionPools.MEDIUM, [0], "Rebellion", number=3),
|
||||||
FillMission("hard", [8], "Rebellion"),
|
FillMission(MissionPools.HARD, [8], "Rebellion"),
|
||||||
FillMission("medium", [4], "Prophecy"),
|
FillMission(MissionPools.MEDIUM, [4], "Prophecy"),
|
||||||
FillMission("hard", [10], "Prophecy"),
|
FillMission(MissionPools.HARD, [10], "Prophecy"),
|
||||||
FillMission("hard", [5], "Char", completion_critical=True),
|
FillMission(MissionPools.HARD, [5], "Char", completion_critical=True),
|
||||||
FillMission("hard", [5], "Char", completion_critical=True),
|
FillMission(MissionPools.HARD, [5], "Char", completion_critical=True),
|
||||||
FillMission("all_in", [12, 13], "Char", completion_critical=True, or_requirements=True)
|
FillMission(MissionPools.FINAL, [12, 13], "Char", completion_critical=True, or_requirements=True)
|
||||||
]
|
]
|
||||||
|
|
||||||
gauntlet_order = [
|
gauntlet_order = [
|
||||||
FillMission("no_build", [-1], "I", completion_critical=True),
|
FillMission(MissionPools.STARTER, [-1], "I", completion_critical=True),
|
||||||
FillMission("easy", [0], "II", completion_critical=True),
|
FillMission(MissionPools.EASY, [0], "II", completion_critical=True),
|
||||||
FillMission("medium", [1], "III", completion_critical=True),
|
FillMission(MissionPools.EASY, [1], "III", completion_critical=True),
|
||||||
FillMission("medium", [2], "IV", completion_critical=True),
|
FillMission(MissionPools.MEDIUM, [2], "IV", completion_critical=True),
|
||||||
FillMission("hard", [3], "V", completion_critical=True),
|
FillMission(MissionPools.MEDIUM, [3], "V", completion_critical=True),
|
||||||
FillMission("hard", [4], "VI", completion_critical=True),
|
FillMission(MissionPools.HARD, [4], "VI", completion_critical=True),
|
||||||
FillMission("all_in", [5], "Final", completion_critical=True)
|
FillMission(MissionPools.FINAL, [5], "Final", completion_critical=True)
|
||||||
]
|
]
|
||||||
|
|
||||||
grid_order = [
|
grid_order = [
|
||||||
FillMission("no_build", [-1], "_1"),
|
FillMission(MissionPools.STARTER, [-1], "_1"),
|
||||||
FillMission("medium", [0], "_1"),
|
FillMission(MissionPools.EASY, [0], "_1"),
|
||||||
FillMission("medium", [1, 6, 3], "_1", or_requirements=True),
|
FillMission(MissionPools.MEDIUM, [1, 6, 3], "_1", or_requirements=True),
|
||||||
FillMission("hard", [2, 7], "_1", or_requirements=True),
|
FillMission(MissionPools.HARD, [2, 7], "_1", or_requirements=True),
|
||||||
FillMission("easy", [0], "_2"),
|
FillMission(MissionPools.EASY, [0], "_2"),
|
||||||
FillMission("medium", [1, 4], "_2", or_requirements=True),
|
FillMission(MissionPools.MEDIUM, [1, 4], "_2", or_requirements=True),
|
||||||
FillMission("hard", [2, 5, 10, 7], "_2", or_requirements=True),
|
FillMission(MissionPools.HARD, [2, 5, 10, 7], "_2", or_requirements=True),
|
||||||
FillMission("hard", [3, 6, 11], "_2", or_requirements=True),
|
FillMission(MissionPools.HARD, [3, 6, 11], "_2", or_requirements=True),
|
||||||
FillMission("medium", [4, 9, 12], "_3", or_requirements=True),
|
FillMission(MissionPools.MEDIUM, [4, 9, 12], "_3", or_requirements=True),
|
||||||
FillMission("hard", [5, 8, 10, 13], "_3", or_requirements=True),
|
FillMission(MissionPools.HARD, [5, 8, 10, 13], "_3", or_requirements=True),
|
||||||
FillMission("hard", [6, 9, 11, 14], "_3", or_requirements=True),
|
FillMission(MissionPools.HARD, [6, 9, 11, 14], "_3", or_requirements=True),
|
||||||
FillMission("hard", [7, 10], "_3", or_requirements=True),
|
FillMission(MissionPools.HARD, [7, 10], "_3", or_requirements=True),
|
||||||
FillMission("hard", [8, 13], "_4", or_requirements=True),
|
FillMission(MissionPools.HARD, [8, 13], "_4", or_requirements=True),
|
||||||
FillMission("hard", [9, 12, 14], "_4", or_requirements=True),
|
FillMission(MissionPools.HARD, [9, 12, 14], "_4", or_requirements=True),
|
||||||
FillMission("hard", [10, 13], "_4", or_requirements=True),
|
FillMission(MissionPools.HARD, [10, 13], "_4", or_requirements=True),
|
||||||
FillMission("all_in", [11, 14], "_4", or_requirements=True)
|
FillMission(MissionPools.FINAL, [11, 14], "_4", or_requirements=True)
|
||||||
]
|
]
|
||||||
|
|
||||||
mini_grid_order = [
|
mini_grid_order = [
|
||||||
FillMission("no_build", [-1], "_1"),
|
FillMission(MissionPools.STARTER, [-1], "_1"),
|
||||||
FillMission("medium", [0], "_1"),
|
FillMission(MissionPools.EASY, [0], "_1"),
|
||||||
FillMission("medium", [1, 5], "_1", or_requirements=True),
|
FillMission(MissionPools.MEDIUM, [1, 5], "_1", or_requirements=True),
|
||||||
FillMission("easy", [0], "_2"),
|
FillMission(MissionPools.EASY, [0], "_2"),
|
||||||
FillMission("medium", [1, 3], "_2", or_requirements=True),
|
FillMission(MissionPools.MEDIUM, [1, 3], "_2", or_requirements=True),
|
||||||
FillMission("hard", [2, 4], "_2", or_requirements=True),
|
FillMission(MissionPools.HARD, [2, 4], "_2", or_requirements=True),
|
||||||
FillMission("medium", [3, 7], "_3", or_requirements=True),
|
FillMission(MissionPools.MEDIUM, [3, 7], "_3", or_requirements=True),
|
||||||
FillMission("hard", [4, 6], "_3", or_requirements=True),
|
FillMission(MissionPools.HARD, [4, 6], "_3", or_requirements=True),
|
||||||
FillMission("all_in", [5, 7], "_3", or_requirements=True)
|
FillMission(MissionPools.FINAL, [5, 7], "_3", or_requirements=True)
|
||||||
]
|
]
|
||||||
|
|
||||||
blitz_order = [
|
blitz_order = [
|
||||||
FillMission("no_build", [-1], "I"),
|
FillMission(MissionPools.STARTER, [-1], "I"),
|
||||||
FillMission("easy", [-1], "I"),
|
FillMission(MissionPools.EASY, [-1], "I"),
|
||||||
FillMission("medium", [0, 1], "II", number=1, or_requirements=True),
|
FillMission(MissionPools.MEDIUM, [0, 1], "II", number=1, or_requirements=True),
|
||||||
FillMission("medium", [0, 1], "II", number=1, or_requirements=True),
|
FillMission(MissionPools.MEDIUM, [0, 1], "II", number=1, or_requirements=True),
|
||||||
FillMission("medium", [0, 1], "III", number=2, or_requirements=True),
|
FillMission(MissionPools.MEDIUM, [0, 1], "III", number=2, or_requirements=True),
|
||||||
FillMission("medium", [0, 1], "III", number=2, or_requirements=True),
|
FillMission(MissionPools.MEDIUM, [0, 1], "III", number=2, or_requirements=True),
|
||||||
FillMission("hard", [0, 1], "IV", number=3, or_requirements=True),
|
FillMission(MissionPools.HARD, [0, 1], "IV", number=3, or_requirements=True),
|
||||||
FillMission("hard", [0, 1], "IV", number=3, or_requirements=True),
|
FillMission(MissionPools.HARD, [0, 1], "IV", number=3, or_requirements=True),
|
||||||
FillMission("hard", [0, 1], "V", number=4, or_requirements=True),
|
FillMission(MissionPools.HARD, [0, 1], "V", number=4, or_requirements=True),
|
||||||
FillMission("hard", [0, 1], "V", number=4, or_requirements=True),
|
FillMission(MissionPools.HARD, [0, 1], "V", number=4, or_requirements=True),
|
||||||
FillMission("hard", [0, 1], "Final", number=5, or_requirements=True),
|
FillMission(MissionPools.HARD, [0, 1], "Final", number=5, or_requirements=True),
|
||||||
FillMission("all_in", [0, 1], "Final", number=5, or_requirements=True)
|
FillMission(MissionPools.FINAL, [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]
|
mission_orders = [vanilla_shuffle_order, vanilla_shuffle_order, mini_campaign_order, grid_order, mini_grid_order, blitz_order, gauntlet_order]
|
||||||
|
@ -176,40 +182,21 @@ vanilla_mission_req_table = {
|
||||||
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 = {
|
starting_mission_locations = {
|
||||||
"Liberation Day": "Liberation Day: Victory",
|
"Liberation Day": "Liberation Day: Victory",
|
||||||
"Breakout": "Breakout: Victory",
|
"Breakout": "Breakout: Victory",
|
||||||
"Ghost of a Chance": "Ghost of a Chance: Victory",
|
"Ghost of a Chance": "Ghost of a Chance: Victory",
|
||||||
"Piercing the Shroud": "Piercing the Shroud: Victory",
|
"Piercing the Shroud": "Piercing the Shroud: Victory",
|
||||||
"Whispers of Doom": "Whispers of Doom: Victory",
|
"Whispers of Doom": "Whispers of Doom: Victory",
|
||||||
"Belly of the Beast": "Belly of the Beast: Victory",
|
"Belly of the Beast": "Belly of the Beast: Victory",
|
||||||
}
|
|
||||||
|
|
||||||
build_starting_mission_locations = {
|
|
||||||
"Zero Hour": "Zero Hour: First Group Rescued",
|
"Zero Hour": "Zero Hour: First Group Rescued",
|
||||||
"Evacuation": "Evacuation: First Chysalis",
|
"Evacuation": "Evacuation: First Chysalis",
|
||||||
"Devil's Playground": "Devil's Playground: Tosh's Miners"
|
"Devil's Playground": "Devil's Playground: Tosh's Miners",
|
||||||
}
|
|
||||||
|
|
||||||
advanced_starting_mission_locations = {
|
|
||||||
"Smash and Grab": "Smash and Grab: First Relic",
|
"Smash and Grab": "Smash and Grab: First Relic",
|
||||||
"The Great Train Robbery": "The Great Train Robbery: North Defiler"
|
"The Great Train Robbery": "The Great Train Robbery: North Defiler"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_starting_mission_locations(multiworld: MultiWorld, player: int) -> Set[str]:
|
|
||||||
if get_option_value(multiworld, player, 'shuffle_no_build') or get_option_value(multiworld, player, 'mission_order') < 2:
|
|
||||||
# Always start with a no-build mission unless explicitly relegating them
|
|
||||||
# Vanilla and Vanilla Shuffled always start with a no-build even when relegated
|
|
||||||
return no_build_starting_mission_locations
|
|
||||||
elif get_option_value(multiworld, player, 'required_tactics') > 0:
|
|
||||||
# Advanced Tactics/No Logic add more starting missions to the pool
|
|
||||||
return {**build_starting_mission_locations, **advanced_starting_mission_locations}
|
|
||||||
else:
|
|
||||||
# Standard starting missions when relegate is on
|
|
||||||
return build_starting_mission_locations
|
|
||||||
|
|
||||||
|
|
||||||
alt_final_mission_locations = {
|
alt_final_mission_locations = {
|
||||||
"Maw of the Void": "Maw of the Void: Victory",
|
"Maw of the Void": "Maw of the Void: Victory",
|
||||||
"Engine of Destruction": "Engine of Destruction: Victory",
|
"Engine of Destruction": "Engine of Destruction: Victory",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from typing import Dict
|
from typing import Dict, FrozenSet, Union
|
||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld
|
||||||
from Options import Choice, Option, Toggle, DefaultOnToggle, ItemSet, OptionSet, Range
|
from Options import Choice, Option, Toggle, DefaultOnToggle, ItemSet, OptionSet, Range
|
||||||
|
from .MissionTables import vanilla_mission_req_table
|
||||||
|
|
||||||
|
|
||||||
class GameDifficulty(Choice):
|
class GameDifficulty(Choice):
|
||||||
|
@ -110,6 +111,7 @@ class ExcludedMissions(OptionSet):
|
||||||
Only applies on shortened mission orders.
|
Only applies on shortened mission orders.
|
||||||
It may be impossible to build a valid campaign if too many missions are excluded."""
|
It may be impossible to build a valid campaign if too many missions are excluded."""
|
||||||
display_name = "Excluded Missions"
|
display_name = "Excluded Missions"
|
||||||
|
valid_keys = {mission_name for mission_name in vanilla_mission_req_table.keys() if mission_name != 'All-In'}
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
|
@ -130,19 +132,10 @@ sc2wol_options: Dict[str, Option] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_option_value(multiworld: MultiWorld, player: int, name: str) -> int:
|
def get_option_value(multiworld: MultiWorld, player: int, name: str) -> Union[int, FrozenSet]:
|
||||||
option = getattr(multiworld, name, None)
|
if multiworld is None:
|
||||||
|
return sc2wol_options[name].default
|
||||||
|
|
||||||
if option is None:
|
player_option = getattr(multiworld, name)[player]
|
||||||
return 0
|
|
||||||
|
|
||||||
return int(option[player].value)
|
return player_option.value
|
||||||
|
|
||||||
|
|
||||||
def get_option_set_value(multiworld: MultiWorld, player: int, name: str) -> set:
|
|
||||||
option = getattr(multiworld, name, None)
|
|
||||||
|
|
||||||
if option is None:
|
|
||||||
return set()
|
|
||||||
|
|
||||||
return option[player].value
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ from typing import Callable, Dict, List, Set
|
||||||
from BaseClasses import MultiWorld, ItemClassification, Item, Location
|
from BaseClasses import MultiWorld, ItemClassification, Item, Location
|
||||||
from .Items import item_table
|
from .Items import item_table
|
||||||
from .MissionTables import no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list,\
|
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
|
mission_orders, MissionInfo, alt_final_mission_locations, MissionPools
|
||||||
from .Options import get_option_value, get_option_set_value
|
from .Options import get_option_value
|
||||||
from .LogicMixin import SC2WoLLogic
|
from .LogicMixin import SC2WoLLogic
|
||||||
|
|
||||||
# Items with associated upgrades
|
# Items with associated upgrades
|
||||||
|
@ -21,34 +21,33 @@ STARPORT_UNITS = {"Medivac", "Wraith", "Viking", "Banshee", "Battlecruiser", "He
|
||||||
PROTOSS_REGIONS = {"A Sinister Turn", "Echoes of the Future", "In Utter Darkness"}
|
PROTOSS_REGIONS = {"A Sinister Turn", "Echoes of the Future", "In Utter Darkness"}
|
||||||
|
|
||||||
|
|
||||||
def filter_missions(multiworld: MultiWorld, player: int) -> Dict[str, List[str]]:
|
def filter_missions(multiworld: MultiWorld, player: int) -> Dict[int, List[str]]:
|
||||||
"""
|
"""
|
||||||
Returns a semi-randomly pruned tuple of no-build, easy, medium, and hard mission sets
|
Returns a semi-randomly pruned tuple of no-build, easy, medium, and hard mission sets
|
||||||
"""
|
"""
|
||||||
|
|
||||||
mission_order_type = get_option_value(multiworld, player, "mission_order")
|
mission_order_type = get_option_value(multiworld, player, "mission_order")
|
||||||
|
shuffle_no_build = get_option_value(multiworld, player, "shuffle_no_build")
|
||||||
shuffle_protoss = get_option_value(multiworld, player, "shuffle_protoss")
|
shuffle_protoss = get_option_value(multiworld, player, "shuffle_protoss")
|
||||||
excluded_missions = set(get_option_set_value(multiworld, player, "excluded_missions"))
|
excluded_missions = get_option_value(multiworld, player, "excluded_missions")
|
||||||
invalid_mission_names = excluded_missions.difference(vanilla_mission_req_table.keys())
|
|
||||||
if invalid_mission_names:
|
|
||||||
raise Exception("Error in locked_missions - the following are not valid mission names: " + ", ".join(invalid_mission_names))
|
|
||||||
mission_count = len(mission_orders[mission_order_type]) - 1
|
mission_count = len(mission_orders[mission_order_type]) - 1
|
||||||
# Vanilla and Vanilla Shuffled use the entire mission pool
|
mission_pools = {
|
||||||
if mission_count == 28:
|
MissionPools.STARTER: no_build_regions_list[:],
|
||||||
return {
|
MissionPools.EASY: easy_regions_list[:],
|
||||||
"no_build": no_build_regions_list[:],
|
MissionPools.MEDIUM: medium_regions_list[:],
|
||||||
"easy": easy_regions_list[:],
|
MissionPools.HARD: hard_regions_list[:],
|
||||||
"medium": medium_regions_list[:],
|
MissionPools.FINAL: []
|
||||||
"hard": hard_regions_list[:],
|
}
|
||||||
"all_in": ["All-In"]
|
if mission_order_type == 0:
|
||||||
}
|
# Vanilla uses the entire mission pool
|
||||||
|
mission_pools[MissionPools.FINAL] = ['All-In']
|
||||||
mission_pools = [
|
return mission_pools
|
||||||
[],
|
elif mission_order_type == 1:
|
||||||
easy_regions_list,
|
# Vanilla Shuffled ignores the player-provided excluded missions
|
||||||
medium_regions_list,
|
excluded_missions = set()
|
||||||
hard_regions_list
|
# Omitting No-Build missions if not shuffling no-build
|
||||||
]
|
if not shuffle_no_build:
|
||||||
|
excluded_missions = excluded_missions.union(no_build_regions_list)
|
||||||
# Omitting Protoss missions if not shuffling protoss
|
# Omitting Protoss missions if not shuffling protoss
|
||||||
if not shuffle_protoss:
|
if not shuffle_protoss:
|
||||||
excluded_missions = excluded_missions.union(PROTOSS_REGIONS)
|
excluded_missions = excluded_missions.union(PROTOSS_REGIONS)
|
||||||
|
@ -58,46 +57,35 @@ def filter_missions(multiworld: MultiWorld, player: int) -> Dict[str, List[str]]
|
||||||
excluded_missions.add(final_mission)
|
excluded_missions.add(final_mission)
|
||||||
else:
|
else:
|
||||||
final_mission = 'All-In'
|
final_mission = 'All-In'
|
||||||
# Yaml settings determine which missions can be placed in the first slot
|
# Excluding missions
|
||||||
mission_pools[0] = [mission for mission in get_starting_mission_locations(multiworld, player).keys() if mission not in excluded_missions]
|
for difficulty, mission_pool in mission_pools.items():
|
||||||
# Removing the new no-build missions from their original sets
|
mission_pools[difficulty] = [mission for mission in mission_pool if mission not in excluded_missions]
|
||||||
for i in range(1, len(mission_pools)):
|
mission_pools[MissionPools.FINAL].append(final_mission)
|
||||||
mission_pools[i] = [mission for mission in mission_pools[i] if mission not in excluded_missions.union(mission_pools[0])]
|
# Mission pool changes on Build-Only
|
||||||
# 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(multiworld, player, 'shuffle_no_build'):
|
if not get_option_value(multiworld, player, 'shuffle_no_build'):
|
||||||
# Swapping Outbreak and The Great Train Robbery
|
def move_mission(mission_name, current_pool, new_pool):
|
||||||
if "Outbreak" in mission_pools[1]:
|
if mission_name in mission_pools[current_pool]:
|
||||||
mission_pools[1].remove("Outbreak")
|
mission_pools[current_pool].remove(mission_name)
|
||||||
mission_pools[2].append("Outbreak")
|
mission_pools[new_pool].append(mission_name)
|
||||||
if "The Great Train Robbery" in mission_pools[2]:
|
# Replacing No Build missions with Easy missions
|
||||||
mission_pools[2].remove("The Great Train Robbery")
|
move_mission("Zero Hour", MissionPools.EASY, MissionPools.STARTER)
|
||||||
mission_pools[1].append("The Great Train Robbery")
|
move_mission("Evacuation", MissionPools.EASY, MissionPools.STARTER)
|
||||||
# Removing random missions from each difficulty set in a cycle
|
move_mission("Devil's Playground", MissionPools.EASY, MissionPools.STARTER)
|
||||||
set_cycle = 0
|
# Pushing Outbreak to Normal, as it cannot be placed as the second mission on Build-Only
|
||||||
current_count = sum(len(mission_pool) for mission_pool in mission_pools)
|
move_mission("Outbreak", MissionPools.EASY, MissionPools.MEDIUM)
|
||||||
|
# Pushing extra Normal missions to Easy
|
||||||
|
move_mission("The Great Train Robbery", MissionPools.MEDIUM, MissionPools.EASY)
|
||||||
|
move_mission("Echoes of the Future", MissionPools.MEDIUM, MissionPools.EASY)
|
||||||
|
move_mission("Cutthroat", MissionPools.MEDIUM, MissionPools.EASY)
|
||||||
|
# Additional changes on Advanced Tactics
|
||||||
|
if get_option_value(multiworld, player, "required_tactics") > 0:
|
||||||
|
move_mission("The Great Train Robbery", MissionPools.EASY, MissionPools.STARTER)
|
||||||
|
move_mission("Smash and Grab", MissionPools.EASY, MissionPools.STARTER)
|
||||||
|
move_mission("Moebius Factor", MissionPools.MEDIUM, MissionPools.EASY)
|
||||||
|
move_mission("Welcome to the Jungle", MissionPools.MEDIUM, MissionPools.EASY)
|
||||||
|
move_mission("Engine of Destruction", MissionPools.HARD, MissionPools.MEDIUM)
|
||||||
|
|
||||||
if current_count < mission_count:
|
return mission_pools
|
||||||
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(multiworld.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):
|
def get_item_upgrades(inventory: List[Item], parent_item: Item or str):
|
||||||
|
@ -135,7 +123,21 @@ class ValidInventory:
|
||||||
requirements = mission_requirements
|
requirements = mission_requirements
|
||||||
cascade_keys = self.cascade_removal_map.keys()
|
cascade_keys = self.cascade_removal_map.keys()
|
||||||
units_always_have_upgrades = get_option_value(self.multiworld, self.player, "units_always_have_upgrades")
|
units_always_have_upgrades = get_option_value(self.multiworld, self.player, "units_always_have_upgrades")
|
||||||
if self.min_units_per_structure > 0:
|
|
||||||
|
# Locking associated items for items that have already been placed when units_always_have_upgrades is on
|
||||||
|
if units_always_have_upgrades:
|
||||||
|
existing_items = self.existing_items[:]
|
||||||
|
while existing_items:
|
||||||
|
existing_item = existing_items.pop()
|
||||||
|
items_to_lock = self.cascade_removal_map.get(existing_item, [existing_item])
|
||||||
|
for item in items_to_lock:
|
||||||
|
if item in inventory:
|
||||||
|
inventory.remove(item)
|
||||||
|
locked_items.append(item)
|
||||||
|
if item in existing_items:
|
||||||
|
existing_items.remove(item)
|
||||||
|
|
||||||
|
if self.min_units_per_structure > 0 and self.has_units_per_structure():
|
||||||
requirements.append(lambda state: state.has_units_per_structure())
|
requirements.append(lambda state: state.has_units_per_structure())
|
||||||
|
|
||||||
def attempt_removal(item: Item) -> bool:
|
def attempt_removal(item: Item) -> bool:
|
||||||
|
@ -151,6 +153,10 @@ class ValidInventory:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# Determining if the full-size inventory can complete campaign
|
||||||
|
if not all(requirement(self) for requirement in requirements):
|
||||||
|
raise Exception("Too many items excluded - campaign is impossible to complete.")
|
||||||
|
|
||||||
while len(inventory) + len(locked_items) > inventory_size:
|
while len(inventory) + len(locked_items) > inventory_size:
|
||||||
if len(inventory) == 0:
|
if len(inventory) == 0:
|
||||||
raise Exception("Reduced item pool generation failed - not enough locations available to place items.")
|
raise Exception("Reduced item pool generation failed - not enough locations available to place items.")
|
||||||
|
|
|
@ -2,7 +2,7 @@ from typing import List, Set, Dict, Tuple, Optional, Callable
|
||||||
from BaseClasses import MultiWorld, Region, Entrance, Location
|
from BaseClasses import MultiWorld, Region, Entrance, Location
|
||||||
from .Locations import LocationData
|
from .Locations import LocationData
|
||||||
from .Options import get_option_value
|
from .Options import get_option_value
|
||||||
from .MissionTables import MissionInfo, mission_orders, vanilla_mission_req_table, alt_final_mission_locations
|
from .MissionTables import MissionInfo, mission_orders, vanilla_mission_req_table, alt_final_mission_locations, MissionPools
|
||||||
from .PoolFilter import filter_missions
|
from .PoolFilter import filter_missions
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,34 +14,18 @@ def create_regions(multiworld: MultiWorld, player: int, locations: Tuple[Locatio
|
||||||
mission_order = mission_orders[mission_order_type]
|
mission_order = mission_orders[mission_order_type]
|
||||||
|
|
||||||
mission_pools = filter_missions(multiworld, player)
|
mission_pools = filter_missions(multiworld, player)
|
||||||
final_mission = mission_pools['all_in'][0]
|
|
||||||
|
|
||||||
used_regions = [mission for mission_pool in mission_pools.values() for mission in mission_pool]
|
|
||||||
regions = [create_region(multiworld, player, locations_per_region, location_cache, "Menu")]
|
regions = [create_region(multiworld, player, locations_per_region, location_cache, "Menu")]
|
||||||
for region_name in used_regions:
|
|
||||||
regions.append(create_region(multiworld, player, locations_per_region, location_cache, region_name))
|
|
||||||
# Changing the completion condition for alternate final missions into an event
|
|
||||||
if final_mission != 'All-In':
|
|
||||||
final_location = alt_final_mission_locations[final_mission]
|
|
||||||
# Final location should be near the end of the cache
|
|
||||||
for i in range(len(location_cache) - 1, -1, -1):
|
|
||||||
if location_cache[i].name == final_location:
|
|
||||||
location_cache[i].locked = True
|
|
||||||
location_cache[i].event = True
|
|
||||||
location_cache[i].address = None
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
final_location = 'All-In: Victory'
|
|
||||||
|
|
||||||
if __debug__:
|
|
||||||
if mission_order_type in (0, 1):
|
|
||||||
throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys())
|
|
||||||
|
|
||||||
multiworld.regions += regions
|
|
||||||
|
|
||||||
names: Dict[str, int] = {}
|
names: Dict[str, int] = {}
|
||||||
|
|
||||||
if mission_order_type == 0:
|
if mission_order_type == 0:
|
||||||
|
|
||||||
|
# Generating all regions and locations
|
||||||
|
for region_name in vanilla_mission_req_table.keys():
|
||||||
|
regions.append(create_region(multiworld, player, locations_per_region, location_cache, region_name))
|
||||||
|
multiworld.regions += regions
|
||||||
|
|
||||||
connect(multiworld, player, names, 'Menu', 'Liberation Day'),
|
connect(multiworld, player, names, 'Menu', 'Liberation Day'),
|
||||||
connect(multiworld, player, names, 'Liberation Day', 'The Outlaws',
|
connect(multiworld, player, names, 'Liberation Day', 'The Outlaws',
|
||||||
lambda state: state.has("Beat Liberation Day", player)),
|
lambda state: state.has("Beat Liberation Day", player)),
|
||||||
|
@ -110,31 +94,32 @@ def create_regions(multiworld: MultiWorld, player: int, locations: Tuple[Locatio
|
||||||
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, 29, final_location
|
return vanilla_mission_req_table, 29, 'All-In: Victory'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
missions = []
|
missions = []
|
||||||
|
|
||||||
|
remove_prophecy = mission_order_type == 1 and not get_option_value(multiworld, player, "shuffle_protoss")
|
||||||
|
|
||||||
|
final_mission = mission_pools[MissionPools.FINAL][0]
|
||||||
|
|
||||||
|
# Determining if missions must be removed
|
||||||
|
mission_pool_size = sum(len(mission_pool) for mission_pool in mission_pools.values())
|
||||||
|
removals = len(mission_order) - mission_pool_size
|
||||||
|
# Removing entire Prophecy chain on vanilla shuffled when not shuffling protoss
|
||||||
|
if remove_prophecy:
|
||||||
|
removals -= 4
|
||||||
|
|
||||||
# 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 mission_order:
|
for mission in mission_order:
|
||||||
if mission is None:
|
# Removing extra missions if mission pool is too small
|
||||||
|
if 0 < mission.removal_priority <= removals or mission.category == 'Prophecy' and remove_prophecy:
|
||||||
missions.append(None)
|
missions.append(None)
|
||||||
elif mission.type == "all_in":
|
elif mission.type == MissionPools.FINAL:
|
||||||
missions.append(final_mission)
|
missions.append(final_mission)
|
||||||
elif mission.relegate and not get_option_value(multiworld, player, "shuffle_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 and are in Vanilla Shuffled
|
|
||||||
if get_option_value(multiworld, player, "shuffle_protoss") == 0 and mission_order_type == 1:
|
|
||||||
missions[22] = "A Sinister Turn"
|
|
||||||
mission_pools['medium'].remove("A Sinister Turn")
|
|
||||||
missions[23] = "Echoes of the Future"
|
|
||||||
mission_pools['medium'].remove("Echoes of the Future")
|
|
||||||
missions[24] = "In Utter Darkness"
|
|
||||||
mission_pools['hard'].remove("In Utter Darkness")
|
|
||||||
|
|
||||||
no_build_slots = []
|
no_build_slots = []
|
||||||
easy_slots = []
|
easy_slots = []
|
||||||
medium_slots = []
|
medium_slots = []
|
||||||
|
@ -144,79 +129,108 @@ def create_regions(multiworld: MultiWorld, player: int, locations: Tuple[Locatio
|
||||||
for i in range(len(missions)):
|
for i in range(len(missions)):
|
||||||
if missions[i] is None:
|
if missions[i] is None:
|
||||||
continue
|
continue
|
||||||
if missions[i] == "no_build":
|
if missions[i] == MissionPools.STARTER:
|
||||||
no_build_slots.append(i)
|
no_build_slots.append(i)
|
||||||
elif missions[i] == "easy":
|
elif missions[i] == MissionPools.EASY:
|
||||||
easy_slots.append(i)
|
easy_slots.append(i)
|
||||||
elif missions[i] == "medium":
|
elif missions[i] == MissionPools.MEDIUM:
|
||||||
medium_slots.append(i)
|
medium_slots.append(i)
|
||||||
elif missions[i] == "hard":
|
elif missions[i] == MissionPools.HARD:
|
||||||
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 = mission_pools['no_build']
|
missions_to_add = mission_pools[MissionPools.STARTER]
|
||||||
|
if len(no_build_slots) > len(missions_to_add):
|
||||||
|
raise Exception("There are no valid No-Build missions. Please exclude fewer missions.")
|
||||||
for slot in no_build_slots:
|
for slot in no_build_slots:
|
||||||
filler = multiworld.random.randint(0, len(missions_to_add) - 1)
|
filler = multiworld.random.randint(0, len(missions_to_add) - 1)
|
||||||
|
|
||||||
missions[slot] = missions_to_add.pop(filler)
|
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 + mission_pools['easy']
|
missions_to_add = missions_to_add + mission_pools[MissionPools.EASY]
|
||||||
|
if len(easy_slots) > len(missions_to_add):
|
||||||
|
raise Exception("There are not enough Easy missions to fill the campaign. Please exclude fewer missions.")
|
||||||
for slot in easy_slots:
|
for slot in easy_slots:
|
||||||
filler = multiworld.random.randint(0, len(missions_to_add) - 1)
|
filler = multiworld.random.randint(0, len(missions_to_add) - 1)
|
||||||
|
|
||||||
missions[slot] = missions_to_add.pop(filler)
|
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 + mission_pools['medium']
|
missions_to_add = missions_to_add + mission_pools[MissionPools.MEDIUM]
|
||||||
|
if len(medium_slots) > len(missions_to_add):
|
||||||
|
raise Exception("There are not enough Easy and Medium missions to fill the campaign. Please exclude fewer missions.")
|
||||||
for slot in medium_slots:
|
for slot in medium_slots:
|
||||||
filler = multiworld.random.randint(0, len(missions_to_add) - 1)
|
filler = multiworld.random.randint(0, len(missions_to_add) - 1)
|
||||||
|
|
||||||
missions[slot] = missions_to_add.pop(filler)
|
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 + mission_pools['hard']
|
missions_to_add = missions_to_add + mission_pools[MissionPools.HARD]
|
||||||
|
if len(hard_slots) > len(missions_to_add):
|
||||||
|
raise Exception("There are not enough missions to fill the campaign. Please exclude fewer missions.")
|
||||||
for slot in hard_slots:
|
for slot in hard_slots:
|
||||||
filler = multiworld.random.randint(0, len(missions_to_add) - 1)
|
filler = multiworld.random.randint(0, len(missions_to_add) - 1)
|
||||||
|
|
||||||
missions[slot] = missions_to_add.pop(filler)
|
missions[slot] = missions_to_add.pop(filler)
|
||||||
|
|
||||||
|
# Generating regions and locations from selected missions
|
||||||
|
for region_name in missions:
|
||||||
|
regions.append(create_region(multiworld, player, locations_per_region, location_cache, region_name))
|
||||||
|
multiworld.regions += regions
|
||||||
|
|
||||||
|
# Mapping original mission slots to shifted mission slots when missions are removed
|
||||||
|
slot_map = []
|
||||||
|
slot_offset = 0
|
||||||
|
for position, mission in enumerate(missions):
|
||||||
|
slot_map.append(position - slot_offset + 1)
|
||||||
|
if mission is None:
|
||||||
|
slot_offset += 1
|
||||||
|
|
||||||
# Loop through missions to create requirements table and connect regions
|
# Loop through missions to create requirements table and connect regions
|
||||||
# TODO: Handle 'and' connections
|
# TODO: Handle 'and' connections
|
||||||
mission_req_table = {}
|
mission_req_table = {}
|
||||||
for i in range(len(missions)):
|
|
||||||
|
for i, mission in enumerate(missions):
|
||||||
|
if mission is None:
|
||||||
|
continue
|
||||||
connections = []
|
connections = []
|
||||||
for connection in mission_order[i].connect_to:
|
for connection in mission_order[i].connect_to:
|
||||||
|
required_mission = missions[connection]
|
||||||
if connection == -1:
|
if connection == -1:
|
||||||
connect(multiworld, player, names, "Menu", missions[i])
|
connect(multiworld, player, names, "Menu", mission)
|
||||||
|
elif required_mission is None:
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
connect(multiworld, player, names, missions[connection], missions[i],
|
connect(multiworld, player, names, required_mission, mission,
|
||||||
(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(multiworld, player,
|
state._sc2wol_cleared_missions(multiworld, player,
|
||||||
missions_req)))
|
missions_req)))
|
||||||
(missions[connection], mission_order[i].number))
|
(missions[connection], mission_order[i].number))
|
||||||
connections.append(connection + 1)
|
connections.append(slot_map[connection])
|
||||||
|
|
||||||
mission_req_table.update({missions[i]: MissionInfo(
|
mission_req_table.update({mission: MissionInfo(
|
||||||
vanilla_mission_req_table[missions[i]].id, connections, mission_order[i].category,
|
vanilla_mission_req_table[mission].id, connections, mission_order[i].category,
|
||||||
number=mission_order[i].number,
|
number=mission_order[i].number,
|
||||||
completion_critical=mission_order[i].completion_critical,
|
completion_critical=mission_order[i].completion_critical,
|
||||||
or_requirements=mission_order[i].or_requirements)})
|
or_requirements=mission_order[i].or_requirements)})
|
||||||
|
|
||||||
final_mission_id = vanilla_mission_req_table[final_mission].id
|
final_mission_id = vanilla_mission_req_table[final_mission].id
|
||||||
return mission_req_table, final_mission_id, final_mission + ': Victory'
|
|
||||||
|
|
||||||
|
# Changing the completion condition for alternate final missions into an event
|
||||||
|
if final_mission != 'All-In':
|
||||||
|
final_location = alt_final_mission_locations[final_mission]
|
||||||
|
# Final location should be near the end of the cache
|
||||||
|
for i in range(len(location_cache) - 1, -1, -1):
|
||||||
|
if location_cache[i].name == final_location:
|
||||||
|
location_cache[i].locked = True
|
||||||
|
location_cache[i].event = True
|
||||||
|
location_cache[i].address = None
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
final_location = 'All-In: Victory'
|
||||||
|
|
||||||
def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: Set[str]):
|
return mission_req_table, final_mission_id, final_location
|
||||||
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,
|
def create_location(player: int, location_data: LocationData, region: Region,
|
||||||
location_cache: List[Location]) -> Location:
|
location_cache: List[Location]) -> Location:
|
||||||
|
|
|
@ -7,10 +7,10 @@ from .Items import StarcraftWoLItem, item_table, filler_items, item_name_groups,
|
||||||
get_basic_units
|
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, get_option_set_value
|
from .Options import sc2wol_options, get_option_value
|
||||||
from .LogicMixin import SC2WoLLogic
|
from .LogicMixin import SC2WoLLogic
|
||||||
from .PoolFilter import filter_missions, filter_items, get_item_upgrades
|
from .PoolFilter import filter_missions, filter_items, get_item_upgrades
|
||||||
from .MissionTables import get_starting_mission_locations, MissionInfo
|
from .MissionTables import starting_mission_locations, MissionInfo
|
||||||
|
|
||||||
|
|
||||||
class Starcraft2WoLWebWorld(WebWorld):
|
class Starcraft2WoLWebWorld(WebWorld):
|
||||||
|
@ -137,7 +137,6 @@ def assign_starter_items(multiworld: MultiWorld, player: int, excluded_items: Se
|
||||||
|
|
||||||
# The first world should also be the starting world
|
# The first world should also be the starting world
|
||||||
first_mission = list(multiworld.worlds[player].mission_req_table)[0]
|
first_mission = list(multiworld.worlds[player].mission_req_table)[0]
|
||||||
starting_mission_locations = get_starting_mission_locations(multiworld, player)
|
|
||||||
if first_mission in starting_mission_locations:
|
if first_mission in starting_mission_locations:
|
||||||
first_location = starting_mission_locations[first_mission]
|
first_location = starting_mission_locations[first_mission]
|
||||||
elif first_mission == "In Utter Darkness":
|
elif first_mission == "In Utter Darkness":
|
||||||
|
@ -174,7 +173,7 @@ def get_item_pool(multiworld: MultiWorld, player: int, mission_req_table: Dict[s
|
||||||
locked_items = []
|
locked_items = []
|
||||||
|
|
||||||
# YAML items
|
# YAML items
|
||||||
yaml_locked_items = get_option_set_value(multiworld, player, 'locked_items')
|
yaml_locked_items = get_option_value(multiworld, 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:
|
||||||
|
|
Loading…
Reference in New Issue