Feature highlights: - Adds many content to the SC2 game - Allows custom mission order - Adds race-swapped missions for build missions (except Epilogue and NCO) - Allows War Council Nerfs (Protoss units can get pre - War Council State, alternative units get another custom nerf to match the power level of base units) - Revamps Predator's upgrade tree (never was considered strategically important) - Adds some units and upgrades - Locked and excluded items can specify quantity - Key mode (if opt-in, missions require keys to be unlocked on top of their regular regular requirements - Victory caches - Victory locations can grant multiple items to the multiworld instead of one - The generator is more resilient for generator failures as it validates logic for item excludes - Fixes the following issues: - https://github.com/ArchipelagoMW/Archipelago/issues/3531 - https://github.com/ArchipelagoMW/Archipelago/issues/3548
917 lines
33 KiB
Python
917 lines
33 KiB
Python
from typing import Dict, Any, Callable, List, Tuple
|
|
import copy
|
|
|
|
from ..mission_groups import MissionGroupNames
|
|
from ..mission_tables import SC2Mission, SC2Campaign
|
|
|
|
preset_mini_wol_with_prophecy = {
|
|
"global": {
|
|
"type": "column",
|
|
"mission_pool": [
|
|
MissionGroupNames.WOL_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
]
|
|
},
|
|
"Mar Sara": {
|
|
"size": 1
|
|
},
|
|
"Colonist": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Mar Sara" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Artifact": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{ "scope": "../Mar Sara" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 1, "entry_rules": [{ "scope": "../..", "amount": 4 }, { "items": { "Key": 1 }}] },
|
|
{ "index": 2, "entry_rules": [{ "scope": "../..", "amount": 8 }, { "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Prophecy": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Artifact/1" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"mission_pool": [
|
|
MissionGroupNames.PROPHECY_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Covert": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Mar Sara" },
|
|
{ "scope": "..", "amount": 2 },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Rebellion": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Mar Sara" },
|
|
{ "scope": "..", "amount": 3 },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Char": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{ "scope": "../Artifact" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "next": [2] },
|
|
{ "index": 1, "entrance": True }
|
|
]
|
|
}
|
|
}
|
|
|
|
preset_mini_wol = copy.deepcopy(preset_mini_wol_with_prophecy)
|
|
preset_mini_prophecy = { "Prophecy": preset_mini_wol.pop("Prophecy") }
|
|
preset_mini_prophecy["Prophecy"].pop("entry_rules")
|
|
preset_mini_prophecy["Prophecy"]["type"] = "gauntlet"
|
|
preset_mini_prophecy["Prophecy"]["display_name"] = ""
|
|
preset_mini_prophecy["Prophecy"]["missions"].append({ "index": "entrances", "entry_rules": [] })
|
|
|
|
preset_mini_hots = {
|
|
"global": {
|
|
"type": "column",
|
|
"mission_pool": [
|
|
MissionGroupNames.HOTS_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
]
|
|
},
|
|
"Umoja": {
|
|
"size": 1,
|
|
},
|
|
"Kaldir": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Umoja" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Char": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Umoja" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Zerus": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Umoja" },
|
|
{ "scope": "..", "amount": 3 },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Skygeirr Station": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Zerus" },
|
|
{ "scope": "..", "amount": 5 },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Dominion Space": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Zerus" },
|
|
{ "scope": "..", "amount": 5 },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Korhal": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Zerus" },
|
|
{ "scope": "..", "amount": 8 },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
}
|
|
}
|
|
|
|
preset_mini_lotv_prologue = {
|
|
"min_difficulty": "easy",
|
|
"Prologue": {
|
|
"display_name": "",
|
|
"type": "gauntlet",
|
|
"size": 2,
|
|
"mission_pool": [
|
|
MissionGroupNames.PROLOGUE_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
],
|
|
"missions": [
|
|
{ "index": 1, "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
}
|
|
}
|
|
|
|
preset_mini_lotv = {
|
|
"global": {
|
|
"type": "column",
|
|
"mission_pool": [
|
|
MissionGroupNames.LOTV_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
]
|
|
},
|
|
"Aiur": {
|
|
"size": 2,
|
|
"missions": [
|
|
{ "index": 1, "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Korhal": {
|
|
"size": 1,
|
|
"entry_rules": [
|
|
{ "scope": "../Aiur" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Shakuras": {
|
|
"size": 1,
|
|
"entry_rules": [
|
|
{ "scope": "../Aiur" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Purifier": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Korhal" },
|
|
{ "scope": "../Shakuras" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 1, "entry_rules": [{ "scope": "../../Ulnar" }, { "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Ulnar": {
|
|
"size": 1,
|
|
"entry_rules": [
|
|
{ "scope": "../Purifier/0" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Tal'darim": {
|
|
"size": 1,
|
|
"entry_rules": [
|
|
{ "scope": "../Ulnar" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Return to Aiur": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Purifier" },
|
|
{ "scope": "../Tal'darim" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
}
|
|
}
|
|
|
|
preset_mini_lotv_epilogue = {
|
|
"min_difficulty": "very hard",
|
|
"Epilogue": {
|
|
"display_name": "",
|
|
"type": "gauntlet",
|
|
"size": 2,
|
|
"mission_pool": [
|
|
MissionGroupNames.EPILOGUE_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
],
|
|
"missions": [
|
|
{ "index": 1, "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
}
|
|
}
|
|
|
|
preset_mini_nco = {
|
|
"min_difficulty": "easy",
|
|
"global": {
|
|
"type": "column",
|
|
"mission_pool": [
|
|
MissionGroupNames.NCO_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
]
|
|
},
|
|
"Mission Pack 1": {
|
|
"size": 2,
|
|
"missions": [
|
|
{ "index": 1, "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Mission Pack 2": {
|
|
"size": 1,
|
|
"entry_rules": [
|
|
{ "scope": "../Mission Pack 1" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Mission Pack 3": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Mission Pack 2" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
}
|
|
|
|
preset_wol_with_prophecy = {
|
|
"global": {
|
|
"type": "column",
|
|
"mission_pool": [
|
|
MissionGroupNames.WOL_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
]
|
|
},
|
|
"Mar Sara": {
|
|
"size": 3,
|
|
"missions": [
|
|
{ "index": 0, "mission_pool": SC2Mission.LIBERATION_DAY.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.THE_OUTLAWS.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.ZERO_HOUR.mission_name },
|
|
{ "index": [1, 2], "entry_rules": [{ "items": { "Key": 1 }}] }
|
|
]
|
|
},
|
|
"Colonist": {
|
|
"size": 4,
|
|
"entry_rules": [
|
|
{ "scope": "../Mar Sara" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": 1, "next": [2, 3] },
|
|
{ "index": 2, "next": [] },
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": [2, 3], "entry_rules": [{ "scope": "../..", "amount": 7 }, { "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.EVACUATION.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.OUTBREAK.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.SAFE_HAVEN.mission_name },
|
|
{ "index": 3, "mission_pool": SC2Mission.HAVENS_FALL.mission_name },
|
|
]
|
|
},
|
|
"Artifact": {
|
|
"size": 5,
|
|
"entry_rules": [
|
|
{ "scope": "../Mar Sara" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 1, "entry_rules": [{ "scope": "../..", "amount": 8 }, { "items": { "Key": 1 }}] },
|
|
{ "index": 2, "entry_rules": [{ "scope": "../..", "amount": 11 }, { "items": { "Key": 1 }}] },
|
|
{ "index": 3, "entry_rules": [{ "scope": "../..", "amount": 14 }, { "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.SMASH_AND_GRAB.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.THE_DIG.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.THE_MOEBIUS_FACTOR.mission_name },
|
|
{ "index": 3, "mission_pool": SC2Mission.SUPERNOVA.mission_name },
|
|
{ "index": 4, "mission_pool": SC2Mission.MAW_OF_THE_VOID.mission_name },
|
|
]
|
|
},
|
|
"Prophecy": {
|
|
"size": 4,
|
|
"entry_rules": [
|
|
{ "scope": "../Artifact/1" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"mission_pool": [
|
|
MissionGroupNames.PROPHECY_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.WHISPERS_OF_DOOM.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.A_SINISTER_TURN.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.ECHOES_OF_THE_FUTURE.mission_name },
|
|
{ "index": 3, "mission_pool": SC2Mission.IN_UTTER_DARKNESS.mission_name },
|
|
]
|
|
},
|
|
"Covert": {
|
|
"size": 4,
|
|
"entry_rules": [
|
|
{ "scope": "../Mar Sara" },
|
|
{ "scope": "..", "amount": 4 },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": 1, "next": [2, 3] },
|
|
{ "index": 2, "next": [] },
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": [2, 3], "entry_rules": [{ "scope": "../..", "amount": 8 }, { "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.DEVILS_PLAYGROUND.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.WELCOME_TO_THE_JUNGLE.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.BREAKOUT.mission_name },
|
|
{ "index": 3, "mission_pool": SC2Mission.GHOST_OF_A_CHANCE.mission_name },
|
|
]
|
|
},
|
|
"Rebellion": {
|
|
"size": 5,
|
|
"entry_rules": [
|
|
{ "scope": "../Mar Sara" },
|
|
{ "scope": "..", "amount": 6 },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.THE_GREAT_TRAIN_ROBBERY.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.CUTTHROAT.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.ENGINE_OF_DESTRUCTION.mission_name },
|
|
{ "index": 3, "mission_pool": SC2Mission.MEDIA_BLITZ.mission_name },
|
|
{ "index": 4, "mission_pool": SC2Mission.PIERCING_OF_THE_SHROUD.mission_name },
|
|
]
|
|
},
|
|
"Char": {
|
|
"size": 4,
|
|
"entry_rules": [
|
|
{ "scope": "../Artifact" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": 0, "next": [1, 2] },
|
|
{ "index": [1, 2], "next": [3] },
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.GATES_OF_HELL.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.BELLY_OF_THE_BEAST.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.SHATTER_THE_SKY.mission_name },
|
|
{ "index": 3, "mission_pool": SC2Mission.ALL_IN.mission_name },
|
|
]
|
|
}
|
|
}
|
|
|
|
preset_wol = copy.deepcopy(preset_wol_with_prophecy)
|
|
preset_prophecy = { "Prophecy": preset_wol.pop("Prophecy") }
|
|
preset_prophecy["Prophecy"].pop("entry_rules")
|
|
preset_prophecy["Prophecy"]["type"] = "gauntlet"
|
|
preset_prophecy["Prophecy"]["display_name"] = ""
|
|
preset_prophecy["Prophecy"]["missions"].append({ "index": "entrances", "entry_rules": [] })
|
|
|
|
preset_hots = {
|
|
"global": {
|
|
"type": "column",
|
|
"mission_pool": [
|
|
MissionGroupNames.HOTS_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
]
|
|
},
|
|
"Umoja": {
|
|
"size": 3,
|
|
"missions": [
|
|
{ "index": [1, 2], "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.LAB_RAT.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.BACK_IN_THE_SADDLE.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.RENDEZVOUS.mission_name },
|
|
]
|
|
},
|
|
"Kaldir": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{ "scope": "../Umoja" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.HARVEST_OF_SCREAMS.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.SHOOT_THE_MESSENGER.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.ENEMY_WITHIN.mission_name },
|
|
]
|
|
},
|
|
"Char": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{ "scope": "../Umoja" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.DOMINATION.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.FIRE_IN_THE_SKY.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.OLD_SOLDIERS.mission_name },
|
|
]
|
|
},
|
|
"Zerus": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{
|
|
"rules": [
|
|
{ "scope": "../Kaldir" },
|
|
{ "scope": "../Char" }
|
|
],
|
|
"amount": 1
|
|
},
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.WAKING_THE_ANCIENT.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.THE_CRUCIBLE.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.SUPREME.mission_name },
|
|
]
|
|
},
|
|
"Skygeirr Station": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{ "scope": ["../Kaldir", "../Char", "../Zerus"] },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.INFESTED.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.HAND_OF_DARKNESS.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.PHANTOMS_OF_THE_VOID.mission_name },
|
|
]
|
|
},
|
|
"Dominion Space": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": ["../Kaldir", "../Char", "../Zerus"] },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.WITH_FRIENDS_LIKE_THESE.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.CONVICTION.mission_name },
|
|
]
|
|
},
|
|
"Korhal": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{ "scope": ["../Skygeirr Station", "../Dominion Space"] },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.PLANETFALL.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.DEATH_FROM_ABOVE.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.THE_RECKONING.mission_name },
|
|
]
|
|
}
|
|
}
|
|
|
|
preset_lotv_prologue = {
|
|
"min_difficulty": "easy",
|
|
"Prologue": {
|
|
"display_name": "",
|
|
"type": "gauntlet",
|
|
"size": 3,
|
|
"mission_pool": [
|
|
MissionGroupNames.PROLOGUE_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
],
|
|
"missions": [
|
|
{ "index": [1, 2], "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.DARK_WHISPERS.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.GHOSTS_IN_THE_FOG.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.EVIL_AWOKEN.mission_name },
|
|
]
|
|
}
|
|
}
|
|
|
|
preset_lotv = {
|
|
"global": {
|
|
"type": "column",
|
|
"mission_pool": [
|
|
MissionGroupNames.LOTV_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
]
|
|
},
|
|
"Aiur": {
|
|
"size": 3,
|
|
"missions": [
|
|
{ "index": [1, 2], "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.FOR_AIUR.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.THE_GROWING_SHADOW.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.THE_SPEAR_OF_ADUN.mission_name },
|
|
]
|
|
},
|
|
"Korhal": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Aiur" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.SKY_SHIELD.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.BROTHERS_IN_ARMS.mission_name },
|
|
]
|
|
},
|
|
"Shakuras": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Aiur" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.AMON_S_REACH.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.LAST_STAND.mission_name },
|
|
]
|
|
},
|
|
"Purifier": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{
|
|
"rules": [
|
|
{ "scope": "../Korhal" },
|
|
{ "scope": "../Shakuras" }
|
|
],
|
|
"amount": 1
|
|
},
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 1, "entry_rules": [{ "scope": "../../Ulnar" }, { "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.FORBIDDEN_WEAPON.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.UNSEALING_THE_PAST.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.PURIFICATION.mission_name },
|
|
]
|
|
},
|
|
"Ulnar": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{
|
|
"scope": [
|
|
"../Korhal",
|
|
"../Shakuras",
|
|
"../Purifier/0"
|
|
]
|
|
},
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.TEMPLE_OF_UNIFICATION.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.THE_INFINITE_CYCLE.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.HARBINGER_OF_OBLIVION.mission_name },
|
|
]
|
|
},
|
|
"Tal'darim": {
|
|
"size": 2,
|
|
"entry_rules": [
|
|
{ "scope": "../Ulnar" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.STEPS_OF_THE_RITE.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.RAK_SHIR.mission_name },
|
|
]
|
|
},
|
|
"Moebius": {
|
|
"size": 1,
|
|
"entry_rules": [
|
|
{
|
|
"rules": [
|
|
{ "scope": "../Purifier" },
|
|
{ "scope": "../Tal'darim" }
|
|
],
|
|
"amount": 1
|
|
},
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.TEMPLAR_S_CHARGE.mission_name },
|
|
]
|
|
},
|
|
"Return to Aiur": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{ "scope": "../Purifier" },
|
|
{ "scope": "../Tal'darim" },
|
|
{ "scope": "../Moebius" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.TEMPLAR_S_RETURN.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.THE_HOST.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.SALVATION.mission_name },
|
|
]
|
|
}
|
|
}
|
|
|
|
preset_lotv_epilogue = {
|
|
"min_difficulty": "very hard",
|
|
"Epilogue": {
|
|
"display_name": "",
|
|
"type": "gauntlet",
|
|
"size": 3,
|
|
"mission_pool": [
|
|
MissionGroupNames.EPILOGUE_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
],
|
|
"missions": [
|
|
{ "index": [1, 2], "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.INTO_THE_VOID.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.THE_ESSENCE_OF_ETERNITY.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.AMON_S_FALL.mission_name },
|
|
]
|
|
}
|
|
}
|
|
|
|
preset_nco = {
|
|
"min_difficulty": "easy",
|
|
"global": {
|
|
"type": "column",
|
|
"mission_pool": [
|
|
MissionGroupNames.NCO_MISSIONS,
|
|
"~ " + MissionGroupNames.RACESWAP_MISSIONS
|
|
]
|
|
},
|
|
"Mission Pack 1": {
|
|
"size": 3,
|
|
"missions": [
|
|
{ "index": [1, 2], "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.THE_ESCAPE.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.SUDDEN_STRIKE.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.ENEMY_INTELLIGENCE.mission_name },
|
|
]
|
|
},
|
|
"Mission Pack 2": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{ "scope": "../Mission Pack 1" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.TROUBLE_IN_PARADISE.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.NIGHT_TERRORS.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.FLASHPOINT.mission_name },
|
|
]
|
|
},
|
|
"Mission Pack 3": {
|
|
"size": 3,
|
|
"entry_rules": [
|
|
{ "scope": "../Mission Pack 2" },
|
|
{ "items": { "Key": 1 }}
|
|
],
|
|
"missions": [
|
|
{ "index": "all", "entry_rules": [{ "items": { "Key": 1 }}] },
|
|
{ "index": 0, "mission_pool": SC2Mission.IN_THE_ENEMY_S_SHADOW.mission_name },
|
|
{ "index": 1, "mission_pool": SC2Mission.DARK_SKIES.mission_name },
|
|
{ "index": 2, "mission_pool": SC2Mission.END_GAME.mission_name },
|
|
]
|
|
},
|
|
}
|
|
|
|
def _build_static_preset(preset: Dict[str, Any], options: Dict[str, Any]) -> Dict[str, Any]:
|
|
# Raceswap shuffling
|
|
raceswaps = options.pop("shuffle_raceswaps", False)
|
|
if not isinstance(raceswaps, bool):
|
|
raise ValueError(
|
|
f"Preset option \"shuffle_raceswaps\" received unknown value \"{raceswaps}\".\n"
|
|
"Valid values are: true, false"
|
|
)
|
|
elif raceswaps == True:
|
|
# Remove "~ Raceswap Missions" operation from mission pool options
|
|
# Also add raceswap variants to plando'd vanilla missions
|
|
for layout in preset.values():
|
|
if type(layout) == dict:
|
|
# Currently mission pools in layouts are always ["X campaign missions", "~ raceswap missions"]
|
|
layout_mission_pool: List[str] = layout.get("mission_pool", None)
|
|
if layout_mission_pool is not None:
|
|
layout_mission_pool.pop()
|
|
layout["mission_pool"] = layout_mission_pool
|
|
if "missions" in layout:
|
|
for slot in layout["missions"]:
|
|
# Currently mission pools in slots are always strings
|
|
slot_mission_pool: str = slot.get("mission_pool", None)
|
|
# Identify raceswappable missions by their race in brackets
|
|
if slot_mission_pool is not None and slot_mission_pool[-1] == ")":
|
|
mission_name = slot_mission_pool[:slot_mission_pool.rfind("(")]
|
|
new_mission_pool = [f"{mission_name}({race})" for race in ["Terran", "Zerg", "Protoss"]]
|
|
slot["mission_pool"] = new_mission_pool
|
|
# The presets are set up for no raceswaps, so raceswaps == False doesn't need to be covered
|
|
|
|
# Mission pool selection
|
|
missions = options.pop("missions", "random")
|
|
if missions == "vanilla":
|
|
pass # use preset as it is
|
|
elif missions == "vanilla_shuffled":
|
|
# remove pre-set missions
|
|
for layout in preset.values():
|
|
if type(layout) == dict and "missions" in layout:
|
|
for slot in layout["missions"]:
|
|
slot.pop("mission_pool", ())
|
|
elif missions == "random":
|
|
# remove pre-set missions and mission pools
|
|
for layout in preset.values():
|
|
if type(layout) == dict:
|
|
layout.pop("mission_pool", ())
|
|
if "missions" in layout:
|
|
for slot in layout["missions"]:
|
|
slot.pop("mission_pool", ())
|
|
else:
|
|
raise ValueError(
|
|
f"Preset option \"missions\" received unknown value \"{missions}\".\n"
|
|
"Valid values are: random, vanilla, vanilla_shuffled"
|
|
)
|
|
|
|
# Key rule selection
|
|
keys = options.pop("keys", "none")
|
|
if keys == "layouts":
|
|
# remove keys from mission entry rules
|
|
for layout in preset.values():
|
|
if type(layout) == dict and "missions" in layout:
|
|
for slot in layout["missions"]:
|
|
if "entry_rules" in slot:
|
|
slot["entry_rules"] = _remove_key_rules(slot["entry_rules"])
|
|
elif keys == "missions":
|
|
# remove keys from layout entry rules
|
|
for layout in preset.values():
|
|
if type(layout) == dict and "entry_rules" in layout:
|
|
layout["entry_rules"] = _remove_key_rules(layout["entry_rules"])
|
|
elif keys == "progressive_layouts":
|
|
# remove keys from mission entry rules, replace keys in layout entry rules with unique-track keys
|
|
for layout in preset.values():
|
|
if type(layout) == dict:
|
|
if "entry_rules" in layout:
|
|
layout["entry_rules"] = _make_key_rules_progressive(layout["entry_rules"], 0)
|
|
if "missions" in layout:
|
|
for slot in layout["missions"]:
|
|
if "entry_rules" in slot:
|
|
slot["entry_rules"] = _remove_key_rules(slot["entry_rules"])
|
|
elif keys == "progressive_missions":
|
|
# remove keys from layout entry rules, replace keys in mission entry rules
|
|
for layout in preset.values():
|
|
if type(layout) == dict:
|
|
if "entry_rules" in layout:
|
|
layout["entry_rules"] = _remove_key_rules(layout["entry_rules"])
|
|
if "missions" in layout:
|
|
for slot in layout["missions"]:
|
|
if "entry_rules" in slot:
|
|
slot["entry_rules"] = _make_key_rules_progressive(slot["entry_rules"], 1)
|
|
elif keys == "progressive_per_layout":
|
|
# remove keys from layout entry rules, replace keys in mission entry rules with unique-track keys
|
|
# specifically ignore layouts that have no entry rules (and are thus the first of their campaign)
|
|
for layout in preset.values():
|
|
if type(layout) == dict and "entry_rules" in layout:
|
|
layout["entry_rules"] = _remove_key_rules(layout["entry_rules"])
|
|
if "missions" in layout:
|
|
for slot in layout["missions"]:
|
|
if "entry_rules" in slot:
|
|
slot["entry_rules"] = _make_key_rules_progressive(slot["entry_rules"], 0)
|
|
elif keys == "none":
|
|
# remove keys from both layout and mission entry rules
|
|
for layout in preset.values():
|
|
if type(layout) == dict:
|
|
if "entry_rules" in layout:
|
|
layout["entry_rules"] = _remove_key_rules(layout["entry_rules"])
|
|
if "missions" in layout:
|
|
for slot in layout["missions"]:
|
|
if "entry_rules" in slot:
|
|
slot["entry_rules"] = _remove_key_rules(slot["entry_rules"])
|
|
else:
|
|
raise ValueError(
|
|
f"Preset option \"keys\" received unknown value \"{keys}\".\n"
|
|
"Valid values are: none, missions, layouts, progressive_missions, progressive_layouts, progressive_per_layout"
|
|
)
|
|
|
|
return preset
|
|
|
|
def _remove_key_rules(entry_rules: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
return [rule for rule in entry_rules if not ("items" in rule and "Key" in rule["items"])]
|
|
|
|
def _make_key_rules_progressive(entry_rules: List[Dict[str, Any]], track: int) -> List[Dict[str, Any]]:
|
|
for rule in entry_rules:
|
|
if "items" in rule and "Key" in rule["items"]:
|
|
new_items: Dict[str, Any] = {}
|
|
for (item, amount) in rule["items"].items():
|
|
if item == "Key":
|
|
new_items["Progressive Key"] = track
|
|
else:
|
|
new_items[item] = amount
|
|
rule["items"] = new_items
|
|
return entry_rules
|
|
|
|
def static_preset(preset: Dict[str, Any]) -> Callable[[Dict[str, Any]], Dict[str, Any]]:
|
|
return lambda options: _build_static_preset(copy.deepcopy(preset), options)
|
|
|
|
def get_used_layout_names() -> Dict[SC2Campaign, Tuple[int, List[str]]]:
|
|
campaign_to_preset: Dict[SC2Campaign, Dict[str, Any]] = {
|
|
SC2Campaign.WOL: preset_wol_with_prophecy,
|
|
SC2Campaign.PROPHECY: preset_prophecy,
|
|
SC2Campaign.HOTS: preset_hots,
|
|
SC2Campaign.PROLOGUE: preset_lotv_prologue,
|
|
SC2Campaign.LOTV: preset_lotv,
|
|
SC2Campaign.EPILOGUE: preset_lotv_epilogue,
|
|
SC2Campaign.NCO: preset_nco
|
|
}
|
|
campaign_to_layout_names: Dict[SC2Campaign, Tuple[int, List[str]]] = { SC2Campaign.GLOBAL: (0, []) }
|
|
for campaign in SC2Campaign:
|
|
if campaign == SC2Campaign.GLOBAL:
|
|
continue
|
|
previous_campaign = [prev for prev in SC2Campaign if prev.id == campaign.id - 1][0]
|
|
previous_size = campaign_to_layout_names[previous_campaign][0]
|
|
preset = campaign_to_preset[campaign]
|
|
new_layouts = [value for value in preset.keys() if isinstance(preset[value], dict) and value != "global"]
|
|
campaign_to_layout_names[campaign] = (previous_size + len(campaign_to_layout_names[previous_campaign][1]), new_layouts)
|
|
campaign_to_layout_names.pop(SC2Campaign.GLOBAL)
|
|
return campaign_to_layout_names
|