Files
Grinch-AP/worlds/sc2/mission_order/presets_static.py
Ziktofel 5f1835c546 SC2: Content update (#5312)
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
2025-09-02 17:40:58 +02:00

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