From c6228237b04125b30fd321a3947fac1bb37683c8 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 23 Apr 2024 00:18:20 -0700 Subject: [PATCH 01/48] sc2: Refactored most item filtering logic to allow for locking and excluding amounts of progressive items --- worlds/sc2/Client.py | 2 +- worlds/sc2/ClientGui.py | 2 +- worlds/sc2/Items.py | 1339 +++++++++++++++------------- worlds/sc2/Locations.py | 9 +- worlds/sc2/Options.py | 70 +- worlds/sc2/PoolFilter.py | 11 +- worlds/sc2/Regions.py | 18 +- worlds/sc2/__init__.py | 507 ++++++++++- worlds/sc2/test/test_base.py | 2 +- worlds/sc2/test/test_generation.py | 68 ++ 10 files changed, 1315 insertions(+), 713 deletions(-) create mode 100644 worlds/sc2/test/test_generation.py diff --git a/worlds/sc2/Client.py b/worlds/sc2/Client.py index 78ce7902a5c7..7290613118fa 100644 --- a/worlds/sc2/Client.py +++ b/worlds/sc2/Client.py @@ -49,7 +49,7 @@ from worlds.sc2.Items import lookup_id_to_name, get_full_item_list, ItemData, type_flaggroups, upgrade_numbers, upgrade_numbers_all from worlds.sc2.Locations import SC2WOL_LOC_ID_OFFSET, LocationType, SC2HOTS_LOC_ID_OFFSET from worlds.sc2.MissionTables import lookup_id_to_mission, SC2Campaign, lookup_name_to_mission, \ - lookup_id_to_campaign, MissionConnection, SC2Mission, campaign_mission_table, SC2Race, get_no_build_missions + lookup_id_to_campaign, MissionConnection, SC2Mission, campaign_mission_table, SC2Race from worlds.sc2.Regions import MissionInfo import colorama diff --git a/worlds/sc2/ClientGui.py b/worlds/sc2/ClientGui.py index 167583fd1ecb..04f8061d5265 100644 --- a/worlds/sc2/ClientGui.py +++ b/worlds/sc2/ClientGui.py @@ -118,7 +118,7 @@ def build_mission_table(self, dt) -> None: if self.ctx.mission_req_table: self.last_checked_locations = self.ctx.checked_locations.copy() self.first_check = False - self.first_mission = get_first_mission(self.ctx.mission_req_table) + self.first_mission = get_first_mission(self.ctx.mission_req_table).mission_name self.mission_id_to_button = {} diff --git a/worlds/sc2/Items.py b/worlds/sc2/Items.py index cc0670ed92cf..2ac607ae4c9b 100644 --- a/worlds/sc2/Items.py +++ b/worlds/sc2/Items.py @@ -3,6 +3,7 @@ from BaseClasses import Item, ItemClassification, MultiWorld import typing +import enum from .Options import get_option_value, RequiredTactics from .MissionTables import SC2Mission, SC2Race, SC2Campaign, campaign_mission_table @@ -10,9 +11,60 @@ from worlds.AutoWorld import World +class ItemType(enum.StrEnum): + Unit = "Unit" + Unit_2 = "Unit 2" + Upgrade = "Upgrade" + Building = "Building" + Progressive = "Progressive Upgrade" + Progressive_2 = "Progressive Upgrade 2" + Armory_1 = "Armory 1" + Armory_2 = "Armory 2" + Armory_3 = "Armory 3" + Armory_4 = "Armory 4" + Armory_5 = "Armory 5" + Armory_6 = "Armory 6" + Nova_Gear = "Nova Gear" + Mercenary = "Mercenary" + Laboratory = "Laboratory" + Morph = "Morph" + + Evolution_Pit = "Evolution Pit" + Strain = "Strain" + Ability = "Ability" + Passive_Ability = "Passive Ability" + Level = "Level" + Primal_Form = "Primal Form" + Mutation_1 = "Mutation 1" + Mutation_2 = "Mutation 2" + Mutation_3 = "Mutation 3" + + Spear_Of_Adun = "Spear of Adun" + Solarite_Core = "Solarite Core" + Forge_1 = "Forge 1" + Forge_2 = "Forge 2" + Forge_3 = "Forge 3" + + Minerals = "Minerals" + Vespene = "Vespene" + Supply = "Supply" + Nothing = "Nothing Group" + + +class ItemOrigin(enum.IntFlag): + WoL = enum.auto() + HotS = enum.auto() + LotV = enum.auto() + NCO = enum.auto() + Melee = enum.auto() + Coop = enum.auto() + BW = enum.auto() + AP = enum.auto() + + class ItemData(typing.NamedTuple): code: int - type: str + type: ItemType number: int # Important for bot commands to send the item into the game race: SC2Race classification: ItemClassification = ItemClassification.useful @@ -44,1448 +96,1448 @@ def get_full_item_list(): item_table = { # WoL ItemNames.MARINE: - ItemData(0 + SC2WOL_ITEM_ID_OFFSET, "Unit", 0, SC2Race.TERRAN, + ItemData(0 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 0, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.MEDIC: - ItemData(1 + SC2WOL_ITEM_ID_OFFSET, "Unit", 1, SC2Race.TERRAN, + ItemData(1 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 1, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.FIREBAT: - ItemData(2 + SC2WOL_ITEM_ID_OFFSET, "Unit", 2, SC2Race.TERRAN, + ItemData(2 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 2, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.MARAUDER: - ItemData(3 + SC2WOL_ITEM_ID_OFFSET, "Unit", 3, SC2Race.TERRAN, + ItemData(3 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 3, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.REAPER: - ItemData(4 + SC2WOL_ITEM_ID_OFFSET, "Unit", 4, SC2Race.TERRAN, + ItemData(4 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 4, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.HELLION: - ItemData(5 + SC2WOL_ITEM_ID_OFFSET, "Unit", 5, SC2Race.TERRAN, + ItemData(5 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 5, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.VULTURE: - ItemData(6 + SC2WOL_ITEM_ID_OFFSET, "Unit", 6, SC2Race.TERRAN, + ItemData(6 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 6, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.GOLIATH: - ItemData(7 + SC2WOL_ITEM_ID_OFFSET, "Unit", 7, SC2Race.TERRAN, + ItemData(7 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 7, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.DIAMONDBACK: - ItemData(8 + SC2WOL_ITEM_ID_OFFSET, "Unit", 8, SC2Race.TERRAN, + ItemData(8 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 8, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.SIEGE_TANK: - ItemData(9 + SC2WOL_ITEM_ID_OFFSET, "Unit", 9, SC2Race.TERRAN, + ItemData(9 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 9, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.MEDIVAC: - ItemData(10 + SC2WOL_ITEM_ID_OFFSET, "Unit", 10, SC2Race.TERRAN, + ItemData(10 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 10, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.WRAITH: - ItemData(11 + SC2WOL_ITEM_ID_OFFSET, "Unit", 11, SC2Race.TERRAN, + ItemData(11 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 11, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.VIKING: - ItemData(12 + SC2WOL_ITEM_ID_OFFSET, "Unit", 12, SC2Race.TERRAN, + ItemData(12 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 12, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.BANSHEE: - ItemData(13 + SC2WOL_ITEM_ID_OFFSET, "Unit", 13, SC2Race.TERRAN, + ItemData(13 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 13, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.BATTLECRUISER: - ItemData(14 + SC2WOL_ITEM_ID_OFFSET, "Unit", 14, SC2Race.TERRAN, + ItemData(14 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 14, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.GHOST: - ItemData(15 + SC2WOL_ITEM_ID_OFFSET, "Unit", 15, SC2Race.TERRAN, + ItemData(15 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 15, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.SPECTRE: - ItemData(16 + SC2WOL_ITEM_ID_OFFSET, "Unit", 16, SC2Race.TERRAN, + ItemData(16 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 16, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.THOR: - ItemData(17 + SC2WOL_ITEM_ID_OFFSET, "Unit", 17, SC2Race.TERRAN, + ItemData(17 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 17, SC2Race.TERRAN, classification=ItemClassification.progression), # EE units ItemNames.LIBERATOR: - ItemData(18 + SC2WOL_ITEM_ID_OFFSET, "Unit", 18, SC2Race.TERRAN, + ItemData(18 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 18, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"nco", "ext"}), ItemNames.VALKYRIE: - ItemData(19 + SC2WOL_ITEM_ID_OFFSET, "Unit", 19, SC2Race.TERRAN, + ItemData(19 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 19, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"bw"}), ItemNames.WIDOW_MINE: - ItemData(20 + SC2WOL_ITEM_ID_OFFSET, "Unit", 20, SC2Race.TERRAN, + ItemData(20 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 20, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), ItemNames.CYCLONE: - ItemData(21 + SC2WOL_ITEM_ID_OFFSET, "Unit", 21, SC2Race.TERRAN, + ItemData(21 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 21, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), ItemNames.HERC: - ItemData(22 + SC2WOL_ITEM_ID_OFFSET, "Unit", 26, SC2Race.TERRAN, + ItemData(22 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 26, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), ItemNames.WARHOUND: - ItemData(23 + SC2WOL_ITEM_ID_OFFSET, "Unit", 27, SC2Race.TERRAN, + ItemData(23 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 27, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), # Some other items are moved to Upgrade group because of the way how the bot message is parsed - ItemNames.PROGRESSIVE_TERRAN_INFANTRY_WEAPON: ItemData(100 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.TERRAN, quantity=3,), - ItemNames.PROGRESSIVE_TERRAN_INFANTRY_ARMOR: ItemData(102 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_VEHICLE_WEAPON: ItemData(103 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_VEHICLE_ARMOR: ItemData(104 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_SHIP_WEAPON: ItemData(105 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_SHIP_ARMOR: ItemData(106 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 10, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_WEAPON: ItemData(100 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 0, SC2Race.TERRAN, quantity=3,), + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_ARMOR: ItemData(102 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 2, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_WEAPON: ItemData(103 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 4, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_ARMOR: ItemData(104 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 6, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_SHIP_WEAPON: ItemData(105 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 8, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_SHIP_ARMOR: ItemData(106 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 10, SC2Race.TERRAN, quantity=3), # Upgrade bundle 'number' values are used as indices to get affected 'number's - ItemNames.PROGRESSIVE_TERRAN_WEAPON_UPGRADE: ItemData(107 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_ARMOR_UPGRADE: ItemData(108 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 1, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE: ItemData(109 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE: ItemData(110 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 3, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_SHIP_UPGRADE: ItemData(111 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE: ItemData(112 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 5, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_WEAPON_UPGRADE: ItemData(107 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 0, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_ARMOR_UPGRADE: ItemData(108 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 1, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE: ItemData(109 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 2, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE: ItemData(110 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 3, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_SHIP_UPGRADE: ItemData(111 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 4, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE: ItemData(112 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 5, SC2Race.TERRAN, quantity=3), # Unit and structure upgrades ItemNames.BUNKER_PROJECTILE_ACCELERATOR: - ItemData(200 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 0, SC2Race.TERRAN, + ItemData(200 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 0, SC2Race.TERRAN, parent_item=ItemNames.BUNKER), ItemNames.BUNKER_NEOSTEEL_BUNKER: - ItemData(201 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 1, SC2Race.TERRAN, + ItemData(201 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 1, SC2Race.TERRAN, parent_item=ItemNames.BUNKER), ItemNames.MISSILE_TURRET_TITANIUM_HOUSING: - ItemData(202 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 2, SC2Race.TERRAN, + ItemData(202 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 2, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MISSILE_TURRET), ItemNames.MISSILE_TURRET_HELLSTORM_BATTERIES: - ItemData(203 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 3, SC2Race.TERRAN, + ItemData(203 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 3, SC2Race.TERRAN, parent_item=ItemNames.MISSILE_TURRET), ItemNames.SCV_ADVANCED_CONSTRUCTION: - ItemData(204 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 4, SC2Race.TERRAN), + ItemData(204 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 4, SC2Race.TERRAN), ItemNames.SCV_DUAL_FUSION_WELDERS: - ItemData(205 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 5, SC2Race.TERRAN), + ItemData(205 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 5, SC2Race.TERRAN), ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM: - ItemData(206 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 24, SC2Race.TERRAN, + ItemData(206 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 24, SC2Race.TERRAN, quantity=2), ItemNames.PROGRESSIVE_ORBITAL_COMMAND: - ItemData(207 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 26, SC2Race.TERRAN, + ItemData(207 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 26, SC2Race.TERRAN, quantity=2, classification=ItemClassification.progression), ItemNames.MARINE_PROGRESSIVE_STIMPACK: - ItemData(208 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 0, SC2Race.TERRAN, + ItemData(208 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 0, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.MARINE, quantity=2), ItemNames.MARINE_COMBAT_SHIELD: - ItemData(209 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 9, SC2Race.TERRAN, + ItemData(209 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 9, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.MARINE), ItemNames.MEDIC_ADVANCED_MEDIC_FACILITIES: - ItemData(210 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 10, SC2Race.TERRAN, + ItemData(210 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 10, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIC), ItemNames.MEDIC_STABILIZER_MEDPACKS: - ItemData(211 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 11, SC2Race.TERRAN, + ItemData(211 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 11, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.MEDIC), ItemNames.FIREBAT_INCINERATOR_GAUNTLETS: - ItemData(212 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 12, SC2Race.TERRAN, + ItemData(212 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 12, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.FIREBAT), ItemNames.FIREBAT_JUGGERNAUT_PLATING: - ItemData(213 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 13, SC2Race.TERRAN, + ItemData(213 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 13, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT), ItemNames.MARAUDER_CONCUSSIVE_SHELLS: - ItemData(214 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 14, SC2Race.TERRAN, + ItemData(214 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 14, SC2Race.TERRAN, parent_item=ItemNames.MARAUDER), ItemNames.MARAUDER_KINETIC_FOAM: - ItemData(215 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 15, SC2Race.TERRAN, + ItemData(215 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 15, SC2Race.TERRAN, parent_item=ItemNames.MARAUDER), ItemNames.REAPER_U238_ROUNDS: - ItemData(216 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 16, SC2Race.TERRAN, + ItemData(216 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 16, SC2Race.TERRAN, parent_item=ItemNames.REAPER), ItemNames.REAPER_G4_CLUSTERBOMB: - ItemData(217 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17, SC2Race.TERRAN, + ItemData(217 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 17, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.REAPER), ItemNames.CYCLONE_MAG_FIELD_ACCELERATORS: - ItemData(218 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 18, SC2Race.TERRAN, + ItemData(218 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 18, SC2Race.TERRAN, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.CYCLONE_MAG_FIELD_LAUNCHERS: - ItemData(219 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 19, SC2Race.TERRAN, + ItemData(219 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 19, SC2Race.TERRAN, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.MARINE_LASER_TARGETING_SYSTEM: - ItemData(220 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 8, SC2Race.TERRAN, + ItemData(220 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 8, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MARINE, origin={"nco"}), ItemNames.MARINE_MAGRAIL_MUNITIONS: - ItemData(221 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 20, SC2Race.TERRAN, + ItemData(221 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 20, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.MARINE, origin={"nco"}), ItemNames.MARINE_OPTIMIZED_LOGISTICS: - ItemData(222 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 21, SC2Race.TERRAN, + ItemData(222 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 21, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MARINE, origin={"nco"}), ItemNames.MEDIC_RESTORATION: - ItemData(223 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 22, SC2Race.TERRAN, + ItemData(223 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 22, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}), ItemNames.MEDIC_OPTICAL_FLARE: - ItemData(224 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 23, SC2Race.TERRAN, + ItemData(224 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 23, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}), ItemNames.MEDIC_RESOURCE_EFFICIENCY: - ItemData(225 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 24, SC2Race.TERRAN, + ItemData(225 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 24, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}), ItemNames.FIREBAT_PROGRESSIVE_STIMPACK: - ItemData(226 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 6, SC2Race.TERRAN, + ItemData(226 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 6, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT, quantity=2, origin={"bw"}), ItemNames.FIREBAT_RESOURCE_EFFICIENCY: - ItemData(227 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 25, SC2Race.TERRAN, + ItemData(227 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 25, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT, origin={"bw"}), ItemNames.MARAUDER_PROGRESSIVE_STIMPACK: - ItemData(228 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 8, SC2Race.TERRAN, + ItemData(228 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 8, SC2Race.TERRAN, parent_item=ItemNames.MARAUDER, quantity=2, origin={"nco"}), ItemNames.MARAUDER_LASER_TARGETING_SYSTEM: - ItemData(229 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 26, SC2Race.TERRAN, + ItemData(229 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 26, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}), ItemNames.MARAUDER_MAGRAIL_MUNITIONS: - ItemData(230 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 27, SC2Race.TERRAN, + ItemData(230 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 27, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}), ItemNames.MARAUDER_INTERNAL_TECH_MODULE: - ItemData(231 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 28, SC2Race.TERRAN, + ItemData(231 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 28, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}), ItemNames.SCV_HOSTILE_ENVIRONMENT_ADAPTATION: - ItemData(232 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 29, SC2Race.TERRAN, + ItemData(232 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 29, SC2Race.TERRAN, classification=ItemClassification.filler, origin={"bw"}), ItemNames.MEDIC_ADAPTIVE_MEDPACKS: - ItemData(233 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 0, SC2Race.TERRAN, + ItemData(233 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 0, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.MEDIC, origin={"ext"}), ItemNames.MEDIC_NANO_PROJECTOR: - ItemData(234 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 1, SC2Race.TERRAN, + ItemData(234 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 1, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"ext"}), ItemNames.FIREBAT_INFERNAL_PRE_IGNITER: - ItemData(235 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 2, SC2Race.TERRAN, + ItemData(235 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 2, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT, origin={"bw"}), ItemNames.FIREBAT_KINETIC_FOAM: - ItemData(236 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 3, SC2Race.TERRAN, + ItemData(236 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 3, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT, origin={"ext"}), ItemNames.FIREBAT_NANO_PROJECTORS: - ItemData(237 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 4, SC2Race.TERRAN, + ItemData(237 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 4, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT, origin={"ext"}), ItemNames.MARAUDER_JUGGERNAUT_PLATING: - ItemData(238 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 5, SC2Race.TERRAN, + ItemData(238 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 5, SC2Race.TERRAN, parent_item=ItemNames.MARAUDER, origin={"ext"}), ItemNames.REAPER_JET_PACK_OVERDRIVE: - ItemData(239 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 6, SC2Race.TERRAN, + ItemData(239 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 6, SC2Race.TERRAN, parent_item=ItemNames.REAPER, origin={"ext"}), ItemNames.HELLION_INFERNAL_PLATING: - ItemData(240 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 7, SC2Race.TERRAN, + ItemData(240 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 7, SC2Race.TERRAN, parent_item=ItemNames.HELLION, origin={"ext"}), ItemNames.VULTURE_AUTO_REPAIR: - ItemData(241 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 8, SC2Race.TERRAN, + ItemData(241 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 8, SC2Race.TERRAN, parent_item=ItemNames.VULTURE, origin={"ext"}), ItemNames.GOLIATH_SHAPED_HULL: - ItemData(242 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 9, SC2Race.TERRAN, + ItemData(242 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 9, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco", "ext"}), ItemNames.GOLIATH_RESOURCE_EFFICIENCY: - ItemData(243 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 10, SC2Race.TERRAN, + ItemData(243 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 10, SC2Race.TERRAN, parent_item=ItemNames.GOLIATH, origin={"nco", "bw"}), ItemNames.GOLIATH_INTERNAL_TECH_MODULE: - ItemData(244 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 11, SC2Race.TERRAN, + ItemData(244 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 11, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco", "bw"}), ItemNames.SIEGE_TANK_SHAPED_HULL: - ItemData(245 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 12, SC2Race.TERRAN, + ItemData(245 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 12, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco", "ext"}), ItemNames.SIEGE_TANK_RESOURCE_EFFICIENCY: - ItemData(246 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 13, SC2Race.TERRAN, + ItemData(246 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 13, SC2Race.TERRAN, parent_item=ItemNames.SIEGE_TANK, origin={"bw"}), ItemNames.PREDATOR_CLOAK: - ItemData(247 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 14, SC2Race.TERRAN, + ItemData(247 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 14, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}), ItemNames.PREDATOR_CHARGE: - ItemData(248 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 15, SC2Race.TERRAN, + ItemData(248 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 15, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}), ItemNames.MEDIVAC_SCATTER_VEIL: - ItemData(249 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 16, SC2Race.TERRAN, + ItemData(249 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 16, SC2Race.TERRAN, parent_item=ItemNames.MEDIVAC, origin={"ext"}), ItemNames.REAPER_PROGRESSIVE_STIMPACK: - ItemData(250 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 10, SC2Race.TERRAN, + ItemData(250 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 10, SC2Race.TERRAN, parent_item=ItemNames.REAPER, quantity=2, origin={"nco"}), ItemNames.REAPER_LASER_TARGETING_SYSTEM: - ItemData(251 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 17, SC2Race.TERRAN, + ItemData(251 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 17, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"nco"}), ItemNames.REAPER_ADVANCED_CLOAKING_FIELD: - ItemData(252 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 18, SC2Race.TERRAN, + ItemData(252 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 18, SC2Race.TERRAN, parent_item=ItemNames.REAPER, origin={"nco"}), ItemNames.REAPER_SPIDER_MINES: - ItemData(253 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 19, SC2Race.TERRAN, + ItemData(253 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 19, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"nco"}, important_for_filtering=True), ItemNames.REAPER_COMBAT_DRUGS: - ItemData(254 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 20, SC2Race.TERRAN, + ItemData(254 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 20, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"ext"}), ItemNames.HELLION_HELLBAT_ASPECT: - ItemData(255 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 21, SC2Race.TERRAN, + ItemData(255 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 21, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.HELLION, origin={"nco"}), ItemNames.HELLION_SMART_SERVOS: - ItemData(256 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 22, SC2Race.TERRAN, + ItemData(256 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 22, SC2Race.TERRAN, parent_item=ItemNames.HELLION, origin={"nco"}), ItemNames.HELLION_OPTIMIZED_LOGISTICS: - ItemData(257 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 23, SC2Race.TERRAN, + ItemData(257 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 23, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.HELLION, origin={"nco"}), ItemNames.HELLION_JUMP_JETS: - ItemData(258 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 24, SC2Race.TERRAN, + ItemData(258 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 24, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.HELLION, origin={"nco"}), ItemNames.HELLION_PROGRESSIVE_STIMPACK: - ItemData(259 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 12, SC2Race.TERRAN, + ItemData(259 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 12, SC2Race.TERRAN, parent_item=ItemNames.HELLION, quantity=2, origin={"nco"}), ItemNames.VULTURE_ION_THRUSTERS: - ItemData(260 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 25, SC2Race.TERRAN, + ItemData(260 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 25, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.VULTURE, origin={"bw"}), ItemNames.VULTURE_AUTO_LAUNCHERS: - ItemData(261 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 26, SC2Race.TERRAN, + ItemData(261 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 26, SC2Race.TERRAN, parent_item=ItemNames.VULTURE, origin={"bw"}), ItemNames.SPIDER_MINE_HIGH_EXPLOSIVE_MUNITION: - ItemData(262 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 27, SC2Race.TERRAN, + ItemData(262 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 27, SC2Race.TERRAN, origin={"bw"}), ItemNames.GOLIATH_JUMP_JETS: - ItemData(263 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 28, SC2Race.TERRAN, + ItemData(263 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 28, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.GOLIATH, origin={"nco"}), ItemNames.GOLIATH_OPTIMIZED_LOGISTICS: - ItemData(264 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 29, SC2Race.TERRAN, + ItemData(264 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 29, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco"}), ItemNames.DIAMONDBACK_HYPERFLUXOR: - ItemData(265 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 0, SC2Race.TERRAN, + ItemData(265 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 0, SC2Race.TERRAN, parent_item=ItemNames.DIAMONDBACK, origin={"ext"}), ItemNames.DIAMONDBACK_BURST_CAPACITORS: - ItemData(266 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 1, SC2Race.TERRAN, + ItemData(266 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 1, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.DIAMONDBACK, origin={"ext"}), ItemNames.DIAMONDBACK_RESOURCE_EFFICIENCY: - ItemData(267 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 2, SC2Race.TERRAN, + ItemData(267 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 2, SC2Race.TERRAN, parent_item=ItemNames.DIAMONDBACK, origin={"ext"}), ItemNames.SIEGE_TANK_JUMP_JETS: - ItemData(268 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 3, SC2Race.TERRAN, + ItemData(268 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 3, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}), ItemNames.SIEGE_TANK_SPIDER_MINES: - ItemData(269 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 4, SC2Race.TERRAN, + ItemData(269 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 4, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, important_for_filtering=True), ItemNames.SIEGE_TANK_SMART_SERVOS: - ItemData(270 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 5, SC2Race.TERRAN, + ItemData(270 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 5, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}), ItemNames.SIEGE_TANK_GRADUATING_RANGE: - ItemData(271 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 6, SC2Race.TERRAN, + ItemData(271 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 6, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK, origin={"ext"}), ItemNames.SIEGE_TANK_LASER_TARGETING_SYSTEM: - ItemData(272 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 7, SC2Race.TERRAN, + ItemData(272 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 7, SC2Race.TERRAN, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}), ItemNames.SIEGE_TANK_ADVANCED_SIEGE_TECH: - ItemData(273 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 8, SC2Race.TERRAN, + ItemData(273 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 8, SC2Race.TERRAN, parent_item=ItemNames.SIEGE_TANK, origin={"ext"}), ItemNames.SIEGE_TANK_INTERNAL_TECH_MODULE: - ItemData(274 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 9, SC2Race.TERRAN, + ItemData(274 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 9, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}), ItemNames.PREDATOR_RESOURCE_EFFICIENCY: - ItemData(275 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 10, SC2Race.TERRAN, + ItemData(275 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 10, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}), ItemNames.MEDIVAC_EXPANDED_HULL: - ItemData(276 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 11, SC2Race.TERRAN, + ItemData(276 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 11, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, origin={"ext"}), ItemNames.MEDIVAC_AFTERBURNERS: - ItemData(277 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 12, SC2Race.TERRAN, + ItemData(277 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 12, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, origin={"ext"}), ItemNames.WRAITH_ADVANCED_LASER_TECHNOLOGY: - ItemData(278 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 13, SC2Race.TERRAN, + ItemData(278 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 13, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.WRAITH, origin={"ext"}), ItemNames.VIKING_SMART_SERVOS: - ItemData(279 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 14, SC2Race.TERRAN, + ItemData(279 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 14, SC2Race.TERRAN, parent_item=ItemNames.VIKING, origin={"ext"}), ItemNames.VIKING_ANTI_MECHANICAL_MUNITION: - ItemData(280 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 15, SC2Race.TERRAN, + ItemData(280 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 15, SC2Race.TERRAN, parent_item=ItemNames.VIKING, origin={"ext"}), ItemNames.DIAMONDBACK_ION_THRUSTERS: - ItemData(281 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 21, SC2Race.TERRAN, + ItemData(281 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 21, SC2Race.TERRAN, parent_item=ItemNames.DIAMONDBACK, origin={"ext"}), ItemNames.WARHOUND_RESOURCE_EFFICIENCY: - ItemData(282 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 13, SC2Race.TERRAN, + ItemData(282 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 13, SC2Race.TERRAN, parent_item=ItemNames.WARHOUND, origin={"ext"}), ItemNames.WARHOUND_REINFORCED_PLATING: - ItemData(283 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 14, SC2Race.TERRAN, + ItemData(283 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 14, SC2Race.TERRAN, parent_item=ItemNames.WARHOUND, origin={"ext"}), ItemNames.HERC_RESOURCE_EFFICIENCY: - ItemData(284 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 15, SC2Race.TERRAN, + ItemData(284 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 15, SC2Race.TERRAN, parent_item=ItemNames.HERC, origin={"ext"}), ItemNames.HERC_JUGGERNAUT_PLATING: - ItemData(285 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 16, SC2Race.TERRAN, + ItemData(285 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 16, SC2Race.TERRAN, parent_item=ItemNames.HERC, origin={"ext"}), ItemNames.HERC_KINETIC_FOAM: - ItemData(286 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 17, SC2Race.TERRAN, + ItemData(286 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 17, SC2Race.TERRAN, parent_item=ItemNames.HERC, origin={"ext"}), ItemNames.HELLION_TWIN_LINKED_FLAMETHROWER: - ItemData(300 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 16, SC2Race.TERRAN, + ItemData(300 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 16, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.HELLION), ItemNames.HELLION_THERMITE_FILAMENTS: - ItemData(301 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 17, SC2Race.TERRAN, + ItemData(301 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 17, SC2Race.TERRAN, parent_item=ItemNames.HELLION), ItemNames.SPIDER_MINE_CERBERUS_MINE: - ItemData(302 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 18, SC2Race.TERRAN, + ItemData(302 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 18, SC2Race.TERRAN, classification=ItemClassification.filler), ItemNames.VULTURE_PROGRESSIVE_REPLENISHABLE_MAGAZINE: - ItemData(303 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 16, SC2Race.TERRAN, + ItemData(303 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 16, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.VULTURE, quantity=2), ItemNames.GOLIATH_MULTI_LOCK_WEAPONS_SYSTEM: - ItemData(304 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 19, SC2Race.TERRAN, + ItemData(304 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 19, SC2Race.TERRAN, parent_item=ItemNames.GOLIATH), ItemNames.GOLIATH_ARES_CLASS_TARGETING_SYSTEM: - ItemData(305 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 20, SC2Race.TERRAN, + ItemData(305 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 20, SC2Race.TERRAN, parent_item=ItemNames.GOLIATH), ItemNames.DIAMONDBACK_PROGRESSIVE_TRI_LITHIUM_POWER_CELL: - ItemData(306 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade 2", 4, SC2Race.TERRAN, + ItemData(306 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive_2, 4, SC2Race.TERRAN, parent_item=ItemNames.DIAMONDBACK, quantity=2), ItemNames.DIAMONDBACK_SHAPED_HULL: - ItemData(307 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 22, SC2Race.TERRAN, + ItemData(307 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 22, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.DIAMONDBACK), ItemNames.SIEGE_TANK_MAELSTROM_ROUNDS: - ItemData(308 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 23, SC2Race.TERRAN, + ItemData(308 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 23, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK), ItemNames.SIEGE_TANK_SHAPED_BLAST: - ItemData(309 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 24, SC2Race.TERRAN, + ItemData(309 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 24, SC2Race.TERRAN, parent_item=ItemNames.SIEGE_TANK), ItemNames.MEDIVAC_RAPID_DEPLOYMENT_TUBE: - ItemData(310 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 25, SC2Race.TERRAN, + ItemData(310 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 25, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC), ItemNames.MEDIVAC_ADVANCED_HEALING_AI: - ItemData(311 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 26, SC2Race.TERRAN, + ItemData(311 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 26, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC), ItemNames.WRAITH_PROGRESSIVE_TOMAHAWK_POWER_CELLS: - ItemData(312 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 18, SC2Race.TERRAN, + ItemData(312 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 18, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.WRAITH, quantity=2), ItemNames.WRAITH_DISPLACEMENT_FIELD: - ItemData(313 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 27, SC2Race.TERRAN, + ItemData(313 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 27, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.WRAITH), ItemNames.VIKING_RIPWAVE_MISSILES: - ItemData(314 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 28, SC2Race.TERRAN, + ItemData(314 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 28, SC2Race.TERRAN, parent_item=ItemNames.VIKING), ItemNames.VIKING_PHOBOS_CLASS_WEAPONS_SYSTEM: - ItemData(315 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 29, SC2Race.TERRAN, + ItemData(315 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 29, SC2Race.TERRAN, parent_item=ItemNames.VIKING), ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS: - ItemData(316 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 2, SC2Race.TERRAN, + ItemData(316 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 2, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, quantity=2), ItemNames.BANSHEE_SHOCKWAVE_MISSILE_BATTERY: - ItemData(317 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 0, SC2Race.TERRAN, + ItemData(317 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 0, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.BANSHEE), ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS: - ItemData(318 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade 2", 2, SC2Race.TERRAN, + ItemData(318 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive_2, 2, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, quantity=2), ItemNames.BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX: - ItemData(319 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 20, SC2Race.TERRAN, + ItemData(319 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 20, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, quantity=2), ItemNames.GHOST_OCULAR_IMPLANTS: - ItemData(320 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 2, SC2Race.TERRAN, + ItemData(320 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 2, SC2Race.TERRAN, parent_item=ItemNames.GHOST), ItemNames.GHOST_CRIUS_SUIT: - ItemData(321 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 3, SC2Race.TERRAN, + ItemData(321 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 3, SC2Race.TERRAN, parent_item=ItemNames.GHOST), ItemNames.SPECTRE_PSIONIC_LASH: - ItemData(322 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 4, SC2Race.TERRAN, + ItemData(322 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 4, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.SPECTRE), ItemNames.SPECTRE_NYX_CLASS_CLOAKING_MODULE: - ItemData(323 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 5, SC2Race.TERRAN, + ItemData(323 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 5, SC2Race.TERRAN, parent_item=ItemNames.SPECTRE), ItemNames.THOR_330MM_BARRAGE_CANNON: - ItemData(324 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 6, SC2Race.TERRAN, + ItemData(324 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 6, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.THOR), ItemNames.THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL: - ItemData(325 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 22, SC2Race.TERRAN, + ItemData(325 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 22, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.THOR, quantity=2), ItemNames.LIBERATOR_ADVANCED_BALLISTICS: - ItemData(326 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 7, SC2Race.TERRAN, + ItemData(326 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 7, SC2Race.TERRAN, parent_item=ItemNames.LIBERATOR, origin={"ext"}), ItemNames.LIBERATOR_RAID_ARTILLERY: - ItemData(327 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 8, SC2Race.TERRAN, + ItemData(327 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 8, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.LIBERATOR, origin={"nco"}), ItemNames.WIDOW_MINE_DRILLING_CLAWS: - ItemData(328 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 9, SC2Race.TERRAN, + ItemData(328 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 9, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}), ItemNames.WIDOW_MINE_CONCEALMENT: - ItemData(329 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 10, SC2Race.TERRAN, + ItemData(329 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 10, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}), ItemNames.MEDIVAC_ADVANCED_CLOAKING_FIELD: - ItemData(330 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 11, SC2Race.TERRAN, + ItemData(330 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 11, SC2Race.TERRAN, parent_item=ItemNames.MEDIVAC, origin={"ext"}), ItemNames.WRAITH_TRIGGER_OVERRIDE: - ItemData(331 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 12, SC2Race.TERRAN, + ItemData(331 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 12, SC2Race.TERRAN, parent_item=ItemNames.WRAITH, origin={"ext"}), ItemNames.WRAITH_INTERNAL_TECH_MODULE: - ItemData(332 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 13, SC2Race.TERRAN, + ItemData(332 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 13, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.WRAITH, origin={"bw"}), ItemNames.WRAITH_RESOURCE_EFFICIENCY: - ItemData(333 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 14, SC2Race.TERRAN, + ItemData(333 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 14, SC2Race.TERRAN, parent_item=ItemNames.WRAITH, origin={"bw"}), ItemNames.VIKING_SHREDDER_ROUNDS: - ItemData(334 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 15, SC2Race.TERRAN, + ItemData(334 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 15, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.VIKING, origin={"ext"}), ItemNames.VIKING_WILD_MISSILES: - ItemData(335 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 16, SC2Race.TERRAN, + ItemData(335 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 16, SC2Race.TERRAN, parent_item=ItemNames.VIKING, origin={"ext"}), ItemNames.BANSHEE_SHAPED_HULL: - ItemData(336 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 17, SC2Race.TERRAN, + ItemData(336 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 17, SC2Race.TERRAN, parent_item=ItemNames.BANSHEE, origin={"ext"}), ItemNames.BANSHEE_ADVANCED_TARGETING_OPTICS: - ItemData(337 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 18, SC2Race.TERRAN, + ItemData(337 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 18, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.BANSHEE, origin={"ext"}), ItemNames.BANSHEE_DISTORTION_BLASTERS: - ItemData(338 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 19, SC2Race.TERRAN, + ItemData(338 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 19, SC2Race.TERRAN, parent_item=ItemNames.BANSHEE, origin={"ext"}), ItemNames.BANSHEE_ROCKET_BARRAGE: - ItemData(339 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 20, SC2Race.TERRAN, + ItemData(339 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 20, SC2Race.TERRAN, parent_item=ItemNames.BANSHEE, origin={"ext"}), ItemNames.GHOST_RESOURCE_EFFICIENCY: - ItemData(340 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 21, SC2Race.TERRAN, + ItemData(340 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 21, SC2Race.TERRAN, parent_item=ItemNames.GHOST, origin={"bw"}), ItemNames.SPECTRE_RESOURCE_EFFICIENCY: - ItemData(341 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 22, SC2Race.TERRAN, + ItemData(341 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 22, SC2Race.TERRAN, parent_item=ItemNames.SPECTRE, origin={"ext"}), ItemNames.THOR_BUTTON_WITH_A_SKULL_ON_IT: - ItemData(342 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 23, SC2Race.TERRAN, + ItemData(342 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 23, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.THOR, origin={"ext"}), ItemNames.THOR_LASER_TARGETING_SYSTEM: - ItemData(343 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 24, SC2Race.TERRAN, + ItemData(343 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 24, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.THOR, origin={"ext"}), ItemNames.THOR_LARGE_SCALE_FIELD_CONSTRUCTION: - ItemData(344 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 25, SC2Race.TERRAN, + ItemData(344 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 25, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.THOR, origin={"ext"}), ItemNames.RAVEN_RESOURCE_EFFICIENCY: - ItemData(345 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 26, SC2Race.TERRAN, + ItemData(345 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 26, SC2Race.TERRAN, parent_item=ItemNames.RAVEN, origin={"ext"}), ItemNames.RAVEN_DURABLE_MATERIALS: - ItemData(346 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 27, SC2Race.TERRAN, + ItemData(346 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 27, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"ext"}), ItemNames.SCIENCE_VESSEL_IMPROVED_NANO_REPAIR: - ItemData(347 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 28, SC2Race.TERRAN, + ItemData(347 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 28, SC2Race.TERRAN, parent_item=ItemNames.SCIENCE_VESSEL, origin={"ext"}), ItemNames.SCIENCE_VESSEL_ADVANCED_AI_SYSTEMS: - ItemData(348 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 29, SC2Race.TERRAN, + ItemData(348 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 29, SC2Race.TERRAN, parent_item=ItemNames.SCIENCE_VESSEL, origin={"ext"}), ItemNames.CYCLONE_RESOURCE_EFFICIENCY: - ItemData(349 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 0, SC2Race.TERRAN, + ItemData(349 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 0, SC2Race.TERRAN, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.BANSHEE_HYPERFLIGHT_ROTORS: - ItemData(350 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 1, SC2Race.TERRAN, + ItemData(350 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 1, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"ext"}), ItemNames.BANSHEE_LASER_TARGETING_SYSTEM: - ItemData(351 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 2, SC2Race.TERRAN, + ItemData(351 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 2, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"nco"}), ItemNames.BANSHEE_INTERNAL_TECH_MODULE: - ItemData(352 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 3, SC2Race.TERRAN, + ItemData(352 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 3, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"nco"}), ItemNames.BATTLECRUISER_TACTICAL_JUMP: - ItemData(353 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 4, SC2Race.TERRAN, + ItemData(353 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 4, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, origin={"nco", "ext"}), ItemNames.BATTLECRUISER_CLOAK: - ItemData(354 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 5, SC2Race.TERRAN, + ItemData(354 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 5, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}), ItemNames.BATTLECRUISER_ATX_LASER_BATTERY: - ItemData(355 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 6, SC2Race.TERRAN, + ItemData(355 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 6, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}), ItemNames.BATTLECRUISER_OPTIMIZED_LOGISTICS: - ItemData(356 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 7, SC2Race.TERRAN, + ItemData(356 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 7, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BATTLECRUISER, origin={"ext"}), ItemNames.BATTLECRUISER_INTERNAL_TECH_MODULE: - ItemData(357 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 8, SC2Race.TERRAN, + ItemData(357 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 8, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}), ItemNames.GHOST_EMP_ROUNDS: - ItemData(358 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 9, SC2Race.TERRAN, + ItemData(358 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 9, SC2Race.TERRAN, parent_item=ItemNames.GHOST, origin={"ext"}), ItemNames.GHOST_LOCKDOWN: - ItemData(359 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 10, SC2Race.TERRAN, + ItemData(359 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 10, SC2Race.TERRAN, parent_item=ItemNames.GHOST, origin={"bw"}), ItemNames.SPECTRE_IMPALER_ROUNDS: - ItemData(360 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 11, SC2Race.TERRAN, + ItemData(360 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 11, SC2Race.TERRAN, parent_item=ItemNames.SPECTRE, origin={"ext"}), ItemNames.THOR_PROGRESSIVE_HIGH_IMPACT_PAYLOAD: - ItemData(361 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 14, SC2Race.TERRAN, + ItemData(361 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 14, SC2Race.TERRAN, parent_item=ItemNames.THOR, quantity=2, origin={"ext"}), ItemNames.RAVEN_BIO_MECHANICAL_REPAIR_DRONE: - ItemData(363 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 12, SC2Race.TERRAN, + ItemData(363 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 12, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.RAVEN, origin={"nco"}), ItemNames.RAVEN_SPIDER_MINES: - ItemData(364 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 13, SC2Race.TERRAN, + ItemData(364 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 13, SC2Race.TERRAN, parent_item=ItemNames.RAVEN, origin={"nco"}, important_for_filtering=True), ItemNames.RAVEN_RAILGUN_TURRET: - ItemData(365 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 14, SC2Race.TERRAN, + ItemData(365 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 14, SC2Race.TERRAN, parent_item=ItemNames.RAVEN, origin={"nco"}), ItemNames.RAVEN_HUNTER_SEEKER_WEAPON: - ItemData(366 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 15, SC2Race.TERRAN, + ItemData(366 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 15, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.RAVEN, origin={"nco"}), ItemNames.RAVEN_INTERFERENCE_MATRIX: - ItemData(367 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 16, SC2Race.TERRAN, + ItemData(367 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 16, SC2Race.TERRAN, parent_item=ItemNames.RAVEN, origin={"ext"}), ItemNames.RAVEN_ANTI_ARMOR_MISSILE: - ItemData(368 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 17, SC2Race.TERRAN, + ItemData(368 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 17, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"ext"}), ItemNames.RAVEN_INTERNAL_TECH_MODULE: - ItemData(369 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 18, SC2Race.TERRAN, + ItemData(369 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 18, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"nco"}), ItemNames.SCIENCE_VESSEL_EMP_SHOCKWAVE: - ItemData(370 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 19, SC2Race.TERRAN, + ItemData(370 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 19, SC2Race.TERRAN, parent_item=ItemNames.SCIENCE_VESSEL, origin={"bw"}), ItemNames.SCIENCE_VESSEL_DEFENSIVE_MATRIX: - ItemData(371 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 20, SC2Race.TERRAN, + ItemData(371 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 20, SC2Race.TERRAN, parent_item=ItemNames.SCIENCE_VESSEL, origin={"bw"}), ItemNames.CYCLONE_TARGETING_OPTICS: - ItemData(372 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 21, SC2Race.TERRAN, + ItemData(372 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 21, SC2Race.TERRAN, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.CYCLONE_RAPID_FIRE_LAUNCHERS: - ItemData(373 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 22, SC2Race.TERRAN, + ItemData(373 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 22, SC2Race.TERRAN, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.LIBERATOR_CLOAK: - ItemData(374 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 23, SC2Race.TERRAN, + ItemData(374 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 23, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}), ItemNames.LIBERATOR_LASER_TARGETING_SYSTEM: - ItemData(375 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 24, SC2Race.TERRAN, + ItemData(375 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 24, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"ext"}), ItemNames.LIBERATOR_OPTIMIZED_LOGISTICS: - ItemData(376 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 25, SC2Race.TERRAN, + ItemData(376 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 25, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}), ItemNames.WIDOW_MINE_BLACK_MARKET_LAUNCHERS: - ItemData(377 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 26, SC2Race.TERRAN, + ItemData(377 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 26, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}), ItemNames.WIDOW_MINE_EXECUTIONER_MISSILES: - ItemData(378 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 27, SC2Race.TERRAN, + ItemData(378 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 27, SC2Race.TERRAN, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}), ItemNames.VALKYRIE_ENHANCED_CLUSTER_LAUNCHERS: - ItemData(379 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 28, + ItemData(379 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 28, SC2Race.TERRAN, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.VALKYRIE_SHAPED_HULL: - ItemData(380 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 29, SC2Race.TERRAN, + ItemData(380 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 29, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.VALKYRIE_FLECHETTE_MISSILES: - ItemData(381 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 0, SC2Race.TERRAN, + ItemData(381 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 0, SC2Race.TERRAN, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.VALKYRIE_AFTERBURNERS: - ItemData(382 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 1, SC2Race.TERRAN, + ItemData(382 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 1, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.CYCLONE_INTERNAL_TECH_MODULE: - ItemData(383 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 2, SC2Race.TERRAN, + ItemData(383 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 2, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.LIBERATOR_SMART_SERVOS: - ItemData(384 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 3, SC2Race.TERRAN, + ItemData(384 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 3, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}), ItemNames.LIBERATOR_RESOURCE_EFFICIENCY: - ItemData(385 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 4, SC2Race.TERRAN, + ItemData(385 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 4, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"ext"}), ItemNames.HERCULES_INTERNAL_FUSION_MODULE: - ItemData(386 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 5, SC2Race.TERRAN, + ItemData(386 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 5, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.HERCULES, origin={"ext"}), ItemNames.HERCULES_TACTICAL_JUMP: - ItemData(387 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 6, SC2Race.TERRAN, + ItemData(387 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 6, SC2Race.TERRAN, parent_item=ItemNames.HERCULES, origin={"ext"}), ItemNames.PLANETARY_FORTRESS_PROGRESSIVE_AUGMENTED_THRUSTERS: - ItemData(388 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 28, SC2Race.TERRAN, + ItemData(388 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 28, SC2Race.TERRAN, parent_item=ItemNames.PLANETARY_FORTRESS, origin={"ext"}, quantity=2), ItemNames.PLANETARY_FORTRESS_ADVANCED_TARGETING: - ItemData(389 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 7, SC2Race.TERRAN, + ItemData(389 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 7, SC2Race.TERRAN, parent_item=ItemNames.PLANETARY_FORTRESS, origin={"ext"}), ItemNames.VALKYRIE_LAUNCHING_VECTOR_COMPENSATOR: - ItemData(390 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 8, SC2Race.TERRAN, + ItemData(390 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 8, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.VALKYRIE_RESOURCE_EFFICIENCY: - ItemData(391 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 9, SC2Race.TERRAN, + ItemData(391 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 9, SC2Race.TERRAN, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.PREDATOR_PREDATOR_S_FURY: - ItemData(392 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 10, SC2Race.TERRAN, + ItemData(392 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 10, SC2Race.TERRAN, parent_item=ItemNames.PREDATOR, origin={"ext"}), ItemNames.BATTLECRUISER_BEHEMOTH_PLATING: - ItemData(393 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 11, SC2Race.TERRAN, + ItemData(393 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 11, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, origin={"ext"}), ItemNames.BATTLECRUISER_COVERT_OPS_ENGINES: - ItemData(394 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 12, SC2Race.TERRAN, + ItemData(394 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 12, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}), #Buildings ItemNames.BUNKER: - ItemData(400 + SC2WOL_ITEM_ID_OFFSET, "Building", 0, SC2Race.TERRAN, + ItemData(400 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 0, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.MISSILE_TURRET: - ItemData(401 + SC2WOL_ITEM_ID_OFFSET, "Building", 1, SC2Race.TERRAN, + ItemData(401 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 1, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.SENSOR_TOWER: - ItemData(402 + SC2WOL_ITEM_ID_OFFSET, "Building", 2, SC2Race.TERRAN), + ItemData(402 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 2, SC2Race.TERRAN), ItemNames.WAR_PIGS: - ItemData(500 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 0, SC2Race.TERRAN, + ItemData(500 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 0, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.DEVIL_DOGS: - ItemData(501 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 1, SC2Race.TERRAN, + ItemData(501 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 1, SC2Race.TERRAN, classification=ItemClassification.filler), ItemNames.HAMMER_SECURITIES: - ItemData(502 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 2, SC2Race.TERRAN), + ItemData(502 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 2, SC2Race.TERRAN), ItemNames.SPARTAN_COMPANY: - ItemData(503 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 3, SC2Race.TERRAN, + ItemData(503 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 3, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.SIEGE_BREAKERS: - ItemData(504 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 4, SC2Race.TERRAN), + ItemData(504 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 4, SC2Race.TERRAN), ItemNames.HELS_ANGELS: - ItemData(505 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 5, SC2Race.TERRAN, + ItemData(505 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 5, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.DUSK_WINGS: - ItemData(506 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 6, SC2Race.TERRAN), + ItemData(506 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 6, SC2Race.TERRAN), ItemNames.JACKSONS_REVENGE: - ItemData(507 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 7, SC2Race.TERRAN), + ItemData(507 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 7, SC2Race.TERRAN), ItemNames.SKIBIS_ANGELS: - ItemData(508 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 8, SC2Race.TERRAN, + ItemData(508 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 8, SC2Race.TERRAN, origin={"ext"}), ItemNames.DEATH_HEADS: - ItemData(509 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 9, SC2Race.TERRAN, + ItemData(509 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 9, SC2Race.TERRAN, origin={"ext"}), ItemNames.WINGED_NIGHTMARES: - ItemData(510 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 10, SC2Race.TERRAN, + ItemData(510 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 10, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), ItemNames.MIDNIGHT_RIDERS: - ItemData(511 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 11, SC2Race.TERRAN, + ItemData(511 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 11, SC2Race.TERRAN, origin={"ext"}), ItemNames.BRYNHILDS: - ItemData(512 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 12, SC2Race.TERRAN, + ItemData(512 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 12, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), ItemNames.JOTUN: - ItemData(513 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 13, SC2Race.TERRAN, + ItemData(513 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 13, SC2Race.TERRAN, origin={"ext"}), ItemNames.ULTRA_CAPACITORS: - ItemData(600 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 0, SC2Race.TERRAN), + ItemData(600 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 0, SC2Race.TERRAN), ItemNames.VANADIUM_PLATING: - ItemData(601 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 1, SC2Race.TERRAN), + ItemData(601 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 1, SC2Race.TERRAN), ItemNames.ORBITAL_DEPOTS: - ItemData(602 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 2, SC2Race.TERRAN), + ItemData(602 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 2, SC2Race.TERRAN), ItemNames.MICRO_FILTERING: - ItemData(603 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 3, SC2Race.TERRAN), + ItemData(603 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 3, SC2Race.TERRAN), ItemNames.AUTOMATED_REFINERY: - ItemData(604 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 4, SC2Race.TERRAN), + ItemData(604 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 4, SC2Race.TERRAN), ItemNames.COMMAND_CENTER_REACTOR: - ItemData(605 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 5, SC2Race.TERRAN), + ItemData(605 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 5, SC2Race.TERRAN), ItemNames.RAVEN: - ItemData(606 + SC2WOL_ITEM_ID_OFFSET, "Unit", 22, SC2Race.TERRAN, + ItemData(606 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 22, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.SCIENCE_VESSEL: - ItemData(607 + SC2WOL_ITEM_ID_OFFSET, "Unit", 23, SC2Race.TERRAN, + ItemData(607 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 23, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.TECH_REACTOR: - ItemData(608 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 6, SC2Race.TERRAN), + ItemData(608 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 6, SC2Race.TERRAN), ItemNames.ORBITAL_STRIKE: - ItemData(609 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 7, SC2Race.TERRAN), + ItemData(609 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 7, SC2Race.TERRAN), ItemNames.BUNKER_SHRIKE_TURRET: - ItemData(610 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 6, SC2Race.TERRAN, + ItemData(610 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 6, SC2Race.TERRAN, parent_item=ItemNames.BUNKER), ItemNames.BUNKER_FORTIFIED_BUNKER: - ItemData(611 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 7, SC2Race.TERRAN, + ItemData(611 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 7, SC2Race.TERRAN, parent_item=ItemNames.BUNKER), ItemNames.PLANETARY_FORTRESS: - ItemData(612 + SC2WOL_ITEM_ID_OFFSET, "Building", 3, SC2Race.TERRAN, + ItemData(612 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 3, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.PERDITION_TURRET: - ItemData(613 + SC2WOL_ITEM_ID_OFFSET, "Building", 4, SC2Race.TERRAN, + ItemData(613 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 4, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.PREDATOR: - ItemData(614 + SC2WOL_ITEM_ID_OFFSET, "Unit", 24, SC2Race.TERRAN, + ItemData(614 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 24, SC2Race.TERRAN, classification=ItemClassification.filler), ItemNames.HERCULES: - ItemData(615 + SC2WOL_ITEM_ID_OFFSET, "Unit", 25, SC2Race.TERRAN, + ItemData(615 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 25, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.CELLULAR_REACTOR: - ItemData(616 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 8, SC2Race.TERRAN), + ItemData(616 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 8, SC2Race.TERRAN), ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: - ItemData(617 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 4, SC2Race.TERRAN, quantity=3, + ItemData(617 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 4, SC2Race.TERRAN, quantity=3, classification= ItemClassification.progression), ItemNames.HIVE_MIND_EMULATOR: - ItemData(618 + SC2WOL_ITEM_ID_OFFSET, "Building", 5, SC2Race.TERRAN, + ItemData(618 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 5, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.PSI_DISRUPTER: - ItemData(619 + SC2WOL_ITEM_ID_OFFSET, "Building", 6, SC2Race.TERRAN, + ItemData(619 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 6, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.STRUCTURE_ARMOR: - ItemData(620 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 9, SC2Race.TERRAN), + ItemData(620 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 9, SC2Race.TERRAN), ItemNames.HI_SEC_AUTO_TRACKING: - ItemData(621 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 10, SC2Race.TERRAN), + ItemData(621 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 10, SC2Race.TERRAN), ItemNames.ADVANCED_OPTICS: - ItemData(622 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 11, SC2Race.TERRAN), + ItemData(622 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 11, SC2Race.TERRAN), ItemNames.ROGUE_FORCES: - ItemData(623 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 12, SC2Race.TERRAN, origin={"ext"}), + ItemData(623 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 12, SC2Race.TERRAN, origin={"ext"}), ItemNames.ZEALOT: - ItemData(700 + SC2WOL_ITEM_ID_OFFSET, "Unit", 0, SC2Race.PROTOSS, + ItemData(700 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 0, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.STALKER: - ItemData(701 + SC2WOL_ITEM_ID_OFFSET, "Unit", 1, SC2Race.PROTOSS, + ItemData(701 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 1, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.HIGH_TEMPLAR: - ItemData(702 + SC2WOL_ITEM_ID_OFFSET, "Unit", 2, SC2Race.PROTOSS, + ItemData(702 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 2, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.DARK_TEMPLAR: - ItemData(703 + SC2WOL_ITEM_ID_OFFSET, "Unit", 3, SC2Race.PROTOSS, + ItemData(703 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 3, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.IMMORTAL: - ItemData(704 + SC2WOL_ITEM_ID_OFFSET, "Unit", 4, SC2Race.PROTOSS, + ItemData(704 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 4, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.COLOSSUS: - ItemData(705 + SC2WOL_ITEM_ID_OFFSET, "Unit", 5, SC2Race.PROTOSS, + ItemData(705 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 5, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.PHOENIX: - ItemData(706 + SC2WOL_ITEM_ID_OFFSET, "Unit", 6, SC2Race.PROTOSS, + ItemData(706 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 6, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.VOID_RAY: - ItemData(707 + SC2WOL_ITEM_ID_OFFSET, "Unit", 7, SC2Race.PROTOSS, + ItemData(707 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 7, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.CARRIER: - ItemData(708 + SC2WOL_ITEM_ID_OFFSET, "Unit", 8, SC2Race.PROTOSS, + ItemData(708 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 8, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), # Filler items to fill remaining spots ItemNames.STARTING_MINERALS: - ItemData(800 + SC2WOL_ITEM_ID_OFFSET, "Minerals", 15, SC2Race.ANY, quantity=0, + ItemData(800 + SC2WOL_ITEM_ID_OFFSET, ItemType.Minerals, 15, SC2Race.ANY, quantity=0, classification=ItemClassification.filler), ItemNames.STARTING_VESPENE: - ItemData(801 + SC2WOL_ITEM_ID_OFFSET, "Vespene", 15, SC2Race.ANY, quantity=0, + ItemData(801 + SC2WOL_ITEM_ID_OFFSET, ItemType.Vespene, 15, SC2Race.ANY, quantity=0, classification=ItemClassification.filler), ItemNames.STARTING_SUPPLY: - ItemData(802 + SC2WOL_ITEM_ID_OFFSET, "Supply", 2, SC2Race.ANY, quantity=0, + ItemData(802 + SC2WOL_ITEM_ID_OFFSET, ItemType.Supply, 2, SC2Race.ANY, quantity=0, classification=ItemClassification.filler), # This item is used to "remove" location from the game. Never placed unless plando'd ItemNames.NOTHING: - ItemData(803 + SC2WOL_ITEM_ID_OFFSET, "Nothing Group", 2, SC2Race.ANY, quantity=0, + ItemData(803 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nothing, 2, SC2Race.ANY, quantity=0, classification=ItemClassification.trap), # Nova gear ItemNames.NOVA_GHOST_VISOR: - ItemData(900 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 0, SC2Race.TERRAN, origin={"nco"}), + ItemData(900 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 0, SC2Race.TERRAN, origin={"nco"}), ItemNames.NOVA_RANGEFINDER_OCULUS: - ItemData(901 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 1, SC2Race.TERRAN, origin={"nco"}), + ItemData(901 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 1, SC2Race.TERRAN, origin={"nco"}), ItemNames.NOVA_DOMINATION: - ItemData(902 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 2, SC2Race.TERRAN, origin={"nco"}, + ItemData(902 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 2, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_BLINK: - ItemData(903 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 3, SC2Race.TERRAN, origin={"nco"}, + ItemData(903 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 3, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE: - ItemData(904 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade 2", 0, SC2Race.TERRAN, quantity=2, origin={"nco"}, + ItemData(904 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive_2, 0, SC2Race.TERRAN, quantity=2, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_ENERGY_SUIT_MODULE: - ItemData(905 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 4, SC2Race.TERRAN, origin={"nco"}), + ItemData(905 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 4, SC2Race.TERRAN, origin={"nco"}), ItemNames.NOVA_ARMORED_SUIT_MODULE: - ItemData(906 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 5, SC2Race.TERRAN, origin={"nco"}, + ItemData(906 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 5, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_JUMP_SUIT_MODULE: - ItemData(907 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 6, SC2Race.TERRAN, origin={"nco"}, + ItemData(907 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 6, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_C20A_CANISTER_RIFLE: - ItemData(908 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 7, SC2Race.TERRAN, origin={"nco"}, + ItemData(908 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 7, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_HELLFIRE_SHOTGUN: - ItemData(909 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 8, SC2Race.TERRAN, origin={"nco"}, + ItemData(909 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 8, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_PLASMA_RIFLE: - ItemData(910 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 9, SC2Race.TERRAN, origin={"nco"}, + ItemData(910 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 9, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_MONOMOLECULAR_BLADE: - ItemData(911 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 10, SC2Race.TERRAN, origin={"nco"}, + ItemData(911 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 10, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_BLAZEFIRE_GUNBLADE: - ItemData(912 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 11, SC2Race.TERRAN, origin={"nco"}, + ItemData(912 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 11, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_STIM_INFUSION: - ItemData(913 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 12, SC2Race.TERRAN, origin={"nco"}, + ItemData(913 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 12, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_PULSE_GRENADES: - ItemData(914 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 13, SC2Race.TERRAN, origin={"nco"}, + ItemData(914 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 13, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_FLASHBANG_GRENADES: - ItemData(915 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 14, SC2Race.TERRAN, origin={"nco"}, + ItemData(915 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 14, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_IONIC_FORCE_FIELD: - ItemData(916 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 15, SC2Race.TERRAN, origin={"nco"}, + ItemData(916 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 15, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_HOLO_DECOY: - ItemData(917 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 16, SC2Race.TERRAN, origin={"nco"}, + ItemData(917 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 16, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_NUKE: - ItemData(918 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 17, SC2Race.TERRAN, origin={"nco"}, + ItemData(918 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 17, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), # HotS ItemNames.ZERGLING: - ItemData(0 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 0, SC2Race.ZERG, + ItemData(0 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 0, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.SWARM_QUEEN: - ItemData(1 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 1, SC2Race.ZERG, + ItemData(1 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 1, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.ROACH: - ItemData(2 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 2, SC2Race.ZERG, + ItemData(2 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 2, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.HYDRALISK: - ItemData(3 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 3, SC2Race.ZERG, + ItemData(3 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 3, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.ZERGLING_BANELING_ASPECT: - ItemData(4 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 5, SC2Race.ZERG, + ItemData(4 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 5, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.ABERRATION: - ItemData(5 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 5, SC2Race.ZERG, + ItemData(5 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 5, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.MUTALISK: - ItemData(6 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 6, SC2Race.ZERG, + ItemData(6 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 6, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.SWARM_HOST: - ItemData(7 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 7, SC2Race.ZERG, + ItemData(7 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 7, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.INFESTOR: - ItemData(8 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 8, SC2Race.ZERG, + ItemData(8 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 8, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.ULTRALISK: - ItemData(9 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 9, SC2Race.ZERG, + ItemData(9 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 9, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.SPORE_CRAWLER: - ItemData(10 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 10, SC2Race.ZERG, + ItemData(10 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 10, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.SPINE_CRAWLER: - ItemData(11 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 11, SC2Race.ZERG, + ItemData(11 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 11, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.CORRUPTOR: - ItemData(12 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 12, SC2Race.ZERG, + ItemData(12 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 12, SC2Race.ZERG, classification=ItemClassification.progression, origin={"ext"}), ItemNames.SCOURGE: - ItemData(13 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 13, SC2Race.ZERG, + ItemData(13 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 13, SC2Race.ZERG, classification=ItemClassification.progression, origin={"bw", "ext"}), ItemNames.BROOD_QUEEN: - ItemData(14 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 4, SC2Race.ZERG, + ItemData(14 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 4, SC2Race.ZERG, classification=ItemClassification.progression, origin={"bw", "ext"}), ItemNames.DEFILER: - ItemData(15 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 14, SC2Race.ZERG, + ItemData(15 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 14, SC2Race.ZERG, classification=ItemClassification.progression, origin={"bw"}), - ItemNames.PROGRESSIVE_ZERG_MELEE_ATTACK: ItemData(100 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_MISSILE_ATTACK: ItemData(101 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_GROUND_CARAPACE: ItemData(102 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_FLYER_ATTACK: ItemData(103 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_FLYER_CARAPACE: ItemData(104 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_MELEE_ATTACK: ItemData(100 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 0, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_MISSILE_ATTACK: ItemData(101 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 2, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_GROUND_CARAPACE: ItemData(102 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 4, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_ATTACK: ItemData(103 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 6, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_CARAPACE: ItemData(104 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 8, SC2Race.ZERG, quantity=3, origin={"hots"}), # Upgrade bundle 'number' values are used as indices to get affected 'number's - ItemNames.PROGRESSIVE_ZERG_WEAPON_UPGRADE: ItemData(105 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_ARMOR_UPGRADE: ItemData(106 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 7, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_GROUND_UPGRADE: ItemData(107 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_FLYER_UPGRADE: ItemData(108 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 9, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 10, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_WEAPON_UPGRADE: ItemData(105 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 6, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_ARMOR_UPGRADE: ItemData(106 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 7, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_GROUND_UPGRADE: ItemData(107 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 8, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_UPGRADE: ItemData(108 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 9, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 10, SC2Race.ZERG, quantity=3, origin={"hots"}), ItemNames.ZERGLING_HARDENED_CARAPACE: - ItemData(200 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), + ItemData(200 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), ItemNames.ZERGLING_ADRENAL_OVERLOAD: - ItemData(201 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), + ItemData(201 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), ItemNames.ZERGLING_METABOLIC_BOOST: - ItemData(202 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 2, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + ItemData(202 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 2, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}, classification=ItemClassification.filler), ItemNames.ROACH_HYDRIODIC_BILE: - ItemData(203 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), + ItemData(203 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), ItemNames.ROACH_ADAPTIVE_PLATING: - ItemData(204 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 4, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), + ItemData(204 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 4, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), ItemNames.ROACH_TUNNELING_CLAWS: - ItemData(205 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 5, SC2Race.ZERG, parent_item=ItemNames.ROACH, + ItemData(205 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 5, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}, classification=ItemClassification.filler), ItemNames.HYDRALISK_FRENZY: - ItemData(206 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 6, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"hots"}), + ItemData(206 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 6, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"hots"}), ItemNames.HYDRALISK_ANCILLARY_CARAPACE: - ItemData(207 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 7, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + ItemData(207 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 7, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"hots"}, classification=ItemClassification.filler), ItemNames.HYDRALISK_GROOVED_SPINES: - ItemData(208 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 8, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + ItemData(208 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 8, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"hots"}), ItemNames.BANELING_CORROSIVE_ACID: - ItemData(209 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 9, SC2Race.ZERG, + ItemData(209 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 9, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}), ItemNames.BANELING_RUPTURE: - ItemData(210 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 10, SC2Race.ZERG, + ItemData(210 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 10, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, classification=ItemClassification.filler), ItemNames.BANELING_REGENERATIVE_ACID: - ItemData(211 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 11, SC2Race.ZERG, + ItemData(211 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 11, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, classification=ItemClassification.filler), ItemNames.MUTALISK_VICIOUS_GLAIVE: - ItemData(212 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 12, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + ItemData(212 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 12, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, origin={"hots"}), ItemNames.MUTALISK_RAPID_REGENERATION: - ItemData(213 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 13, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + ItemData(213 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 13, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, origin={"hots"}), ItemNames.MUTALISK_SUNDERING_GLAIVE: - ItemData(214 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 14, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + ItemData(214 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 14, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, origin={"hots"}), ItemNames.SWARM_HOST_BURROW: - ItemData(215 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 15, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(215 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 15, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"hots"}, classification=ItemClassification.filler), ItemNames.SWARM_HOST_RAPID_INCUBATION: - ItemData(216 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 16, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(216 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 16, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"hots"}), ItemNames.SWARM_HOST_PRESSURIZED_GLANDS: - ItemData(217 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 17, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(217 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 17, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"hots"}, classification=ItemClassification.progression), ItemNames.ULTRALISK_BURROW_CHARGE: - ItemData(218 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 18, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(218 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 18, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"hots"}), ItemNames.ULTRALISK_TISSUE_ASSIMILATION: - ItemData(219 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 19, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(219 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 19, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"hots"}), ItemNames.ULTRALISK_MONARCH_BLADES: - ItemData(220 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 20, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(220 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 20, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"hots"}), ItemNames.CORRUPTOR_CAUSTIC_SPRAY: - ItemData(221 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 21, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, + ItemData(221 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 21, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, origin={"ext"}), ItemNames.CORRUPTOR_CORRUPTION: - ItemData(222 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 22, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, + ItemData(222 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 22, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, origin={"ext"}), ItemNames.SCOURGE_VIRULENT_SPORES: - ItemData(223 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 23, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + ItemData(223 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 23, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, origin={"ext"}), ItemNames.SCOURGE_RESOURCE_EFFICIENCY: - ItemData(224 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 24, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + ItemData(224 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 24, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, origin={"ext"}, classification=ItemClassification.progression), ItemNames.SCOURGE_SWARM_SCOURGE: - ItemData(225 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 25, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + ItemData(225 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 25, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, origin={"ext"}), ItemNames.ZERGLING_SHREDDING_CLAWS: - ItemData(226 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 26, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + ItemData(226 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 26, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"ext"}), ItemNames.ROACH_GLIAL_RECONSTITUTION: - ItemData(227 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 27, SC2Race.ZERG, parent_item=ItemNames.ROACH, + ItemData(227 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 27, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"ext"}), ItemNames.ROACH_ORGANIC_CARAPACE: - ItemData(228 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 28, SC2Race.ZERG, parent_item=ItemNames.ROACH, + ItemData(228 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 28, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"ext"}), ItemNames.HYDRALISK_MUSCULAR_AUGMENTS: - ItemData(229 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 29, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + ItemData(229 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 29, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"bw"}), ItemNames.HYDRALISK_RESOURCE_EFFICIENCY: - ItemData(230 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 0, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + ItemData(230 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 0, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"bw"}), ItemNames.BANELING_CENTRIFUGAL_HOOKS: - ItemData(231 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 1, SC2Race.ZERG, + ItemData(231 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}), ItemNames.BANELING_TUNNELING_JAWS: - ItemData(232 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 2, SC2Race.ZERG, + ItemData(232 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 2, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}), ItemNames.BANELING_RAPID_METAMORPH: - ItemData(233 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 3, SC2Race.ZERG, + ItemData(233 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 3, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}), ItemNames.MUTALISK_SEVERING_GLAIVE: - ItemData(234 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 4, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + ItemData(234 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 4, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, origin={"ext"}), ItemNames.MUTALISK_AERODYNAMIC_GLAIVE_SHAPE: - ItemData(235 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 5, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + ItemData(235 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 5, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, origin={"ext"}), ItemNames.SWARM_HOST_LOCUST_METABOLIC_BOOST: - ItemData(236 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 6, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(236 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 6, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"ext"}, classification=ItemClassification.filler), ItemNames.SWARM_HOST_ENDURING_LOCUSTS: - ItemData(237 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 7, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(237 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 7, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"ext"}), ItemNames.SWARM_HOST_ORGANIC_CARAPACE: - ItemData(238 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(238 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"ext"}), ItemNames.SWARM_HOST_RESOURCE_EFFICIENCY: - ItemData(239 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(239 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"ext"}), ItemNames.ULTRALISK_ANABOLIC_SYNTHESIS: - ItemData(240 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 10, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(240 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 10, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"bw"}, classification=ItemClassification.filler), ItemNames.ULTRALISK_CHITINOUS_PLATING: - ItemData(241 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 11, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(241 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 11, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"bw"}), ItemNames.ULTRALISK_ORGANIC_CARAPACE: - ItemData(242 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(242 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"ext"}), ItemNames.ULTRALISK_RESOURCE_EFFICIENCY: - ItemData(243 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(243 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"bw"}), ItemNames.DEVOURER_CORROSIVE_SPRAY: - ItemData(244 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 14, SC2Race.ZERG, + ItemData(244 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 14, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), ItemNames.DEVOURER_GAPING_MAW: - ItemData(245 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 15, SC2Race.ZERG, + ItemData(245 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 15, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), ItemNames.DEVOURER_IMPROVED_OSMOSIS: - ItemData(246 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 16, SC2Race.ZERG, + ItemData(246 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 16, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}, classification=ItemClassification.filler), ItemNames.DEVOURER_PRESCIENT_SPORES: - ItemData(247 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 17, SC2Race.ZERG, + ItemData(247 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 17, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), ItemNames.GUARDIAN_PROLONGED_DISPERSION: - ItemData(248 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 18, SC2Race.ZERG, + ItemData(248 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 18, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), ItemNames.GUARDIAN_PRIMAL_ADAPTATION: - ItemData(249 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 19, SC2Race.ZERG, + ItemData(249 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 19, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), ItemNames.GUARDIAN_SORONAN_ACID: - ItemData(250 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 20, SC2Race.ZERG, + ItemData(250 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 20, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), ItemNames.IMPALER_ADAPTIVE_TALONS: - ItemData(251 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 21, SC2Race.ZERG, + ItemData(251 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 21, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}, classification=ItemClassification.filler), ItemNames.IMPALER_SECRETION_GLANDS: - ItemData(252 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 22, SC2Race.ZERG, + ItemData(252 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 22, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}), ItemNames.IMPALER_HARDENED_TENTACLE_SPINES: - ItemData(253 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 23, SC2Race.ZERG, + ItemData(253 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 23, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}), ItemNames.LURKER_SEISMIC_SPINES: - ItemData(254 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 24, SC2Race.ZERG, + ItemData(254 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 24, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK_LURKER_ASPECT, origin={"ext"}), ItemNames.LURKER_ADAPTED_SPINES: - ItemData(255 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 25, SC2Race.ZERG, + ItemData(255 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 25, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK_LURKER_ASPECT, origin={"ext"}), ItemNames.RAVAGER_POTENT_BILE: - ItemData(256 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 26, SC2Race.ZERG, + ItemData(256 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 26, SC2Race.ZERG, parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), ItemNames.RAVAGER_BLOATED_BILE_DUCTS: - ItemData(257 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 27, SC2Race.ZERG, + ItemData(257 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 27, SC2Race.ZERG, parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), ItemNames.RAVAGER_DEEP_TUNNEL: - ItemData(258 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 28, SC2Race.ZERG, + ItemData(258 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 28, SC2Race.ZERG, parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), ItemNames.VIPER_PARASITIC_BOMB: - ItemData(259 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 29, SC2Race.ZERG, + ItemData(259 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 29, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), ItemNames.VIPER_PARALYTIC_BARBS: - ItemData(260 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 0, SC2Race.ZERG, + ItemData(260 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 0, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), ItemNames.VIPER_VIRULENT_MICROBES: - ItemData(261 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 1, SC2Race.ZERG, + ItemData(261 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 1, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), ItemNames.BROOD_LORD_POROUS_CARTILAGE: - ItemData(262 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 2, SC2Race.ZERG, + ItemData(262 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 2, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), ItemNames.BROOD_LORD_EVOLVED_CARAPACE: - ItemData(263 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 3, SC2Race.ZERG, + ItemData(263 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 3, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), ItemNames.BROOD_LORD_SPLITTER_MITOSIS: - ItemData(264 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 4, SC2Race.ZERG, + ItemData(264 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 4, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), ItemNames.BROOD_LORD_RESOURCE_EFFICIENCY: - ItemData(265 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 5, SC2Race.ZERG, + ItemData(265 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 5, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), ItemNames.INFESTOR_INFESTED_TERRAN: - ItemData(266 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 6, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, + ItemData(266 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 6, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, origin={"ext"}), ItemNames.INFESTOR_MICROBIAL_SHROUD: - ItemData(267 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 7, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, + ItemData(267 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 7, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, origin={"ext"}), ItemNames.SWARM_QUEEN_SPAWN_LARVAE: - ItemData(268 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(268 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}), ItemNames.SWARM_QUEEN_DEEP_TUNNEL: - ItemData(269 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(269 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}), ItemNames.SWARM_QUEEN_ORGANIC_CARAPACE: - ItemData(270 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(270 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}, classification=ItemClassification.filler), ItemNames.SWARM_QUEEN_BIO_MECHANICAL_TRANSFUSION: - ItemData(271 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(271 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}), ItemNames.SWARM_QUEEN_RESOURCE_EFFICIENCY: - ItemData(272 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 12, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(272 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 12, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}), ItemNames.SWARM_QUEEN_INCUBATOR_CHAMBER: - ItemData(273 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 13, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(273 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 13, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}), ItemNames.BROOD_QUEEN_FUNGAL_GROWTH: - ItemData(274 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 14, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + ItemData(274 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 14, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, origin={"ext"}), ItemNames.BROOD_QUEEN_ENSNARE: - ItemData(275 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 15, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + ItemData(275 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 15, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, origin={"ext"}), ItemNames.BROOD_QUEEN_ENHANCED_MITOCHONDRIA: - ItemData(276 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 16, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + ItemData(276 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 16, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, origin={"ext"}), ItemNames.ZERGLING_RAPTOR_STRAIN: - ItemData(300 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + ItemData(300 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), ItemNames.ZERGLING_SWARMLING_STRAIN: - ItemData(301 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + ItemData(301 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), ItemNames.ROACH_VILE_STRAIN: - ItemData(302 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 2, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), + ItemData(302 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 2, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), ItemNames.ROACH_CORPSER_STRAIN: - ItemData(303 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), + ItemData(303 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), ItemNames.HYDRALISK_IMPALER_ASPECT: - ItemData(304 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 0, SC2Race.ZERG, origin={"hots"}, + ItemData(304 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), ItemNames.HYDRALISK_LURKER_ASPECT: - ItemData(305 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 1, SC2Race.ZERG, origin={"hots"}, + ItemData(305 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 1, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), ItemNames.BANELING_SPLITTER_STRAIN: - ItemData(306 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 6, SC2Race.ZERG, + ItemData(306 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 6, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}), ItemNames.BANELING_HUNTER_STRAIN: - ItemData(307 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 7, SC2Race.ZERG, + ItemData(307 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 7, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}), ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT: - ItemData(308 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 2, SC2Race.ZERG, origin={"hots"}, + ItemData(308 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 2, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT: - ItemData(309 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 3, SC2Race.ZERG, origin={"hots"}, + ItemData(309 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 3, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), ItemNames.SWARM_HOST_CARRION_STRAIN: - ItemData(310 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(310 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"hots"}), ItemNames.SWARM_HOST_CREEPER_STRAIN: - ItemData(311 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(311 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"hots"}, classification=ItemClassification.filler), ItemNames.ULTRALISK_NOXIOUS_STRAIN: - ItemData(312 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(312 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"hots"}, classification=ItemClassification.filler), ItemNames.ULTRALISK_TORRASQUE_STRAIN: - ItemData(313 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(313 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"hots"}), - ItemNames.KERRIGAN_KINETIC_BLAST: ItemData(400 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_HEROIC_FORTITUDE: ItemData(401 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 1, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEAPING_STRIKE: ItemData(402 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 2, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_CRUSHING_GRIP: ItemData(403 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 3, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_CHAIN_REACTION: ItemData(404 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 4, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_PSIONIC_SHIFT: ItemData(405 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 5, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_ZERGLING_RECONSTITUTION: ItemData(406 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.filler), - ItemNames.KERRIGAN_IMPROVED_OVERLORDS: ItemData(407 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 1, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_AUTOMATED_EXTRACTORS: ItemData(408 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 2, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_WILD_MUTATION: ItemData(409 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 6, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_SPAWN_BANELINGS: ItemData(410 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 7, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_MEND: ItemData(411 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 8, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_TWIN_DRONES: ItemData(412 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 3, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_MALIGNANT_CREEP: ItemData(413 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 4, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_VESPENE_EFFICIENCY: ItemData(414 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 5, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_INFEST_BROODLINGS: ItemData(415 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 9, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_FURY: ItemData(416 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 10, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_ABILITY_EFFICIENCY: ItemData(417 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 11, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_APOCALYPSE: ItemData(418 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 12, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_SPAWN_LEVIATHAN: ItemData(419 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 13, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_DROP_PODS: ItemData(420 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 14, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_KINETIC_BLAST: ItemData(400 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_HEROIC_FORTITUDE: ItemData(401 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 1, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEAPING_STRIKE: ItemData(402 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 2, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_CRUSHING_GRIP: ItemData(403 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 3, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_CHAIN_REACTION: ItemData(404 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 4, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_PSIONIC_SHIFT: ItemData(405 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 5, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_ZERGLING_RECONSTITUTION: ItemData(406 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.filler), + ItemNames.KERRIGAN_IMPROVED_OVERLORDS: ItemData(407 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 1, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_AUTOMATED_EXTRACTORS: ItemData(408 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 2, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_WILD_MUTATION: ItemData(409 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 6, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_SPAWN_BANELINGS: ItemData(410 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 7, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_MEND: ItemData(411 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 8, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_TWIN_DRONES: ItemData(412 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 3, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_MALIGNANT_CREEP: ItemData(413 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 4, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_VESPENE_EFFICIENCY: ItemData(414 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 5, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_INFEST_BROODLINGS: ItemData(415 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 9, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_FURY: ItemData(416 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 10, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_ABILITY_EFFICIENCY: ItemData(417 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 11, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_APOCALYPSE: ItemData(418 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 12, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_SPAWN_LEVIATHAN: ItemData(419 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 13, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_DROP_PODS: ItemData(420 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 14, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), # Handled separately from other abilities - ItemNames.KERRIGAN_PRIMAL_FORM: ItemData(421 + SC2HOTS_ITEM_ID_OFFSET, "Primal Form", 0, SC2Race.ZERG, origin={"hots"}), - - ItemNames.KERRIGAN_LEVELS_10: ItemData(500 + SC2HOTS_ITEM_ID_OFFSET, "Level", 10, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_9: ItemData(501 + SC2HOTS_ITEM_ID_OFFSET, "Level", 9, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_8: ItemData(502 + SC2HOTS_ITEM_ID_OFFSET, "Level", 8, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_7: ItemData(503 + SC2HOTS_ITEM_ID_OFFSET, "Level", 7, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_6: ItemData(504 + SC2HOTS_ITEM_ID_OFFSET, "Level", 6, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_5: ItemData(505 + SC2HOTS_ITEM_ID_OFFSET, "Level", 5, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_4: ItemData(506 + SC2HOTS_ITEM_ID_OFFSET, "Level", 4, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), - ItemNames.KERRIGAN_LEVELS_3: ItemData(507 + SC2HOTS_ITEM_ID_OFFSET, "Level", 3, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), - ItemNames.KERRIGAN_LEVELS_2: ItemData(508 + SC2HOTS_ITEM_ID_OFFSET, "Level", 2, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), - ItemNames.KERRIGAN_LEVELS_1: ItemData(509 + SC2HOTS_ITEM_ID_OFFSET, "Level", 1, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), - ItemNames.KERRIGAN_LEVELS_14: ItemData(510 + SC2HOTS_ITEM_ID_OFFSET, "Level", 14, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_35: ItemData(511 + SC2HOTS_ITEM_ID_OFFSET, "Level", 35, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_70: ItemData(512 + SC2HOTS_ITEM_ID_OFFSET, "Level", 70, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_PRIMAL_FORM: ItemData(421 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Primal_Form, 0, SC2Race.ZERG, origin={"hots"}), + + ItemNames.KERRIGAN_LEVELS_10: ItemData(500 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 10, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_9: ItemData(501 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 9, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_8: ItemData(502 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 8, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_7: ItemData(503 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 7, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_6: ItemData(504 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 6, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_5: ItemData(505 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 5, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_4: ItemData(506 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 4, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_3: ItemData(507 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 3, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_2: ItemData(508 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 2, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_1: ItemData(509 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 1, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_14: ItemData(510 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 14, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_35: ItemData(511 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 35, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_70: ItemData(512 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 70, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), # Zerg Mercs - ItemNames.INFESTED_MEDICS: ItemData(600 + SC2HOTS_ITEM_ID_OFFSET, "Mercenary", 0, SC2Race.ZERG, origin={"ext"}), - ItemNames.INFESTED_SIEGE_TANKS: ItemData(601 + SC2HOTS_ITEM_ID_OFFSET, "Mercenary", 1, SC2Race.ZERG, origin={"ext"}), - ItemNames.INFESTED_BANSHEES: ItemData(602 + SC2HOTS_ITEM_ID_OFFSET, "Mercenary", 2, SC2Race.ZERG, origin={"ext"}), + ItemNames.INFESTED_MEDICS: ItemData(600 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mercenary, 0, SC2Race.ZERG, origin={"ext"}), + ItemNames.INFESTED_SIEGE_TANKS: ItemData(601 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mercenary, 1, SC2Race.ZERG, origin={"ext"}), + ItemNames.INFESTED_BANSHEES: ItemData(602 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mercenary, 2, SC2Race.ZERG, origin={"ext"}), # Misc Upgrades - ItemNames.OVERLORD_VENTRAL_SACS: ItemData(700 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 6, SC2Race.ZERG, origin={"bw"}), + ItemNames.OVERLORD_VENTRAL_SACS: ItemData(700 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 6, SC2Race.ZERG, origin={"bw"}), # Morphs - ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT: ItemData(800 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 6, SC2Race.ZERG, origin={"bw"}), - ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT: ItemData(801 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 7, SC2Race.ZERG, origin={"bw"}), - ItemNames.ROACH_RAVAGER_ASPECT: ItemData(802 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 8, SC2Race.ZERG, origin={"ext"}), + ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT: ItemData(800 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 6, SC2Race.ZERG, origin={"bw"}), + ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT: ItemData(801 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 7, SC2Race.ZERG, origin={"bw"}), + ItemNames.ROACH_RAVAGER_ASPECT: ItemData(802 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 8, SC2Race.ZERG, origin={"ext"}), # Protoss Units (those that aren't as items in WoL (Prophecy)) - ItemNames.OBSERVER: ItemData(0 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 9, SC2Race.PROTOSS, + ItemNames.OBSERVER: ItemData(0 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 9, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"wol"}), - ItemNames.CENTURION: ItemData(1 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 10, SC2Race.PROTOSS, + ItemNames.CENTURION: ItemData(1 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 10, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SENTINEL: ItemData(2 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 11, SC2Race.PROTOSS, + ItemNames.SENTINEL: ItemData(2 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 11, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SUPPLICANT: ItemData(3 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 12, SC2Race.PROTOSS, + ItemNames.SUPPLICANT: ItemData(3 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 12, SC2Race.PROTOSS, classification=ItemClassification.filler, important_for_filtering=True, origin={"ext"}), - ItemNames.INSTIGATOR: ItemData(4 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 13, SC2Race.PROTOSS, + ItemNames.INSTIGATOR: ItemData(4 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 13, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.SLAYER: ItemData(5 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 14, SC2Race.PROTOSS, + ItemNames.SLAYER: ItemData(5 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 14, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.SENTRY: ItemData(6 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 15, SC2Race.PROTOSS, + ItemNames.SENTRY: ItemData(6 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 15, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.ENERGIZER: ItemData(7 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 16, SC2Race.PROTOSS, + ItemNames.ENERGIZER: ItemData(7 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 16, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.HAVOC: ItemData(8 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 17, SC2Race.PROTOSS, + ItemNames.HAVOC: ItemData(8 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 17, SC2Race.PROTOSS, origin={"lotv"}, important_for_filtering=True), - ItemNames.SIGNIFIER: ItemData(9 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 18, SC2Race.PROTOSS, + ItemNames.SIGNIFIER: ItemData(9 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 18, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.ASCENDANT: ItemData(10 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 19, SC2Race.PROTOSS, + ItemNames.ASCENDANT: ItemData(10 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 19, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.AVENGER: ItemData(11 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 20, SC2Race.PROTOSS, + ItemNames.AVENGER: ItemData(11 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 20, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.BLOOD_HUNTER: ItemData(12 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 21, SC2Race.PROTOSS, + ItemNames.BLOOD_HUNTER: ItemData(12 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 21, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.DRAGOON: ItemData(13 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 22, SC2Race.PROTOSS, + ItemNames.DRAGOON: ItemData(13 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 22, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.DARK_ARCHON: ItemData(14 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 23, SC2Race.PROTOSS, + ItemNames.DARK_ARCHON: ItemData(14 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 23, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.ADEPT: ItemData(15 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 24, SC2Race.PROTOSS, + ItemNames.ADEPT: ItemData(15 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 24, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.WARP_PRISM: ItemData(16 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 25, SC2Race.PROTOSS, + ItemNames.WARP_PRISM: ItemData(16 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 25, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.ANNIHILATOR: ItemData(17 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 26, SC2Race.PROTOSS, + ItemNames.ANNIHILATOR: ItemData(17 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 26, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.VANGUARD: ItemData(18 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 27, SC2Race.PROTOSS, + ItemNames.VANGUARD: ItemData(18 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 27, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.WRATHWALKER: ItemData(19 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 28, SC2Race.PROTOSS, + ItemNames.WRATHWALKER: ItemData(19 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 28, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.REAVER: ItemData(20 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 29, SC2Race.PROTOSS, + ItemNames.REAVER: ItemData(20 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 29, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.DISRUPTOR: ItemData(21 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 0, SC2Race.PROTOSS, + ItemNames.DISRUPTOR: ItemData(21 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 0, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.MIRAGE: ItemData(22 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 1, SC2Race.PROTOSS, + ItemNames.MIRAGE: ItemData(22 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 1, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.CORSAIR: ItemData(23 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 2, SC2Race.PROTOSS, + ItemNames.CORSAIR: ItemData(23 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 2, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.DESTROYER: ItemData(24 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 3, SC2Race.PROTOSS, + ItemNames.DESTROYER: ItemData(24 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 3, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SCOUT: ItemData(25 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 4, SC2Race.PROTOSS, + ItemNames.SCOUT: ItemData(25 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 4, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.TEMPEST: ItemData(26 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 5, SC2Race.PROTOSS, + ItemNames.TEMPEST: ItemData(26 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 5, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.MOTHERSHIP: ItemData(27 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 6, SC2Race.PROTOSS, + ItemNames.MOTHERSHIP: ItemData(27 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 6, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.ARBITER: ItemData(28 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 7, SC2Race.PROTOSS, + ItemNames.ARBITER: ItemData(28 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 7, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.ORACLE: ItemData(29 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 8, SC2Race.PROTOSS, + ItemNames.ORACLE: ItemData(29 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 8, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), # Protoss Upgrades - ItemNames.PROGRESSIVE_PROTOSS_GROUND_WEAPON: ItemData(100 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_GROUND_ARMOR: ItemData(101 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_SHIELDS: ItemData(102 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_AIR_WEAPON: ItemData(103 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_AIR_ARMOR: ItemData(104 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_GROUND_WEAPON: ItemData(100 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 0, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_GROUND_ARMOR: ItemData(101 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 2, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_SHIELDS: ItemData(102 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 4, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_WEAPON: ItemData(103 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 6, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_ARMOR: ItemData(104 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 8, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), # Upgrade bundle 'number' values are used as indices to get affected 'number's - ItemNames.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE: ItemData(105 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 11, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE: ItemData(106 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 12, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_GROUND_UPGRADE: ItemData(107 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 13, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_AIR_UPGRADE: ItemData(108 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 14, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 15, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE: ItemData(105 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 11, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE: ItemData(106 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 12, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_GROUND_UPGRADE: ItemData(107 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 13, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_UPGRADE: ItemData(108 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 14, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 15, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), # Protoss Buildings - ItemNames.PHOTON_CANNON: ItemData(200 + SC2LOTV_ITEM_ID_OFFSET, "Building", 0, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), - ItemNames.KHAYDARIN_MONOLITH: ItemData(201 + SC2LOTV_ITEM_ID_OFFSET, "Building", 1, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SHIELD_BATTERY: ItemData(202 + SC2LOTV_ITEM_ID_OFFSET, "Building", 2, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.PHOTON_CANNON: ItemData(200 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Building, 0, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), + ItemNames.KHAYDARIN_MONOLITH: ItemData(201 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Building, 1, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SHIELD_BATTERY: ItemData(202 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Building, 2, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), # Protoss Unit Upgrades - ItemNames.SUPPLICANT_BLOOD_SHIELD: ItemData(300 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 0, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), - ItemNames.SUPPLICANT_SOUL_AUGMENTATION: ItemData(301 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), - ItemNames.SUPPLICANT_SHIELD_REGENERATION: ItemData(302 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), - ItemNames.ADEPT_SHOCKWAVE: ItemData(303 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 3, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), - ItemNames.ADEPT_RESONATING_GLAIVES: ItemData(304 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 4, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), - ItemNames.ADEPT_PHASE_BULWARK: ItemData(305 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), - ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES: ItemData(306 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 6, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), - ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION: ItemData(307 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 7, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), - ItemNames.DRAGOON_HIGH_IMPACT_PHASE_DISRUPTORS: ItemData(308 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 8, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), - ItemNames.DRAGOON_TRILLIC_COMPRESSION_SYSTEM: ItemData(309 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), - ItemNames.DRAGOON_SINGULARITY_CHARGE: ItemData(310 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 10, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.DRAGOON), - ItemNames.DRAGOON_ENHANCED_STRIDER_SERVOS: ItemData(311 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.DRAGOON), - ItemNames.SCOUT_COMBAT_SENSOR_ARRAY: ItemData(312 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), - ItemNames.SCOUT_APIAL_SENSORS: ItemData(313 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 13, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), - ItemNames.SCOUT_GRAVITIC_THRUSTERS: ItemData(314 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), - ItemNames.SCOUT_ADVANCED_PHOTON_BLASTERS: ItemData(315 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), - ItemNames.TEMPEST_TECTONIC_DESTABILIZERS: ItemData(316 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 16, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), - ItemNames.TEMPEST_QUANTIC_REACTOR: ItemData(317 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 17, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), - ItemNames.TEMPEST_GRAVITY_SLING: ItemData(318 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 18, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.TEMPEST), - ItemNames.PHOENIX_MIRAGE_IONIC_WAVELENGTH_FLUX: ItemData(319 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 19, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.PHOENIX_MIRAGE_ANION_PULSE_CRYSTALS: ItemData(320 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 20, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.CORSAIR_STEALTH_DRIVE: ItemData(321 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.CORSAIR), - ItemNames.CORSAIR_ARGUS_JEWEL: ItemData(322 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 22, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), - ItemNames.CORSAIR_SUSTAINING_DISRUPTION: ItemData(323 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 23, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), - ItemNames.CORSAIR_NEUTRON_SHIELDS: ItemData(324 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 24, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.CORSAIR), - ItemNames.ORACLE_STEALTH_DRIVE: ItemData(325 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 25, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), - ItemNames.ORACLE_STASIS_CALIBRATION: ItemData(326 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 26, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), - ItemNames.ORACLE_TEMPORAL_ACCELERATION_BEAM: ItemData(327 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 27, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), - ItemNames.ARBITER_CHRONOSTATIC_REINFORCEMENT: ItemData(328 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 28, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), - ItemNames.ARBITER_KHAYDARIN_CORE: ItemData(329 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 29, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), - ItemNames.ARBITER_SPACETIME_ANCHOR: ItemData(330 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 0, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), - ItemNames.ARBITER_RESOURCE_EFFICIENCY: ItemData(331 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), - ItemNames.ARBITER_ENHANCED_CLOAK_FIELD: ItemData(332 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.SUPPLICANT_BLOOD_SHIELD: ItemData(300 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 0, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.SUPPLICANT_SOUL_AUGMENTATION: ItemData(301 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.SUPPLICANT_SHIELD_REGENERATION: ItemData(302 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.ADEPT_SHOCKWAVE: ItemData(303 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 3, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.ADEPT_RESONATING_GLAIVES: ItemData(304 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 4, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.ADEPT_PHASE_BULWARK: ItemData(305 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES: ItemData(306 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 6, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), + ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION: ItemData(307 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 7, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), + ItemNames.DRAGOON_HIGH_IMPACT_PHASE_DISRUPTORS: ItemData(308 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 8, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_TRILLIC_COMPRESSION_SYSTEM: ItemData(309 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_SINGULARITY_CHARGE: ItemData(310 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 10, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_ENHANCED_STRIDER_SERVOS: ItemData(311 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.DRAGOON), + ItemNames.SCOUT_COMBAT_SENSOR_ARRAY: ItemData(312 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_APIAL_SENSORS: ItemData(313 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 13, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_GRAVITIC_THRUSTERS: ItemData(314 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_ADVANCED_PHOTON_BLASTERS: ItemData(315 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), + ItemNames.TEMPEST_TECTONIC_DESTABILIZERS: ItemData(316 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 16, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.TEMPEST_QUANTIC_REACTOR: ItemData(317 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 17, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.TEMPEST_GRAVITY_SLING: ItemData(318 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 18, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.PHOENIX_MIRAGE_IONIC_WAVELENGTH_FLUX: ItemData(319 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 19, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.PHOENIX_MIRAGE_ANION_PULSE_CRYSTALS: ItemData(320 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 20, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.CORSAIR_STEALTH_DRIVE: ItemData(321 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_ARGUS_JEWEL: ItemData(322 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 22, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_SUSTAINING_DISRUPTION: ItemData(323 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 23, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_NEUTRON_SHIELDS: ItemData(324 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 24, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.ORACLE_STEALTH_DRIVE: ItemData(325 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 25, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ORACLE_STASIS_CALIBRATION: ItemData(326 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 26, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ORACLE_TEMPORAL_ACCELERATION_BEAM: ItemData(327 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 27, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ARBITER_CHRONOSTATIC_REINFORCEMENT: ItemData(328 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 28, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_KHAYDARIN_CORE: ItemData(329 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 29, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_SPACETIME_ANCHOR: ItemData(330 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 0, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_RESOURCE_EFFICIENCY: ItemData(331 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_ENHANCED_CLOAK_FIELD: ItemData(332 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), ItemNames.CARRIER_GRAVITON_CATAPULT: - ItemData(333 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 3, SC2Race.PROTOSS, origin={"wol"}, + ItemData(333 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 3, SC2Race.PROTOSS, origin={"wol"}, parent_item=ItemNames.CARRIER), ItemNames.CARRIER_HULL_OF_PAST_GLORIES: - ItemData(334 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 4, SC2Race.PROTOSS, origin={"bw"}, + ItemData(334 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 4, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CARRIER), ItemNames.VOID_RAY_DESTROYER_FLUX_VANES: - ItemData(335 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 5, SC2Race.PROTOSS, classification=ItemClassification.filler, + ItemData(335 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 5, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}), ItemNames.DESTROYER_REFORGED_BLOODSHARD_CORE: - ItemData(336 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 6, SC2Race.PROTOSS, origin={"ext"}, + ItemData(336 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 6, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DESTROYER), ItemNames.WARP_PRISM_GRAVITIC_DRIVE: - ItemData(337 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 7, SC2Race.PROTOSS, classification=ItemClassification.filler, + ItemData(337 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 7, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.WARP_PRISM), ItemNames.WARP_PRISM_PHASE_BLASTER: - ItemData(338 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 8, SC2Race.PROTOSS, + ItemData(338 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 8, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}, parent_item=ItemNames.WARP_PRISM), - ItemNames.WARP_PRISM_WAR_CONFIGURATION: ItemData(339 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WARP_PRISM), - ItemNames.OBSERVER_GRAVITIC_BOOSTERS: ItemData(340 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), - ItemNames.OBSERVER_SENSOR_ARRAY: ItemData(341 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), - ItemNames.REAVER_SCARAB_DAMAGE: ItemData(342 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 12, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), - ItemNames.REAVER_SOLARITE_PAYLOAD: ItemData(343 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.REAVER), - ItemNames.REAVER_REAVER_CAPACITY: ItemData(344 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.REAVER), - ItemNames.REAVER_RESOURCE_EFFICIENCY: ItemData(345 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 15, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), - ItemNames.VANGUARD_AGONY_LAUNCHERS: ItemData(346 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 16, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), - ItemNames.VANGUARD_MATTER_DISPERSION: ItemData(347 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 17, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), - ItemNames.IMMORTAL_ANNIHILATOR_SINGULARITY_CHARGE: ItemData(348 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 18, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS: ItemData(349 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 19, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.COLOSSUS_PACIFICATION_PROTOCOL: ItemData(350 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 20, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.COLOSSUS), - ItemNames.WRATHWALKER_RAPID_POWER_CYCLING: ItemData(351 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), - ItemNames.WRATHWALKER_EYE_OF_WRATH: ItemData(352 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 22, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), - ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHROUD_OF_ADUN: ItemData(353 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 23, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHADOW_GUARD_TRAINING: ItemData(354 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 24, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK: ItemData(355 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 25, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_RESOURCE_EFFICIENCY: ItemData(356 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 26, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.DARK_TEMPLAR_DARK_ARCHON_MELD: ItemData(357 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 27, SC2Race.PROTOSS, origin={"bw"}, important_for_filtering=True ,parent_item=ItemNames.DARK_TEMPLAR), - ItemNames.HIGH_TEMPLAR_SIGNIFIER_UNSHACKLED_PSIONIC_STORM: ItemData(358 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 28, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.HIGH_TEMPLAR_SIGNIFIER_HALLUCINATION: ItemData(359 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 29, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}), - ItemNames.HIGH_TEMPLAR_SIGNIFIER_KHAYDARIN_AMULET: ItemData(360 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 0, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.ARCHON_HIGH_ARCHON: ItemData(361 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 1, SC2Race.PROTOSS, origin={"ext"}, important_for_filtering=True), - ItemNames.DARK_ARCHON_FEEDBACK: ItemData(362 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 2, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.DARK_ARCHON_MAELSTROM: ItemData(363 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 3, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.DARK_ARCHON_ARGUS_TALISMAN: ItemData(364 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 4, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.ASCENDANT_POWER_OVERWHELMING: ItemData(365 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), - ItemNames.ASCENDANT_CHAOTIC_ATTUNEMENT: ItemData(366 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 6, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), - ItemNames.ASCENDANT_BLOOD_AMULET: ItemData(367 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 7, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), - ItemNames.SENTRY_ENERGIZER_HAVOC_CLOAKING_MODULE: ItemData(368 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 8, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.SENTRY_ENERGIZER_HAVOC_SHIELD_BATTERY_RAPID_RECHARGING: ItemData(369 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 9, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.SENTRY_FORCE_FIELD: ItemData(370 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), - ItemNames.SENTRY_HALLUCINATION: ItemData(371 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), - ItemNames.ENERGIZER_RECLAMATION: ItemData(372 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), - ItemNames.ENERGIZER_FORGED_CHASSIS: ItemData(373 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), - ItemNames.HAVOC_DETECT_WEAKNESS: ItemData(374 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 14, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), - ItemNames.HAVOC_BLOODSHARD_RESONANCE: ItemData(375 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), - ItemNames.ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS: ItemData(376 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 16, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY: ItemData(377 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 17, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.WARP_PRISM_WAR_CONFIGURATION: ItemData(339 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WARP_PRISM), + ItemNames.OBSERVER_GRAVITIC_BOOSTERS: ItemData(340 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), + ItemNames.OBSERVER_SENSOR_ARRAY: ItemData(341 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), + ItemNames.REAVER_SCARAB_DAMAGE: ItemData(342 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 12, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_SOLARITE_PAYLOAD: ItemData(343 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_REAVER_CAPACITY: ItemData(344 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_RESOURCE_EFFICIENCY: ItemData(345 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 15, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.VANGUARD_AGONY_LAUNCHERS: ItemData(346 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 16, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), + ItemNames.VANGUARD_MATTER_DISPERSION: ItemData(347 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 17, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), + ItemNames.IMMORTAL_ANNIHILATOR_SINGULARITY_CHARGE: ItemData(348 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 18, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS: ItemData(349 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 19, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), + ItemNames.COLOSSUS_PACIFICATION_PROTOCOL: ItemData(350 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 20, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.COLOSSUS), + ItemNames.WRATHWALKER_RAPID_POWER_CYCLING: ItemData(351 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), + ItemNames.WRATHWALKER_EYE_OF_WRATH: ItemData(352 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 22, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHROUD_OF_ADUN: ItemData(353 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 23, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHADOW_GUARD_TRAINING: ItemData(354 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 24, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK: ItemData(355 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 25, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_RESOURCE_EFFICIENCY: ItemData(356 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 26, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.DARK_TEMPLAR_DARK_ARCHON_MELD: ItemData(357 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 27, SC2Race.PROTOSS, origin={"bw"}, important_for_filtering=True ,parent_item=ItemNames.DARK_TEMPLAR), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_UNSHACKLED_PSIONIC_STORM: ItemData(358 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 28, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_HALLUCINATION: ItemData(359 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 29, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_KHAYDARIN_AMULET: ItemData(360 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 0, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ARCHON_HIGH_ARCHON: ItemData(361 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 1, SC2Race.PROTOSS, origin={"ext"}, important_for_filtering=True), + ItemNames.DARK_ARCHON_FEEDBACK: ItemData(362 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 2, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_ARCHON_MAELSTROM: ItemData(363 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 3, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_ARCHON_ARGUS_TALISMAN: ItemData(364 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 4, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ASCENDANT_POWER_OVERWHELMING: ItemData(365 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.ASCENDANT_CHAOTIC_ATTUNEMENT: ItemData(366 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 6, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.ASCENDANT_BLOOD_AMULET: ItemData(367 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 7, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.SENTRY_ENERGIZER_HAVOC_CLOAKING_MODULE: ItemData(368 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 8, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SENTRY_ENERGIZER_HAVOC_SHIELD_BATTERY_RAPID_RECHARGING: ItemData(369 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 9, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SENTRY_FORCE_FIELD: ItemData(370 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), + ItemNames.SENTRY_HALLUCINATION: ItemData(371 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), + ItemNames.ENERGIZER_RECLAMATION: ItemData(372 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), + ItemNames.ENERGIZER_FORGED_CHASSIS: ItemData(373 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), + ItemNames.HAVOC_DETECT_WEAKNESS: ItemData(374 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 14, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), + ItemNames.HAVOC_BLOODSHARD_RESONANCE: ItemData(375 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), + ItemNames.ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS: ItemData(376 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 16, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY: ItemData(377 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 17, SC2Race.PROTOSS, origin={"bw"}), # SoA Calldown powers - ItemNames.SOA_CHRONO_SURGE: ItemData(700 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 0, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_PROGRESSIVE_PROXY_PYLON: ItemData(701 + SC2LOTV_ITEM_ID_OFFSET, "Progressive Upgrade", 0, SC2Race.PROTOSS, origin={"lotv"}, quantity=2), - ItemNames.SOA_PYLON_OVERCHARGE: ItemData(702 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 1, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.SOA_ORBITAL_STRIKE: ItemData(703 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 2, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_TEMPORAL_FIELD: ItemData(704 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 3, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_SOLAR_LANCE: ItemData(705 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 4, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SOA_MASS_RECALL: ItemData(706 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 5, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_SHIELD_OVERCHARGE: ItemData(707 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 6, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_DEPLOY_FENIX: ItemData(708 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 7, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SOA_PURIFIER_BEAM: ItemData(709 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 8, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_TIME_STOP: ItemData(710 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 9, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SOA_SOLAR_BOMBARDMENT: ItemData(711 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 10, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_CHRONO_SURGE: ItemData(700 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 0, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_PROGRESSIVE_PROXY_PYLON: ItemData(701 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Progressive, 0, SC2Race.PROTOSS, origin={"lotv"}, quantity=2), + ItemNames.SOA_PYLON_OVERCHARGE: ItemData(702 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 1, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SOA_ORBITAL_STRIKE: ItemData(703 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 2, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_TEMPORAL_FIELD: ItemData(704 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 3, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_SOLAR_LANCE: ItemData(705 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 4, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_MASS_RECALL: ItemData(706 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 5, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_SHIELD_OVERCHARGE: ItemData(707 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 6, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_DEPLOY_FENIX: ItemData(708 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 7, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_PURIFIER_BEAM: ItemData(709 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 8, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_TIME_STOP: ItemData(710 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 9, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_SOLAR_BOMBARDMENT: ItemData(711 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 10, SC2Race.PROTOSS, origin={"lotv"}), # Generic Protoss Upgrades ItemNames.MATRIX_OVERLOAD: - ItemData(800 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 0, SC2Race.PROTOSS, origin={"lotv"}), + ItemData(800 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 0, SC2Race.PROTOSS, origin={"lotv"}), ItemNames.QUATRO: - ItemData(801 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 1, SC2Race.PROTOSS, origin={"ext"}), + ItemData(801 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 1, SC2Race.PROTOSS, origin={"ext"}), ItemNames.NEXUS_OVERCHARGE: - ItemData(802 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 2, SC2Race.PROTOSS, origin={"lotv"}, important_for_filtering=True), + ItemData(802 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 2, SC2Race.PROTOSS, origin={"lotv"}, important_for_filtering=True), ItemNames.ORBITAL_ASSIMILATORS: - ItemData(803 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 3, SC2Race.PROTOSS, origin={"lotv"}), + ItemData(803 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 3, SC2Race.PROTOSS, origin={"lotv"}), ItemNames.WARP_HARMONIZATION: - ItemData(804 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 4, SC2Race.PROTOSS, origin={"lotv"}), + ItemData(804 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 4, SC2Race.PROTOSS, origin={"lotv"}), ItemNames.GUARDIAN_SHELL: - ItemData(805 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 5, SC2Race.PROTOSS, origin={"lotv"}), + ItemData(805 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 5, SC2Race.PROTOSS, origin={"lotv"}), ItemNames.RECONSTRUCTION_BEAM: - ItemData(806 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 6, SC2Race.PROTOSS, + ItemData(806 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 6, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), ItemNames.OVERWATCH: - ItemData(807 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 7, SC2Race.PROTOSS, origin={"ext"}), + ItemData(807 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 7, SC2Race.PROTOSS, origin={"ext"}), ItemNames.SUPERIOR_WARP_GATES: - ItemData(808 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 8, SC2Race.PROTOSS, origin={"ext"}), + ItemData(808 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 8, SC2Race.PROTOSS, origin={"ext"}), ItemNames.ENHANCED_TARGETING: - ItemData(809 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 9, SC2Race.PROTOSS, origin={"ext"}), + ItemData(809 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 9, SC2Race.PROTOSS, origin={"ext"}), ItemNames.OPTIMIZED_ORDNANCE: - ItemData(810 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 10, SC2Race.PROTOSS, origin={"ext"}), + ItemData(810 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 10, SC2Race.PROTOSS, origin={"ext"}), ItemNames.KHALAI_INGENUITY: - ItemData(811 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 11, SC2Race.PROTOSS, origin={"ext"}), + ItemData(811 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 11, SC2Race.PROTOSS, origin={"ext"}), ItemNames.AMPLIFIED_ASSIMILATORS: - ItemData(812 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 12, SC2Race.PROTOSS, origin={"ext"}), + ItemData(812 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 12, SC2Race.PROTOSS, origin={"ext"}), } @@ -1639,10 +1691,10 @@ def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: ItemNames.ROGUE_FORCES, # Mercenaries (All races) *[item_name for item_name, item_data in get_full_item_list().items() - if item_data.type == "Mercenary"], + if item_data.type == ItemType.Mercenary], # Kerrigan and Nova levels, abilities and generally useful stuff *[item_name for item_name, item_data in get_full_item_list().items() - if item_data.type in ("Level", "Ability", "Evolution Pit", "Nova Gear")], + if item_data.type in (ItemType.Level, ItemType.Ability, ItemType.Evolution_Pit, ItemType.Nova_Gear)], ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, # Zerg static defenses ItemNames.SPORE_CRAWLER, @@ -1714,8 +1766,10 @@ def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: ItemNames.MISSILE_TURRET: 2, } -kerrigan_levels = [item_name for item_name, item_data in get_full_item_list().items() - if item_data.type == "Level" and item_data.race == SC2Race.ZERG] +kerrigan_levels = [ + item_name for item_name, item_data in item_table.items() + if item_data.type == ItemType.Level and item_data.race == SC2Race.ZERG +] spider_mine_sources = { ItemNames.VULTURE, @@ -1788,7 +1842,7 @@ def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: nova_equipment = { *[item_name for item_name, item_data in get_full_item_list().items() - if item_data.type == "Nova Gear"], + if item_data.type == ItemType.Nova_Gear], ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE } @@ -1871,52 +1925,51 @@ def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: # Map type to expected int type_flaggroups: typing.Dict[SC2Race, typing.Dict[str, int]] = { SC2Race.ANY: { - "Minerals": 0, - "Vespene": 1, - "Supply": 2, - "Goal": 3, - "Nothing Group": 4, + ItemType.Minerals: 0, + ItemType.Vespene: 1, + ItemType.Supply: 2, + ItemType.Nothing: 4, }, SC2Race.TERRAN: { - "Armory 1": 0, - "Armory 2": 1, - "Armory 3": 2, - "Armory 4": 3, - "Armory 5": 4, - "Armory 6": 5, - "Progressive Upgrade": 6, # Unit upgrades that exist multiple times (Stimpack / Super Stimpack) - "Laboratory": 7, - "Upgrade": 8, # Weapon / Armor upgrades - "Unit": 9, - "Building": 10, - "Mercenary": 11, - "Nova Gear": 12, - "Progressive Upgrade 2": 13, + ItemType.Armory_1: 0, + ItemType.Armory_2: 1, + ItemType.Armory_3: 2, + ItemType.Armory_4: 3, + ItemType.Armory_5: 4, + ItemType.Armory_6: 5, + ItemType.Progressive: 6, # Unit upgrades that exist multiple times (Stimpack / Super Stimpack) + ItemType.Laboratory: 7, + ItemType.Upgrade: 8, # Weapon / Armor upgrades + ItemType.Unit: 9, + ItemType.Building: 10, + ItemType.Mercenary: 11, + ItemType.Nova_Gear: 12, + ItemType.Progressive_2: 13, }, SC2Race.ZERG: { - "Ability": 0, - "Mutation 1": 1, - "Strain": 2, - "Morph": 3, - "Upgrade": 4, - "Mercenary": 5, - "Unit": 6, - "Level": 7, - "Primal Form": 8, - "Evolution Pit": 9, - "Mutation 2": 10, - "Mutation 3": 11 + ItemType.Ability: 0, + ItemType.Mutation_1: 1, + ItemType.Strain: 2, + ItemType.Morph: 3, + ItemType.Upgrade: 4, + ItemType.Mercenary: 5, + ItemType.Unit: 6, + ItemType.Level: 7, + ItemType.Primal_Form: 8, + ItemType.Evolution_Pit: 9, + ItemType.Mutation_2: 10, + ItemType.Mutation_3: 11 }, SC2Race.PROTOSS: { - "Unit": 0, - "Unit 2": 1, - "Upgrade": 2, # Weapon / Armor upgrades - "Building": 3, - "Progressive Upgrade": 4, - "Spear of Adun": 5, - "Solarite Core": 6, - "Forge 1": 7, - "Forge 2": 8, - "Forge 3": 9, + ItemType.Unit: 0, + ItemType.Unit_2: 1, + ItemType.Upgrade: 2, # Weapon / Armor upgrades + ItemType.Building: 3, + ItemType.Progressive: 4, + ItemType.Spear_Of_Adun: 5, + ItemType.Solarite_Core: 6, + ItemType.Forge_1: 7, + ItemType.Forge_2: 8, + ItemType.Forge_3: 9, } } diff --git a/worlds/sc2/Locations.py b/worlds/sc2/Locations.py index bf9c06fa3f78..60b515116cbf 100644 --- a/worlds/sc2/Locations.py +++ b/worlds/sc2/Locations.py @@ -1,5 +1,5 @@ from enum import IntEnum -from typing import List, Tuple, Optional, Callable, NamedTuple, Set, Any +from typing import List, Tuple, Optional, Callable, NamedTuple, Set, Any, TYPE_CHECKING from BaseClasses import MultiWorld from . import ItemNames from .Options import get_option_value, kerrigan_unit_available, RequiredTactics, GrantStoryTech, LocationInclusion, \ @@ -9,6 +9,9 @@ from BaseClasses import Location from worlds.AutoWorld import World +if TYPE_CHECKING: + from BaseClasses import CollectionState + SC2WOL_LOC_ID_OFFSET = 1000 SC2HOTS_LOC_ID_OFFSET = 20000000 # Avoid clashes with The Legend of Zelda SC2LOTV_LOC_ID_OFFSET = SC2HOTS_LOC_ID_OFFSET + 2000 @@ -32,10 +35,10 @@ class LocationData(NamedTuple): name: str code: Optional[int] type: LocationType - rule: Optional[Callable[[Any], bool]] = Location.access_rule + rule: Optional[Callable[['CollectionState'], bool]] = Location.access_rule -def get_location_types(world: World, inclusion_type: LocationInclusion) -> Set[LocationType]: +def get_location_types(world: World, inclusion_type: int) -> Set[LocationType]: """ :param multiworld: diff --git a/worlds/sc2/Options.py b/worlds/sc2/Options.py index e599512d894d..4dec08013968 100644 --- a/worlds/sc2/Options.py +++ b/worlds/sc2/Options.py @@ -1,7 +1,10 @@ from dataclasses import dataclass, fields, Field -from typing import FrozenSet, Union, Set +from typing import * -from Options import Choice, Toggle, DefaultOnToggle, ItemSet, OptionSet, Range, PerGameCommonOptions +from Options import (Choice, Toggle, DefaultOnToggle, ItemDict, OptionSet, Range, OptionDict, + PerGameCommonOptions, Option, VerifyKeys) +from Utils import get_fuzzy_results +from BaseClasses import PlandoOptions from .MissionTables import SC2Campaign, SC2Mission, lookup_name_to_mission, MissionPools, get_no_build_missions, \ campaign_mission_table from worlds.AutoWorld import World @@ -380,7 +383,6 @@ class KerriganPresence(Choice): display_name = "Kerrigan Presence" option_vanilla = 0 option_not_present = 1 - option_not_present_and_no_passives = 2 class KerriganLevelsPerMissionCompleted(Range): @@ -621,12 +623,70 @@ class TakeOverAIAllies(Toggle): display_name = "Take Over AI Allies" -class LockedItems(ItemSet): +class Sc2ItemDict(Option[Dict[str, int]], VerifyKeys, Mapping[str, int]): + """A branch of ItemDict that supports item counts of 0""" + default: Dict[str, int] = {} + supports_weighting = False + verify_item_name = True + # convert_name_groups = True + display_name = 'Unnamed dictionary' + minimum_value: int = 0 + + def __init__(self, value: Dict[str, int]): + self.value = {key: val for key, val in value.items()} + + @classmethod + def from_any(cls, data: Union[List[str], Dict[str, int]]) -> 'Sc2ItemDict': + if isinstance(data, list): + # This is a little default that gets us backwards compatibility with lists. + # It doesn't play nice with trigger merging dicts and lists together, though, so best not to advertise it overmuch. + data = {item: 0 for item in data} + if isinstance(data, dict): + cls.verify_keys(data) + for key, value in data.items(): + if not isinstance(value, int): + raise ValueError(f"Invalid type in '{cls.display_name}': element '{key}' maps to '{value}', expected an integer") + if value < cls.minimum_value: + raise ValueError(f"Invalid value for '{cls.display_name}': element '{key}' maps to {value}, which is less than the minimum ({cls.minimum_value})") + return cls(data) + else: + raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}") + + def verify(self, world: Type[World], player_name: str, plando_options: PlandoOptions) -> None: + """Overridden version of function from Options.VerifyKeys for a better error message""" + if self.convert_name_groups: + new_value: dict[str, int] = {} + for group_name in self.value: + item_names = world.item_name_groups.get(group_name, {group_name}) + for item_name in item_names: + new_value[item_name] = new_value.get(item_name, 0) + self.value[group_name] + self.value = new_value + for item_name in self.value: + if item_name not in world.item_names: + picks = get_fuzzy_results(item_name, world.item_names, limit=1) + raise Exception(f"Item {item_name} from option {self} " + f"is not a valid item name from {world.game}. " + f"Did you mean '{picks[0][0]}' ({picks[0][1]}% sure)") + + def get_option_name(self, value): + return ", ".join(f"{key}: {v}" for key, v in value.items()) + + def __getitem__(self, item: str) -> int: + return self.value.__getitem__(item) + + def __iter__(self) -> Iterator[str]: + return self.value.__iter__() + + def __len__(self) -> int: + return self.value.__len__() + + +class LockedItems(Sc2ItemDict): """Guarantees that these items will be unlockable""" display_name = "Locked Items" -class ExcludedItems(ItemSet): +class ExcludedItems(Sc2ItemDict): """Guarantees that these items will not be unlockable""" display_name = "Excluded Items" diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py index c2cda5c542e8..531645b42c34 100644 --- a/worlds/sc2/PoolFilter.py +++ b/worlds/sc2/PoolFilter.py @@ -2,9 +2,10 @@ from BaseClasses import Item, Location from .Items import get_full_item_list, spider_mine_sources, second_pass_placeable_items, progressive_if_nco, \ progressive_if_ext, spear_of_adun_calldowns, spear_of_adun_castable_passives, nova_equipment -from .MissionTables import mission_orders, MissionInfo, MissionPools, \ - get_campaign_goal_priority, campaign_final_mission_locations, campaign_alt_final_mission_locations, \ - SC2Campaign, SC2Race, SC2CampaignGoalPriority, SC2Mission +from .MissionTables import (mission_orders, MissionInfo, MissionPools, MissionFlag, + get_campaign_goal_priority, campaign_final_mission_locations, campaign_alt_final_mission_locations, + SC2Campaign, SC2Race, SC2CampaignGoalPriority, SC2Mission, +) from .Options import get_option_value, MissionOrder, \ get_enabled_campaigns, get_disabled_campaigns, RequiredTactics, kerrigan_unit_available, GrantStoryTech, \ TakeOverAIAllies, SpearOfAdunPresence, SpearOfAdunAutonomouslyCastAbilityPresence, campaign_depending_orders, \ @@ -597,8 +598,6 @@ def __init__(self, world: World , self.locked_items.append(item) else: self.item_pool.append(item) - elif item_info.type == "Goal": - self.locked_items.append(item) else: self.item_pool.append(item) self.item_children: Dict[Item, List[Item]] = dict() @@ -654,7 +653,7 @@ def get_used_races(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], def is_nova_equipment_used(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> bool: missions = missions_in_mission_table(mission_req_table) - return any([mission.campaign == SC2Campaign.NCO for mission in missions]) + return any([MissionFlag.Nova in mission.flags for mission in missions]) def missions_in_mission_table(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> Set[SC2Mission]: diff --git a/worlds/sc2/Regions.py b/worlds/sc2/Regions.py index 84830a9a32bd..60da745463e8 100644 --- a/worlds/sc2/Regions.py +++ b/worlds/sc2/Regions.py @@ -405,20 +405,20 @@ def create_structured_regions( removals = len(mission_order[campaign]) - campaign_mission_pool_size - for mission in mission_order[campaign]: + for fill_mission in mission_order[campaign]: # Removing extra missions if mission pool is too small - if 0 < mission.removal_priority <= removals: + if 0 < fill_mission.removal_priority <= removals: mission_slots.append(SC2MissionSlot(campaign, None)) - elif mission.type == MissionPools.FINAL: + elif fill_mission.type == MissionPools.FINAL: if campaign == final_mission.campaign: # Campaign is elected to be goal mission_slots.append(SC2MissionSlot(campaign, final_mission)) else: # Not the goal, find the most difficult mission in the pool and set the difficulty - campaign_difficulty = max(mission.pool for mission in campaign_mission_pool) + campaign_difficulty = max(fill_mission.pool for fill_mission in campaign_mission_pool) mission_slots.append(SC2MissionSlot(campaign, campaign_difficulty)) else: - mission_slots.append(SC2MissionSlot(campaign, mission.type)) + mission_slots.append(SC2MissionSlot(campaign, fill_mission.type)) else: order = mission_order[SC2Campaign.GLOBAL] # Determining if missions must be removed @@ -426,14 +426,14 @@ def create_structured_regions( removals = len(order) - mission_pool_size # Initial fill out of mission list and marking All-In mission - for mission in order: + for fill_mission in order: # Removing extra missions if mission pool is too small - if 0 < mission.removal_priority <= removals: + if 0 < fill_mission.removal_priority <= removals: mission_slots.append(SC2MissionSlot(SC2Campaign.GLOBAL, None)) - elif mission.type == MissionPools.FINAL: + elif fill_mission.type == MissionPools.FINAL: mission_slots.append(SC2MissionSlot(SC2Campaign.GLOBAL, final_mission)) else: - mission_slots.append(SC2MissionSlot(SC2Campaign.GLOBAL, mission.type)) + mission_slots.append(SC2MissionSlot(SC2Campaign.GLOBAL, fill_mission.type)) no_build_slots = [] easy_slots = [] diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 59c6fe900197..d01b814eb03b 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -1,24 +1,51 @@ -import typing from dataclasses import fields +import enum +import logging -from typing import List, Set, Iterable, Sequence, Dict, Callable, Union +from typing import * from math import floor, ceil -from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification +from dataclasses import dataclass +from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification, CollectionState from worlds.AutoWorld import WebWorld, World from . import ItemNames -from .Items import StarcraftItem, filler_items, get_item_table, get_full_item_list, \ +from .Items import StarcraftItem, filler_items, get_full_item_list, \ get_basic_units, ItemData, upgrade_included_names, progressive_if_nco, kerrigan_actives, kerrigan_passives, \ kerrigan_only_passives, progressive_if_ext, not_balanced_starting_units, spear_of_adun_calldowns, \ spear_of_adun_castable_passives, nova_equipment +from . import Items from .ItemGroups import item_name_groups -from .Locations import get_locations, LocationType, get_location_types, get_plando_locations +from .Locations import get_locations, get_location_types, get_plando_locations from .Regions import create_regions -from .Options import get_option_value, LocationInclusion, KerriganLevelItemDistribution, \ - KerriganPresence, KerriganPrimalStatus, RequiredTactics, kerrigan_unit_available, StarterUnit, SpearOfAdunPresence, \ - get_enabled_campaigns, SpearOfAdunAutonomouslyCastAbilityPresence, Starcraft2Options -from .PoolFilter import filter_items, get_item_upgrades, UPGRADABLE_ITEMS, missions_in_mission_table, get_used_races -from .MissionTables import MissionInfo, SC2Campaign, lookup_name_to_mission, SC2Mission, \ - SC2Race +from .Options import (get_option_value, LocationInclusion, KerriganLevelItemDistribution, + KerriganPresence, KerriganPrimalStatus, RequiredTactics, kerrigan_unit_available, StarterUnit, SpearOfAdunPresence, + get_enabled_campaigns, SpearOfAdunAutonomouslyCastAbilityPresence, Starcraft2Options, SpearOfAdunPresentInNoBuild +) +from .PoolFilter import filter_items, get_item_upgrades, get_used_races +from .MissionTables import ( + MissionInfo, SC2Campaign, lookup_name_to_mission, SC2Mission, SC2Race, MissionFlag +) + +logger = logging.getLogger("Starcraft 2") + + +class ItemFilterFlags(enum.IntFlag): + Available = 0 + Locked = enum.auto() + StartInventory = enum.auto() + NonLocal = enum.auto() + Removed = enum.auto() + Plando = enum.auto() + Excluded = enum.auto() + + Unremovable = Locked|StartInventory|Plando + + +@dataclass +class FilterItem: + name: str + data: ItemData + index: int = 0 + flags: ItemFilterFlags = ItemFilterFlags.Available class Starcraft2WebWorld(WebWorld): @@ -49,8 +76,9 @@ class SC2World(World): options: Starcraft2Options item_name_groups = item_name_groups - locked_locations: typing.List[str] - location_cache: typing.List[Location] + locked_locations: List[str] + """Locations locked to contain specific items, such as victory events or forced resources""" + location_cache: List[Location] mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = {} final_mission_id: int victory_item: str @@ -71,17 +99,34 @@ def create_regions(self): ) def create_items(self): + # Starcraft 2-specific item setup: + # * Filter item pool based on player options + # * Plando starter units + # * Start-inventory units if necessary for logic + # * Plando filler items based on location exclusions + # * If the item pool is less than the location count, add some filler items + setup_events(self.player, self.locked_locations, self.location_cache) - excluded_items = get_excluded_items(self) + item_list: List[FilterItem] = create_and_flag_explicit_item_locks_and_excludes(self) + flag_mission_based_item_excludes(self, item_list) + flag_start_inventory(self, item_list) + flag_unused_upgrade_types(self, item_list) + flag_excluded_item_sets(self, item_list) + flag_and_add_resource_locations(self, item_list) + pool: List[Item] = prune_item_pool(self, item_list) + pad_item_pool_with_filler(self, len(self.location_cache) - len(self.locked_locations) - len(pool), pool) + + # old + # excluded_items = get_excluded_items(self) - starter_items = assign_starter_items(self, excluded_items, self.locked_locations, self.location_cache) + # starter_items = assign_starter_items(self, excluded_items, self.locked_locations, self.location_cache) - fill_resource_locations(self, self.locked_locations, self.location_cache) + # fill_resource_locations(self, self.locked_locations, self.location_cache) - pool = get_item_pool(self, self.mission_req_table, starter_items, excluded_items, self.location_cache) + # pool = get_item_pool(self, self.mission_req_table, starter_items, excluded_items, self.location_cache) - fill_item_pool_with_dummy_items(self, self.locked_locations, self.location_cache, pool) + # fill_item_pool_with_dummy_items(self, self.locked_locations, self.location_cache, pool) self.multiworld.itempool += pool @@ -125,7 +170,7 @@ def fill_slot_data(self): return slot_data -def setup_events(player: int, locked_locations: typing.List[str], location_cache: typing.List[Location]): +def setup_events(player: int, locked_locations: List[str], location_cache: List[Location]): for location in location_cache: if location.address is None: item = Item(location.name, ItemClassification.progression, None, player) @@ -135,6 +180,392 @@ def setup_events(player: int, locked_locations: typing.List[str], location_cache location.place_locked_item(item) +def create_and_flag_explicit_item_locks_and_excludes(world: SC2World) -> List[FilterItem]: + """ + Handles `excluded_items`, `locked_items`, and `start_inventory` + Returns a list of all possible non-filler items that can be added, with an accompanying flags bitfield. + """ + excluded_items = world.options.excluded_items + locked_items = world.options.locked_items + start_inventory = world.options.start_inventory + result: List[FilterItem] = [] + for item_name, item_data in Items.item_table.items(): + if not item_data.quantity: + continue + max_count = item_data.quantity + excluded_count = excluded_items.get(item_name) + locked_count = locked_items.get(item_name) + start_count: Optional[int] = start_inventory.get(item_name) + # specifying 0 in the yaml means exclude / lock all + # start_inventory doesn't allow specifying 0 + # not specifying means don't exclude/lock/start + if excluded_count == 0: + excluded_count = max_count + elif excluded_count is None: + excluded_count = 0 + if locked_count == 0: + locked_count = max_count + elif locked_count is None: + locked_count = 0 + if start_count is None: + start_count = 0 + + # Priority: start_inventory >> locked_items >> excluded_items >> unspecified + if start_count > max_count: + logger.warning(f"Item {item_name} had start amount greater than maximum amount ({start_count} > {max_count}). Capping start amount to max.") + start_count = max_count + locked_count = 0 + excluded_count = 0 + elif locked_count + start_count > max_count: + logger.warning(f"Item {item_name} had locked + start amount greater than maximum amount " + f"({locked_count} + {start_count} > {max_count}). Capping locked amount to max - start.") + locked_count = max_count - start_count + excluded_count = 0 + elif excluded_count + locked_count + start_count > max_count: + logger.warning(f"Item {item_name} had excluded + locked + start amounts greater than maximum amount " + f"({excluded_count} + {locked_count} + {start_count} > {max_count}). Decreasing excluded amount.") + excluded_count = max_count - start_count - locked_count + for index in range(max_count - excluded_count): + result.append(FilterItem(item_name, item_data, index)) + if index < start_count: + result[-1].flags |= ItemFilterFlags.StartInventory + if index < locked_count + start_count: + result[-1].flags |= ItemFilterFlags.Locked + if item_name in world.options.non_local_items: + result[-1].flags |= ItemFilterFlags.NonLocal + return result + + +def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem]) -> None: + """ + Excludes items based on mission / campaign presence. e.g. Nova Gear is excluded if there are no Nova missions. + """ + missions: List[SC2Mission] = [] + for campaign in world.mission_req_table.values(): + missions.extend(mission_info.mission for _, mission_info in campaign.items()) + + kerrigan_missions = len([mission for mission in missions if MissionFlag.Kerrigan in mission.flags]) > 0 + nova_missions = [mission for mission in missions if MissionFlag.Nova in mission.flags] + + kerrigan_is_present = kerrigan_missions and world.options.kerrigan_presence == KerriganPresence.option_vanilla + + # TvZ build missions -- check flags Terran and VsZerg are true and NoBuild is false + tvz_build_mask = MissionFlag.Terran|MissionFlag.VsZerg|MissionFlag.NoBuild + tvz_expect_value = MissionFlag.Terran|MissionFlag.VsZerg + if world.options.take_over_ai_allies: + tvz_build_mask |= MissionFlag.AiTerranAlly + tvz_expect_value |= MissionFlag.AiTerranAlly + tvz_build_missions = [mission for mission in missions if tvz_build_mask & mission.flags == tvz_expect_value] + + # Check if SOA actives should be present + if world.options.spear_of_adun_presence == SpearOfAdunPresence.option_not_present: + soa_missions = missions + if not world.options.spear_of_adun_present_in_no_build: + soa_missions = [m for m in soa_missions if MissionFlag.NoBuild not in m.flags] + if world.options.spear_of_adun_presence == SpearOfAdunPresence.option_lotv_protoss: + soa_missions = [m for m in soa_missions if m.campaign == SC2Campaign.LOTV] + elif world.options.spear_of_adun_presence == SpearOfAdunPresence.option_protoss: + soa_missions = [m for m in soa_missions if MissionFlag.Protoss in m.flags] + + soa_presence = len(soa_missions) > 0 + else: + soa_presence = False + + # Check if SOA passives should be present + if world.options.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present: + soa_missions = missions + if not world.options.spear_of_adun_autonomously_cast_present_in_no_build: + soa_missions = [m for m in soa_missions if MissionFlag.NoBuild not in m.flags] + if world.options.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_lotv_protoss: + soa_missions = [m for m in soa_missions if m.campaign == SC2Campaign.LOTV] + elif world.options.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_protoss: + soa_missions = [m for m in soa_missions if MissionFlag.Protoss in m.flags] + + soa_passive_presence = len(soa_missions) > 0 + else: + soa_passive_presence = False + + for item in item_list: + # Filter Nova equipment if you never get Nova + if not nova_missions and item.data.type == Items.ItemType.Nova_Gear: + item.flags |= ItemFilterFlags.Excluded + + # Todo(mm): How should no-build only / grant_story_tech affect excluding Kerrigan items? + # Exclude Primal form based on Kerrigan presence or primal form option + if (item.data.type == Items.ItemType.Primal_Form + and ((not kerrigan_is_present) or world.options.kerrigan_primal_status != KerriganPrimalStatus.option_item) + ): + item.flags |= ItemFilterFlags.Excluded + + # Remove Kerrigan abilities if there's no kerrigan + if (item.data.type == Items.ItemType.Ability and not kerrigan_is_present): + item.flags |= ItemFilterFlags.Excluded + + # Remove Spear of Adun if it's off + if item.name in Items.spear_of_adun_calldowns and not soa_presence: + item.flags |= ItemFilterFlags.Excluded + + # Remove Spear of Adun passives + if item.name in Items.spear_of_adun_castable_passives and not soa_passive_presence: + item.flags |= ItemFilterFlags.Excluded + + # Remove Psi Disrupter and Hive Mind Emulator if you never play a build TvZ + if (item.name in (ItemNames.HIVE_MIND_EMULATOR, ItemNames.PSI_DISRUPTER) + and not tvz_build_missions + ): + item.flags |= ItemFilterFlags.Excluded + return + + +def flag_start_inventory(world: SC2World, item_list: List[FilterItem]) -> None: + """Adds items to start_inventory based on first mission logic and options like `starter_unit` and `start_primary_abilities`""" + first_mission_name = get_first_mission(world.mission_req_table).mission_name + starter_unit = int(world.options.starter_unit) + + # If starter_unit is off and the first mission doesn't have a no-logic location, force starter_unit on + if starter_unit == StarterUnit.option_off: + start_collection_state = CollectionState(world.multiworld) + starter_mission_locations = [location.name for location in world.location_cache + if location.parent_region + and location.parent_region.name == first_mission_name + and location.access_rule(start_collection_state)] + if not starter_mission_locations: + # Force early unit if first mission is impossible without one + starter_unit = StarterUnit.option_any_starter_unit + + if starter_unit != StarterUnit.option_off: + resolve_start_unit(world, item_list, starter_unit) + + resolve_start_abilities(world, item_list) + + +def resolve_start_unit(world: SC2World, item_list: List[FilterItem], starter_unit: int) -> None: + first_mission = get_first_mission(world.mission_req_table) + first_race = first_mission.race + + if first_race == SC2Race.ANY: + # If the first mission is a logic-less no-build + races = get_used_races(world.mission_req_table, world) + races.remove(SC2Race.ANY) + if first_mission.race in races: + # The campaign's race is in (At least one mission that's not logic-less no-build exists) + first_race = first_mission.campaign.race + elif len(races) > 0: + # The campaign only has logic-less no-build missions. Find any other valid race + first_race = world.random.choice(list(races)) + + if first_race != SC2Race.ANY: + possible_starter_items = { + item.name: item for item in item_list if (ItemFilterFlags.Plando|ItemFilterFlags.Excluded) & item.flags == 0 + } + + # The race of the early unit has been chosen + basic_units = get_basic_units(world, first_race) + if starter_unit == StarterUnit.option_balanced: + basic_units = basic_units.difference(not_balanced_starting_units) + if first_mission == SC2Mission.DARK_WHISPERS: + # Special case - you don't have a logicless location but need an AA + basic_units = basic_units.difference( + {ItemNames.ZEALOT, ItemNames.CENTURION, ItemNames.SENTINEL, ItemNames.BLOOD_HUNTER, + ItemNames.AVENGER, ItemNames.IMMORTAL, ItemNames.ANNIHILATOR, ItemNames.VANGUARD}) + if first_mission == SC2Mission.SUDDEN_STRIKE: + # Special case - cliffjumpers + basic_units = {ItemNames.REAPER, ItemNames.GOLIATH, ItemNames.SIEGE_TANK, ItemNames.VIKING, ItemNames.BANSHEE} + basic_unit_options = [ + item for item in possible_starter_items.values() + if item.name in basic_units + and ItemFilterFlags.StartInventory not in item.flags + ] + + # For Sudden Strike, starter units need an upgrade to help them get around + nco_support_items = { + ItemNames.REAPER: ItemNames.REAPER_SPIDER_MINES, + ItemNames.GOLIATH: ItemNames.GOLIATH_JUMP_JETS, + ItemNames.SIEGE_TANK: ItemNames.SIEGE_TANK_JUMP_JETS, + ItemNames.VIKING: ItemNames.VIKING_SMART_SERVOS, + } + if first_mission == SC2Mission.SUDDEN_STRIKE: + basic_unit_options = [ + item for item in basic_unit_options + if item.name not in nco_support_items + or nco_support_items[item.name] in possible_starter_items + and ((ItemFilterFlags.Plando|ItemFilterFlags.Excluded) & possible_starter_items[nco_support_items[item.name]].flags) == 0 + ] + if not basic_unit_options: + raise Exception("Early Unit: At least one basic unit must be included") + local_basic_unit = [item for item in basic_unit_options if ItemFilterFlags.NonLocal not in item.flags] + if local_basic_unit: + basic_unit_options = local_basic_unit + + unit = world.random.choice(basic_unit_options) + unit.flags |= ItemFilterFlags.StartInventory + + # NCO-only specific rules + if first_mission == SC2Mission.SUDDEN_STRIKE: + if unit.name in nco_support_items: + support_item = possible_starter_items[nco_support_items[unit.name]] + support_item.flags |= ItemFilterFlags.StartInventory + if ItemNames.NOVA_JUMP_SUIT_MODULE in possible_starter_items: + possible_starter_items[ItemNames.NOVA_JUMP_SUIT_MODULE].flags |= ItemFilterFlags.StartInventory + if MissionFlag.Nova in first_mission.flags: + possible_starter_weapons = ( + ItemNames.NOVA_HELLFIRE_SHOTGUN, + ItemNames.NOVA_PLASMA_RIFLE, + ItemNames.NOVA_PULSE_GRENADES, + ) + starter_weapon_options = [item for item in possible_starter_items.values() if item.name in possible_starter_weapons] + starter_weapon = world.random.choice(starter_weapon_options) + starter_weapon.flags |= ItemFilterFlags.StartInventory + mission_count = sum(len(campaign) for campaign in world.mission_req_table.values()) + if mission_count <= 10 and world.final_mission_id == SC2Mission.END_GAME.id: + if ItemNames.LIBERATOR_RAID_ARTILLERY in possible_starter_items: + possible_starter_items[ItemNames.LIBERATOR_RAID_ARTILLERY].flags |= ItemFilterFlags.StartInventory + else: + logger.warning(f"Tried and failed to add {ItemNames.LIBERATOR_RAID_ARTILLERY} to the start inventory because it was excluded or plando'd") + + +def resolve_start_abilities(world: SC2World, item_list: List[FilterItem]) -> None: + starter_abilities = world.options.start_primary_abilities + if not starter_abilities: + return + assert starter_abilities <= 4 + ability_count = int(starter_abilities) + ability_tiers = [0, 1, 3] + world.random.shuffle(ability_tiers) + if ability_count >= 4: + # Avoid picking an ultimate unless 4 starter abilities were asked for. + # Without this check, it would be possible to pick an ultimate if a previous tier failed + # to pick due to exclusions + ability_tiers.append(6) + for tier in ability_tiers: + abilities_in_tier = kerrigan_actives[tier].union(kerrigan_passives[tier]) + potential_starter_abilities = [ + item for item in item_list + if item.name in abilities_in_tier + and (ItemFilterFlags.Excluded|ItemFilterFlags.StartInventory|ItemFilterFlags.Plando) & item.flags == 0 + ] + # Try to avoid giving non-local items unless there is no alternative + abilities = [item for item in potential_starter_abilities if ItemFilterFlags.NonLocal not in item.flags] + if not abilities: + abilities = potential_starter_abilities + if abilities: + ability_count -= 1 + ability = world.random.choice(abilities) + ability.flags |= ItemFilterFlags.StartInventory + if ability_count <= 0: + break + + +def flag_unused_upgrade_types(world: SC2World, item_list: List[FilterItem]) -> None: + """Excludes +armour/attack upgrades based on generic upgrade strategy.""" + include_upgrades = world.options.generic_upgrade_missions == 0 + upgrade_items = world.options.generic_upgrade_items + for item in item_list: + if item.data.type == Items.ItemType.Upgrade: + if not include_upgrades or (item.name not in upgrade_included_names[upgrade_items]): + item.flags |= ItemFilterFlags.Excluded + + +def flag_excluded_item_sets(world: SC2World, item_list: List[FilterItem]) -> None: + """Excludes items based on item set options (`nco_items`, `bw_items`, `ext_items`)""" + # Note(mm): These options should just be removed in favour of better item sets and a single "vanilla only" switch + item_sets = {'wol', 'hots', 'lotv'} + if get_option_value(world, 'nco_items') or SC2Campaign.NCO in get_enabled_campaigns(world): + item_sets.add('nco') + if get_option_value(world, 'bw_items'): + item_sets.add('bw') + if get_option_value(world, 'ext_items'): + item_sets.add('ext') + + def allowed_quantity(name: str, data: ItemData) -> int: + if not data.origin.intersection(item_sets): + return 0 + elif name in progressive_if_nco and 'nco' not in item_sets: + return 1 + elif name in progressive_if_ext and 'ext' not in item_sets: + return 1 + else: + return data.quantity + + amounts: Dict[str, int] = {} + for item in item_list: + if ItemFilterFlags.Excluded in item.flags: + continue + max_quantity = allowed_quantity(item.name, item.data) + amount_in_pool = amounts.get(item.name, 0) + if max_quantity > amount_in_pool: + amounts[item.name] = amount_in_pool + 1 + else: + item.flags |= ItemFilterFlags.Excluded + + +def flag_and_add_resource_locations(world: SC2World, item_list: List[FilterItem]) -> None: + """ + Filters the locations in the world using a trash or Nothing item + :param world: The sc2 world object + :param item_list: The current list of items to append to + """ + open_locations = [location for location in world.location_cache if location.item is None] + plando_locations = get_plando_locations(world) + resource_location_types = get_location_types(world, LocationInclusion.option_resources) + location_data = {sc2_location.name: sc2_location for sc2_location in get_locations(world)} + for location in open_locations: + # Go through the locations that aren't locked yet (early unit, etc) + if location.name not in plando_locations: + # The location is not plando'd + sc2_location = location_data[location.name] + if sc2_location.type in resource_location_types: + item_name = world.random.choice(filler_items) + item = create_item_with_correct_settings(world.player, item_name) + location.place_locked_item(item) + item_list.append(FilterItem(item_name, Items.item_table[item_name], 0, ItemFilterFlags.Plando|ItemFilterFlags.Locked)) + world.locked_locations.append(location.name) + + +def prune_item_pool(world: SC2World, item_list: List[FilterItem]) -> List[Item]: + """Prunes the item pool size to be less than the number of available locations""" + + item_list = [item for item in item_list if ItemFilterFlags.Unremovable & item.flags or ItemFilterFlags.Excluded not in item.flags] + num_items = len(item_list) + last_num_items = -1 + while num_items != last_num_items: + # Remove orphan items until there are no more being removed + item_list = [item for item in item_list if ItemFilterFlags.Unremovable & item.flags or item_list_contains_parent(item.data, item_list)] + last_num_items = num_items + num_items = len(item_list) + + pool: List[Item] = [] + locked_items: List[Item] = [] + existing_items: List[Item] = [] + for item in item_list: + ap_item = create_item_with_correct_settings(world.player, item.name) + if ItemFilterFlags.StartInventory in item.flags: + existing_items.append(ap_item) + elif ItemFilterFlags.Locked in item.flags: + locked_items.append(ap_item) + else: + pool.append(ap_item) + + fill_pool_with_kerrigan_levels(world, pool) + filtered_pool = filter_items(world, world.mission_req_table, world.location_cache, pool, existing_items, locked_items) + return filtered_pool + + +def item_list_contains_parent(item_data: ItemData, item_list: List[FilterItem]) -> bool: + if item_data.parent_item is None: + # The item has no associated parent, the item is valid + return True + parent_item = item_data.parent_item + # Check if the pool contains the parent item + return parent_item in [item.name for item in item_list] + + +def pad_item_pool_with_filler(self: SC2World, num_items: int, pool: List[Item]): + for _ in range(num_items): + item = create_item_with_correct_settings(self.player, self.get_filler_item_name()) + pool.append(item) + + def get_excluded_items(world: World) -> Set[str]: excluded_items: Set[str] = set(get_option_value(world, 'excluded_items')) for item in world.multiworld.precollected_items[world.player]: @@ -176,19 +607,14 @@ def smart_exclude(item_choices: Set[str], choices_to_keep: int): kerrigan_presence = get_option_value(world, "kerrigan_presence") # Exclude Primal Form item if option is not set or Kerrigan is unavailable if get_option_value(world, "kerrigan_primal_status") != KerriganPrimalStatus.option_item or \ - (kerrigan_presence in {KerriganPresence.option_not_present, KerriganPresence.option_not_present_and_no_passives}): + (kerrigan_presence == KerriganPresence.option_not_present): excluded_items.add(ItemNames.KERRIGAN_PRIMAL_FORM) - # no Kerrigan & remove all passives => remove all abilities - if kerrigan_presence == KerriganPresence.option_not_present_and_no_passives: + # no Kerrigan, but keep non-Kerrigan passives + if kerrigan_presence == KerriganPresence.option_not_present: + smart_exclude(kerrigan_only_passives, 0) for tier in range(7): - smart_exclude(kerrigan_actives[tier].union(kerrigan_passives[tier]), 0) - else: - # no Kerrigan, but keep non-Kerrigan passives - if kerrigan_presence == KerriganPresence.option_not_present: - smart_exclude(kerrigan_only_passives, 0) - for tier in range(7): - smart_exclude(kerrigan_actives[tier], 0) + smart_exclude(kerrigan_actives[tier], 0) # SOA exclusion, other cases are handled by generic race logic if (soa_presence == SpearOfAdunPresence.option_lotv_protoss and SC2Campaign.LOTV not in enabled_campaigns) \ @@ -202,12 +628,12 @@ def smart_exclude(item_choices: Set[str], choices_to_keep: int): return excluded_items -def assign_starter_items(world: World, excluded_items: Set[str], locked_locations: List[str], location_cache: typing.List[Location]) -> List[Item]: +def assign_starter_items(world: SC2World, excluded_items: Set[str], locked_locations: List[str], location_cache: List[Location]) -> List[Item]: starter_items: List[Item] = [] non_local_items = get_option_value(world, "non_local_items") starter_unit = get_option_value(world, "starter_unit") enabled_campaigns = get_enabled_campaigns(world) - first_mission = get_first_mission(world.mission_req_table) + first_mission = get_first_mission(world.mission_req_table).mission_name # Ensuring that first mission is completable if starter_unit == StarterUnit.option_off: starter_mission_locations = [location.name for location in location_cache @@ -300,13 +726,12 @@ def assign_starter_items(world: World, excluded_items: Set[str], locked_location return starter_items -def get_first_mission(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> str: +def get_first_mission(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> SC2Mission: # The first world should also be the starting world campaigns = mission_req_table.keys() lowest_id = min([campaign.id for campaign in campaigns]) first_campaign = [campaign for campaign in campaigns if campaign.id == lowest_id][0] - first_mission = list(mission_req_table[first_campaign])[0] - return first_mission + return list(mission_req_table[first_campaign].values())[0].mission def add_starter_item(world: World, excluded_items: Set[str], item_list: Sequence[str]) -> Item: @@ -322,7 +747,7 @@ def add_starter_item(world: World, excluded_items: Set[str], item_list: Sequence return item -def get_item_pool(world: World, mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], +def get_item_pool(world: SC2World, mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], starter_items: List[Item], excluded_items: Set[str], location_cache: List[Location]) -> List[Item]: pool: List[Item] = [] @@ -360,8 +785,8 @@ def allowed_quantity(name: str, data: ItemData) -> int: else: return data.quantity - for name, data in get_item_table().items(): - for _ in range(allowed_quantity(name, data)): + for name, data in Items.item_table.items(): + for index in range(allowed_quantity(name, data)): item = create_item_with_correct_settings(world.player, name) if name in yaml_locked_items: locked_items.append(item) @@ -395,7 +820,7 @@ def fill_item_pool_with_dummy_items(self: SC2World, locked_locations: List[str], def create_item_with_correct_settings(player: int, name: str) -> Item: - data = get_full_item_list()[name] + data = Items.item_table[name] item = Item(name, data.classification, data.code, player) @@ -437,12 +862,6 @@ def fill_resource_locations(world: World, locked_locations: List[str], location_ locked_locations.append(location.name) -def place_exclusion_item(item_name, location, locked_locations, player): - item = create_item_with_correct_settings(player, item_name) - location.place_locked_item(item) - locked_locations.append(location.name) - - def fill_pool_with_kerrigan_levels(world: World, item_pool: List[Item]): total_levels = get_option_value(world, "kerrigan_level_item_sum") if get_option_value(world, "kerrigan_presence") not in kerrigan_unit_available \ diff --git a/worlds/sc2/test/test_base.py b/worlds/sc2/test/test_base.py index 28529e37edd5..ceabfc2b95d3 100644 --- a/worlds/sc2/test/test_base.py +++ b/worlds/sc2/test/test_base.py @@ -1,6 +1,6 @@ from typing import * -from test.TestBase import WorldTestBase +from test.bases import WorldTestBase from .. import SC2World from .. import Client diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py new file mode 100644 index 000000000000..d5d54ff96266 --- /dev/null +++ b/worlds/sc2/test/test_generation.py @@ -0,0 +1,68 @@ +""" +Unit tests for world generation +""" +from typing import * +import unittest +import random + +from .. import Options, MissionTables, ItemNames, Items +from .. import FilterItem, ItemFilterFlags, create_and_flag_explicit_item_locks_and_excludes, SC2World + +from BaseClasses import MultiWorld, CollectionState +from argparse import Namespace +from worlds import AutoWorld +from Generate import get_seed_name + +class Sc2SetupTestBase(unittest.TestCase): + seed: Optional[int] = None + game = SC2World.game + player = 1 + def setUp(self) -> None: + self.multiworld = MultiWorld(1) + self.multiworld.game[self.player] = self.game + self.multiworld.player_name = {self.player: "Tester"} + self.multiworld.set_seed(self.seed) + self.multiworld.state = CollectionState(self.multiworld) + random.seed(self.multiworld.seed) + self.multiworld.seed_name = get_seed_name(random) # only called to get same RNG progression as Generate.py + args = Namespace() + for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items(): + setattr(args, name, { + 1: option.from_any(self.options.get(name, option.default)) + }) + self.multiworld.set_options(args) + self.world = self.multiworld.worlds[self.player] + + +class TestItemFiltering(Sc2SetupTestBase): + options = { + 'locked_items': { + ItemNames.MARINE: 0, + ItemNames.MARAUDER: 0, + ItemNames.MEDIVAC: 1, + ItemNames.FIREBAT: 1, + ItemNames.ZEALOT: 0, + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2, + }, + 'excluded_items': { + ItemNames.MARINE: 0, + ItemNames.MARAUDER: 0, + ItemNames.MEDIVAC: 0, + ItemNames.FIREBAT: 1, + ItemNames.ZERGLING: 0, + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2, + } + } + def test_explicit_locks_excludes_interact_and_set_flags(self): + item_list = create_and_flag_explicit_item_locks_and_excludes(self.world) + self.assertNotIn(ItemNames.ZERGLING, [x.name for x in item_list], msg=f'{ItemNames.ZERGLING} did not get properly excluded') + self.assertIn(FilterItem(ItemNames.MARINE, Items.item_table[ItemNames.MARINE], flags=ItemFilterFlags.Locked), item_list) + self.assertIn(FilterItem(ItemNames.MARAUDER, Items.item_table[ItemNames.MARAUDER], flags=ItemFilterFlags.Locked), item_list) + self.assertIn(FilterItem(ItemNames.MEDIVAC, Items.item_table[ItemNames.MEDIVAC], flags=ItemFilterFlags.Locked), item_list) + self.assertIn(FilterItem(ItemNames.FIREBAT, Items.item_table[ItemNames.FIREBAT], flags=ItemFilterFlags.Locked), item_list) + self.assertIn(FilterItem(ItemNames.ZEALOT, Items.item_table[ItemNames.ZEALOT], flags=ItemFilterFlags.Locked), item_list) + self.assertIn(FilterItem(ItemNames.DRAGOON, Items.item_table[ItemNames.DRAGOON]), item_list) + regen_biosteel_items = [x for x in item_list if x.name == ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL] + self.assertEqual(len(regen_biosteel_items), 2) + + From a35d1af6635682a92f7e549e715d62333b0b4c2b Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 23 Apr 2024 00:22:21 -0700 Subject: [PATCH 02/48] sc2: Removed unused old item pool creation functions --- worlds/sc2/__init__.py | 291 ----------------------------------------- 1 file changed, 291 deletions(-) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index d01b814eb03b..f3af14d11d41 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -117,17 +117,6 @@ def create_items(self): pool: List[Item] = prune_item_pool(self, item_list) pad_item_pool_with_filler(self, len(self.location_cache) - len(self.locked_locations) - len(pool), pool) - # old - # excluded_items = get_excluded_items(self) - - # starter_items = assign_starter_items(self, excluded_items, self.locked_locations, self.location_cache) - - # fill_resource_locations(self, self.locked_locations, self.location_cache) - - # pool = get_item_pool(self, self.mission_req_table, starter_items, excluded_items, self.location_cache) - - # fill_item_pool_with_dummy_items(self, self.locked_locations, self.location_cache, pool) - self.multiworld.itempool += pool def set_rules(self): @@ -566,166 +555,6 @@ def pad_item_pool_with_filler(self: SC2World, num_items: int, pool: List[Item]): pool.append(item) -def get_excluded_items(world: World) -> Set[str]: - excluded_items: Set[str] = set(get_option_value(world, 'excluded_items')) - for item in world.multiworld.precollected_items[world.player]: - excluded_items.add(item.name) - locked_items: Set[str] = set(get_option_value(world, 'locked_items')) - # Starter items are also excluded items - starter_items: Set[str] = set(get_option_value(world, 'start_inventory')) - item_table = get_full_item_list() - soa_presence = get_option_value(world, "spear_of_adun_presence") - soa_autocast_presence = get_option_value(world, "spear_of_adun_autonomously_cast_ability_presence") - enabled_campaigns = get_enabled_campaigns(world) - - # Ensure no item is both guaranteed and excluded - invalid_items = excluded_items.intersection(locked_items) - invalid_count = len(invalid_items) - # Don't count starter items that can appear multiple times - invalid_count -= len([item for item in starter_items.intersection(locked_items) if item_table[item].quantity != 1]) - if invalid_count > 0: - raise Exception(f"{invalid_count} item{'s are' if invalid_count > 1 else ' is'} both locked and excluded from generation. Please adjust your excluded items and locked items.") - - def smart_exclude(item_choices: Set[str], choices_to_keep: int): - expected_choices = len(item_choices) - if expected_choices == 0: - return - item_choices = set(item_choices) - starter_choices = item_choices.intersection(starter_items) - excluded_choices = item_choices.intersection(excluded_items) - item_choices.difference_update(excluded_choices) - item_choices.difference_update(locked_items) - candidates = sorted(item_choices) - exclude_amount = min(expected_choices - choices_to_keep - len(excluded_choices) + len(starter_choices), len(candidates)) - if exclude_amount > 0: - excluded_items.update(world.random.sample(candidates, exclude_amount)) - - # Nova gear exclusion if NCO not in campaigns - if SC2Campaign.NCO not in enabled_campaigns: - excluded_items = excluded_items.union(nova_equipment) - - kerrigan_presence = get_option_value(world, "kerrigan_presence") - # Exclude Primal Form item if option is not set or Kerrigan is unavailable - if get_option_value(world, "kerrigan_primal_status") != KerriganPrimalStatus.option_item or \ - (kerrigan_presence == KerriganPresence.option_not_present): - excluded_items.add(ItemNames.KERRIGAN_PRIMAL_FORM) - - # no Kerrigan, but keep non-Kerrigan passives - if kerrigan_presence == KerriganPresence.option_not_present: - smart_exclude(kerrigan_only_passives, 0) - for tier in range(7): - smart_exclude(kerrigan_actives[tier], 0) - - # SOA exclusion, other cases are handled by generic race logic - if (soa_presence == SpearOfAdunPresence.option_lotv_protoss and SC2Campaign.LOTV not in enabled_campaigns) \ - or soa_presence == SpearOfAdunPresence.option_not_present: - excluded_items.update(spear_of_adun_calldowns) - if (soa_autocast_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_lotv_protoss \ - and SC2Campaign.LOTV not in enabled_campaigns) \ - or soa_autocast_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present: - excluded_items.update(spear_of_adun_castable_passives) - - return excluded_items - - -def assign_starter_items(world: SC2World, excluded_items: Set[str], locked_locations: List[str], location_cache: List[Location]) -> List[Item]: - starter_items: List[Item] = [] - non_local_items = get_option_value(world, "non_local_items") - starter_unit = get_option_value(world, "starter_unit") - enabled_campaigns = get_enabled_campaigns(world) - first_mission = get_first_mission(world.mission_req_table).mission_name - # Ensuring that first mission is completable - if starter_unit == StarterUnit.option_off: - starter_mission_locations = [location.name for location in location_cache - if location.parent_region.name == first_mission - and location.access_rule == Location.access_rule] - if not starter_mission_locations: - # Force early unit if first mission is impossible without one - starter_unit = StarterUnit.option_any_starter_unit - - if starter_unit != StarterUnit.option_off: - first_race = lookup_name_to_mission[first_mission].race - - if first_race == SC2Race.ANY: - # If the first mission is a logic-less no-build - mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = world.mission_req_table - races = get_used_races(mission_req_table, world) - races.remove(SC2Race.ANY) - if lookup_name_to_mission[first_mission].race in races: - # The campaign's race is in (At least one mission that's not logic-less no-build exists) - first_race = lookup_name_to_mission[first_mission].campaign.race - elif len(races) > 0: - # The campaign only has logic-less no-build missions. Find any other valid race - first_race = world.random.choice(list(races)) - - if first_race != SC2Race.ANY: - # The race of the early unit has been chosen - basic_units = get_basic_units(world, first_race) - if starter_unit == StarterUnit.option_balanced: - basic_units = basic_units.difference(not_balanced_starting_units) - if first_mission == SC2Mission.DARK_WHISPERS.mission_name: - # Special case - you don't have a logicless location but need an AA - basic_units = basic_units.difference( - {ItemNames.ZEALOT, ItemNames.CENTURION, ItemNames.SENTINEL, ItemNames.BLOOD_HUNTER, - ItemNames.AVENGER, ItemNames.IMMORTAL, ItemNames.ANNIHILATOR, ItemNames.VANGUARD}) - if first_mission == SC2Mission.SUDDEN_STRIKE.mission_name: - # Special case - cliffjumpers - basic_units = {ItemNames.REAPER, ItemNames.GOLIATH, ItemNames.SIEGE_TANK, ItemNames.VIKING, ItemNames.BANSHEE} - local_basic_unit = sorted(item for item in basic_units if item not in non_local_items and item not in excluded_items) - if not local_basic_unit: - # Drop non_local_items constraint - local_basic_unit = sorted(item for item in basic_units if item not in excluded_items) - if not local_basic_unit: - raise Exception("Early Unit: At least one basic unit must be included") - - unit: Item = add_starter_item(world, excluded_items, local_basic_unit) - starter_items.append(unit) - - # NCO-only specific rules - if first_mission == SC2Mission.SUDDEN_STRIKE.mission_name: - support_item: Union[str, None] = None - if unit.name == ItemNames.REAPER: - support_item = ItemNames.REAPER_SPIDER_MINES - elif unit.name == ItemNames.GOLIATH: - support_item = ItemNames.GOLIATH_JUMP_JETS - elif unit.name == ItemNames.SIEGE_TANK: - support_item = ItemNames.SIEGE_TANK_JUMP_JETS - elif unit.name == ItemNames.VIKING: - support_item = ItemNames.VIKING_SMART_SERVOS - if support_item is not None: - starter_items.append(add_starter_item(world, excluded_items, [support_item])) - starter_items.append(add_starter_item(world, excluded_items, [ItemNames.NOVA_JUMP_SUIT_MODULE])) - starter_items.append( - add_starter_item(world, excluded_items, - [ - ItemNames.NOVA_HELLFIRE_SHOTGUN, - ItemNames.NOVA_PLASMA_RIFLE, - ItemNames.NOVA_PULSE_GRENADES - ])) - if enabled_campaigns == {SC2Campaign.NCO}: - starter_items.append(add_starter_item(world, excluded_items, [ItemNames.LIBERATOR_RAID_ARTILLERY])) - - starter_abilities = get_option_value(world, 'start_primary_abilities') - assert isinstance(starter_abilities, int) - if starter_abilities: - ability_count = starter_abilities - ability_tiers = [0, 1, 3] - world.random.shuffle(ability_tiers) - if ability_count > 3: - ability_tiers.append(6) - for tier in ability_tiers: - abilities = kerrigan_actives[tier].union(kerrigan_passives[tier]).difference(excluded_items, non_local_items) - if not abilities: - abilities = kerrigan_actives[tier].union(kerrigan_passives[tier]).difference(excluded_items) - if abilities: - ability_count -= 1 - starter_items.append(add_starter_item(world, excluded_items, list(abilities))) - if ability_count == 0: - break - - return starter_items - - def get_first_mission(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> SC2Mission: # The first world should also be the starting world campaigns = mission_req_table.keys() @@ -734,91 +563,6 @@ def get_first_mission(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo return list(mission_req_table[first_campaign].values())[0].mission -def add_starter_item(world: World, excluded_items: Set[str], item_list: Sequence[str]) -> Item: - - item_name = world.random.choice(sorted(item_list)) - - excluded_items.add(item_name) - - item = create_item_with_correct_settings(world.player, item_name) - - world.multiworld.push_precollected(item) - - return item - - -def get_item_pool(world: SC2World, mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], - starter_items: List[Item], excluded_items: Set[str], location_cache: List[Location]) -> List[Item]: - pool: List[Item] = [] - - # For the future: goal items like Artifact Shards go here - locked_items = [] - - # YAML items - yaml_locked_items = get_option_value(world, 'locked_items') - assert not isinstance(yaml_locked_items, int) - - # Adjust generic upgrade availability based on options - include_upgrades = get_option_value(world, 'generic_upgrade_missions') == 0 - upgrade_items = get_option_value(world, 'generic_upgrade_items') - assert isinstance(upgrade_items, int) - - # Include items from outside main campaigns - item_sets = {'wol', 'hots', 'lotv'} - if get_option_value(world, 'nco_items') \ - or SC2Campaign.NCO in get_enabled_campaigns(world): - item_sets.add('nco') - if get_option_value(world, 'bw_items'): - item_sets.add('bw') - if get_option_value(world, 'ext_items'): - item_sets.add('ext') - - def allowed_quantity(name: str, data: ItemData) -> int: - if name in excluded_items \ - or data.type == "Upgrade" and (not include_upgrades or name not in upgrade_included_names[upgrade_items]) \ - or not data.origin.intersection(item_sets): - return 0 - elif name in progressive_if_nco and 'nco' not in item_sets: - return 1 - elif name in progressive_if_ext and 'ext' not in item_sets: - return 1 - else: - return data.quantity - - for name, data in Items.item_table.items(): - for index in range(allowed_quantity(name, data)): - item = create_item_with_correct_settings(world.player, name) - if name in yaml_locked_items: - locked_items.append(item) - else: - pool.append(item) - - existing_items = starter_items + [item for item in world.multiworld.precollected_items[world.player] if item not in starter_items] - existing_names = [item.name for item in existing_items] - - # Check the parent item integrity, exclude items - pool[:] = [item for item in pool if pool_contains_parent(item, pool + locked_items + existing_items)] - - # Removing upgrades for excluded items - for item_name in excluded_items: - if item_name in existing_names: - continue - invalid_upgrades = get_item_upgrades(pool, item_name) - for invalid_upgrade in invalid_upgrades: - pool.remove(invalid_upgrade) - - fill_pool_with_kerrigan_levels(world, pool) - filtered_pool = filter_items(world, mission_req_table, location_cache, pool, existing_items, locked_items) - return filtered_pool - - -def fill_item_pool_with_dummy_items(self: SC2World, locked_locations: List[str], - location_cache: List[Location], pool: List[Item]): - for _ in range(len(location_cache) - len(locked_locations) - len(pool)): - item = create_item_with_correct_settings(self.player, self.get_filler_item_name()) - pool.append(item) - - def create_item_with_correct_settings(player: int, name: str) -> Item: data = Items.item_table[name] @@ -827,41 +571,6 @@ def create_item_with_correct_settings(player: int, name: str) -> Item: return item -def pool_contains_parent(item: Item, pool: Iterable[Item]): - item_data = get_full_item_list().get(item.name) - if item_data.parent_item is None: - # The item has not associated parent, the item is valid - return True - parent_item = item_data.parent_item - # Check if the pool contains the parent item - return parent_item in [pool_item.name for pool_item in pool] - - -def fill_resource_locations(world: World, locked_locations: List[str], location_cache: List[Location]): - """ - Filters the locations in the world using a trash or Nothing item - :param multiworld: - :param player: - :param locked_locations: - :param location_cache: - :return: - """ - open_locations = [location for location in location_cache if location.item is None] - plando_locations = get_plando_locations(world) - resource_location_types = get_location_types(world, LocationInclusion.option_resources) - location_data = {sc2_location.name: sc2_location for sc2_location in get_locations(world)} - for location in open_locations: - # Go through the locations that aren't locked yet (early unit, etc) - if location.name not in plando_locations: - # The location is not plando'd - sc2_location = location_data[location.name] - if sc2_location.type in resource_location_types: - item_name = world.random.choice(filler_items) - item = create_item_with_correct_settings(world.player, item_name) - location.place_locked_item(item) - locked_locations.append(location.name) - - def fill_pool_with_kerrigan_levels(world: World, item_pool: List[Item]): total_levels = get_option_value(world, "kerrigan_level_item_sum") if get_option_value(world, "kerrigan_presence") not in kerrigan_unit_available \ From 8c14b53459f3dd8fe83fdca296a0e70530c3d3be Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 23 Apr 2024 00:22:53 -0700 Subject: [PATCH 03/48] sc2: Fixing a function naming inconsistency --- worlds/sc2/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index f3af14d11d41..1fd46cd10e75 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -323,12 +323,12 @@ def flag_start_inventory(world: SC2World, item_list: List[FilterItem]) -> None: starter_unit = StarterUnit.option_any_starter_unit if starter_unit != StarterUnit.option_off: - resolve_start_unit(world, item_list, starter_unit) + flag_start_unit(world, item_list, starter_unit) - resolve_start_abilities(world, item_list) + flag_start_abilities(world, item_list) -def resolve_start_unit(world: SC2World, item_list: List[FilterItem], starter_unit: int) -> None: +def flag_start_unit(world: SC2World, item_list: List[FilterItem], starter_unit: int) -> None: first_mission = get_first_mission(world.mission_req_table) first_race = first_mission.race @@ -413,7 +413,7 @@ def resolve_start_unit(world: SC2World, item_list: List[FilterItem], starter_uni logger.warning(f"Tried and failed to add {ItemNames.LIBERATOR_RAID_ARTILLERY} to the start inventory because it was excluded or plando'd") -def resolve_start_abilities(world: SC2World, item_list: List[FilterItem]) -> None: +def flag_start_abilities(world: SC2World, item_list: List[FilterItem]) -> None: starter_abilities = world.options.start_primary_abilities if not starter_abilities: return From 09f700fe4de9d8a7efbbec56b19a4f780829b367 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 23 Apr 2024 00:24:37 -0700 Subject: [PATCH 04/48] sc2: Cleaning up some imports --- worlds/sc2/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 1fd46cd10e75..ea4a7895eab2 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -8,21 +8,21 @@ from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification, CollectionState from worlds.AutoWorld import WebWorld, World from . import ItemNames -from .Items import StarcraftItem, filler_items, get_full_item_list, \ - get_basic_units, ItemData, upgrade_included_names, progressive_if_nco, kerrigan_actives, kerrigan_passives, \ - kerrigan_only_passives, progressive_if_ext, not_balanced_starting_units, spear_of_adun_calldowns, \ - spear_of_adun_castable_passives, nova_equipment +from .Items import (StarcraftItem, filler_items, get_full_item_list, + get_basic_units, ItemData, upgrade_included_names, progressive_if_nco, kerrigan_actives, kerrigan_passives, + progressive_if_ext, not_balanced_starting_units, +) from . import Items from .ItemGroups import item_name_groups from .Locations import get_locations, get_location_types, get_plando_locations from .Regions import create_regions from .Options import (get_option_value, LocationInclusion, KerriganLevelItemDistribution, - KerriganPresence, KerriganPrimalStatus, RequiredTactics, kerrigan_unit_available, StarterUnit, SpearOfAdunPresence, - get_enabled_campaigns, SpearOfAdunAutonomouslyCastAbilityPresence, Starcraft2Options, SpearOfAdunPresentInNoBuild + KerriganPresence, KerriganPrimalStatus, kerrigan_unit_available, StarterUnit, SpearOfAdunPresence, + get_enabled_campaigns, SpearOfAdunAutonomouslyCastAbilityPresence, Starcraft2Options ) -from .PoolFilter import filter_items, get_item_upgrades, get_used_races +from .PoolFilter import filter_items, get_used_races from .MissionTables import ( - MissionInfo, SC2Campaign, lookup_name_to_mission, SC2Mission, SC2Race, MissionFlag + MissionInfo, SC2Campaign, SC2Mission, SC2Race, MissionFlag ) logger = logging.getLogger("Starcraft 2") From c8a8a3381e794b797b22995e0025c0a0d45691a3 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 27 Apr 2024 02:22:59 -0700 Subject: [PATCH 05/48] sc2: Refactored ItemType enum and updated all usages to compare against enum values --- worlds/sc2/Client.py | 34 +- worlds/sc2/ItemGroups.py | 11 +- worlds/sc2/Items.py | 1365 +++++++++++++++++------------------ worlds/sc2/MissionTables.py | 4 +- worlds/sc2/PoolFilter.py | 19 +- worlds/sc2/__init__.py | 8 +- 6 files changed, 712 insertions(+), 729 deletions(-) diff --git a/worlds/sc2/Client.py b/worlds/sc2/Client.py index 7290613118fa..94fbf2babc10 100644 --- a/worlds/sc2/Client.py +++ b/worlds/sc2/Client.py @@ -31,7 +31,7 @@ LocationInclusion, ExtraLocations, MasteryLocations, ChallengeLocations, VanillaLocations, DisableForcedCamera, SkipCutscenes, GrantStoryTech, GrantStoryLevels, TakeOverAIAllies, RequiredTactics, SpearOfAdunPresence, SpearOfAdunPresentInNoBuild, SpearOfAdunAutonomouslyCastAbilityPresence, - SpearOfAdunAutonomouslyCastPresentInNoBuild, MineralsPerItem, VespenePerItem, StartingSupplyPerItem, + SpearOfAdunAutonomouslyCastPresentInNoBuild, ) @@ -46,7 +46,10 @@ from worlds._sc2common.bot.data import Race from worlds._sc2common.bot.main import run_game from worlds._sc2common.bot.player import Bot -from worlds.sc2.Items import lookup_id_to_name, get_full_item_list, ItemData, type_flaggroups, upgrade_numbers, upgrade_numbers_all +from worlds.sc2.Items import ( + lookup_id_to_name, get_full_item_list, ItemData, type_flaggroups, upgrade_numbers, upgrade_numbers_all, + race_to_item_type, upgrade_item_types, +) from worlds.sc2.Locations import SC2WOL_LOC_ID_OFFSET, LocationType, SC2HOTS_LOC_ID_OFFSET from worlds.sc2.MissionTables import lookup_id_to_mission, SC2Campaign, lookup_name_to_mission, \ lookup_id_to_campaign, MissionConnection, SC2Mission, campaign_mission_table, SC2Race @@ -801,7 +804,10 @@ def calculate_items(ctx: SC2Context) -> typing.Dict[SC2Race, typing.List[int]]: items.extend(compat_item_to_network_items(compat_item)) network_item: NetworkItem - accumulators: typing.Dict[SC2Race, typing.List[int]] = {race: [0 for _ in type_flaggroups[race]] for race in SC2Race} + accumulators: typing.Dict[SC2Race, typing.List[int]] = { + race: [0 for _ in item_type_enum_class] + for race, item_type_enum_class in race_to_item_type.items() + } # Protoss Shield grouped item specific logic shields_from_ground_upgrade: int = 0 @@ -814,14 +820,14 @@ def calculate_items(ctx: SC2Context) -> typing.Dict[SC2Race, typing.List[int]]: # exists exactly once if item_data.quantity == 1: - accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] |= 1 << item_data.number + accumulators[item_data.race][item_data.type.flag_word] |= 1 << item_data.number # exists multiple times - elif item_data.type in ["Upgrade", "Progressive Upgrade","Progressive Upgrade 2"]: - flaggroup = type_flaggroups[item_data.race][item_data.type] + elif item_data.quantity > 1: + flaggroup = item_data.type.flag_word # Generic upgrades apply only to Weapon / Armor upgrades - if item_data.type != "Upgrade" or ctx.generic_upgrade_items == 0: + if item_data.type not in upgrade_item_types or ctx.generic_upgrade_items == 0: accumulators[item_data.race][flaggroup] += 1 << item_data.number else: if name == ItemNames.PROGRESSIVE_PROTOSS_GROUND_UPGRADE: @@ -840,28 +846,28 @@ def calculate_items(ctx: SC2Context) -> typing.Dict[SC2Race, typing.List[int]]: # sum else: if name == ItemNames.STARTING_MINERALS: - accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += ctx.minerals_per_item + accumulators[item_data.race][item_data.type.flag_word] += ctx.minerals_per_item elif name == ItemNames.STARTING_VESPENE: - accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += ctx.vespene_per_item + accumulators[item_data.race][item_data.type.flag_word] += ctx.vespene_per_item elif name == ItemNames.STARTING_SUPPLY: - accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += ctx.starting_supply_per_item + accumulators[item_data.race][item_data.type.flag_word] += ctx.starting_supply_per_item else: - accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += item_data.number + accumulators[item_data.race][item_data.type.flag_word] += item_data.number # Fix Shields from generic upgrades by unit class (Maximum of ground/air upgrades) if shields_from_ground_upgrade > 0 or shields_from_air_upgrade > 0: shield_upgrade_level = max(shields_from_ground_upgrade, shields_from_air_upgrade) shield_upgrade_item = item_list[ItemNames.PROGRESSIVE_PROTOSS_SHIELDS] for _ in range(0, shield_upgrade_level): - accumulators[shield_upgrade_item.race][type_flaggroups[shield_upgrade_item.race][shield_upgrade_item.type]] += 1 << shield_upgrade_item.number + accumulators[shield_upgrade_item.race][item_data.type.flag_word] += 1 << shield_upgrade_item.number # Upgrades from completed missions if ctx.generic_upgrade_missions > 0: total_missions = sum(len(ctx.mission_req_table[campaign]) for campaign in ctx.mission_req_table) for race in SC2Race: - if "Upgrade" not in type_flaggroups[race]: + if race == SC2Race.ANY: continue - upgrade_flaggroup = type_flaggroups[race]["Upgrade"] + upgrade_flaggroup = race_to_item_type[race]["Upgrade"].flag_word num_missions = ctx.generic_upgrade_missions * total_missions amounts = [ num_missions // 100, diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index a77fb920f64d..866ca3b43850 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -47,15 +47,8 @@ # All items get sorted into their data type for item, data in Items.get_full_item_list().items(): - # Items get assigned to their flaggroup's type - item_name_groups.setdefault(data.type, []).append(item) - # Numbered flaggroups get sorted into an unnumbered group - # Currently supports numbers of one or two digits - if data.type[-2:].strip().isnumeric: - type_group = data.type[:-2].strip() - item_name_groups.setdefault(type_group, []).append(item) - # Flaggroups with numbers are unlisted - unlisted_item_name_groups.add(data.type) + # Items get assigned to their flaggroup's display type + item_name_groups.setdefault(data.type.display_name, []).append(item) # Items with a bracket get a short-hand name group for ease of use in YAMLs if '(' in item: short_name = item[:item.find(' (')] diff --git a/worlds/sc2/Items.py b/worlds/sc2/Items.py index 2ac607ae4c9b..08b8628939bf 100644 --- a/worlds/sc2/Items.py +++ b/worlds/sc2/Items.py @@ -1,4 +1,4 @@ -import inspect +from typing import * from pydoc import describe from BaseClasses import Item, ItemClassification, MultiWorld @@ -11,44 +11,77 @@ from worlds.AutoWorld import World -class ItemType(enum.StrEnum): - Unit = "Unit" - Unit_2 = "Unit 2" - Upgrade = "Upgrade" - Building = "Building" - Progressive = "Progressive Upgrade" - Progressive_2 = "Progressive Upgrade 2" - Armory_1 = "Armory 1" - Armory_2 = "Armory 2" - Armory_3 = "Armory 3" - Armory_4 = "Armory 4" - Armory_5 = "Armory 5" - Armory_6 = "Armory 6" - Nova_Gear = "Nova Gear" - Mercenary = "Mercenary" - Laboratory = "Laboratory" - Morph = "Morph" - - Evolution_Pit = "Evolution Pit" - Strain = "Strain" - Ability = "Ability" - Passive_Ability = "Passive Ability" - Level = "Level" - Primal_Form = "Primal Form" - Mutation_1 = "Mutation 1" - Mutation_2 = "Mutation 2" - Mutation_3 = "Mutation 3" - - Spear_Of_Adun = "Spear of Adun" - Solarite_Core = "Solarite Core" - Forge_1 = "Forge 1" - Forge_2 = "Forge 2" - Forge_3 = "Forge 3" - - Minerals = "Minerals" - Vespene = "Vespene" - Supply = "Supply" - Nothing = "Nothing Group" +class ItemTypeEnum(enum.Enum): + def __new__(cls, *args, **kwargs): + value = len(cls.__members__) + 1 + obj = object.__new__(cls) + obj._value_ = value + return obj + + def __init__(self, name: str, flag_word: int): + self.display_name = name + self.flag_word = flag_word + + +class TerranItemType(ItemTypeEnum): + Armory_1 = "Armory", 0 + Armory_2 = "Armory", 1 + Armory_3 = "Armory", 2 + Armory_4 = "Armory", 3 + Armory_5 = "Armory", 4 + Armory_6 = "Armory", 5 + Progressive = "Progressive Upgrade", 6 + Laboratory = "Laboratory", 7 + Upgrade = "Upgrade", 8 + Unit = "Unit", 9 + Building = "Building", 10 + Mercenary = "Mercenary", 11 + Nova_Gear = "Nova Gear", 12 + Progressive_2 = "Progressive Upgrade", 13 + + +class ZergItemType(ItemTypeEnum): + Ability = "Ability", 0 + Mutation_1 = "Mutation", 1 + Strain = "Strain", 2 + Morph = "Morph", 3 + Upgrade = "Upgrade", 4 + Mercenary = "Mercenary", 5 + Unit = "Unit", 6 + Level = "Level", 7 + Primal_Form = "Primal Form", 8 + Evolution_Pit = "Evolution Pit", 9 + Mutation_2 = "Mutation", 10 + Mutation_3 = "Mutation", 11 + + +class ProtossItemType(ItemTypeEnum): + Unit = "Unit", 0 + Unit_2 = "Unit 2", 1 + Upgrade = "Upgrade", 2 + Building = "Building", 3 + Progressive = "Progressive Upgrade", 4 + Spear_Of_Adun = "Spear of Adun", 5 + Solarite_Core = "Solarite Core", 6 + Forge_1 = "Forge 1", 7 + Forge_2 = "Forge 2", 8 + Forge_3 = "Forge 3", 9 + + +class FactionlessItemType(ItemTypeEnum): + Minerals = "Minerals", 0 + Vespene = "Vespene", 1 + Supply = "Supply", 2 + Nothing = "Nothing Group", 4 + + +ItemType = Union[TerranItemType, ZergItemType, ProtossItemType, FactionlessItemType] +race_to_item_type: Dict[SC2Race, Type[ItemTypeEnum]] = { + SC2Race.ANY: FactionlessItemType, + SC2Race.TERRAN: TerranItemType, + SC2Race.ZERG: ZergItemType, + SC2Race.PROTOSS: ProtossItemType, +} class ItemOrigin(enum.IntFlag): @@ -96,1448 +129,1448 @@ def get_full_item_list(): item_table = { # WoL ItemNames.MARINE: - ItemData(0 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 0, SC2Race.TERRAN, + ItemData(0 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 0, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.MEDIC: - ItemData(1 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 1, SC2Race.TERRAN, + ItemData(1 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 1, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.FIREBAT: - ItemData(2 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 2, SC2Race.TERRAN, + ItemData(2 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 2, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.MARAUDER: - ItemData(3 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 3, SC2Race.TERRAN, + ItemData(3 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 3, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.REAPER: - ItemData(4 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 4, SC2Race.TERRAN, + ItemData(4 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 4, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.HELLION: - ItemData(5 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 5, SC2Race.TERRAN, + ItemData(5 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 5, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.VULTURE: - ItemData(6 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 6, SC2Race.TERRAN, + ItemData(6 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 6, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.GOLIATH: - ItemData(7 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 7, SC2Race.TERRAN, + ItemData(7 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 7, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.DIAMONDBACK: - ItemData(8 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 8, SC2Race.TERRAN, + ItemData(8 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 8, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.SIEGE_TANK: - ItemData(9 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 9, SC2Race.TERRAN, + ItemData(9 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 9, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.MEDIVAC: - ItemData(10 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 10, SC2Race.TERRAN, + ItemData(10 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 10, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.WRAITH: - ItemData(11 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 11, SC2Race.TERRAN, + ItemData(11 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 11, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.VIKING: - ItemData(12 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 12, SC2Race.TERRAN, + ItemData(12 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 12, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.BANSHEE: - ItemData(13 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 13, SC2Race.TERRAN, + ItemData(13 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 13, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.BATTLECRUISER: - ItemData(14 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 14, SC2Race.TERRAN, + ItemData(14 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 14, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.GHOST: - ItemData(15 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 15, SC2Race.TERRAN, + ItemData(15 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 15, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.SPECTRE: - ItemData(16 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 16, SC2Race.TERRAN, + ItemData(16 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 16, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.THOR: - ItemData(17 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 17, SC2Race.TERRAN, + ItemData(17 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 17, SC2Race.TERRAN, classification=ItemClassification.progression), # EE units ItemNames.LIBERATOR: - ItemData(18 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 18, SC2Race.TERRAN, + ItemData(18 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 18, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"nco", "ext"}), ItemNames.VALKYRIE: - ItemData(19 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 19, SC2Race.TERRAN, + ItemData(19 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 19, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"bw"}), ItemNames.WIDOW_MINE: - ItemData(20 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 20, SC2Race.TERRAN, + ItemData(20 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 20, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), ItemNames.CYCLONE: - ItemData(21 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 21, SC2Race.TERRAN, + ItemData(21 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 21, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), ItemNames.HERC: - ItemData(22 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 26, SC2Race.TERRAN, + ItemData(22 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 26, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), ItemNames.WARHOUND: - ItemData(23 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 27, SC2Race.TERRAN, + ItemData(23 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 27, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), # Some other items are moved to Upgrade group because of the way how the bot message is parsed - ItemNames.PROGRESSIVE_TERRAN_INFANTRY_WEAPON: ItemData(100 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 0, SC2Race.TERRAN, quantity=3,), - ItemNames.PROGRESSIVE_TERRAN_INFANTRY_ARMOR: ItemData(102 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 2, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_VEHICLE_WEAPON: ItemData(103 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 4, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_VEHICLE_ARMOR: ItemData(104 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 6, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_SHIP_WEAPON: ItemData(105 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 8, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_SHIP_ARMOR: ItemData(106 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 10, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_WEAPON: ItemData(100 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 0, SC2Race.TERRAN, quantity=3,), + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_ARMOR: ItemData(102 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 2, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_WEAPON: ItemData(103 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 4, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_ARMOR: ItemData(104 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 6, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_SHIP_WEAPON: ItemData(105 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 8, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_SHIP_ARMOR: ItemData(106 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 10, SC2Race.TERRAN, quantity=3), # Upgrade bundle 'number' values are used as indices to get affected 'number's - ItemNames.PROGRESSIVE_TERRAN_WEAPON_UPGRADE: ItemData(107 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 0, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_ARMOR_UPGRADE: ItemData(108 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 1, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE: ItemData(109 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 2, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE: ItemData(110 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 3, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_SHIP_UPGRADE: ItemData(111 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 4, SC2Race.TERRAN, quantity=3), - ItemNames.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE: ItemData(112 + SC2WOL_ITEM_ID_OFFSET, ItemType.Upgrade, 5, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_WEAPON_UPGRADE: ItemData(107 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 0, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_ARMOR_UPGRADE: ItemData(108 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 1, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE: ItemData(109 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 2, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE: ItemData(110 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 3, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_SHIP_UPGRADE: ItemData(111 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 4, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE: ItemData(112 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 5, SC2Race.TERRAN, quantity=3), # Unit and structure upgrades ItemNames.BUNKER_PROJECTILE_ACCELERATOR: - ItemData(200 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 0, SC2Race.TERRAN, + ItemData(200 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 0, SC2Race.TERRAN, parent_item=ItemNames.BUNKER), ItemNames.BUNKER_NEOSTEEL_BUNKER: - ItemData(201 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 1, SC2Race.TERRAN, + ItemData(201 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 1, SC2Race.TERRAN, parent_item=ItemNames.BUNKER), ItemNames.MISSILE_TURRET_TITANIUM_HOUSING: - ItemData(202 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 2, SC2Race.TERRAN, + ItemData(202 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 2, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MISSILE_TURRET), ItemNames.MISSILE_TURRET_HELLSTORM_BATTERIES: - ItemData(203 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 3, SC2Race.TERRAN, + ItemData(203 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 3, SC2Race.TERRAN, parent_item=ItemNames.MISSILE_TURRET), ItemNames.SCV_ADVANCED_CONSTRUCTION: - ItemData(204 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 4, SC2Race.TERRAN), + ItemData(204 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 4, SC2Race.TERRAN), ItemNames.SCV_DUAL_FUSION_WELDERS: - ItemData(205 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 5, SC2Race.TERRAN), + ItemData(205 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 5, SC2Race.TERRAN), ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM: - ItemData(206 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 24, SC2Race.TERRAN, + ItemData(206 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 24, SC2Race.TERRAN, quantity=2), ItemNames.PROGRESSIVE_ORBITAL_COMMAND: - ItemData(207 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 26, SC2Race.TERRAN, + ItemData(207 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 26, SC2Race.TERRAN, quantity=2, classification=ItemClassification.progression), ItemNames.MARINE_PROGRESSIVE_STIMPACK: - ItemData(208 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 0, SC2Race.TERRAN, + ItemData(208 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 0, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.MARINE, quantity=2), ItemNames.MARINE_COMBAT_SHIELD: - ItemData(209 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 9, SC2Race.TERRAN, + ItemData(209 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 9, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.MARINE), ItemNames.MEDIC_ADVANCED_MEDIC_FACILITIES: - ItemData(210 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 10, SC2Race.TERRAN, + ItemData(210 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 10, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIC), ItemNames.MEDIC_STABILIZER_MEDPACKS: - ItemData(211 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 11, SC2Race.TERRAN, + ItemData(211 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 11, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.MEDIC), ItemNames.FIREBAT_INCINERATOR_GAUNTLETS: - ItemData(212 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 12, SC2Race.TERRAN, + ItemData(212 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 12, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.FIREBAT), ItemNames.FIREBAT_JUGGERNAUT_PLATING: - ItemData(213 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 13, SC2Race.TERRAN, + ItemData(213 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 13, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT), ItemNames.MARAUDER_CONCUSSIVE_SHELLS: - ItemData(214 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 14, SC2Race.TERRAN, + ItemData(214 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 14, SC2Race.TERRAN, parent_item=ItemNames.MARAUDER), ItemNames.MARAUDER_KINETIC_FOAM: - ItemData(215 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 15, SC2Race.TERRAN, + ItemData(215 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 15, SC2Race.TERRAN, parent_item=ItemNames.MARAUDER), ItemNames.REAPER_U238_ROUNDS: - ItemData(216 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 16, SC2Race.TERRAN, + ItemData(216 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 16, SC2Race.TERRAN, parent_item=ItemNames.REAPER), ItemNames.REAPER_G4_CLUSTERBOMB: - ItemData(217 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 17, SC2Race.TERRAN, + ItemData(217 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 17, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.REAPER), ItemNames.CYCLONE_MAG_FIELD_ACCELERATORS: - ItemData(218 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 18, SC2Race.TERRAN, + ItemData(218 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 18, SC2Race.TERRAN, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.CYCLONE_MAG_FIELD_LAUNCHERS: - ItemData(219 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 19, SC2Race.TERRAN, + ItemData(219 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 19, SC2Race.TERRAN, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.MARINE_LASER_TARGETING_SYSTEM: - ItemData(220 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 8, SC2Race.TERRAN, + ItemData(220 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 8, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MARINE, origin={"nco"}), ItemNames.MARINE_MAGRAIL_MUNITIONS: - ItemData(221 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 20, SC2Race.TERRAN, + ItemData(221 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 20, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.MARINE, origin={"nco"}), ItemNames.MARINE_OPTIMIZED_LOGISTICS: - ItemData(222 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 21, SC2Race.TERRAN, + ItemData(222 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 21, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MARINE, origin={"nco"}), ItemNames.MEDIC_RESTORATION: - ItemData(223 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 22, SC2Race.TERRAN, + ItemData(223 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 22, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}), ItemNames.MEDIC_OPTICAL_FLARE: - ItemData(224 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 23, SC2Race.TERRAN, + ItemData(224 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 23, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}), ItemNames.MEDIC_RESOURCE_EFFICIENCY: - ItemData(225 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 24, SC2Race.TERRAN, + ItemData(225 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 24, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}), ItemNames.FIREBAT_PROGRESSIVE_STIMPACK: - ItemData(226 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 6, SC2Race.TERRAN, + ItemData(226 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 6, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT, quantity=2, origin={"bw"}), ItemNames.FIREBAT_RESOURCE_EFFICIENCY: - ItemData(227 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 25, SC2Race.TERRAN, + ItemData(227 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 25, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT, origin={"bw"}), ItemNames.MARAUDER_PROGRESSIVE_STIMPACK: - ItemData(228 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 8, SC2Race.TERRAN, + ItemData(228 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 8, SC2Race.TERRAN, parent_item=ItemNames.MARAUDER, quantity=2, origin={"nco"}), ItemNames.MARAUDER_LASER_TARGETING_SYSTEM: - ItemData(229 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 26, SC2Race.TERRAN, + ItemData(229 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 26, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}), ItemNames.MARAUDER_MAGRAIL_MUNITIONS: - ItemData(230 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 27, SC2Race.TERRAN, + ItemData(230 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 27, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}), ItemNames.MARAUDER_INTERNAL_TECH_MODULE: - ItemData(231 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 28, SC2Race.TERRAN, + ItemData(231 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 28, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}), ItemNames.SCV_HOSTILE_ENVIRONMENT_ADAPTATION: - ItemData(232 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 29, SC2Race.TERRAN, + ItemData(232 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 29, SC2Race.TERRAN, classification=ItemClassification.filler, origin={"bw"}), ItemNames.MEDIC_ADAPTIVE_MEDPACKS: - ItemData(233 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 0, SC2Race.TERRAN, + ItemData(233 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 0, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.MEDIC, origin={"ext"}), ItemNames.MEDIC_NANO_PROJECTOR: - ItemData(234 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 1, SC2Race.TERRAN, + ItemData(234 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 1, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"ext"}), ItemNames.FIREBAT_INFERNAL_PRE_IGNITER: - ItemData(235 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 2, SC2Race.TERRAN, + ItemData(235 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 2, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT, origin={"bw"}), ItemNames.FIREBAT_KINETIC_FOAM: - ItemData(236 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 3, SC2Race.TERRAN, + ItemData(236 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 3, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT, origin={"ext"}), ItemNames.FIREBAT_NANO_PROJECTORS: - ItemData(237 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 4, SC2Race.TERRAN, + ItemData(237 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 4, SC2Race.TERRAN, parent_item=ItemNames.FIREBAT, origin={"ext"}), ItemNames.MARAUDER_JUGGERNAUT_PLATING: - ItemData(238 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 5, SC2Race.TERRAN, + ItemData(238 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 5, SC2Race.TERRAN, parent_item=ItemNames.MARAUDER, origin={"ext"}), ItemNames.REAPER_JET_PACK_OVERDRIVE: - ItemData(239 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 6, SC2Race.TERRAN, + ItemData(239 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 6, SC2Race.TERRAN, parent_item=ItemNames.REAPER, origin={"ext"}), ItemNames.HELLION_INFERNAL_PLATING: - ItemData(240 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 7, SC2Race.TERRAN, + ItemData(240 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 7, SC2Race.TERRAN, parent_item=ItemNames.HELLION, origin={"ext"}), ItemNames.VULTURE_AUTO_REPAIR: - ItemData(241 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 8, SC2Race.TERRAN, + ItemData(241 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 8, SC2Race.TERRAN, parent_item=ItemNames.VULTURE, origin={"ext"}), ItemNames.GOLIATH_SHAPED_HULL: - ItemData(242 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 9, SC2Race.TERRAN, + ItemData(242 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 9, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco", "ext"}), ItemNames.GOLIATH_RESOURCE_EFFICIENCY: - ItemData(243 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 10, SC2Race.TERRAN, + ItemData(243 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 10, SC2Race.TERRAN, parent_item=ItemNames.GOLIATH, origin={"nco", "bw"}), ItemNames.GOLIATH_INTERNAL_TECH_MODULE: - ItemData(244 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 11, SC2Race.TERRAN, + ItemData(244 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 11, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco", "bw"}), ItemNames.SIEGE_TANK_SHAPED_HULL: - ItemData(245 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 12, SC2Race.TERRAN, + ItemData(245 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 12, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco", "ext"}), ItemNames.SIEGE_TANK_RESOURCE_EFFICIENCY: - ItemData(246 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 13, SC2Race.TERRAN, + ItemData(246 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 13, SC2Race.TERRAN, parent_item=ItemNames.SIEGE_TANK, origin={"bw"}), ItemNames.PREDATOR_CLOAK: - ItemData(247 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 14, SC2Race.TERRAN, + ItemData(247 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 14, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}), ItemNames.PREDATOR_CHARGE: - ItemData(248 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 15, SC2Race.TERRAN, + ItemData(248 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 15, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}), ItemNames.MEDIVAC_SCATTER_VEIL: - ItemData(249 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 16, SC2Race.TERRAN, + ItemData(249 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 16, SC2Race.TERRAN, parent_item=ItemNames.MEDIVAC, origin={"ext"}), ItemNames.REAPER_PROGRESSIVE_STIMPACK: - ItemData(250 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 10, SC2Race.TERRAN, + ItemData(250 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 10, SC2Race.TERRAN, parent_item=ItemNames.REAPER, quantity=2, origin={"nco"}), ItemNames.REAPER_LASER_TARGETING_SYSTEM: - ItemData(251 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 17, SC2Race.TERRAN, + ItemData(251 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 17, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"nco"}), ItemNames.REAPER_ADVANCED_CLOAKING_FIELD: - ItemData(252 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 18, SC2Race.TERRAN, + ItemData(252 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 18, SC2Race.TERRAN, parent_item=ItemNames.REAPER, origin={"nco"}), ItemNames.REAPER_SPIDER_MINES: - ItemData(253 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 19, SC2Race.TERRAN, + ItemData(253 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 19, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"nco"}, important_for_filtering=True), ItemNames.REAPER_COMBAT_DRUGS: - ItemData(254 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 20, SC2Race.TERRAN, + ItemData(254 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 20, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"ext"}), ItemNames.HELLION_HELLBAT_ASPECT: - ItemData(255 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 21, SC2Race.TERRAN, + ItemData(255 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 21, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.HELLION, origin={"nco"}), ItemNames.HELLION_SMART_SERVOS: - ItemData(256 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 22, SC2Race.TERRAN, + ItemData(256 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 22, SC2Race.TERRAN, parent_item=ItemNames.HELLION, origin={"nco"}), ItemNames.HELLION_OPTIMIZED_LOGISTICS: - ItemData(257 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 23, SC2Race.TERRAN, + ItemData(257 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 23, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.HELLION, origin={"nco"}), ItemNames.HELLION_JUMP_JETS: - ItemData(258 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 24, SC2Race.TERRAN, + ItemData(258 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 24, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.HELLION, origin={"nco"}), ItemNames.HELLION_PROGRESSIVE_STIMPACK: - ItemData(259 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 12, SC2Race.TERRAN, + ItemData(259 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 12, SC2Race.TERRAN, parent_item=ItemNames.HELLION, quantity=2, origin={"nco"}), ItemNames.VULTURE_ION_THRUSTERS: - ItemData(260 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 25, SC2Race.TERRAN, + ItemData(260 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 25, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.VULTURE, origin={"bw"}), ItemNames.VULTURE_AUTO_LAUNCHERS: - ItemData(261 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 26, SC2Race.TERRAN, + ItemData(261 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 26, SC2Race.TERRAN, parent_item=ItemNames.VULTURE, origin={"bw"}), ItemNames.SPIDER_MINE_HIGH_EXPLOSIVE_MUNITION: - ItemData(262 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 27, SC2Race.TERRAN, + ItemData(262 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 27, SC2Race.TERRAN, origin={"bw"}), ItemNames.GOLIATH_JUMP_JETS: - ItemData(263 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 28, SC2Race.TERRAN, + ItemData(263 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 28, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.GOLIATH, origin={"nco"}), ItemNames.GOLIATH_OPTIMIZED_LOGISTICS: - ItemData(264 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_2, 29, SC2Race.TERRAN, + ItemData(264 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_2, 29, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco"}), ItemNames.DIAMONDBACK_HYPERFLUXOR: - ItemData(265 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 0, SC2Race.TERRAN, + ItemData(265 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 0, SC2Race.TERRAN, parent_item=ItemNames.DIAMONDBACK, origin={"ext"}), ItemNames.DIAMONDBACK_BURST_CAPACITORS: - ItemData(266 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 1, SC2Race.TERRAN, + ItemData(266 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 1, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.DIAMONDBACK, origin={"ext"}), ItemNames.DIAMONDBACK_RESOURCE_EFFICIENCY: - ItemData(267 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 2, SC2Race.TERRAN, + ItemData(267 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 2, SC2Race.TERRAN, parent_item=ItemNames.DIAMONDBACK, origin={"ext"}), ItemNames.SIEGE_TANK_JUMP_JETS: - ItemData(268 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 3, SC2Race.TERRAN, + ItemData(268 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 3, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}), ItemNames.SIEGE_TANK_SPIDER_MINES: - ItemData(269 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 4, SC2Race.TERRAN, + ItemData(269 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 4, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, important_for_filtering=True), ItemNames.SIEGE_TANK_SMART_SERVOS: - ItemData(270 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 5, SC2Race.TERRAN, + ItemData(270 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 5, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}), ItemNames.SIEGE_TANK_GRADUATING_RANGE: - ItemData(271 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 6, SC2Race.TERRAN, + ItemData(271 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 6, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK, origin={"ext"}), ItemNames.SIEGE_TANK_LASER_TARGETING_SYSTEM: - ItemData(272 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 7, SC2Race.TERRAN, + ItemData(272 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 7, SC2Race.TERRAN, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}), ItemNames.SIEGE_TANK_ADVANCED_SIEGE_TECH: - ItemData(273 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 8, SC2Race.TERRAN, + ItemData(273 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 8, SC2Race.TERRAN, parent_item=ItemNames.SIEGE_TANK, origin={"ext"}), ItemNames.SIEGE_TANK_INTERNAL_TECH_MODULE: - ItemData(274 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 9, SC2Race.TERRAN, + ItemData(274 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 9, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}), ItemNames.PREDATOR_RESOURCE_EFFICIENCY: - ItemData(275 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 10, SC2Race.TERRAN, + ItemData(275 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 10, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}), ItemNames.MEDIVAC_EXPANDED_HULL: - ItemData(276 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 11, SC2Race.TERRAN, + ItemData(276 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 11, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, origin={"ext"}), ItemNames.MEDIVAC_AFTERBURNERS: - ItemData(277 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 12, SC2Race.TERRAN, + ItemData(277 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 12, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, origin={"ext"}), ItemNames.WRAITH_ADVANCED_LASER_TECHNOLOGY: - ItemData(278 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 13, SC2Race.TERRAN, + ItemData(278 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 13, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.WRAITH, origin={"ext"}), ItemNames.VIKING_SMART_SERVOS: - ItemData(279 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 14, SC2Race.TERRAN, + ItemData(279 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 14, SC2Race.TERRAN, parent_item=ItemNames.VIKING, origin={"ext"}), ItemNames.VIKING_ANTI_MECHANICAL_MUNITION: - ItemData(280 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 15, SC2Race.TERRAN, + ItemData(280 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 15, SC2Race.TERRAN, parent_item=ItemNames.VIKING, origin={"ext"}), ItemNames.DIAMONDBACK_ION_THRUSTERS: - ItemData(281 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 21, SC2Race.TERRAN, + ItemData(281 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 21, SC2Race.TERRAN, parent_item=ItemNames.DIAMONDBACK, origin={"ext"}), ItemNames.WARHOUND_RESOURCE_EFFICIENCY: - ItemData(282 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 13, SC2Race.TERRAN, + ItemData(282 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 13, SC2Race.TERRAN, parent_item=ItemNames.WARHOUND, origin={"ext"}), ItemNames.WARHOUND_REINFORCED_PLATING: - ItemData(283 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 14, SC2Race.TERRAN, + ItemData(283 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 14, SC2Race.TERRAN, parent_item=ItemNames.WARHOUND, origin={"ext"}), ItemNames.HERC_RESOURCE_EFFICIENCY: - ItemData(284 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 15, SC2Race.TERRAN, + ItemData(284 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 15, SC2Race.TERRAN, parent_item=ItemNames.HERC, origin={"ext"}), ItemNames.HERC_JUGGERNAUT_PLATING: - ItemData(285 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 16, SC2Race.TERRAN, + ItemData(285 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 16, SC2Race.TERRAN, parent_item=ItemNames.HERC, origin={"ext"}), ItemNames.HERC_KINETIC_FOAM: - ItemData(286 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 17, SC2Race.TERRAN, + ItemData(286 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 17, SC2Race.TERRAN, parent_item=ItemNames.HERC, origin={"ext"}), ItemNames.HELLION_TWIN_LINKED_FLAMETHROWER: - ItemData(300 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 16, SC2Race.TERRAN, + ItemData(300 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 16, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.HELLION), ItemNames.HELLION_THERMITE_FILAMENTS: - ItemData(301 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 17, SC2Race.TERRAN, + ItemData(301 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 17, SC2Race.TERRAN, parent_item=ItemNames.HELLION), ItemNames.SPIDER_MINE_CERBERUS_MINE: - ItemData(302 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 18, SC2Race.TERRAN, + ItemData(302 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 18, SC2Race.TERRAN, classification=ItemClassification.filler), ItemNames.VULTURE_PROGRESSIVE_REPLENISHABLE_MAGAZINE: - ItemData(303 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 16, SC2Race.TERRAN, + ItemData(303 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 16, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.VULTURE, quantity=2), ItemNames.GOLIATH_MULTI_LOCK_WEAPONS_SYSTEM: - ItemData(304 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 19, SC2Race.TERRAN, + ItemData(304 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 19, SC2Race.TERRAN, parent_item=ItemNames.GOLIATH), ItemNames.GOLIATH_ARES_CLASS_TARGETING_SYSTEM: - ItemData(305 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 20, SC2Race.TERRAN, + ItemData(305 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 20, SC2Race.TERRAN, parent_item=ItemNames.GOLIATH), ItemNames.DIAMONDBACK_PROGRESSIVE_TRI_LITHIUM_POWER_CELL: - ItemData(306 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive_2, 4, SC2Race.TERRAN, + ItemData(306 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive_2, 4, SC2Race.TERRAN, parent_item=ItemNames.DIAMONDBACK, quantity=2), ItemNames.DIAMONDBACK_SHAPED_HULL: - ItemData(307 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 22, SC2Race.TERRAN, + ItemData(307 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 22, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.DIAMONDBACK), ItemNames.SIEGE_TANK_MAELSTROM_ROUNDS: - ItemData(308 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 23, SC2Race.TERRAN, + ItemData(308 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 23, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK), ItemNames.SIEGE_TANK_SHAPED_BLAST: - ItemData(309 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 24, SC2Race.TERRAN, + ItemData(309 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 24, SC2Race.TERRAN, parent_item=ItemNames.SIEGE_TANK), ItemNames.MEDIVAC_RAPID_DEPLOYMENT_TUBE: - ItemData(310 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 25, SC2Race.TERRAN, + ItemData(310 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 25, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC), ItemNames.MEDIVAC_ADVANCED_HEALING_AI: - ItemData(311 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 26, SC2Race.TERRAN, + ItemData(311 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 26, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC), ItemNames.WRAITH_PROGRESSIVE_TOMAHAWK_POWER_CELLS: - ItemData(312 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 18, SC2Race.TERRAN, + ItemData(312 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 18, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.WRAITH, quantity=2), ItemNames.WRAITH_DISPLACEMENT_FIELD: - ItemData(313 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 27, SC2Race.TERRAN, + ItemData(313 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 27, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.WRAITH), ItemNames.VIKING_RIPWAVE_MISSILES: - ItemData(314 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 28, SC2Race.TERRAN, + ItemData(314 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 28, SC2Race.TERRAN, parent_item=ItemNames.VIKING), ItemNames.VIKING_PHOBOS_CLASS_WEAPONS_SYSTEM: - ItemData(315 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_3, 29, SC2Race.TERRAN, + ItemData(315 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_3, 29, SC2Race.TERRAN, parent_item=ItemNames.VIKING), ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS: - ItemData(316 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 2, SC2Race.TERRAN, + ItemData(316 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 2, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, quantity=2), ItemNames.BANSHEE_SHOCKWAVE_MISSILE_BATTERY: - ItemData(317 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 0, SC2Race.TERRAN, + ItemData(317 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 0, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.BANSHEE), ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS: - ItemData(318 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive_2, 2, SC2Race.TERRAN, + ItemData(318 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive_2, 2, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, quantity=2), ItemNames.BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX: - ItemData(319 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 20, SC2Race.TERRAN, + ItemData(319 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 20, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, quantity=2), ItemNames.GHOST_OCULAR_IMPLANTS: - ItemData(320 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 2, SC2Race.TERRAN, + ItemData(320 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 2, SC2Race.TERRAN, parent_item=ItemNames.GHOST), ItemNames.GHOST_CRIUS_SUIT: - ItemData(321 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 3, SC2Race.TERRAN, + ItemData(321 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 3, SC2Race.TERRAN, parent_item=ItemNames.GHOST), ItemNames.SPECTRE_PSIONIC_LASH: - ItemData(322 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 4, SC2Race.TERRAN, + ItemData(322 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 4, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.SPECTRE), ItemNames.SPECTRE_NYX_CLASS_CLOAKING_MODULE: - ItemData(323 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 5, SC2Race.TERRAN, + ItemData(323 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 5, SC2Race.TERRAN, parent_item=ItemNames.SPECTRE), ItemNames.THOR_330MM_BARRAGE_CANNON: - ItemData(324 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 6, SC2Race.TERRAN, + ItemData(324 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 6, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.THOR), ItemNames.THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL: - ItemData(325 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 22, SC2Race.TERRAN, + ItemData(325 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 22, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.THOR, quantity=2), ItemNames.LIBERATOR_ADVANCED_BALLISTICS: - ItemData(326 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 7, SC2Race.TERRAN, + ItemData(326 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 7, SC2Race.TERRAN, parent_item=ItemNames.LIBERATOR, origin={"ext"}), ItemNames.LIBERATOR_RAID_ARTILLERY: - ItemData(327 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 8, SC2Race.TERRAN, + ItemData(327 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 8, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.LIBERATOR, origin={"nco"}), ItemNames.WIDOW_MINE_DRILLING_CLAWS: - ItemData(328 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 9, SC2Race.TERRAN, + ItemData(328 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 9, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}), ItemNames.WIDOW_MINE_CONCEALMENT: - ItemData(329 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 10, SC2Race.TERRAN, + ItemData(329 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 10, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}), ItemNames.MEDIVAC_ADVANCED_CLOAKING_FIELD: - ItemData(330 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 11, SC2Race.TERRAN, + ItemData(330 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 11, SC2Race.TERRAN, parent_item=ItemNames.MEDIVAC, origin={"ext"}), ItemNames.WRAITH_TRIGGER_OVERRIDE: - ItemData(331 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 12, SC2Race.TERRAN, + ItemData(331 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 12, SC2Race.TERRAN, parent_item=ItemNames.WRAITH, origin={"ext"}), ItemNames.WRAITH_INTERNAL_TECH_MODULE: - ItemData(332 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 13, SC2Race.TERRAN, + ItemData(332 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 13, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.WRAITH, origin={"bw"}), ItemNames.WRAITH_RESOURCE_EFFICIENCY: - ItemData(333 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 14, SC2Race.TERRAN, + ItemData(333 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 14, SC2Race.TERRAN, parent_item=ItemNames.WRAITH, origin={"bw"}), ItemNames.VIKING_SHREDDER_ROUNDS: - ItemData(334 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 15, SC2Race.TERRAN, + ItemData(334 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 15, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.VIKING, origin={"ext"}), ItemNames.VIKING_WILD_MISSILES: - ItemData(335 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 16, SC2Race.TERRAN, + ItemData(335 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 16, SC2Race.TERRAN, parent_item=ItemNames.VIKING, origin={"ext"}), ItemNames.BANSHEE_SHAPED_HULL: - ItemData(336 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 17, SC2Race.TERRAN, + ItemData(336 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 17, SC2Race.TERRAN, parent_item=ItemNames.BANSHEE, origin={"ext"}), ItemNames.BANSHEE_ADVANCED_TARGETING_OPTICS: - ItemData(337 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 18, SC2Race.TERRAN, + ItemData(337 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 18, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.BANSHEE, origin={"ext"}), ItemNames.BANSHEE_DISTORTION_BLASTERS: - ItemData(338 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 19, SC2Race.TERRAN, + ItemData(338 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 19, SC2Race.TERRAN, parent_item=ItemNames.BANSHEE, origin={"ext"}), ItemNames.BANSHEE_ROCKET_BARRAGE: - ItemData(339 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 20, SC2Race.TERRAN, + ItemData(339 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 20, SC2Race.TERRAN, parent_item=ItemNames.BANSHEE, origin={"ext"}), ItemNames.GHOST_RESOURCE_EFFICIENCY: - ItemData(340 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 21, SC2Race.TERRAN, + ItemData(340 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 21, SC2Race.TERRAN, parent_item=ItemNames.GHOST, origin={"bw"}), ItemNames.SPECTRE_RESOURCE_EFFICIENCY: - ItemData(341 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 22, SC2Race.TERRAN, + ItemData(341 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 22, SC2Race.TERRAN, parent_item=ItemNames.SPECTRE, origin={"ext"}), ItemNames.THOR_BUTTON_WITH_A_SKULL_ON_IT: - ItemData(342 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 23, SC2Race.TERRAN, + ItemData(342 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 23, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.THOR, origin={"ext"}), ItemNames.THOR_LASER_TARGETING_SYSTEM: - ItemData(343 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 24, SC2Race.TERRAN, + ItemData(343 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 24, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.THOR, origin={"ext"}), ItemNames.THOR_LARGE_SCALE_FIELD_CONSTRUCTION: - ItemData(344 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 25, SC2Race.TERRAN, + ItemData(344 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 25, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.THOR, origin={"ext"}), ItemNames.RAVEN_RESOURCE_EFFICIENCY: - ItemData(345 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 26, SC2Race.TERRAN, + ItemData(345 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 26, SC2Race.TERRAN, parent_item=ItemNames.RAVEN, origin={"ext"}), ItemNames.RAVEN_DURABLE_MATERIALS: - ItemData(346 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 27, SC2Race.TERRAN, + ItemData(346 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 27, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"ext"}), ItemNames.SCIENCE_VESSEL_IMPROVED_NANO_REPAIR: - ItemData(347 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 28, SC2Race.TERRAN, + ItemData(347 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 28, SC2Race.TERRAN, parent_item=ItemNames.SCIENCE_VESSEL, origin={"ext"}), ItemNames.SCIENCE_VESSEL_ADVANCED_AI_SYSTEMS: - ItemData(348 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_4, 29, SC2Race.TERRAN, + ItemData(348 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_4, 29, SC2Race.TERRAN, parent_item=ItemNames.SCIENCE_VESSEL, origin={"ext"}), ItemNames.CYCLONE_RESOURCE_EFFICIENCY: - ItemData(349 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 0, SC2Race.TERRAN, + ItemData(349 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 0, SC2Race.TERRAN, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.BANSHEE_HYPERFLIGHT_ROTORS: - ItemData(350 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 1, SC2Race.TERRAN, + ItemData(350 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 1, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"ext"}), ItemNames.BANSHEE_LASER_TARGETING_SYSTEM: - ItemData(351 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 2, SC2Race.TERRAN, + ItemData(351 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 2, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"nco"}), ItemNames.BANSHEE_INTERNAL_TECH_MODULE: - ItemData(352 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 3, SC2Race.TERRAN, + ItemData(352 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 3, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"nco"}), ItemNames.BATTLECRUISER_TACTICAL_JUMP: - ItemData(353 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 4, SC2Race.TERRAN, + ItemData(353 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 4, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, origin={"nco", "ext"}), ItemNames.BATTLECRUISER_CLOAK: - ItemData(354 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 5, SC2Race.TERRAN, + ItemData(354 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 5, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}), ItemNames.BATTLECRUISER_ATX_LASER_BATTERY: - ItemData(355 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 6, SC2Race.TERRAN, + ItemData(355 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 6, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}), ItemNames.BATTLECRUISER_OPTIMIZED_LOGISTICS: - ItemData(356 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 7, SC2Race.TERRAN, + ItemData(356 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 7, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BATTLECRUISER, origin={"ext"}), ItemNames.BATTLECRUISER_INTERNAL_TECH_MODULE: - ItemData(357 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 8, SC2Race.TERRAN, + ItemData(357 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 8, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}), ItemNames.GHOST_EMP_ROUNDS: - ItemData(358 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 9, SC2Race.TERRAN, + ItemData(358 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 9, SC2Race.TERRAN, parent_item=ItemNames.GHOST, origin={"ext"}), ItemNames.GHOST_LOCKDOWN: - ItemData(359 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 10, SC2Race.TERRAN, + ItemData(359 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 10, SC2Race.TERRAN, parent_item=ItemNames.GHOST, origin={"bw"}), ItemNames.SPECTRE_IMPALER_ROUNDS: - ItemData(360 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 11, SC2Race.TERRAN, + ItemData(360 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 11, SC2Race.TERRAN, parent_item=ItemNames.SPECTRE, origin={"ext"}), ItemNames.THOR_PROGRESSIVE_HIGH_IMPACT_PAYLOAD: - ItemData(361 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 14, SC2Race.TERRAN, + ItemData(361 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 14, SC2Race.TERRAN, parent_item=ItemNames.THOR, quantity=2, origin={"ext"}), ItemNames.RAVEN_BIO_MECHANICAL_REPAIR_DRONE: - ItemData(363 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 12, SC2Race.TERRAN, + ItemData(363 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 12, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.RAVEN, origin={"nco"}), ItemNames.RAVEN_SPIDER_MINES: - ItemData(364 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 13, SC2Race.TERRAN, + ItemData(364 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 13, SC2Race.TERRAN, parent_item=ItemNames.RAVEN, origin={"nco"}, important_for_filtering=True), ItemNames.RAVEN_RAILGUN_TURRET: - ItemData(365 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 14, SC2Race.TERRAN, + ItemData(365 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 14, SC2Race.TERRAN, parent_item=ItemNames.RAVEN, origin={"nco"}), ItemNames.RAVEN_HUNTER_SEEKER_WEAPON: - ItemData(366 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 15, SC2Race.TERRAN, + ItemData(366 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 15, SC2Race.TERRAN, classification=ItemClassification.progression, parent_item=ItemNames.RAVEN, origin={"nco"}), ItemNames.RAVEN_INTERFERENCE_MATRIX: - ItemData(367 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 16, SC2Race.TERRAN, + ItemData(367 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 16, SC2Race.TERRAN, parent_item=ItemNames.RAVEN, origin={"ext"}), ItemNames.RAVEN_ANTI_ARMOR_MISSILE: - ItemData(368 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 17, SC2Race.TERRAN, + ItemData(368 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 17, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"ext"}), ItemNames.RAVEN_INTERNAL_TECH_MODULE: - ItemData(369 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 18, SC2Race.TERRAN, + ItemData(369 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 18, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"nco"}), ItemNames.SCIENCE_VESSEL_EMP_SHOCKWAVE: - ItemData(370 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 19, SC2Race.TERRAN, + ItemData(370 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 19, SC2Race.TERRAN, parent_item=ItemNames.SCIENCE_VESSEL, origin={"bw"}), ItemNames.SCIENCE_VESSEL_DEFENSIVE_MATRIX: - ItemData(371 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 20, SC2Race.TERRAN, + ItemData(371 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 20, SC2Race.TERRAN, parent_item=ItemNames.SCIENCE_VESSEL, origin={"bw"}), ItemNames.CYCLONE_TARGETING_OPTICS: - ItemData(372 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 21, SC2Race.TERRAN, + ItemData(372 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 21, SC2Race.TERRAN, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.CYCLONE_RAPID_FIRE_LAUNCHERS: - ItemData(373 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 22, SC2Race.TERRAN, + ItemData(373 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 22, SC2Race.TERRAN, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.LIBERATOR_CLOAK: - ItemData(374 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 23, SC2Race.TERRAN, + ItemData(374 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 23, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}), ItemNames.LIBERATOR_LASER_TARGETING_SYSTEM: - ItemData(375 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 24, SC2Race.TERRAN, + ItemData(375 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 24, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"ext"}), ItemNames.LIBERATOR_OPTIMIZED_LOGISTICS: - ItemData(376 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 25, SC2Race.TERRAN, + ItemData(376 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 25, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}), ItemNames.WIDOW_MINE_BLACK_MARKET_LAUNCHERS: - ItemData(377 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 26, SC2Race.TERRAN, + ItemData(377 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 26, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}), ItemNames.WIDOW_MINE_EXECUTIONER_MISSILES: - ItemData(378 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 27, SC2Race.TERRAN, + ItemData(378 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 27, SC2Race.TERRAN, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}), ItemNames.VALKYRIE_ENHANCED_CLUSTER_LAUNCHERS: - ItemData(379 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 28, + ItemData(379 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 28, SC2Race.TERRAN, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.VALKYRIE_SHAPED_HULL: - ItemData(380 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_5, 29, SC2Race.TERRAN, + ItemData(380 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_5, 29, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.VALKYRIE_FLECHETTE_MISSILES: - ItemData(381 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 0, SC2Race.TERRAN, + ItemData(381 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 0, SC2Race.TERRAN, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.VALKYRIE_AFTERBURNERS: - ItemData(382 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 1, SC2Race.TERRAN, + ItemData(382 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 1, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.CYCLONE_INTERNAL_TECH_MODULE: - ItemData(383 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 2, SC2Race.TERRAN, + ItemData(383 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 2, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.CYCLONE, origin={"ext"}), ItemNames.LIBERATOR_SMART_SERVOS: - ItemData(384 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 3, SC2Race.TERRAN, + ItemData(384 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 3, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}), ItemNames.LIBERATOR_RESOURCE_EFFICIENCY: - ItemData(385 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 4, SC2Race.TERRAN, + ItemData(385 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 4, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"ext"}), ItemNames.HERCULES_INTERNAL_FUSION_MODULE: - ItemData(386 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 5, SC2Race.TERRAN, + ItemData(386 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 5, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.HERCULES, origin={"ext"}), ItemNames.HERCULES_TACTICAL_JUMP: - ItemData(387 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 6, SC2Race.TERRAN, + ItemData(387 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 6, SC2Race.TERRAN, parent_item=ItemNames.HERCULES, origin={"ext"}), ItemNames.PLANETARY_FORTRESS_PROGRESSIVE_AUGMENTED_THRUSTERS: - ItemData(388 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 28, SC2Race.TERRAN, + ItemData(388 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 28, SC2Race.TERRAN, parent_item=ItemNames.PLANETARY_FORTRESS, origin={"ext"}, quantity=2), ItemNames.PLANETARY_FORTRESS_ADVANCED_TARGETING: - ItemData(389 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 7, SC2Race.TERRAN, + ItemData(389 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 7, SC2Race.TERRAN, parent_item=ItemNames.PLANETARY_FORTRESS, origin={"ext"}), ItemNames.VALKYRIE_LAUNCHING_VECTOR_COMPENSATOR: - ItemData(390 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 8, SC2Race.TERRAN, + ItemData(390 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 8, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.VALKYRIE_RESOURCE_EFFICIENCY: - ItemData(391 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 9, SC2Race.TERRAN, + ItemData(391 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 9, SC2Race.TERRAN, parent_item=ItemNames.VALKYRIE, origin={"ext"}), ItemNames.PREDATOR_PREDATOR_S_FURY: - ItemData(392 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 10, SC2Race.TERRAN, + ItemData(392 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 10, SC2Race.TERRAN, parent_item=ItemNames.PREDATOR, origin={"ext"}), ItemNames.BATTLECRUISER_BEHEMOTH_PLATING: - ItemData(393 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 11, SC2Race.TERRAN, + ItemData(393 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 11, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, origin={"ext"}), ItemNames.BATTLECRUISER_COVERT_OPS_ENGINES: - ItemData(394 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_6, 12, SC2Race.TERRAN, + ItemData(394 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 12, SC2Race.TERRAN, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}), #Buildings ItemNames.BUNKER: - ItemData(400 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 0, SC2Race.TERRAN, + ItemData(400 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Building, 0, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.MISSILE_TURRET: - ItemData(401 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 1, SC2Race.TERRAN, + ItemData(401 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Building, 1, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.SENSOR_TOWER: - ItemData(402 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 2, SC2Race.TERRAN), + ItemData(402 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Building, 2, SC2Race.TERRAN), ItemNames.WAR_PIGS: - ItemData(500 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 0, SC2Race.TERRAN, + ItemData(500 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 0, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.DEVIL_DOGS: - ItemData(501 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 1, SC2Race.TERRAN, + ItemData(501 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 1, SC2Race.TERRAN, classification=ItemClassification.filler), ItemNames.HAMMER_SECURITIES: - ItemData(502 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 2, SC2Race.TERRAN), + ItemData(502 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 2, SC2Race.TERRAN), ItemNames.SPARTAN_COMPANY: - ItemData(503 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 3, SC2Race.TERRAN, + ItemData(503 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 3, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.SIEGE_BREAKERS: - ItemData(504 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 4, SC2Race.TERRAN), + ItemData(504 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 4, SC2Race.TERRAN), ItemNames.HELS_ANGELS: - ItemData(505 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 5, SC2Race.TERRAN, + ItemData(505 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 5, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.DUSK_WINGS: - ItemData(506 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 6, SC2Race.TERRAN), + ItemData(506 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 6, SC2Race.TERRAN), ItemNames.JACKSONS_REVENGE: - ItemData(507 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 7, SC2Race.TERRAN), + ItemData(507 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 7, SC2Race.TERRAN), ItemNames.SKIBIS_ANGELS: - ItemData(508 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 8, SC2Race.TERRAN, + ItemData(508 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 8, SC2Race.TERRAN, origin={"ext"}), ItemNames.DEATH_HEADS: - ItemData(509 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 9, SC2Race.TERRAN, + ItemData(509 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 9, SC2Race.TERRAN, origin={"ext"}), ItemNames.WINGED_NIGHTMARES: - ItemData(510 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 10, SC2Race.TERRAN, + ItemData(510 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 10, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), ItemNames.MIDNIGHT_RIDERS: - ItemData(511 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 11, SC2Race.TERRAN, + ItemData(511 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 11, SC2Race.TERRAN, origin={"ext"}), ItemNames.BRYNHILDS: - ItemData(512 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 12, SC2Race.TERRAN, + ItemData(512 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 12, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), ItemNames.JOTUN: - ItemData(513 + SC2WOL_ITEM_ID_OFFSET, ItemType.Mercenary, 13, SC2Race.TERRAN, + ItemData(513 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 13, SC2Race.TERRAN, origin={"ext"}), ItemNames.ULTRA_CAPACITORS: - ItemData(600 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 0, SC2Race.TERRAN), + ItemData(600 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 0, SC2Race.TERRAN), ItemNames.VANADIUM_PLATING: - ItemData(601 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 1, SC2Race.TERRAN), + ItemData(601 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 1, SC2Race.TERRAN), ItemNames.ORBITAL_DEPOTS: - ItemData(602 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 2, SC2Race.TERRAN), + ItemData(602 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 2, SC2Race.TERRAN), ItemNames.MICRO_FILTERING: - ItemData(603 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 3, SC2Race.TERRAN), + ItemData(603 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 3, SC2Race.TERRAN), ItemNames.AUTOMATED_REFINERY: - ItemData(604 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 4, SC2Race.TERRAN), + ItemData(604 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 4, SC2Race.TERRAN), ItemNames.COMMAND_CENTER_REACTOR: - ItemData(605 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 5, SC2Race.TERRAN), + ItemData(605 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 5, SC2Race.TERRAN), ItemNames.RAVEN: - ItemData(606 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 22, SC2Race.TERRAN, + ItemData(606 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 22, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.SCIENCE_VESSEL: - ItemData(607 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 23, SC2Race.TERRAN, + ItemData(607 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 23, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.TECH_REACTOR: - ItemData(608 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 6, SC2Race.TERRAN), + ItemData(608 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 6, SC2Race.TERRAN), ItemNames.ORBITAL_STRIKE: - ItemData(609 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 7, SC2Race.TERRAN), + ItemData(609 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 7, SC2Race.TERRAN), ItemNames.BUNKER_SHRIKE_TURRET: - ItemData(610 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 6, SC2Race.TERRAN, + ItemData(610 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 6, SC2Race.TERRAN, parent_item=ItemNames.BUNKER), ItemNames.BUNKER_FORTIFIED_BUNKER: - ItemData(611 + SC2WOL_ITEM_ID_OFFSET, ItemType.Armory_1, 7, SC2Race.TERRAN, + ItemData(611 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_1, 7, SC2Race.TERRAN, parent_item=ItemNames.BUNKER), ItemNames.PLANETARY_FORTRESS: - ItemData(612 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 3, SC2Race.TERRAN, + ItemData(612 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Building, 3, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.PERDITION_TURRET: - ItemData(613 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 4, SC2Race.TERRAN, + ItemData(613 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Building, 4, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.PREDATOR: - ItemData(614 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 24, SC2Race.TERRAN, + ItemData(614 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 24, SC2Race.TERRAN, classification=ItemClassification.filler), ItemNames.HERCULES: - ItemData(615 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 25, SC2Race.TERRAN, + ItemData(615 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 25, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.CELLULAR_REACTOR: - ItemData(616 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 8, SC2Race.TERRAN), + ItemData(616 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 8, SC2Race.TERRAN), ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: - ItemData(617 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive, 4, SC2Race.TERRAN, quantity=3, + ItemData(617 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive, 4, SC2Race.TERRAN, quantity=3, classification= ItemClassification.progression), ItemNames.HIVE_MIND_EMULATOR: - ItemData(618 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 5, SC2Race.TERRAN, + ItemData(618 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Building, 5, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.PSI_DISRUPTER: - ItemData(619 + SC2WOL_ITEM_ID_OFFSET, ItemType.Building, 6, SC2Race.TERRAN, + ItemData(619 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Building, 6, SC2Race.TERRAN, classification=ItemClassification.progression), ItemNames.STRUCTURE_ARMOR: - ItemData(620 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 9, SC2Race.TERRAN), + ItemData(620 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 9, SC2Race.TERRAN), ItemNames.HI_SEC_AUTO_TRACKING: - ItemData(621 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 10, SC2Race.TERRAN), + ItemData(621 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 10, SC2Race.TERRAN), ItemNames.ADVANCED_OPTICS: - ItemData(622 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 11, SC2Race.TERRAN), + ItemData(622 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 11, SC2Race.TERRAN), ItemNames.ROGUE_FORCES: - ItemData(623 + SC2WOL_ITEM_ID_OFFSET, ItemType.Laboratory, 12, SC2Race.TERRAN, origin={"ext"}), + ItemData(623 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 12, SC2Race.TERRAN, origin={"ext"}), ItemNames.ZEALOT: - ItemData(700 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 0, SC2Race.PROTOSS, + ItemData(700 + SC2WOL_ITEM_ID_OFFSET, ProtossItemType.Unit, 0, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.STALKER: - ItemData(701 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 1, SC2Race.PROTOSS, + ItemData(701 + SC2WOL_ITEM_ID_OFFSET, ProtossItemType.Unit, 1, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.HIGH_TEMPLAR: - ItemData(702 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 2, SC2Race.PROTOSS, + ItemData(702 + SC2WOL_ITEM_ID_OFFSET, ProtossItemType.Unit, 2, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.DARK_TEMPLAR: - ItemData(703 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 3, SC2Race.PROTOSS, + ItemData(703 + SC2WOL_ITEM_ID_OFFSET, ProtossItemType.Unit, 3, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.IMMORTAL: - ItemData(704 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 4, SC2Race.PROTOSS, + ItemData(704 + SC2WOL_ITEM_ID_OFFSET, ProtossItemType.Unit, 4, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.COLOSSUS: - ItemData(705 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 5, SC2Race.PROTOSS, + ItemData(705 + SC2WOL_ITEM_ID_OFFSET, ProtossItemType.Unit, 5, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.PHOENIX: - ItemData(706 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 6, SC2Race.PROTOSS, + ItemData(706 + SC2WOL_ITEM_ID_OFFSET, ProtossItemType.Unit, 6, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.VOID_RAY: - ItemData(707 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 7, SC2Race.PROTOSS, + ItemData(707 + SC2WOL_ITEM_ID_OFFSET, ProtossItemType.Unit, 7, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), ItemNames.CARRIER: - ItemData(708 + SC2WOL_ITEM_ID_OFFSET, ItemType.Unit, 8, SC2Race.PROTOSS, + ItemData(708 + SC2WOL_ITEM_ID_OFFSET, ProtossItemType.Unit, 8, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), # Filler items to fill remaining spots ItemNames.STARTING_MINERALS: - ItemData(800 + SC2WOL_ITEM_ID_OFFSET, ItemType.Minerals, 15, SC2Race.ANY, quantity=0, + ItemData(800 + SC2WOL_ITEM_ID_OFFSET, FactionlessItemType.Minerals, 15, SC2Race.ANY, quantity=0, classification=ItemClassification.filler), ItemNames.STARTING_VESPENE: - ItemData(801 + SC2WOL_ITEM_ID_OFFSET, ItemType.Vespene, 15, SC2Race.ANY, quantity=0, + ItemData(801 + SC2WOL_ITEM_ID_OFFSET, FactionlessItemType.Vespene, 15, SC2Race.ANY, quantity=0, classification=ItemClassification.filler), ItemNames.STARTING_SUPPLY: - ItemData(802 + SC2WOL_ITEM_ID_OFFSET, ItemType.Supply, 2, SC2Race.ANY, quantity=0, + ItemData(802 + SC2WOL_ITEM_ID_OFFSET, FactionlessItemType.Supply, 2, SC2Race.ANY, quantity=0, classification=ItemClassification.filler), # This item is used to "remove" location from the game. Never placed unless plando'd ItemNames.NOTHING: - ItemData(803 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nothing, 2, SC2Race.ANY, quantity=0, + ItemData(803 + SC2WOL_ITEM_ID_OFFSET, FactionlessItemType.Nothing, 2, SC2Race.ANY, quantity=0, classification=ItemClassification.trap), # Nova gear ItemNames.NOVA_GHOST_VISOR: - ItemData(900 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 0, SC2Race.TERRAN, origin={"nco"}), + ItemData(900 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 0, SC2Race.TERRAN, origin={"nco"}), ItemNames.NOVA_RANGEFINDER_OCULUS: - ItemData(901 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 1, SC2Race.TERRAN, origin={"nco"}), + ItemData(901 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 1, SC2Race.TERRAN, origin={"nco"}), ItemNames.NOVA_DOMINATION: - ItemData(902 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 2, SC2Race.TERRAN, origin={"nco"}, + ItemData(902 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 2, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_BLINK: - ItemData(903 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 3, SC2Race.TERRAN, origin={"nco"}, + ItemData(903 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 3, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE: - ItemData(904 + SC2WOL_ITEM_ID_OFFSET, ItemType.Progressive_2, 0, SC2Race.TERRAN, quantity=2, origin={"nco"}, + ItemData(904 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Progressive_2, 0, SC2Race.TERRAN, quantity=2, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_ENERGY_SUIT_MODULE: - ItemData(905 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 4, SC2Race.TERRAN, origin={"nco"}), + ItemData(905 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 4, SC2Race.TERRAN, origin={"nco"}), ItemNames.NOVA_ARMORED_SUIT_MODULE: - ItemData(906 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 5, SC2Race.TERRAN, origin={"nco"}, + ItemData(906 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 5, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_JUMP_SUIT_MODULE: - ItemData(907 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 6, SC2Race.TERRAN, origin={"nco"}, + ItemData(907 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 6, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_C20A_CANISTER_RIFLE: - ItemData(908 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 7, SC2Race.TERRAN, origin={"nco"}, + ItemData(908 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 7, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_HELLFIRE_SHOTGUN: - ItemData(909 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 8, SC2Race.TERRAN, origin={"nco"}, + ItemData(909 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 8, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_PLASMA_RIFLE: - ItemData(910 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 9, SC2Race.TERRAN, origin={"nco"}, + ItemData(910 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 9, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_MONOMOLECULAR_BLADE: - ItemData(911 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 10, SC2Race.TERRAN, origin={"nco"}, + ItemData(911 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 10, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_BLAZEFIRE_GUNBLADE: - ItemData(912 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 11, SC2Race.TERRAN, origin={"nco"}, + ItemData(912 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 11, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_STIM_INFUSION: - ItemData(913 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 12, SC2Race.TERRAN, origin={"nco"}, + ItemData(913 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 12, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_PULSE_GRENADES: - ItemData(914 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 13, SC2Race.TERRAN, origin={"nco"}, + ItemData(914 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 13, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_FLASHBANG_GRENADES: - ItemData(915 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 14, SC2Race.TERRAN, origin={"nco"}, + ItemData(915 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 14, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_IONIC_FORCE_FIELD: - ItemData(916 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 15, SC2Race.TERRAN, origin={"nco"}, + ItemData(916 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 15, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_HOLO_DECOY: - ItemData(917 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 16, SC2Race.TERRAN, origin={"nco"}, + ItemData(917 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 16, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), ItemNames.NOVA_NUKE: - ItemData(918 + SC2WOL_ITEM_ID_OFFSET, ItemType.Nova_Gear, 17, SC2Race.TERRAN, origin={"nco"}, + ItemData(918 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Nova_Gear, 17, SC2Race.TERRAN, origin={"nco"}, classification=ItemClassification.progression), # HotS ItemNames.ZERGLING: - ItemData(0 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 0, SC2Race.ZERG, + ItemData(0 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 0, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.SWARM_QUEEN: - ItemData(1 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 1, SC2Race.ZERG, + ItemData(1 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 1, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.ROACH: - ItemData(2 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 2, SC2Race.ZERG, + ItemData(2 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 2, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.HYDRALISK: - ItemData(3 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 3, SC2Race.ZERG, + ItemData(3 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 3, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.ZERGLING_BANELING_ASPECT: - ItemData(4 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 5, SC2Race.ZERG, + ItemData(4 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Morph, 5, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.ABERRATION: - ItemData(5 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 5, SC2Race.ZERG, + ItemData(5 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 5, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.MUTALISK: - ItemData(6 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 6, SC2Race.ZERG, + ItemData(6 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 6, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.SWARM_HOST: - ItemData(7 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 7, SC2Race.ZERG, + ItemData(7 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 7, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.INFESTOR: - ItemData(8 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 8, SC2Race.ZERG, + ItemData(8 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 8, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.ULTRALISK: - ItemData(9 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 9, SC2Race.ZERG, + ItemData(9 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 9, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.SPORE_CRAWLER: - ItemData(10 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 10, SC2Race.ZERG, + ItemData(10 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 10, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.SPINE_CRAWLER: - ItemData(11 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 11, SC2Race.ZERG, + ItemData(11 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 11, SC2Race.ZERG, classification=ItemClassification.progression, origin={"hots"}), ItemNames.CORRUPTOR: - ItemData(12 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 12, SC2Race.ZERG, + ItemData(12 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 12, SC2Race.ZERG, classification=ItemClassification.progression, origin={"ext"}), ItemNames.SCOURGE: - ItemData(13 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 13, SC2Race.ZERG, + ItemData(13 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 13, SC2Race.ZERG, classification=ItemClassification.progression, origin={"bw", "ext"}), ItemNames.BROOD_QUEEN: - ItemData(14 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 4, SC2Race.ZERG, + ItemData(14 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 4, SC2Race.ZERG, classification=ItemClassification.progression, origin={"bw", "ext"}), ItemNames.DEFILER: - ItemData(15 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Unit, 14, SC2Race.ZERG, + ItemData(15 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 14, SC2Race.ZERG, classification=ItemClassification.progression, origin={"bw"}), - ItemNames.PROGRESSIVE_ZERG_MELEE_ATTACK: ItemData(100 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 0, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_MISSILE_ATTACK: ItemData(101 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 2, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_GROUND_CARAPACE: ItemData(102 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 4, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_FLYER_ATTACK: ItemData(103 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 6, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_FLYER_CARAPACE: ItemData(104 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 8, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_MELEE_ATTACK: ItemData(100 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 0, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_MISSILE_ATTACK: ItemData(101 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 2, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_GROUND_CARAPACE: ItemData(102 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 4, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_ATTACK: ItemData(103 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 6, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_CARAPACE: ItemData(104 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 8, SC2Race.ZERG, quantity=3, origin={"hots"}), # Upgrade bundle 'number' values are used as indices to get affected 'number's - ItemNames.PROGRESSIVE_ZERG_WEAPON_UPGRADE: ItemData(105 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 6, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_ARMOR_UPGRADE: ItemData(106 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 7, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_GROUND_UPGRADE: ItemData(107 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 8, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_FLYER_UPGRADE: ItemData(108 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 9, SC2Race.ZERG, quantity=3, origin={"hots"}), - ItemNames.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Upgrade, 10, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_WEAPON_UPGRADE: ItemData(105 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 6, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_ARMOR_UPGRADE: ItemData(106 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 7, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_GROUND_UPGRADE: ItemData(107 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 8, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_UPGRADE: ItemData(108 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 9, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 10, SC2Race.ZERG, quantity=3, origin={"hots"}), ItemNames.ZERGLING_HARDENED_CARAPACE: - ItemData(200 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), + ItemData(200 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), ItemNames.ZERGLING_ADRENAL_OVERLOAD: - ItemData(201 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), + ItemData(201 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), ItemNames.ZERGLING_METABOLIC_BOOST: - ItemData(202 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 2, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + ItemData(202 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 2, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}, classification=ItemClassification.filler), ItemNames.ROACH_HYDRIODIC_BILE: - ItemData(203 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), + ItemData(203 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), ItemNames.ROACH_ADAPTIVE_PLATING: - ItemData(204 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 4, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), + ItemData(204 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 4, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), ItemNames.ROACH_TUNNELING_CLAWS: - ItemData(205 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 5, SC2Race.ZERG, parent_item=ItemNames.ROACH, + ItemData(205 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 5, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}, classification=ItemClassification.filler), ItemNames.HYDRALISK_FRENZY: - ItemData(206 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 6, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"hots"}), + ItemData(206 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 6, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"hots"}), ItemNames.HYDRALISK_ANCILLARY_CARAPACE: - ItemData(207 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 7, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + ItemData(207 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 7, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"hots"}, classification=ItemClassification.filler), ItemNames.HYDRALISK_GROOVED_SPINES: - ItemData(208 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 8, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + ItemData(208 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 8, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"hots"}), ItemNames.BANELING_CORROSIVE_ACID: - ItemData(209 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 9, SC2Race.ZERG, + ItemData(209 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 9, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}), ItemNames.BANELING_RUPTURE: - ItemData(210 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 10, SC2Race.ZERG, + ItemData(210 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 10, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, classification=ItemClassification.filler), ItemNames.BANELING_REGENERATIVE_ACID: - ItemData(211 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 11, SC2Race.ZERG, + ItemData(211 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 11, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, classification=ItemClassification.filler), ItemNames.MUTALISK_VICIOUS_GLAIVE: - ItemData(212 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 12, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + ItemData(212 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 12, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, origin={"hots"}), ItemNames.MUTALISK_RAPID_REGENERATION: - ItemData(213 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 13, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + ItemData(213 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 13, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, origin={"hots"}), ItemNames.MUTALISK_SUNDERING_GLAIVE: - ItemData(214 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 14, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + ItemData(214 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 14, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, origin={"hots"}), ItemNames.SWARM_HOST_BURROW: - ItemData(215 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 15, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(215 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 15, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"hots"}, classification=ItemClassification.filler), ItemNames.SWARM_HOST_RAPID_INCUBATION: - ItemData(216 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 16, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(216 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 16, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"hots"}), ItemNames.SWARM_HOST_PRESSURIZED_GLANDS: - ItemData(217 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 17, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(217 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 17, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"hots"}, classification=ItemClassification.progression), ItemNames.ULTRALISK_BURROW_CHARGE: - ItemData(218 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 18, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(218 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 18, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"hots"}), ItemNames.ULTRALISK_TISSUE_ASSIMILATION: - ItemData(219 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 19, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(219 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 19, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"hots"}), ItemNames.ULTRALISK_MONARCH_BLADES: - ItemData(220 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 20, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(220 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 20, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"hots"}), ItemNames.CORRUPTOR_CAUSTIC_SPRAY: - ItemData(221 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 21, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, + ItemData(221 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 21, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, origin={"ext"}), ItemNames.CORRUPTOR_CORRUPTION: - ItemData(222 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 22, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, + ItemData(222 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 22, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, origin={"ext"}), ItemNames.SCOURGE_VIRULENT_SPORES: - ItemData(223 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 23, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + ItemData(223 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 23, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, origin={"ext"}), ItemNames.SCOURGE_RESOURCE_EFFICIENCY: - ItemData(224 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 24, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + ItemData(224 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 24, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, origin={"ext"}, classification=ItemClassification.progression), ItemNames.SCOURGE_SWARM_SCOURGE: - ItemData(225 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 25, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + ItemData(225 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 25, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, origin={"ext"}), ItemNames.ZERGLING_SHREDDING_CLAWS: - ItemData(226 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 26, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + ItemData(226 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 26, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"ext"}), ItemNames.ROACH_GLIAL_RECONSTITUTION: - ItemData(227 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 27, SC2Race.ZERG, parent_item=ItemNames.ROACH, + ItemData(227 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 27, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"ext"}), ItemNames.ROACH_ORGANIC_CARAPACE: - ItemData(228 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 28, SC2Race.ZERG, parent_item=ItemNames.ROACH, + ItemData(228 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 28, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"ext"}), ItemNames.HYDRALISK_MUSCULAR_AUGMENTS: - ItemData(229 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_1, 29, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + ItemData(229 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 29, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"bw"}), ItemNames.HYDRALISK_RESOURCE_EFFICIENCY: - ItemData(230 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 0, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + ItemData(230 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 0, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, origin={"bw"}), ItemNames.BANELING_CENTRIFUGAL_HOOKS: - ItemData(231 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 1, SC2Race.ZERG, + ItemData(231 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}), ItemNames.BANELING_TUNNELING_JAWS: - ItemData(232 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 2, SC2Race.ZERG, + ItemData(232 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 2, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}), ItemNames.BANELING_RAPID_METAMORPH: - ItemData(233 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 3, SC2Race.ZERG, + ItemData(233 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 3, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}), ItemNames.MUTALISK_SEVERING_GLAIVE: - ItemData(234 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 4, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + ItemData(234 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 4, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, origin={"ext"}), ItemNames.MUTALISK_AERODYNAMIC_GLAIVE_SHAPE: - ItemData(235 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 5, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + ItemData(235 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 5, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, origin={"ext"}), ItemNames.SWARM_HOST_LOCUST_METABOLIC_BOOST: - ItemData(236 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 6, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(236 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 6, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"ext"}, classification=ItemClassification.filler), ItemNames.SWARM_HOST_ENDURING_LOCUSTS: - ItemData(237 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 7, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(237 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 7, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"ext"}), ItemNames.SWARM_HOST_ORGANIC_CARAPACE: - ItemData(238 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(238 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"ext"}), ItemNames.SWARM_HOST_RESOURCE_EFFICIENCY: - ItemData(239 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(239 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"ext"}), ItemNames.ULTRALISK_ANABOLIC_SYNTHESIS: - ItemData(240 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 10, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(240 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 10, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"bw"}, classification=ItemClassification.filler), ItemNames.ULTRALISK_CHITINOUS_PLATING: - ItemData(241 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 11, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(241 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 11, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"bw"}), ItemNames.ULTRALISK_ORGANIC_CARAPACE: - ItemData(242 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(242 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"ext"}), ItemNames.ULTRALISK_RESOURCE_EFFICIENCY: - ItemData(243 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(243 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"bw"}), ItemNames.DEVOURER_CORROSIVE_SPRAY: - ItemData(244 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 14, SC2Race.ZERG, + ItemData(244 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 14, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), ItemNames.DEVOURER_GAPING_MAW: - ItemData(245 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 15, SC2Race.ZERG, + ItemData(245 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 15, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), ItemNames.DEVOURER_IMPROVED_OSMOSIS: - ItemData(246 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 16, SC2Race.ZERG, + ItemData(246 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 16, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}, classification=ItemClassification.filler), ItemNames.DEVOURER_PRESCIENT_SPORES: - ItemData(247 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 17, SC2Race.ZERG, + ItemData(247 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 17, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), ItemNames.GUARDIAN_PROLONGED_DISPERSION: - ItemData(248 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 18, SC2Race.ZERG, + ItemData(248 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 18, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), ItemNames.GUARDIAN_PRIMAL_ADAPTATION: - ItemData(249 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 19, SC2Race.ZERG, + ItemData(249 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 19, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), ItemNames.GUARDIAN_SORONAN_ACID: - ItemData(250 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 20, SC2Race.ZERG, + ItemData(250 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 20, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), ItemNames.IMPALER_ADAPTIVE_TALONS: - ItemData(251 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 21, SC2Race.ZERG, + ItemData(251 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 21, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}, classification=ItemClassification.filler), ItemNames.IMPALER_SECRETION_GLANDS: - ItemData(252 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 22, SC2Race.ZERG, + ItemData(252 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 22, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}), ItemNames.IMPALER_HARDENED_TENTACLE_SPINES: - ItemData(253 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 23, SC2Race.ZERG, + ItemData(253 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 23, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}), ItemNames.LURKER_SEISMIC_SPINES: - ItemData(254 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 24, SC2Race.ZERG, + ItemData(254 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 24, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK_LURKER_ASPECT, origin={"ext"}), ItemNames.LURKER_ADAPTED_SPINES: - ItemData(255 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 25, SC2Race.ZERG, + ItemData(255 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 25, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK_LURKER_ASPECT, origin={"ext"}), ItemNames.RAVAGER_POTENT_BILE: - ItemData(256 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 26, SC2Race.ZERG, + ItemData(256 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 26, SC2Race.ZERG, parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), ItemNames.RAVAGER_BLOATED_BILE_DUCTS: - ItemData(257 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 27, SC2Race.ZERG, + ItemData(257 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 27, SC2Race.ZERG, parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), ItemNames.RAVAGER_DEEP_TUNNEL: - ItemData(258 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 28, SC2Race.ZERG, + ItemData(258 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 28, SC2Race.ZERG, parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), ItemNames.VIPER_PARASITIC_BOMB: - ItemData(259 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_2, 29, SC2Race.ZERG, + ItemData(259 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_2, 29, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), ItemNames.VIPER_PARALYTIC_BARBS: - ItemData(260 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 0, SC2Race.ZERG, + ItemData(260 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 0, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), ItemNames.VIPER_VIRULENT_MICROBES: - ItemData(261 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 1, SC2Race.ZERG, + ItemData(261 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 1, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), ItemNames.BROOD_LORD_POROUS_CARTILAGE: - ItemData(262 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 2, SC2Race.ZERG, + ItemData(262 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 2, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), ItemNames.BROOD_LORD_EVOLVED_CARAPACE: - ItemData(263 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 3, SC2Race.ZERG, + ItemData(263 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 3, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), ItemNames.BROOD_LORD_SPLITTER_MITOSIS: - ItemData(264 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 4, SC2Race.ZERG, + ItemData(264 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 4, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), ItemNames.BROOD_LORD_RESOURCE_EFFICIENCY: - ItemData(265 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 5, SC2Race.ZERG, + ItemData(265 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 5, SC2Race.ZERG, parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), ItemNames.INFESTOR_INFESTED_TERRAN: - ItemData(266 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 6, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, + ItemData(266 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 6, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, origin={"ext"}), ItemNames.INFESTOR_MICROBIAL_SHROUD: - ItemData(267 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 7, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, + ItemData(267 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 7, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, origin={"ext"}), ItemNames.SWARM_QUEEN_SPAWN_LARVAE: - ItemData(268 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(268 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}), ItemNames.SWARM_QUEEN_DEEP_TUNNEL: - ItemData(269 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(269 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}), ItemNames.SWARM_QUEEN_ORGANIC_CARAPACE: - ItemData(270 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(270 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}, classification=ItemClassification.filler), ItemNames.SWARM_QUEEN_BIO_MECHANICAL_TRANSFUSION: - ItemData(271 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(271 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}), ItemNames.SWARM_QUEEN_RESOURCE_EFFICIENCY: - ItemData(272 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 12, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(272 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 12, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}), ItemNames.SWARM_QUEEN_INCUBATOR_CHAMBER: - ItemData(273 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 13, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + ItemData(273 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 13, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, origin={"ext"}), ItemNames.BROOD_QUEEN_FUNGAL_GROWTH: - ItemData(274 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 14, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + ItemData(274 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 14, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, origin={"ext"}), ItemNames.BROOD_QUEEN_ENSNARE: - ItemData(275 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 15, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + ItemData(275 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 15, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, origin={"ext"}), ItemNames.BROOD_QUEEN_ENHANCED_MITOCHONDRIA: - ItemData(276 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mutation_3, 16, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + ItemData(276 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_3, 16, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, origin={"ext"}), ItemNames.ZERGLING_RAPTOR_STRAIN: - ItemData(300 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + ItemData(300 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Strain, 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), ItemNames.ZERGLING_SWARMLING_STRAIN: - ItemData(301 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + ItemData(301 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Strain, 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, origin={"hots"}), ItemNames.ROACH_VILE_STRAIN: - ItemData(302 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 2, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), + ItemData(302 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Strain, 2, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), ItemNames.ROACH_CORPSER_STRAIN: - ItemData(303 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), + ItemData(303 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Strain, 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}), ItemNames.HYDRALISK_IMPALER_ASPECT: - ItemData(304 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 0, SC2Race.ZERG, origin={"hots"}, + ItemData(304 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Morph, 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), ItemNames.HYDRALISK_LURKER_ASPECT: - ItemData(305 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 1, SC2Race.ZERG, origin={"hots"}, + ItemData(305 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Morph, 1, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), ItemNames.BANELING_SPLITTER_STRAIN: - ItemData(306 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 6, SC2Race.ZERG, + ItemData(306 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Strain, 6, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}), ItemNames.BANELING_HUNTER_STRAIN: - ItemData(307 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 7, SC2Race.ZERG, + ItemData(307 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Strain, 7, SC2Race.ZERG, parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}), ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT: - ItemData(308 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 2, SC2Race.ZERG, origin={"hots"}, + ItemData(308 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Morph, 2, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT: - ItemData(309 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 3, SC2Race.ZERG, origin={"hots"}, + ItemData(309 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Morph, 3, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), ItemNames.SWARM_HOST_CARRION_STRAIN: - ItemData(310 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(310 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Strain, 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"hots"}), ItemNames.SWARM_HOST_CREEPER_STRAIN: - ItemData(311 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + ItemData(311 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Strain, 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, origin={"hots"}, classification=ItemClassification.filler), ItemNames.ULTRALISK_NOXIOUS_STRAIN: - ItemData(312 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(312 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Strain, 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"hots"}, classification=ItemClassification.filler), ItemNames.ULTRALISK_TORRASQUE_STRAIN: - ItemData(313 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Strain, 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + ItemData(313 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Strain, 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, origin={"hots"}), - ItemNames.KERRIGAN_KINETIC_BLAST: ItemData(400 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_HEROIC_FORTITUDE: ItemData(401 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 1, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEAPING_STRIKE: ItemData(402 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 2, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_CRUSHING_GRIP: ItemData(403 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 3, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_CHAIN_REACTION: ItemData(404 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 4, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_PSIONIC_SHIFT: ItemData(405 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 5, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_ZERGLING_RECONSTITUTION: ItemData(406 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.filler), - ItemNames.KERRIGAN_IMPROVED_OVERLORDS: ItemData(407 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 1, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_AUTOMATED_EXTRACTORS: ItemData(408 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 2, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_WILD_MUTATION: ItemData(409 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 6, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_SPAWN_BANELINGS: ItemData(410 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 7, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_MEND: ItemData(411 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 8, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_TWIN_DRONES: ItemData(412 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 3, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_MALIGNANT_CREEP: ItemData(413 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 4, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_VESPENE_EFFICIENCY: ItemData(414 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 5, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_INFEST_BROODLINGS: ItemData(415 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 9, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_FURY: ItemData(416 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 10, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_ABILITY_EFFICIENCY: ItemData(417 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 11, SC2Race.ZERG, origin={"hots"}), - ItemNames.KERRIGAN_APOCALYPSE: ItemData(418 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 12, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_SPAWN_LEVIATHAN: ItemData(419 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 13, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), - ItemNames.KERRIGAN_DROP_PODS: ItemData(420 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Ability, 14, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_KINETIC_BLAST: ItemData(400 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_HEROIC_FORTITUDE: ItemData(401 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 1, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEAPING_STRIKE: ItemData(402 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 2, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_CRUSHING_GRIP: ItemData(403 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 3, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_CHAIN_REACTION: ItemData(404 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 4, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_PSIONIC_SHIFT: ItemData(405 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 5, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_ZERGLING_RECONSTITUTION: ItemData(406 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Evolution_Pit, 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.filler), + ItemNames.KERRIGAN_IMPROVED_OVERLORDS: ItemData(407 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Evolution_Pit, 1, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_AUTOMATED_EXTRACTORS: ItemData(408 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Evolution_Pit, 2, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_WILD_MUTATION: ItemData(409 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 6, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_SPAWN_BANELINGS: ItemData(410 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 7, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_MEND: ItemData(411 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 8, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_TWIN_DRONES: ItemData(412 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Evolution_Pit, 3, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_MALIGNANT_CREEP: ItemData(413 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Evolution_Pit, 4, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_VESPENE_EFFICIENCY: ItemData(414 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Evolution_Pit, 5, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_INFEST_BROODLINGS: ItemData(415 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 9, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_FURY: ItemData(416 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 10, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_ABILITY_EFFICIENCY: ItemData(417 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 11, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_APOCALYPSE: ItemData(418 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 12, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_SPAWN_LEVIATHAN: ItemData(419 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 13, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_DROP_PODS: ItemData(420 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Ability, 14, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), # Handled separately from other abilities - ItemNames.KERRIGAN_PRIMAL_FORM: ItemData(421 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Primal_Form, 0, SC2Race.ZERG, origin={"hots"}), - - ItemNames.KERRIGAN_LEVELS_10: ItemData(500 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 10, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_9: ItemData(501 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 9, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_8: ItemData(502 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 8, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_7: ItemData(503 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 7, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_6: ItemData(504 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 6, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_5: ItemData(505 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 5, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_4: ItemData(506 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 4, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), - ItemNames.KERRIGAN_LEVELS_3: ItemData(507 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 3, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), - ItemNames.KERRIGAN_LEVELS_2: ItemData(508 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 2, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), - ItemNames.KERRIGAN_LEVELS_1: ItemData(509 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 1, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), - ItemNames.KERRIGAN_LEVELS_14: ItemData(510 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 14, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_35: ItemData(511 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 35, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), - ItemNames.KERRIGAN_LEVELS_70: ItemData(512 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Level, 70, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_PRIMAL_FORM: ItemData(421 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Primal_Form, 0, SC2Race.ZERG, origin={"hots"}), + + ItemNames.KERRIGAN_LEVELS_10: ItemData(500 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 10, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_9: ItemData(501 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 9, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_8: ItemData(502 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 8, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_7: ItemData(503 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 7, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_6: ItemData(504 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 6, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_5: ItemData(505 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 5, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_4: ItemData(506 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 4, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_3: ItemData(507 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 3, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_2: ItemData(508 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 2, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_1: ItemData(509 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 1, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_14: ItemData(510 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 14, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_35: ItemData(511 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 35, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_70: ItemData(512 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Level, 70, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), # Zerg Mercs - ItemNames.INFESTED_MEDICS: ItemData(600 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mercenary, 0, SC2Race.ZERG, origin={"ext"}), - ItemNames.INFESTED_SIEGE_TANKS: ItemData(601 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mercenary, 1, SC2Race.ZERG, origin={"ext"}), - ItemNames.INFESTED_BANSHEES: ItemData(602 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Mercenary, 2, SC2Race.ZERG, origin={"ext"}), + ItemNames.INFESTED_MEDICS: ItemData(600 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mercenary, 0, SC2Race.ZERG, origin={"ext"}), + ItemNames.INFESTED_SIEGE_TANKS: ItemData(601 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mercenary, 1, SC2Race.ZERG, origin={"ext"}), + ItemNames.INFESTED_BANSHEES: ItemData(602 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mercenary, 2, SC2Race.ZERG, origin={"ext"}), # Misc Upgrades - ItemNames.OVERLORD_VENTRAL_SACS: ItemData(700 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Evolution_Pit, 6, SC2Race.ZERG, origin={"bw"}), + ItemNames.OVERLORD_VENTRAL_SACS: ItemData(700 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Evolution_Pit, 6, SC2Race.ZERG, origin={"bw"}), # Morphs - ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT: ItemData(800 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 6, SC2Race.ZERG, origin={"bw"}), - ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT: ItemData(801 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 7, SC2Race.ZERG, origin={"bw"}), - ItemNames.ROACH_RAVAGER_ASPECT: ItemData(802 + SC2HOTS_ITEM_ID_OFFSET, ItemType.Morph, 8, SC2Race.ZERG, origin={"ext"}), + ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT: ItemData(800 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Morph, 6, SC2Race.ZERG, origin={"bw"}), + ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT: ItemData(801 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Morph, 7, SC2Race.ZERG, origin={"bw"}), + ItemNames.ROACH_RAVAGER_ASPECT: ItemData(802 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Morph, 8, SC2Race.ZERG, origin={"ext"}), # Protoss Units (those that aren't as items in WoL (Prophecy)) - ItemNames.OBSERVER: ItemData(0 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 9, SC2Race.PROTOSS, + ItemNames.OBSERVER: ItemData(0 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 9, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"wol"}), - ItemNames.CENTURION: ItemData(1 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 10, SC2Race.PROTOSS, + ItemNames.CENTURION: ItemData(1 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 10, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SENTINEL: ItemData(2 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 11, SC2Race.PROTOSS, + ItemNames.SENTINEL: ItemData(2 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 11, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SUPPLICANT: ItemData(3 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 12, SC2Race.PROTOSS, + ItemNames.SUPPLICANT: ItemData(3 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 12, SC2Race.PROTOSS, classification=ItemClassification.filler, important_for_filtering=True, origin={"ext"}), - ItemNames.INSTIGATOR: ItemData(4 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 13, SC2Race.PROTOSS, + ItemNames.INSTIGATOR: ItemData(4 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 13, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.SLAYER: ItemData(5 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 14, SC2Race.PROTOSS, + ItemNames.SLAYER: ItemData(5 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 14, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.SENTRY: ItemData(6 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 15, SC2Race.PROTOSS, + ItemNames.SENTRY: ItemData(6 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 15, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.ENERGIZER: ItemData(7 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 16, SC2Race.PROTOSS, + ItemNames.ENERGIZER: ItemData(7 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 16, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.HAVOC: ItemData(8 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 17, SC2Race.PROTOSS, + ItemNames.HAVOC: ItemData(8 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 17, SC2Race.PROTOSS, origin={"lotv"}, important_for_filtering=True), - ItemNames.SIGNIFIER: ItemData(9 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 18, SC2Race.PROTOSS, + ItemNames.SIGNIFIER: ItemData(9 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 18, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.ASCENDANT: ItemData(10 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 19, SC2Race.PROTOSS, + ItemNames.ASCENDANT: ItemData(10 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 19, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.AVENGER: ItemData(11 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 20, SC2Race.PROTOSS, + ItemNames.AVENGER: ItemData(11 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 20, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.BLOOD_HUNTER: ItemData(12 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 21, SC2Race.PROTOSS, + ItemNames.BLOOD_HUNTER: ItemData(12 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 21, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.DRAGOON: ItemData(13 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 22, SC2Race.PROTOSS, + ItemNames.DRAGOON: ItemData(13 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 22, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.DARK_ARCHON: ItemData(14 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 23, SC2Race.PROTOSS, + ItemNames.DARK_ARCHON: ItemData(14 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 23, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.ADEPT: ItemData(15 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 24, SC2Race.PROTOSS, + ItemNames.ADEPT: ItemData(15 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 24, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.WARP_PRISM: ItemData(16 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 25, SC2Race.PROTOSS, + ItemNames.WARP_PRISM: ItemData(16 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 25, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.ANNIHILATOR: ItemData(17 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 26, SC2Race.PROTOSS, + ItemNames.ANNIHILATOR: ItemData(17 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 26, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.VANGUARD: ItemData(18 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 27, SC2Race.PROTOSS, + ItemNames.VANGUARD: ItemData(18 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 27, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.WRATHWALKER: ItemData(19 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 28, SC2Race.PROTOSS, + ItemNames.WRATHWALKER: ItemData(19 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 28, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.REAVER: ItemData(20 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit, 29, SC2Race.PROTOSS, + ItemNames.REAVER: ItemData(20 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit, 29, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.DISRUPTOR: ItemData(21 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 0, SC2Race.PROTOSS, + ItemNames.DISRUPTOR: ItemData(21 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit_2, 0, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.MIRAGE: ItemData(22 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 1, SC2Race.PROTOSS, + ItemNames.MIRAGE: ItemData(22 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit_2, 1, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.CORSAIR: ItemData(23 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 2, SC2Race.PROTOSS, + ItemNames.CORSAIR: ItemData(23 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit_2, 2, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.DESTROYER: ItemData(24 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 3, SC2Race.PROTOSS, + ItemNames.DESTROYER: ItemData(24 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit_2, 3, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SCOUT: ItemData(25 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 4, SC2Race.PROTOSS, + ItemNames.SCOUT: ItemData(25 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit_2, 4, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.TEMPEST: ItemData(26 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 5, SC2Race.PROTOSS, + ItemNames.TEMPEST: ItemData(26 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit_2, 5, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.MOTHERSHIP: ItemData(27 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 6, SC2Race.PROTOSS, + ItemNames.MOTHERSHIP: ItemData(27 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit_2, 6, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.ARBITER: ItemData(28 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 7, SC2Race.PROTOSS, + ItemNames.ARBITER: ItemData(28 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit_2, 7, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.ORACLE: ItemData(29 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Unit_2, 8, SC2Race.PROTOSS, + ItemNames.ORACLE: ItemData(29 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Unit_2, 8, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), # Protoss Upgrades - ItemNames.PROGRESSIVE_PROTOSS_GROUND_WEAPON: ItemData(100 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 0, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_GROUND_ARMOR: ItemData(101 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 2, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_SHIELDS: ItemData(102 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 4, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_AIR_WEAPON: ItemData(103 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 6, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_AIR_ARMOR: ItemData(104 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 8, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_GROUND_WEAPON: ItemData(100 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 0, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_GROUND_ARMOR: ItemData(101 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 2, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_SHIELDS: ItemData(102 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 4, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_WEAPON: ItemData(103 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 6, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_ARMOR: ItemData(104 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 8, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), # Upgrade bundle 'number' values are used as indices to get affected 'number's - ItemNames.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE: ItemData(105 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 11, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE: ItemData(106 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 12, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_GROUND_UPGRADE: ItemData(107 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 13, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_AIR_UPGRADE: ItemData(108 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 14, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), - ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Upgrade, 15, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE: ItemData(105 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 11, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE: ItemData(106 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 12, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_GROUND_UPGRADE: ItemData(107 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 13, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_UPGRADE: ItemData(108 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 14, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 15, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), # Protoss Buildings - ItemNames.PHOTON_CANNON: ItemData(200 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Building, 0, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), - ItemNames.KHAYDARIN_MONOLITH: ItemData(201 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Building, 1, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SHIELD_BATTERY: ItemData(202 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Building, 2, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.PHOTON_CANNON: ItemData(200 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Building, 0, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), + ItemNames.KHAYDARIN_MONOLITH: ItemData(201 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Building, 1, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SHIELD_BATTERY: ItemData(202 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Building, 2, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), # Protoss Unit Upgrades - ItemNames.SUPPLICANT_BLOOD_SHIELD: ItemData(300 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 0, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), - ItemNames.SUPPLICANT_SOUL_AUGMENTATION: ItemData(301 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), - ItemNames.SUPPLICANT_SHIELD_REGENERATION: ItemData(302 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), - ItemNames.ADEPT_SHOCKWAVE: ItemData(303 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 3, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), - ItemNames.ADEPT_RESONATING_GLAIVES: ItemData(304 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 4, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), - ItemNames.ADEPT_PHASE_BULWARK: ItemData(305 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), - ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES: ItemData(306 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 6, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), - ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION: ItemData(307 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 7, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), - ItemNames.DRAGOON_HIGH_IMPACT_PHASE_DISRUPTORS: ItemData(308 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 8, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), - ItemNames.DRAGOON_TRILLIC_COMPRESSION_SYSTEM: ItemData(309 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), - ItemNames.DRAGOON_SINGULARITY_CHARGE: ItemData(310 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 10, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.DRAGOON), - ItemNames.DRAGOON_ENHANCED_STRIDER_SERVOS: ItemData(311 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.DRAGOON), - ItemNames.SCOUT_COMBAT_SENSOR_ARRAY: ItemData(312 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), - ItemNames.SCOUT_APIAL_SENSORS: ItemData(313 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 13, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), - ItemNames.SCOUT_GRAVITIC_THRUSTERS: ItemData(314 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), - ItemNames.SCOUT_ADVANCED_PHOTON_BLASTERS: ItemData(315 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), - ItemNames.TEMPEST_TECTONIC_DESTABILIZERS: ItemData(316 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 16, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), - ItemNames.TEMPEST_QUANTIC_REACTOR: ItemData(317 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 17, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), - ItemNames.TEMPEST_GRAVITY_SLING: ItemData(318 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 18, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.TEMPEST), - ItemNames.PHOENIX_MIRAGE_IONIC_WAVELENGTH_FLUX: ItemData(319 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 19, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.PHOENIX_MIRAGE_ANION_PULSE_CRYSTALS: ItemData(320 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 20, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.CORSAIR_STEALTH_DRIVE: ItemData(321 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.CORSAIR), - ItemNames.CORSAIR_ARGUS_JEWEL: ItemData(322 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 22, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), - ItemNames.CORSAIR_SUSTAINING_DISRUPTION: ItemData(323 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 23, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), - ItemNames.CORSAIR_NEUTRON_SHIELDS: ItemData(324 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 24, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.CORSAIR), - ItemNames.ORACLE_STEALTH_DRIVE: ItemData(325 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 25, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), - ItemNames.ORACLE_STASIS_CALIBRATION: ItemData(326 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 26, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), - ItemNames.ORACLE_TEMPORAL_ACCELERATION_BEAM: ItemData(327 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 27, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), - ItemNames.ARBITER_CHRONOSTATIC_REINFORCEMENT: ItemData(328 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 28, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), - ItemNames.ARBITER_KHAYDARIN_CORE: ItemData(329 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_1, 29, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), - ItemNames.ARBITER_SPACETIME_ANCHOR: ItemData(330 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 0, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), - ItemNames.ARBITER_RESOURCE_EFFICIENCY: ItemData(331 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), - ItemNames.ARBITER_ENHANCED_CLOAK_FIELD: ItemData(332 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.SUPPLICANT_BLOOD_SHIELD: ItemData(300 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 0, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.SUPPLICANT_SOUL_AUGMENTATION: ItemData(301 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.SUPPLICANT_SHIELD_REGENERATION: ItemData(302 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.ADEPT_SHOCKWAVE: ItemData(303 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 3, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.ADEPT_RESONATING_GLAIVES: ItemData(304 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 4, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.ADEPT_PHASE_BULWARK: ItemData(305 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES: ItemData(306 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 6, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), + ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION: ItemData(307 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 7, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), + ItemNames.DRAGOON_HIGH_IMPACT_PHASE_DISRUPTORS: ItemData(308 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 8, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_TRILLIC_COMPRESSION_SYSTEM: ItemData(309 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_SINGULARITY_CHARGE: ItemData(310 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 10, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_ENHANCED_STRIDER_SERVOS: ItemData(311 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.DRAGOON), + ItemNames.SCOUT_COMBAT_SENSOR_ARRAY: ItemData(312 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_APIAL_SENSORS: ItemData(313 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 13, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_GRAVITIC_THRUSTERS: ItemData(314 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_ADVANCED_PHOTON_BLASTERS: ItemData(315 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), + ItemNames.TEMPEST_TECTONIC_DESTABILIZERS: ItemData(316 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 16, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.TEMPEST_QUANTIC_REACTOR: ItemData(317 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 17, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.TEMPEST_GRAVITY_SLING: ItemData(318 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 18, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.PHOENIX_MIRAGE_IONIC_WAVELENGTH_FLUX: ItemData(319 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 19, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.PHOENIX_MIRAGE_ANION_PULSE_CRYSTALS: ItemData(320 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 20, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.CORSAIR_STEALTH_DRIVE: ItemData(321 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_ARGUS_JEWEL: ItemData(322 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 22, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_SUSTAINING_DISRUPTION: ItemData(323 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 23, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_NEUTRON_SHIELDS: ItemData(324 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 24, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.ORACLE_STEALTH_DRIVE: ItemData(325 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 25, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ORACLE_STASIS_CALIBRATION: ItemData(326 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 26, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ORACLE_TEMPORAL_ACCELERATION_BEAM: ItemData(327 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 27, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ARBITER_CHRONOSTATIC_REINFORCEMENT: ItemData(328 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 28, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_KHAYDARIN_CORE: ItemData(329 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 29, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_SPACETIME_ANCHOR: ItemData(330 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 0, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_RESOURCE_EFFICIENCY: ItemData(331 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_ENHANCED_CLOAK_FIELD: ItemData(332 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), ItemNames.CARRIER_GRAVITON_CATAPULT: - ItemData(333 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 3, SC2Race.PROTOSS, origin={"wol"}, + ItemData(333 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 3, SC2Race.PROTOSS, origin={"wol"}, parent_item=ItemNames.CARRIER), ItemNames.CARRIER_HULL_OF_PAST_GLORIES: - ItemData(334 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 4, SC2Race.PROTOSS, origin={"bw"}, + ItemData(334 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 4, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CARRIER), ItemNames.VOID_RAY_DESTROYER_FLUX_VANES: - ItemData(335 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 5, SC2Race.PROTOSS, classification=ItemClassification.filler, + ItemData(335 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 5, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}), ItemNames.DESTROYER_REFORGED_BLOODSHARD_CORE: - ItemData(336 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 6, SC2Race.PROTOSS, origin={"ext"}, + ItemData(336 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 6, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DESTROYER), ItemNames.WARP_PRISM_GRAVITIC_DRIVE: - ItemData(337 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 7, SC2Race.PROTOSS, classification=ItemClassification.filler, + ItemData(337 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 7, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.WARP_PRISM), ItemNames.WARP_PRISM_PHASE_BLASTER: - ItemData(338 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 8, SC2Race.PROTOSS, + ItemData(338 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 8, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}, parent_item=ItemNames.WARP_PRISM), - ItemNames.WARP_PRISM_WAR_CONFIGURATION: ItemData(339 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WARP_PRISM), - ItemNames.OBSERVER_GRAVITIC_BOOSTERS: ItemData(340 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), - ItemNames.OBSERVER_SENSOR_ARRAY: ItemData(341 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), - ItemNames.REAVER_SCARAB_DAMAGE: ItemData(342 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 12, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), - ItemNames.REAVER_SOLARITE_PAYLOAD: ItemData(343 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.REAVER), - ItemNames.REAVER_REAVER_CAPACITY: ItemData(344 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.REAVER), - ItemNames.REAVER_RESOURCE_EFFICIENCY: ItemData(345 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 15, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), - ItemNames.VANGUARD_AGONY_LAUNCHERS: ItemData(346 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 16, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), - ItemNames.VANGUARD_MATTER_DISPERSION: ItemData(347 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 17, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), - ItemNames.IMMORTAL_ANNIHILATOR_SINGULARITY_CHARGE: ItemData(348 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 18, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS: ItemData(349 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 19, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.COLOSSUS_PACIFICATION_PROTOCOL: ItemData(350 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 20, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.COLOSSUS), - ItemNames.WRATHWALKER_RAPID_POWER_CYCLING: ItemData(351 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), - ItemNames.WRATHWALKER_EYE_OF_WRATH: ItemData(352 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 22, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), - ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHROUD_OF_ADUN: ItemData(353 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 23, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHADOW_GUARD_TRAINING: ItemData(354 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 24, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK: ItemData(355 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 25, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), - ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_RESOURCE_EFFICIENCY: ItemData(356 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 26, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.DARK_TEMPLAR_DARK_ARCHON_MELD: ItemData(357 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 27, SC2Race.PROTOSS, origin={"bw"}, important_for_filtering=True ,parent_item=ItemNames.DARK_TEMPLAR), - ItemNames.HIGH_TEMPLAR_SIGNIFIER_UNSHACKLED_PSIONIC_STORM: ItemData(358 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 28, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.HIGH_TEMPLAR_SIGNIFIER_HALLUCINATION: ItemData(359 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_2, 29, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}), - ItemNames.HIGH_TEMPLAR_SIGNIFIER_KHAYDARIN_AMULET: ItemData(360 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 0, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.ARCHON_HIGH_ARCHON: ItemData(361 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 1, SC2Race.PROTOSS, origin={"ext"}, important_for_filtering=True), - ItemNames.DARK_ARCHON_FEEDBACK: ItemData(362 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 2, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.DARK_ARCHON_MAELSTROM: ItemData(363 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 3, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.DARK_ARCHON_ARGUS_TALISMAN: ItemData(364 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 4, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.ASCENDANT_POWER_OVERWHELMING: ItemData(365 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), - ItemNames.ASCENDANT_CHAOTIC_ATTUNEMENT: ItemData(366 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 6, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), - ItemNames.ASCENDANT_BLOOD_AMULET: ItemData(367 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 7, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), - ItemNames.SENTRY_ENERGIZER_HAVOC_CLOAKING_MODULE: ItemData(368 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 8, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.SENTRY_ENERGIZER_HAVOC_SHIELD_BATTERY_RAPID_RECHARGING: ItemData(369 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 9, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.SENTRY_FORCE_FIELD: ItemData(370 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), - ItemNames.SENTRY_HALLUCINATION: ItemData(371 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), - ItemNames.ENERGIZER_RECLAMATION: ItemData(372 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), - ItemNames.ENERGIZER_FORGED_CHASSIS: ItemData(373 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), - ItemNames.HAVOC_DETECT_WEAKNESS: ItemData(374 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 14, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), - ItemNames.HAVOC_BLOODSHARD_RESONANCE: ItemData(375 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), - ItemNames.ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS: ItemData(376 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 16, SC2Race.PROTOSS, origin={"bw"}), - ItemNames.ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY: ItemData(377 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Forge_3, 17, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.WARP_PRISM_WAR_CONFIGURATION: ItemData(339 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WARP_PRISM), + ItemNames.OBSERVER_GRAVITIC_BOOSTERS: ItemData(340 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), + ItemNames.OBSERVER_SENSOR_ARRAY: ItemData(341 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), + ItemNames.REAVER_SCARAB_DAMAGE: ItemData(342 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 12, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_SOLARITE_PAYLOAD: ItemData(343 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_REAVER_CAPACITY: ItemData(344 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_RESOURCE_EFFICIENCY: ItemData(345 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 15, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.VANGUARD_AGONY_LAUNCHERS: ItemData(346 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 16, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), + ItemNames.VANGUARD_MATTER_DISPERSION: ItemData(347 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 17, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), + ItemNames.IMMORTAL_ANNIHILATOR_SINGULARITY_CHARGE: ItemData(348 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 18, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS: ItemData(349 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 19, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), + ItemNames.COLOSSUS_PACIFICATION_PROTOCOL: ItemData(350 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 20, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.COLOSSUS), + ItemNames.WRATHWALKER_RAPID_POWER_CYCLING: ItemData(351 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), + ItemNames.WRATHWALKER_EYE_OF_WRATH: ItemData(352 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 22, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHROUD_OF_ADUN: ItemData(353 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 23, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHADOW_GUARD_TRAINING: ItemData(354 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 24, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK: ItemData(355 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 25, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_RESOURCE_EFFICIENCY: ItemData(356 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 26, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.DARK_TEMPLAR_DARK_ARCHON_MELD: ItemData(357 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 27, SC2Race.PROTOSS, origin={"bw"}, important_for_filtering=True ,parent_item=ItemNames.DARK_TEMPLAR), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_UNSHACKLED_PSIONIC_STORM: ItemData(358 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 28, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_HALLUCINATION: ItemData(359 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_2, 29, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_KHAYDARIN_AMULET: ItemData(360 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 0, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ARCHON_HIGH_ARCHON: ItemData(361 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 1, SC2Race.PROTOSS, origin={"ext"}, important_for_filtering=True), + ItemNames.DARK_ARCHON_FEEDBACK: ItemData(362 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 2, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_ARCHON_MAELSTROM: ItemData(363 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 3, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_ARCHON_ARGUS_TALISMAN: ItemData(364 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 4, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ASCENDANT_POWER_OVERWHELMING: ItemData(365 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.ASCENDANT_CHAOTIC_ATTUNEMENT: ItemData(366 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 6, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.ASCENDANT_BLOOD_AMULET: ItemData(367 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 7, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.SENTRY_ENERGIZER_HAVOC_CLOAKING_MODULE: ItemData(368 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 8, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SENTRY_ENERGIZER_HAVOC_SHIELD_BATTERY_RAPID_RECHARGING: ItemData(369 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 9, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SENTRY_FORCE_FIELD: ItemData(370 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), + ItemNames.SENTRY_HALLUCINATION: ItemData(371 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), + ItemNames.ENERGIZER_RECLAMATION: ItemData(372 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), + ItemNames.ENERGIZER_FORGED_CHASSIS: ItemData(373 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), + ItemNames.HAVOC_DETECT_WEAKNESS: ItemData(374 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 14, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), + ItemNames.HAVOC_BLOODSHARD_RESONANCE: ItemData(375 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), + ItemNames.ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS: ItemData(376 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 16, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY: ItemData(377 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 17, SC2Race.PROTOSS, origin={"bw"}), # SoA Calldown powers - ItemNames.SOA_CHRONO_SURGE: ItemData(700 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 0, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_PROGRESSIVE_PROXY_PYLON: ItemData(701 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Progressive, 0, SC2Race.PROTOSS, origin={"lotv"}, quantity=2), - ItemNames.SOA_PYLON_OVERCHARGE: ItemData(702 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 1, SC2Race.PROTOSS, origin={"ext"}), - ItemNames.SOA_ORBITAL_STRIKE: ItemData(703 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 2, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_TEMPORAL_FIELD: ItemData(704 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 3, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_SOLAR_LANCE: ItemData(705 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 4, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SOA_MASS_RECALL: ItemData(706 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 5, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_SHIELD_OVERCHARGE: ItemData(707 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 6, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_DEPLOY_FENIX: ItemData(708 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 7, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SOA_PURIFIER_BEAM: ItemData(709 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 8, SC2Race.PROTOSS, origin={"lotv"}), - ItemNames.SOA_TIME_STOP: ItemData(710 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 9, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), - ItemNames.SOA_SOLAR_BOMBARDMENT: ItemData(711 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Spear_Of_Adun, 10, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_CHRONO_SURGE: ItemData(700 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 0, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_PROGRESSIVE_PROXY_PYLON: ItemData(701 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Progressive, 0, SC2Race.PROTOSS, origin={"lotv"}, quantity=2), + ItemNames.SOA_PYLON_OVERCHARGE: ItemData(702 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 1, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SOA_ORBITAL_STRIKE: ItemData(703 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 2, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_TEMPORAL_FIELD: ItemData(704 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 3, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_SOLAR_LANCE: ItemData(705 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 4, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_MASS_RECALL: ItemData(706 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 5, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_SHIELD_OVERCHARGE: ItemData(707 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 6, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_DEPLOY_FENIX: ItemData(708 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 7, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_PURIFIER_BEAM: ItemData(709 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 8, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_TIME_STOP: ItemData(710 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 9, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_SOLAR_BOMBARDMENT: ItemData(711 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 10, SC2Race.PROTOSS, origin={"lotv"}), # Generic Protoss Upgrades ItemNames.MATRIX_OVERLOAD: - ItemData(800 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 0, SC2Race.PROTOSS, origin={"lotv"}), + ItemData(800 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 0, SC2Race.PROTOSS, origin={"lotv"}), ItemNames.QUATRO: - ItemData(801 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 1, SC2Race.PROTOSS, origin={"ext"}), + ItemData(801 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 1, SC2Race.PROTOSS, origin={"ext"}), ItemNames.NEXUS_OVERCHARGE: - ItemData(802 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 2, SC2Race.PROTOSS, origin={"lotv"}, important_for_filtering=True), + ItemData(802 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 2, SC2Race.PROTOSS, origin={"lotv"}, important_for_filtering=True), ItemNames.ORBITAL_ASSIMILATORS: - ItemData(803 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 3, SC2Race.PROTOSS, origin={"lotv"}), + ItemData(803 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 3, SC2Race.PROTOSS, origin={"lotv"}), ItemNames.WARP_HARMONIZATION: - ItemData(804 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 4, SC2Race.PROTOSS, origin={"lotv"}), + ItemData(804 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 4, SC2Race.PROTOSS, origin={"lotv"}), ItemNames.GUARDIAN_SHELL: - ItemData(805 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 5, SC2Race.PROTOSS, origin={"lotv"}), + ItemData(805 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 5, SC2Race.PROTOSS, origin={"lotv"}), ItemNames.RECONSTRUCTION_BEAM: - ItemData(806 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 6, SC2Race.PROTOSS, + ItemData(806 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 6, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), ItemNames.OVERWATCH: - ItemData(807 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 7, SC2Race.PROTOSS, origin={"ext"}), + ItemData(807 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 7, SC2Race.PROTOSS, origin={"ext"}), ItemNames.SUPERIOR_WARP_GATES: - ItemData(808 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 8, SC2Race.PROTOSS, origin={"ext"}), + ItemData(808 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 8, SC2Race.PROTOSS, origin={"ext"}), ItemNames.ENHANCED_TARGETING: - ItemData(809 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 9, SC2Race.PROTOSS, origin={"ext"}), + ItemData(809 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 9, SC2Race.PROTOSS, origin={"ext"}), ItemNames.OPTIMIZED_ORDNANCE: - ItemData(810 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 10, SC2Race.PROTOSS, origin={"ext"}), + ItemData(810 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 10, SC2Race.PROTOSS, origin={"ext"}), ItemNames.KHALAI_INGENUITY: - ItemData(811 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 11, SC2Race.PROTOSS, origin={"ext"}), + ItemData(811 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 11, SC2Race.PROTOSS, origin={"ext"}), ItemNames.AMPLIFIED_ASSIMILATORS: - ItemData(812 + SC2LOTV_ITEM_ID_OFFSET, ItemType.Solarite_Core, 12, SC2Race.PROTOSS, origin={"ext"}), + ItemData(812 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 12, SC2Race.PROTOSS, origin={"ext"}), } @@ -1690,11 +1723,11 @@ def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: ItemNames.ADVANCED_OPTICS, ItemNames.ROGUE_FORCES, # Mercenaries (All races) - *[item_name for item_name, item_data in get_full_item_list().items() - if item_data.type == ItemType.Mercenary], + *[item_name for item_name, item_data in item_table.items() + if item_data.type in (TerranItemType.Mercenary, ZergItemType.Mercenary)], # Kerrigan and Nova levels, abilities and generally useful stuff *[item_name for item_name, item_data in get_full_item_list().items() - if item_data.type in (ItemType.Level, ItemType.Ability, ItemType.Evolution_Pit, ItemType.Nova_Gear)], + if item_data.type in (ZergItemType.Level, ZergItemType.Ability, ZergItemType.Evolution_Pit, TerranItemType.Nova_Gear)], ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, # Zerg static defenses ItemNames.SPORE_CRAWLER, @@ -1768,7 +1801,7 @@ def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: kerrigan_levels = [ item_name for item_name, item_data in item_table.items() - if item_data.type == ItemType.Level and item_data.race == SC2Race.ZERG + if item_data.type == ZergItemType.Level and item_data.race == SC2Race.ZERG ] spider_mine_sources = { @@ -1842,7 +1875,7 @@ def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: nova_equipment = { *[item_name for item_name, item_data in get_full_item_list().items() - if item_data.type == ItemType.Nova_Gear], + if item_data.type == TerranItemType.Nova_Gear], ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE } @@ -1922,54 +1955,4 @@ def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in get_full_item_list().items() if data.code} -# Map type to expected int -type_flaggroups: typing.Dict[SC2Race, typing.Dict[str, int]] = { - SC2Race.ANY: { - ItemType.Minerals: 0, - ItemType.Vespene: 1, - ItemType.Supply: 2, - ItemType.Nothing: 4, - }, - SC2Race.TERRAN: { - ItemType.Armory_1: 0, - ItemType.Armory_2: 1, - ItemType.Armory_3: 2, - ItemType.Armory_4: 3, - ItemType.Armory_5: 4, - ItemType.Armory_6: 5, - ItemType.Progressive: 6, # Unit upgrades that exist multiple times (Stimpack / Super Stimpack) - ItemType.Laboratory: 7, - ItemType.Upgrade: 8, # Weapon / Armor upgrades - ItemType.Unit: 9, - ItemType.Building: 10, - ItemType.Mercenary: 11, - ItemType.Nova_Gear: 12, - ItemType.Progressive_2: 13, - }, - SC2Race.ZERG: { - ItemType.Ability: 0, - ItemType.Mutation_1: 1, - ItemType.Strain: 2, - ItemType.Morph: 3, - ItemType.Upgrade: 4, - ItemType.Mercenary: 5, - ItemType.Unit: 6, - ItemType.Level: 7, - ItemType.Primal_Form: 8, - ItemType.Evolution_Pit: 9, - ItemType.Mutation_2: 10, - ItemType.Mutation_3: 11 - }, - SC2Race.PROTOSS: { - ItemType.Unit: 0, - ItemType.Unit_2: 1, - ItemType.Upgrade: 2, # Weapon / Armor upgrades - ItemType.Building: 3, - ItemType.Progressive: 4, - ItemType.Spear_Of_Adun: 5, - ItemType.Solarite_Core: 6, - ItemType.Forge_1: 7, - ItemType.Forge_2: 8, - ItemType.Forge_3: 9, - } -} +upgrade_item_types = (TerranItemType.Upgrade, ZergItemType.Upgrade, ProtossItemType.Upgrade) diff --git a/worlds/sc2/MissionTables.py b/worlds/sc2/MissionTables.py index 1f5806b65d3b..67820580d3ba 100644 --- a/worlds/sc2/MissionTables.py +++ b/worlds/sc2/MissionTables.py @@ -1,4 +1,4 @@ -from typing import NamedTuple, Dict, List, Set, Union, Literal, Iterable, Callable +from typing import NamedTuple, Dict, List, Set, Union, Literal, Iterable, Callable, Optional from enum import IntEnum, Enum, IntFlag, auto @@ -669,7 +669,7 @@ class SC2CampaignGoal(NamedTuple): location: str -campaign_final_mission_locations: Dict[SC2Campaign, SC2CampaignGoal] = { +campaign_final_mission_locations: Dict[SC2Campaign, Optional[SC2CampaignGoal]] = { SC2Campaign.WOL: SC2CampaignGoal(SC2Mission.ALL_IN, "All-In: Victory"), SC2Campaign.PROPHECY: SC2CampaignGoal(SC2Mission.IN_UTTER_DARKNESS, "In Utter Darkness: Kills"), SC2Campaign.HOTS: None, diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py index 531645b42c34..21d769dfd0cd 100644 --- a/worlds/sc2/PoolFilter.py +++ b/worlds/sc2/PoolFilter.py @@ -1,15 +1,18 @@ from typing import Callable, Dict, List, Set, Union, Tuple, Optional from BaseClasses import Item, Location -from .Items import get_full_item_list, spider_mine_sources, second_pass_placeable_items, progressive_if_nco, \ - progressive_if_ext, spear_of_adun_calldowns, spear_of_adun_castable_passives, nova_equipment +from .Items import (get_full_item_list, spider_mine_sources, second_pass_placeable_items, progressive_if_nco, + progressive_if_ext, spear_of_adun_calldowns, spear_of_adun_castable_passives, nova_equipment, + upgrade_item_types, +) from .MissionTables import (mission_orders, MissionInfo, MissionPools, MissionFlag, get_campaign_goal_priority, campaign_final_mission_locations, campaign_alt_final_mission_locations, SC2Campaign, SC2Race, SC2CampaignGoalPriority, SC2Mission, ) -from .Options import get_option_value, MissionOrder, \ - get_enabled_campaigns, get_disabled_campaigns, RequiredTactics, kerrigan_unit_available, GrantStoryTech, \ - TakeOverAIAllies, SpearOfAdunPresence, SpearOfAdunAutonomouslyCastAbilityPresence, campaign_depending_orders, \ - ShuffleCampaigns, get_excluded_missions, ShuffleNoBuild, ExtraLocations, GrantStoryLevels +from .Options import (get_option_value, MissionOrder, + get_enabled_campaigns, get_disabled_campaigns, RequiredTactics, kerrigan_unit_available, GrantStoryTech, + TakeOverAIAllies, SpearOfAdunPresence, SpearOfAdunAutonomouslyCastAbilityPresence, campaign_depending_orders, + ShuffleCampaigns, get_excluded_missions, ShuffleNoBuild, ExtraLocations, GrantStoryLevels, +) from . import ItemNames from worlds.AutoWorld import World @@ -37,7 +40,6 @@ def filter_missions(world: World) -> Dict[MissionPools, List[SC2Mission]]: """ Returns a semi-randomly pruned tuple of no-build, easy, medium, and hard mission sets """ - world: World = world mission_order_type = get_option_value(world, "mission_order") shuffle_no_build = get_option_value(world, "shuffle_no_build") enabled_campaigns = get_enabled_campaigns(world) @@ -76,7 +78,6 @@ def filter_missions(world: World) -> Dict[MissionPools, List[SC2Mission]]: goal_priorities = {campaign: get_campaign_goal_priority(campaign, excluded_missions) for campaign in enabled_campaigns} goal_level = max(goal_priorities.values()) candidate_campaigns: List[SC2Campaign] = [campaign for campaign, goal_priority in goal_priorities.items() if goal_priority == goal_level] - candidate_campaigns.sort(key=lambda it: it.id) candidate_campaigns.sort(key=lambda it: it.id) goal_campaign = world.random.choice(candidate_campaigns) @@ -589,7 +590,7 @@ def __init__(self, world: World , if item.name in nova_equipment and not nova_equipment_used: # Drop Nova equipment if there's no NCO mission generated continue - if item_info.type == "Upgrade": + if item_info.type in upgrade_item_types: # Locking upgrades based on mission duration if item.name not in item_quantities: item_quantities[item.name] = 0 diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index ea4a7895eab2..a580e7a236ae 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -276,18 +276,18 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem for item in item_list: # Filter Nova equipment if you never get Nova - if not nova_missions and item.data.type == Items.ItemType.Nova_Gear: + if not nova_missions and item.data.type == Items.TerranItemType.Nova_Gear: item.flags |= ItemFilterFlags.Excluded # Todo(mm): How should no-build only / grant_story_tech affect excluding Kerrigan items? # Exclude Primal form based on Kerrigan presence or primal form option - if (item.data.type == Items.ItemType.Primal_Form + if (item.data.type == Items.ZergItemType.Primal_Form and ((not kerrigan_is_present) or world.options.kerrigan_primal_status != KerriganPrimalStatus.option_item) ): item.flags |= ItemFilterFlags.Excluded # Remove Kerrigan abilities if there's no kerrigan - if (item.data.type == Items.ItemType.Ability and not kerrigan_is_present): + if (item.data.type == Items.ZergItemType.Ability and not kerrigan_is_present): item.flags |= ItemFilterFlags.Excluded # Remove Spear of Adun if it's off @@ -450,7 +450,7 @@ def flag_unused_upgrade_types(world: SC2World, item_list: List[FilterItem]) -> N include_upgrades = world.options.generic_upgrade_missions == 0 upgrade_items = world.options.generic_upgrade_items for item in item_list: - if item.data.type == Items.ItemType.Upgrade: + if item.data.type in Items.upgrade_item_types: if not include_upgrades or (item.name not in upgrade_included_names[upgrade_items]): item.flags |= ItemFilterFlags.Excluded From 4b329f6aaa1e202b49e8b4ce5455ab3626bc49f1 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 27 Apr 2024 11:53:32 -0700 Subject: [PATCH 06/48] sc2: Fixed remaining reference to item_flaggroups, fixing unit tests --- worlds/sc2/Client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/sc2/Client.py b/worlds/sc2/Client.py index 94fbf2babc10..1bb7fb4f719c 100644 --- a/worlds/sc2/Client.py +++ b/worlds/sc2/Client.py @@ -47,8 +47,8 @@ from worlds._sc2common.bot.main import run_game from worlds._sc2common.bot.player import Bot from worlds.sc2.Items import ( - lookup_id_to_name, get_full_item_list, ItemData, type_flaggroups, upgrade_numbers, upgrade_numbers_all, - race_to_item_type, upgrade_item_types, + lookup_id_to_name, get_full_item_list, ItemData, upgrade_numbers, upgrade_numbers_all, + race_to_item_type, upgrade_item_types, ZergItemType, ) from worlds.sc2.Locations import SC2WOL_LOC_ID_OFFSET, LocationType, SC2HOTS_LOC_ID_OFFSET from worlds.sc2.MissionTables import lookup_id_to_mission, SC2Campaign, lookup_name_to_mission, \ @@ -900,7 +900,7 @@ def calc_difficulty(difficulty: int): def get_kerrigan_level(ctx: SC2Context, items: typing.Dict[SC2Race, typing.List[int]], missions_beaten: int) -> int: - item_value = items[SC2Race.ZERG][type_flaggroups[SC2Race.ZERG]["Level"]] + item_value = items[SC2Race.ZERG][ZergItemType.Level.flag_word] mission_value = missions_beaten * ctx.kerrigan_levels_per_mission_completed if ctx.kerrigan_levels_per_mission_completed_cap != -1: mission_value = min(mission_value, ctx.kerrigan_levels_per_mission_completed_cap) From 175b9ce64e1556410247bb5cdee963805b32c233 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 27 Apr 2024 20:55:38 -0700 Subject: [PATCH 07/48] sc2: Added several generation unit tests; fixed several generation failures and bugs --- worlds/sc2/ItemGroups.py | 33 ++++- worlds/sc2/Items.py | 8 ++ worlds/sc2/Options.py | 18 +-- worlds/sc2/PoolFilter.py | 58 +++------ worlds/sc2/Rules.py | 21 +--- worlds/sc2/__init__.py | 93 ++++++++++++-- worlds/sc2/test/test_generation.py | 190 +++++++++++++++++++++++++---- 7 files changed, 323 insertions(+), 98 deletions(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index 866ca3b43850..b98093f8d1a0 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -70,23 +70,48 @@ # Hand-made groups -item_name_groups["Aiur"] = [ +class ItemGroupNames: + BARRACKS_UNITS = "Barracks Units" + FACTORY_UNITS = "Factory Units" + STARPORT_UNITS = "Starport Units" + + AIUR = "Aiur" + NERAZIM = "Nerazim" + TAL_DARIM = "Tal'Darim" + PURIFIER = "PURIFIER" + + +item_name_groups[ItemGroupNames.BARRACKS_UNITS] = barracks_units = [ + ItemNames.MARINE, ItemNames.MEDIC, ItemNames.FIREBAT, ItemNames.MARAUDER, + ItemNames.REAPER, ItemNames.GHOST, ItemNames.SPECTRE, ItemNames.HERC, +] +item_name_groups[ItemGroupNames.FACTORY_UNITS] = factory_units = [ + ItemNames.HELLION, ItemNames.VULTURE, ItemNames.GOLIATH, ItemNames.DIAMONDBACK, + ItemNames.SIEGE_TANK, ItemNames.THOR, ItemNames.PREDATOR, ItemNames.WIDOW_MINE, + ItemNames.CYCLONE, ItemNames.WARHOUND, +] +item_name_groups[ItemGroupNames.STARPORT_UNITS] = starport_units = [ + ItemNames.MEDIVAC, ItemNames.WRAITH, ItemNames.VIKING, ItemNames.BANSHEE, + ItemNames.BATTLECRUISER, ItemNames.HERCULES, ItemNames.SCIENCE_VESSEL, ItemNames.RAVEN, + ItemNames.LIBERATOR, ItemNames.VALKYRIE, +] +item_name_groups[ItemGroupNames.AIUR] = [ ItemNames.ZEALOT, ItemNames.DRAGOON, ItemNames.SENTRY, ItemNames.AVENGER, ItemNames.HIGH_TEMPLAR, ItemNames.IMMORTAL, ItemNames.REAVER, ItemNames.PHOENIX, ItemNames.SCOUT, ItemNames.ARBITER, ItemNames.CARRIER, ] -item_name_groups["Nerazim"] = [ +item_name_groups[ItemGroupNames.NERAZIM] = [ ItemNames.CENTURION, ItemNames.STALKER, ItemNames.DARK_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.DARK_ARCHON, ItemNames.ANNIHILATOR, ItemNames.CORSAIR, ItemNames.ORACLE, ItemNames.VOID_RAY, ] -item_name_groups["Tal'Darim"] = [ +item_name_groups[ItemGroupNames.TAL_DARIM] = [ ItemNames.SUPPLICANT, ItemNames.SLAYER, ItemNames.HAVOC, ItemNames.BLOOD_HUNTER, ItemNames.ASCENDANT, ItemNames.VANGUARD, ItemNames.WRATHWALKER, ItemNames.DESTROYER, ItemNames.MOTHERSHIP, ItemNames.WARP_PRISM_PHASE_BLASTER, ] -item_name_groups["Purifier"] = [ +item_name_groups[ItemGroupNames.PURIFIER] = [ ItemNames.SENTINEL, ItemNames.ADEPT, ItemNames.INSTIGATOR, ItemNames.ENERGIZER, ItemNames.COLOSSUS, ItemNames.DISRUPTOR, ItemNames.MIRAGE, ItemNames.TEMPEST, diff --git a/worlds/sc2/Items.py b/worlds/sc2/Items.py index 08b8628939bf..35bd358ac189 100644 --- a/worlds/sc2/Items.py +++ b/worlds/sc2/Items.py @@ -25,6 +25,7 @@ def __init__(self, name: str, flag_word: int): class TerranItemType(ItemTypeEnum): Armory_1 = "Armory", 0 + """General Terran unit upgrades""" Armory_2 = "Armory", 1 Armory_3 = "Armory", 2 Armory_4 = "Armory", 3 @@ -42,6 +43,7 @@ class TerranItemType(ItemTypeEnum): class ZergItemType(ItemTypeEnum): Ability = "Ability", 0 + """Kerrigan abilities""" Mutation_1 = "Mutation", 1 Strain = "Strain", 2 Morph = "Morph", 3 @@ -49,8 +51,10 @@ class ZergItemType(ItemTypeEnum): Mercenary = "Mercenary", 5 Unit = "Unit", 6 Level = "Level", 7 + """Kerrigan level packs""" Primal_Form = "Primal Form", 8 Evolution_Pit = "Evolution Pit", 9 + """Zerg global economy upgrades, like automated extractors""" Mutation_2 = "Mutation", 10 Mutation_3 = "Mutation", 11 @@ -63,9 +67,13 @@ class ProtossItemType(ItemTypeEnum): Progressive = "Progressive Upgrade", 4 Spear_Of_Adun = "Spear of Adun", 5 Solarite_Core = "Solarite Core", 6 + """Protoss global effects, such as reconstruction beam or automated assimilators""" Forge_1 = "Forge 1", 7 + """General Protoss unit upgrades""" Forge_2 = "Forge 2", 8 + """General Protoss unit upgrades""" Forge_3 = "Forge 3", 9 + """General Protoss unit upgrades""" class FactionlessItemType(ItemTypeEnum): diff --git a/worlds/sc2/Options.py b/worlds/sc2/Options.py index 4dec08013968..664c3ba03520 100644 --- a/worlds/sc2/Options.py +++ b/worlds/sc2/Options.py @@ -9,6 +9,9 @@ campaign_mission_table from worlds.AutoWorld import World +if TYPE_CHECKING: + from . import SC2World + class GameDifficulty(Choice): """ @@ -652,15 +655,14 @@ def from_any(cls, data: Union[List[str], Dict[str, int]]) -> 'Sc2ItemDict': else: raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}") - def verify(self, world: Type[World], player_name: str, plando_options: PlandoOptions) -> None: + def verify(self, world: Type['SC2World'], player_name: str, plando_options: PlandoOptions) -> None: """Overridden version of function from Options.VerifyKeys for a better error message""" - if self.convert_name_groups: - new_value: dict[str, int] = {} - for group_name in self.value: - item_names = world.item_name_groups.get(group_name, {group_name}) - for item_name in item_names: - new_value[item_name] = new_value.get(item_name, 0) + self.value[group_name] - self.value = new_value + new_value: dict[str, int] = {} + for group_name in self.value: + item_names = world.item_name_groups.get(group_name, {group_name}) + for item_name in item_names: + new_value[item_name] = new_value.get(item_name, 0) + self.value[group_name] + self.value = new_value for item_name in self.value: if item_name not in world.item_names: picks = get_fuzzy_results(item_name, world.item_names, limit=1) diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py index 21d769dfd0cd..e60ccaad6442 100644 --- a/worlds/sc2/PoolFilter.py +++ b/worlds/sc2/PoolFilter.py @@ -1,4 +1,4 @@ -from typing import Callable, Dict, List, Set, Union, Tuple, Optional +from typing import Callable, Dict, List, Set, Union, Tuple, Optional, TYPE_CHECKING from BaseClasses import Item, Location from .Items import (get_full_item_list, spider_mine_sources, second_pass_placeable_items, progressive_if_nco, progressive_if_ext, spear_of_adun_calldowns, spear_of_adun_castable_passives, nova_equipment, @@ -13,29 +13,20 @@ TakeOverAIAllies, SpearOfAdunPresence, SpearOfAdunAutonomouslyCastAbilityPresence, campaign_depending_orders, ShuffleCampaigns, get_excluded_missions, ShuffleNoBuild, ExtraLocations, GrantStoryLevels, ) -from . import ItemNames -from worlds.AutoWorld import World +from . import ItemNames, ItemGroups + +if TYPE_CHECKING: + from . import SC2World + # Items with associated upgrades UPGRADABLE_ITEMS = {item.parent_item for item in get_full_item_list().values() if item.parent_item} +BARRACKS_UNITS = set(ItemGroups.barracks_units) +FACTORY_UNITS = set(ItemGroups.factory_units) +STARPORT_UNITS = set(ItemGroups.starport_units) -BARRACKS_UNITS = { - ItemNames.MARINE, ItemNames.MEDIC, ItemNames.FIREBAT, ItemNames.MARAUDER, - ItemNames.REAPER, ItemNames.GHOST, ItemNames.SPECTRE, ItemNames.HERC, -} -FACTORY_UNITS = { - ItemNames.HELLION, ItemNames.VULTURE, ItemNames.GOLIATH, ItemNames.DIAMONDBACK, - ItemNames.SIEGE_TANK, ItemNames.THOR, ItemNames.PREDATOR, ItemNames.WIDOW_MINE, - ItemNames.CYCLONE, ItemNames.WARHOUND, -} -STARPORT_UNITS = { - ItemNames.MEDIVAC, ItemNames.WRAITH, ItemNames.VIKING, ItemNames.BANSHEE, - ItemNames.BATTLECRUISER, ItemNames.HERCULES, ItemNames.SCIENCE_VESSEL, ItemNames.RAVEN, - ItemNames.LIBERATOR, ItemNames.VALKYRIE, -} - -def filter_missions(world: World) -> Dict[MissionPools, List[SC2Mission]]: +def filter_missions(world: 'SC2World') -> Dict[MissionPools, List[SC2Mission]]: """ Returns a semi-randomly pruned tuple of no-build, easy, medium, and hard mission sets @@ -212,7 +203,7 @@ def get_item_upgrades(inventory: List[Item], parent_item: Union[Item, str]) -> L ] -def get_item_quantity(item: Item, world: World): +def get_item_quantity(item: Item, world: 'SC2World'): if (not get_option_value(world, "nco_items")) \ and SC2Campaign.NCO in get_disabled_campaigns(world) \ and item.name in progressive_if_nco: @@ -227,17 +218,6 @@ def copy_item(item: Item): return Item(item.name, item.classification, item.code, item.player) -def num_missions(world: World) -> int: - mission_order_type = get_option_value(world, "mission_order") - if mission_order_type != MissionOrder.option_grid: - mission_order = mission_orders[mission_order_type]() - misssions = [mission for campaign in mission_order for mission in mission_order[campaign]] - return len(misssions) - 1 # Menu - else: - mission_pools = filter_missions(world) - return sum(len(pool) for _, pool in mission_pools.items()) - - class ValidInventory: def has(self, item: str, player: int): @@ -557,12 +537,12 @@ def attempt_removal(item: Item) -> bool: return inventory - def __init__(self, world: World , + def __init__(self, world: 'SC2World', item_pool: List[Item], existing_items: List[Item], locked_items: List[Item], used_races: Set[SC2Race], nova_equipment_used: bool): self.multiworld = world.multiworld self.player = world.player - self.world: World = world + self.world: 'SC2World' = world self.logical_inventory = list() self.locked_items = locked_items[:] self.existing_items = existing_items @@ -572,7 +552,7 @@ def __init__(self, world: World , self.item_pool = [] item_quantities: dict[str, int] = dict() # Inventory restrictiveness based on number of missions with checks - mission_count = num_missions(world) + mission_count = mission_count = sum(len(campaign) for campaign in world.mission_req_table.values()) self.min_units_per_structure = int(mission_count / 7) min_upgrades = 1 if mission_count < 10 else 2 for item in item_pool: @@ -607,7 +587,7 @@ def __init__(self, world: World , self.item_children[item] = get_item_upgrades(self.item_pool, item) -def filter_items(world: World, mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], location_cache: List[Location], +def filter_items(world: 'SC2World', mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], location_cache: List[Location], item_pool: List[Item], existing_items: List[Item], locked_items: List[Item]) -> List[Item]: """ Returns a semi-randomly pruned set of items based on number of available locations. @@ -624,12 +604,14 @@ def filter_items(world: World, mission_req_table: Dict[SC2Campaign, Dict[str, Mi return valid_items -def get_used_races(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], world: World) -> Set[SC2Race]: +def get_used_races(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], world: 'SC2World') -> Set[SC2Race]: grant_story_tech = get_option_value(world, "grant_story_tech") take_over_ai_allies = get_option_value(world, "take_over_ai_allies") - kerrigan_presence = get_option_value(world, "kerrigan_presence") in kerrigan_unit_available \ - and SC2Campaign.HOTS in get_enabled_campaigns(world) missions = missions_in_mission_table(mission_req_table) + kerrigan_presence = ( + world.options.kerrigan_presence in kerrigan_unit_available + and any([MissionFlag.Kerrigan in mission.flags for mission in missions]) + ) # By missions races = set([mission.race for mission in missions]) diff --git a/worlds/sc2/Rules.py b/worlds/sc2/Rules.py index 8b9097ea1d78..c51b1f5a25db 100644 --- a/worlds/sc2/Rules.py +++ b/worlds/sc2/Rules.py @@ -13,17 +13,6 @@ class SC2Logic: - def lock_any_item(self, state: CollectionState, items: Set[str]) -> bool: - """ - Guarantees that at least one of these items will remain in the world. Doesn't affect placement. - Needed for cases when the dynamic pool filtering could remove all the item prerequisites - :param state: - :param items: - :return: - """ - return self.is_item_placement(state) \ - or state.has_any(items, self.player) - def is_item_placement(self, state): """ Tells if it's item placement or item pool filter @@ -607,12 +596,10 @@ def protoss_competent_comp(self, state: CollectionState) -> bool: def protoss_stalker_upgrade(self, state: CollectionState) -> bool: return ( - state.has_any( - { - ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES, - ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION - }, self.player) - and self.lock_any_item(state, {ItemNames.STALKER, ItemNames.INSTIGATOR, ItemNames.SLAYER}) + state.has_any({ + ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES, + ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION + }, self.player) ) def steps_of_the_rite_requirement(self, state: CollectionState) -> bool: diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index a580e7a236ae..a9b3a2c1f660 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -36,6 +36,8 @@ class ItemFilterFlags(enum.IntFlag): Removed = enum.auto() Plando = enum.auto() Excluded = enum.auto() + AllowedOrphan = enum.auto() + """Used to flag items that shouldn't be filtered out with their parents""" Unremovable = Locked|StartInventory|Plando @@ -109,10 +111,12 @@ def create_items(self): setup_events(self.player, self.locked_locations, self.location_cache) item_list: List[FilterItem] = create_and_flag_explicit_item_locks_and_excludes(self) + flag_faction_unit_excludes_based_on_mission_excludes(self, item_list) flag_mission_based_item_excludes(self, item_list) + flag_allowed_orphan_items(self, item_list) flag_start_inventory(self, item_list) flag_unused_upgrade_types(self, item_list) - flag_excluded_item_sets(self, item_list) + flag_user_excluded_item_sets(self, item_list) flag_and_add_resource_locations(self, item_list) pool: List[Item] = prune_item_pool(self, item_list) pad_item_pool_with_filler(self, len(self.location_cache) - len(self.locked_locations) - len(pool), pool) @@ -225,13 +229,57 @@ def create_and_flag_explicit_item_locks_and_excludes(world: SC2World) -> List[Fi return result +def flag_faction_unit_excludes_based_on_mission_excludes(world: SC2World, item_list: List[FilterItem]) -> None: + """ + Excludes units based on whether a mission they can be built exists in the mission table + """ + missions = get_all_missions(world.mission_req_table) + build_missions = [mission for mission in missions if MissionFlag.NoBuild not in mission.flags] + if world.options.take_over_ai_allies: + terran_build_missions = [mission for mission in build_missions if MissionFlag.Terran|MissionFlag.AiZergAlly & mission.flags] + zerg_build_missions = [mission for mission in build_missions if MissionFlag.Zerg|MissionFlag.AiTerranAlly & mission.flags] + protoss_build_missions = [mission for mission in build_missions if MissionFlag.Protoss|MissionFlag.AiProtossAlly & mission.flags] + else: + terran_build_missions = [mission for mission in build_missions if MissionFlag.Terran & mission.flags] + zerg_build_missions = [mission for mission in build_missions if MissionFlag.Zerg & mission.flags] + protoss_build_missions = [mission for mission in build_missions if MissionFlag.Protoss & mission.flags] + for item in item_list: + if (not terran_build_missions + and item.data.type in (Items.TerranItemType.Unit, Items.TerranItemType.Building, Items.TerranItemType.Mercenary) + ): + item.flags |= ItemFilterFlags.Excluded + if (not zerg_build_missions + and item.data.type in (Items.ZergItemType.Unit, Items.ZergItemType.Mercenary, Items.ZergItemType.Evolution_Pit) + ): + if (SC2Mission.ENEMY_WITHIN not in missions + or item.name not in (ItemNames.ZERGLING, ItemNames.ROACH, ItemNames.HYDRALISK, ItemNames.INFESTOR) + ): + item.flags |= ItemFilterFlags.Excluded + if (not protoss_build_missions + and item.data.type in ( + Items.ProtossItemType.Unit, + Items.ProtossItemType.Unit_2, + Items.ProtossItemType.Building, + ) + ): + # Note(mm): This doesn't exclude things like automated assimilators or warp gate improvements + # because that item type is mixed in with e.g. Reconstruction Beam and Overwatch + if (SC2Mission.TEMPLAR_S_RETURN not in missions + or item.name not in ( + ItemNames.IMMORTAL, ItemNames.ANNIHILATOR, + ItemNames.COLOSSUS, ItemNames.VANGUARD, ItemNames.REAVER, ItemNames.DARK_TEMPLAR, + ItemNames.SENTRY, ItemNames.HIGH_TEMPLAR, + ) + ): + item.flags |= ItemFilterFlags.Excluded + + + def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem]) -> None: """ - Excludes items based on mission / campaign presence. e.g. Nova Gear is excluded if there are no Nova missions. + Excludes items based on mission / campaign presence: Nova Gear, Kerrigan abilities, SOA """ - missions: List[SC2Mission] = [] - for campaign in world.mission_req_table.values(): - missions.extend(mission_info.mission for _, mission_info in campaign.items()) + missions = get_all_missions(world.mission_req_table) kerrigan_missions = len([mission for mission in missions if MissionFlag.Kerrigan in mission.flags]) > 0 nova_missions = [mission for mission in missions if MissionFlag.Nova in mission.flags] @@ -306,6 +354,28 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem return +def flag_allowed_orphan_items(world: SC2World, item_list: List[FilterItem]) -> None: + """Adds the `Allowed_Orphan` flag to items that shouldn't be filtered with their parents, like combat shield""" + missions = get_all_missions(world.mission_req_table) + terran_missions = any(MissionFlag.Terran in mission.flags for mission in missions) + zerg_missions = any(MissionFlag.Terran in mission.flags for mission in missions) + protoss_missions = any(MissionFlag.Terran in mission.flags for mission in missions) + for item in item_list: + if item.name in ( + ItemNames.MARINE_COMBAT_SHIELD, ItemNames.MARINE_PROGRESSIVE_STIMPACK, ItemNames.MARINE_MAGRAIL_MUNITIONS, + ItemNames.MEDIC_STABILIZER_MEDPACKS, ItemNames.MEDIC_NANO_PROJECTOR, + ) and terran_missions: + item.flags |= ItemFilterFlags.AllowedOrphan + if item.name in ( + ItemNames.ZERGLING_ADRENAL_OVERLOAD, ItemNames.ZERGLING_METABOLIC_BOOST, + ) and zerg_missions: + item.flags |= ItemFilterFlags.AllowedOrphan + if item.name in ( + ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES, ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION, + ) and protoss_missions: + item.flags |= ItemFilterFlags.AllowedOrphan + + def flag_start_inventory(world: SC2World, item_list: List[FilterItem]) -> None: """Adds items to start_inventory based on first mission logic and options like `starter_unit` and `start_primary_abilities`""" first_mission_name = get_first_mission(world.mission_req_table).mission_name @@ -455,7 +525,7 @@ def flag_unused_upgrade_types(world: SC2World, item_list: List[FilterItem]) -> N item.flags |= ItemFilterFlags.Excluded -def flag_excluded_item_sets(world: SC2World, item_list: List[FilterItem]) -> None: +def flag_user_excluded_item_sets(world: SC2World, item_list: List[FilterItem]) -> None: """Excludes items based on item set options (`nco_items`, `bw_items`, `ext_items`)""" # Note(mm): These options should just be removed in favour of better item sets and a single "vanilla only" switch item_sets = {'wol', 'hots', 'lotv'} @@ -519,7 +589,9 @@ def prune_item_pool(world: SC2World, item_list: List[FilterItem]) -> List[Item]: last_num_items = -1 while num_items != last_num_items: # Remove orphan items until there are no more being removed - item_list = [item for item in item_list if ItemFilterFlags.Unremovable & item.flags or item_list_contains_parent(item.data, item_list)] + item_list = [item for item in item_list + if (ItemFilterFlags.Unremovable|ItemFilterFlags.AllowedOrphan) & item.flags + or item_list_contains_parent(item.data, item_list)] last_num_items = num_items num_items = len(item_list) @@ -563,6 +635,13 @@ def get_first_mission(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo return list(mission_req_table[first_campaign].values())[0].mission +def get_all_missions(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> List[SC2Mission]: + missions: List[SC2Mission] = [] + for campaign in mission_req_table.values(): + missions.extend(mission_info.mission for _, mission_info in campaign.items()) + return missions + + def create_item_with_correct_settings(player: int, name: str) -> Item: data = Items.item_table[name] diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index d5d54ff96266..cb818dea9dcb 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -5,19 +5,21 @@ import unittest import random -from .. import Options, MissionTables, ItemNames, Items +from .. import Options, MissionTables, ItemNames, Items, ItemGroups from .. import FilterItem, ItemFilterFlags, create_and_flag_explicit_item_locks_and_excludes, SC2World -from BaseClasses import MultiWorld, CollectionState +from BaseClasses import MultiWorld, CollectionState, PlandoOptions from argparse import Namespace from worlds import AutoWorld from Generate import get_seed_name +from test.general import gen_steps, call_all + class Sc2SetupTestBase(unittest.TestCase): seed: Optional[int] = None game = SC2World.game player = 1 - def setUp(self) -> None: + def generate_world(self, options: Dict[str, Any]) -> None: self.multiworld = MultiWorld(1) self.multiworld.game[self.player] = self.game self.multiworld.player_name = {self.player: "Tester"} @@ -27,33 +29,38 @@ def setUp(self) -> None: self.multiworld.seed_name = get_seed_name(random) # only called to get same RNG progression as Generate.py args = Namespace() for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items(): + new_option = option.from_any(options.get(name, option.default)) + new_option.verify(SC2World, "Tester", PlandoOptions.items|PlandoOptions.connections|PlandoOptions.texts|PlandoOptions.bosses) setattr(args, name, { - 1: option.from_any(self.options.get(name, option.default)) + 1: new_option }) self.multiworld.set_options(args) - self.world = self.multiworld.worlds[self.player] + self.world: SC2World = self.multiworld.worlds[self.player] + for step in gen_steps: + call_all(self.multiworld, step) class TestItemFiltering(Sc2SetupTestBase): - options = { - 'locked_items': { - ItemNames.MARINE: 0, - ItemNames.MARAUDER: 0, - ItemNames.MEDIVAC: 1, - ItemNames.FIREBAT: 1, - ItemNames.ZEALOT: 0, - ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2, - }, - 'excluded_items': { - ItemNames.MARINE: 0, - ItemNames.MARAUDER: 0, - ItemNames.MEDIVAC: 0, - ItemNames.FIREBAT: 1, - ItemNames.ZERGLING: 0, - ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2, - } - } def test_explicit_locks_excludes_interact_and_set_flags(self): + options = { + 'locked_items': { + ItemNames.MARINE: 0, + ItemNames.MARAUDER: 0, + ItemNames.MEDIVAC: 1, + ItemNames.FIREBAT: 1, + ItemNames.ZEALOT: 0, + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2, + }, + 'excluded_items': { + ItemNames.MARINE: 0, + ItemNames.MARAUDER: 0, + ItemNames.MEDIVAC: 0, + ItemNames.FIREBAT: 1, + ItemNames.ZERGLING: 0, + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2, + } + } + self.generate_world(options) item_list = create_and_flag_explicit_item_locks_and_excludes(self.world) self.assertNotIn(ItemNames.ZERGLING, [x.name for x in item_list], msg=f'{ItemNames.ZERGLING} did not get properly excluded') self.assertIn(FilterItem(ItemNames.MARINE, Items.item_table[ItemNames.MARINE], flags=ItemFilterFlags.Locked), item_list) @@ -64,5 +71,140 @@ def test_explicit_locks_excludes_interact_and_set_flags(self): self.assertIn(FilterItem(ItemNames.DRAGOON, Items.item_table[ItemNames.DRAGOON]), item_list) regen_biosteel_items = [x for x in item_list if x.name == ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL] self.assertEqual(len(regen_biosteel_items), 2) - + + def test_excluding_groups_excludes_all_items_in_group(self): + options = { + 'excluded_items': [ + ItemGroups.ItemGroupNames.BARRACKS_UNITS, + ] + } + self.generate_world(options) + item_names = [item.name for item in self.multiworld.itempool] + self.assertIn(ItemNames.MARINE, self.world.options.excluded_items) + for item_name in ItemGroups.barracks_units: + self.assertNotIn(item_name, item_names) + + def test_excluding_campaigns_excludes_campaign_specific_items(self) -> None: + options = { + 'enable_wol_missions': True, + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'enable_nco_missions': False, + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + item_data_entries = [Items.item_table[item.name] for item in self.multiworld.itempool] + for item_data in item_data_entries: + self.assertNotIn(item_data.type, Items.ProtossItemType) + self.assertNotIn(item_data.type, Items.ZergItemType) + self.assertNotEqual(item_data.type, Items.TerranItemType.Nova_Gear) + + def test_excluding_all_terran_missions_excludes_all_terran_items(self) -> None: + options = { + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'excluded_missions': [ + mission.mission_name for mission in MissionTables.SC2Mission + if MissionTables.MissionFlag.Terran in mission.flags + ], + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] + for item_name, item_data in items: + self.assertNotIn(item_data.type, Items.TerranItemType, f"Item '{item_name}' included when all terran missions are excluded") + + def test_excluding_all_terran_build_missions_excludes_all_terran_units(self) -> None: + options = { + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'excluded_missions': [ + mission.mission_name for mission in MissionTables.SC2Mission + if MissionTables.MissionFlag.Terran in mission.flags + and MissionTables.MissionFlag.NoBuild not in mission.flags + ], + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] + for item_name, item_data in items: + self.assertNotEqual(item_data.type, Items.TerranItemType.Unit, f"Item '{item_name}' included when all terran build missions are excluded") + self.assertNotEqual(item_data.type, Items.TerranItemType.Mercenary, f"Item '{item_name}' included when all terran build missions are excluded") + self.assertNotEqual(item_data.type, Items.TerranItemType.Building, f"Item '{item_name}' included when all terran build missions are excluded") + + def test_excluding_all_zerg_and_kerrigan_missions_excludes_all_zerg_items(self) -> None: + options = { + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'excluded_missions': [ + mission.mission_name for mission in MissionTables.SC2Mission + if (MissionTables.MissionFlag.Kerrigan | MissionTables.MissionFlag.Zerg) & mission.flags + ], + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] + for item_name, item_data in items: + self.assertNotIn(item_data.type, Items.ZergItemType, f"Item '{item_name}' included when all zerg missions are excluded") + + def test_excluding_all_zerg_build_missions_excludes_zerg_units(self) -> None: + options = { + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'excluded_missions': [ + *[mission.mission_name + for mission in MissionTables.SC2Mission + if MissionTables.MissionFlag.Zerg in mission.flags + and MissionTables.MissionFlag.NoBuild not in mission.flags], + MissionTables.SC2Mission.ENEMY_WITHIN.mission_name, + ], + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] + for item_name, item_data in items: + self.assertNotEqual(item_data.type, Items.ZergItemType.Unit, f"Item '{item_name}' included when all zerg build missions are excluded") + self.assertNotEqual(item_data.type, Items.ZergItemType.Mercenary, f"Item '{item_name}' included when all zerg build missions are excluded") + + def test_excluding_all_protoss_missions_excludes_all_protoss_items(self) -> None: + options = { + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'accessibility': 'locations', + 'excluded_missions': [ + *[mission.mission_name + for mission in MissionTables.SC2Mission + if MissionTables.MissionFlag.Protoss in mission.flags], + ], + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] + for item_name, item_data in items: + self.assertNotIn(item_data.type, Items.ProtossItemType, f"Item '{item_name}' included when all protoss missions are excluded") + + def test_excluding_all_protoss_build_missions_excludes_protoss_units(self) -> None: + options = { + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'accessibility': 'locations', + 'excluded_missions': [ + *[mission.mission_name + for mission in MissionTables.SC2Mission + if mission.race == MissionTables.SC2Race.PROTOSS + and MissionTables.MissionFlag.NoBuild not in mission.flags], + MissionTables.SC2Mission.TEMPLAR_S_RETURN.mission_name, + ], + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] + for item_name, item_data in items: + self.assertNotEqual(item_data.type, Items.ProtossItemType.Unit, f"Item '{item_name}' included when all protoss build missions are excluded") + self.assertNotEqual(item_data.type, Items.ProtossItemType.Unit_2, f"Item '{item_name}' included when all protoss build missions are excluded") + self.assertNotEqual(item_data.type, Items.ProtossItemType.Building, f"Item '{item_name}' included when all protoss build missions are excluded") + From a869f4930e9e516e9992f30ff567c482153f9005 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 27 Apr 2024 21:30:26 -0700 Subject: [PATCH 08/48] sc2: Fixed some mypy type errors --- worlds/sc2/Locations.py | 8 ++++---- worlds/sc2/Options.py | 30 +++++++++++++++--------------- worlds/sc2/Regions.py | 2 ++ worlds/sc2/test/test_generation.py | 2 +- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/worlds/sc2/Locations.py b/worlds/sc2/Locations.py index 60b515116cbf..e29ef1480aab 100644 --- a/worlds/sc2/Locations.py +++ b/worlds/sc2/Locations.py @@ -1,6 +1,5 @@ from enum import IntEnum from typing import List, Tuple, Optional, Callable, NamedTuple, Set, Any, TYPE_CHECKING -from BaseClasses import MultiWorld from . import ItemNames from .Options import get_option_value, kerrigan_unit_available, RequiredTactics, GrantStoryTech, LocationInclusion, \ EnableHotsMissions @@ -11,6 +10,7 @@ if TYPE_CHECKING: from BaseClasses import CollectionState + from . import SC2World SC2WOL_LOC_ID_OFFSET = 1000 SC2HOTS_LOC_ID_OFFSET = 20000000 # Avoid clashes with The Legend of Zelda @@ -33,9 +33,9 @@ class LocationType(IntEnum): class LocationData(NamedTuple): region: str name: str - code: Optional[int] + code: int type: LocationType - rule: Optional[Callable[['CollectionState'], bool]] = Location.access_rule + rule: Callable[['CollectionState'], bool] = Location.access_rule def get_location_types(world: World, inclusion_type: int) -> Set[LocationType]: @@ -78,7 +78,7 @@ def get_plando_locations(world: World) -> List[str]: return plando_locations -def get_locations(world: Optional[World]) -> Tuple[LocationData, ...]: +def get_locations(world: Optional['SC2World']) -> Tuple[LocationData, ...]: # Note: rules which are ended with or True are rules identified as needed later when restricted units is an option logic_level = get_option_value(world, 'required_tactics') adv_tactics = logic_level != RequiredTactics.option_standard diff --git a/worlds/sc2/Options.py b/worlds/sc2/Options.py index 664c3ba03520..2ca2af00288d 100644 --- a/worlds/sc2/Options.py +++ b/worlds/sc2/Options.py @@ -7,9 +7,9 @@ from BaseClasses import PlandoOptions from .MissionTables import SC2Campaign, SC2Mission, lookup_name_to_mission, MissionPools, get_no_build_missions, \ campaign_mission_table -from worlds.AutoWorld import World if TYPE_CHECKING: + from worlds.AutoWorld import World from . import SC2World @@ -628,7 +628,7 @@ class TakeOverAIAllies(Toggle): class Sc2ItemDict(Option[Dict[str, int]], VerifyKeys, Mapping[str, int]): """A branch of ItemDict that supports item counts of 0""" - default: Dict[str, int] = {} + default = {} supports_weighting = False verify_item_name = True # convert_name_groups = True @@ -655,7 +655,7 @@ def from_any(cls, data: Union[List[str], Dict[str, int]]) -> 'Sc2ItemDict': else: raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}") - def verify(self, world: Type['SC2World'], player_name: str, plando_options: PlandoOptions) -> None: + def verify(self, world: Type['World'], player_name: str, plando_options: PlandoOptions) -> None: """Overridden version of function from Options.VerifyKeys for a better error message""" new_value: dict[str, int] = {} for group_name in self.value: @@ -665,7 +665,7 @@ def verify(self, world: Type['SC2World'], player_name: str, plando_options: Plan self.value = new_value for item_name in self.value: if item_name not in world.item_names: - picks = get_fuzzy_results(item_name, world.item_names, limit=1) + picks = get_fuzzy_results(item_name, list(world.item_names), limit=1) raise Exception(f"Item {item_name} from option {self} " f"is not a valid item name from {world.game}. " f"Did you mean '{picks[0][0]}' ({picks[0][1]}% sure)") @@ -889,7 +889,7 @@ class Starcraft2Options(PerGameCommonOptions): starting_supply_per_item: StartingSupplyPerItem -def get_option_value(world: World, name: str) -> Union[int, FrozenSet]: +def get_option_value(world: 'SC2World', name: str) -> Union[int, FrozenSet]: if world is None: field: Field = [class_field for class_field in fields(Starcraft2Options) if class_field.name == name][0] return field.type.default @@ -899,7 +899,7 @@ def get_option_value(world: World, name: str) -> Union[int, FrozenSet]: return player_option.value -def get_enabled_campaigns(world: World) -> Set[SC2Campaign]: +def get_enabled_campaigns(world: 'SC2World') -> Set[SC2Campaign]: enabled_campaigns = set() if get_option_value(world, "enable_wol_missions"): enabled_campaigns.add(SC2Campaign.WOL) @@ -918,7 +918,7 @@ def get_enabled_campaigns(world: World) -> Set[SC2Campaign]: return enabled_campaigns -def get_disabled_campaigns(world: World) -> Set[SC2Campaign]: +def get_disabled_campaigns(world: 'SC2World') -> Set[SC2Campaign]: all_campaigns = set(SC2Campaign) enabled_campaigns = get_enabled_campaigns(world) disabled_campaigns = all_campaigns.difference(enabled_campaigns) @@ -926,23 +926,23 @@ def get_disabled_campaigns(world: World) -> Set[SC2Campaign]: return disabled_campaigns -def get_excluded_missions(world: World) -> Set[SC2Mission]: - mission_order_type = get_option_value(world, "mission_order") - excluded_mission_names = get_option_value(world, "excluded_missions") - shuffle_no_build = get_option_value(world, "shuffle_no_build") +def get_excluded_missions(world: 'SC2World') -> Set[SC2Mission]: + mission_order_type = world.options.mission_order.value + excluded_mission_names = world.options.excluded_missions.value + shuffle_no_build = world.options.shuffle_no_build disabled_campaigns = get_disabled_campaigns(world) excluded_missions: Set[SC2Mission] = set([lookup_name_to_mission[name] for name in excluded_mission_names]) # Excluding Very Hard missions depending on options - if (get_option_value(world, "exclude_very_hard_missions") == ExcludeVeryHardMissions.option_true + if (world.options.exclude_very_hard_missions == ExcludeVeryHardMissions.option_true ) or ( - get_option_value(world, "exclude_very_hard_missions") == ExcludeVeryHardMissions.option_default + world.options.exclude_very_hard_missions == ExcludeVeryHardMissions.option_default and ( mission_order_type not in [MissionOrder.option_vanilla_shuffled, MissionOrder.option_grid] or ( mission_order_type == MissionOrder.option_grid - and get_option_value(world, "maximum_campaign_size") < 20 + and world.options.maximum_campaign_size < 20 ) ) ): @@ -968,4 +968,4 @@ def get_excluded_missions(world: World) -> Set[SC2Mission]: kerrigan_unit_available = [ KerriganPresence.option_vanilla, -] \ No newline at end of file +] diff --git a/worlds/sc2/Regions.py b/worlds/sc2/Regions.py index 60da745463e8..8045e819876c 100644 --- a/worlds/sc2/Regions.py +++ b/worlds/sc2/Regions.py @@ -362,6 +362,7 @@ def create_grid_regions( final_mission_id = final_mission.id # Changing the completion condition for alternate final missions into an event final_location = get_goal_location(final_mission) + assert final_location, f"Unable to find a goal location for mission {final_mission}" setup_final_location(final_location, location_cache) return {SC2Campaign.GLOBAL: mission_req_table}, final_mission_id, final_location @@ -597,6 +598,7 @@ def build_connection_rule(mission_names: List[str], missions_req: int) -> Callab final_mission_id = final_mission.id # Changing the completion condition for alternate final missions into an event final_location = get_goal_location(final_mission) + assert final_location, f"Unable to find a goal location for mission {final_mission}" setup_final_location(final_location, location_cache) return mission_req_table, final_mission_id, final_location diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index cb818dea9dcb..1c8b11f80927 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -35,7 +35,7 @@ def generate_world(self, options: Dict[str, Any]) -> None: 1: new_option }) self.multiworld.set_options(args) - self.world: SC2World = self.multiworld.worlds[self.player] + self.world: SC2World = cast(SC2World, self.multiworld.worlds[self.player]) for step in gen_steps: call_all(self.multiworld, step) From 1d1250da9a598e1f80abf91b41e7ff25b6648df8 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 27 Apr 2024 21:57:04 -0700 Subject: [PATCH 09/48] sc2: Updated test to do a complete generation rather than looking at one filter step --- worlds/sc2/test/test_generation.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index 1c8b11f80927..8fb332141d47 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -61,15 +61,15 @@ def test_explicit_locks_excludes_interact_and_set_flags(self): } } self.generate_world(options) - item_list = create_and_flag_explicit_item_locks_and_excludes(self.world) - self.assertNotIn(ItemNames.ZERGLING, [x.name for x in item_list], msg=f'{ItemNames.ZERGLING} did not get properly excluded') - self.assertIn(FilterItem(ItemNames.MARINE, Items.item_table[ItemNames.MARINE], flags=ItemFilterFlags.Locked), item_list) - self.assertIn(FilterItem(ItemNames.MARAUDER, Items.item_table[ItemNames.MARAUDER], flags=ItemFilterFlags.Locked), item_list) - self.assertIn(FilterItem(ItemNames.MEDIVAC, Items.item_table[ItemNames.MEDIVAC], flags=ItemFilterFlags.Locked), item_list) - self.assertIn(FilterItem(ItemNames.FIREBAT, Items.item_table[ItemNames.FIREBAT], flags=ItemFilterFlags.Locked), item_list) - self.assertIn(FilterItem(ItemNames.ZEALOT, Items.item_table[ItemNames.ZEALOT], flags=ItemFilterFlags.Locked), item_list) - self.assertIn(FilterItem(ItemNames.DRAGOON, Items.item_table[ItemNames.DRAGOON]), item_list) - regen_biosteel_items = [x for x in item_list if x.name == ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL] + self.assertTrue(self.multiworld.itempool) + item_names = [item.name for item in self.multiworld.itempool] + self.assertIn(ItemNames.MARINE, item_names) + self.assertIn(ItemNames.MARAUDER, item_names) + self.assertIn(ItemNames.MEDIVAC, item_names) + self.assertIn(ItemNames.FIREBAT, item_names) + self.assertIn(ItemNames.ZEALOT, item_names) + self.assertNotIn(ItemNames.ZERGLING, item_names) + regen_biosteel_items = [x for x in item_names if x == ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL] self.assertEqual(len(regen_biosteel_items), 2) def test_excluding_groups_excludes_all_items_in_group(self): From ecce3238a4e07ebb8b9cbbfc44a9df7c64472c18 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 27 Apr 2024 22:17:46 -0700 Subject: [PATCH 10/48] sc2: Fully moved nova gear removal logic to filtering in init.py --- worlds/sc2/PoolFilter.py | 12 ++---------- worlds/sc2/__init__.py | 2 +- worlds/sc2/test/test_generation.py | 5 +++-- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py index e60ccaad6442..44f5fd42aad4 100644 --- a/worlds/sc2/PoolFilter.py +++ b/worlds/sc2/PoolFilter.py @@ -539,7 +539,7 @@ def attempt_removal(item: Item) -> bool: def __init__(self, world: 'SC2World', item_pool: List[Item], existing_items: List[Item], locked_items: List[Item], - used_races: Set[SC2Race], nova_equipment_used: bool): + used_races: Set[SC2Race]): self.multiworld = world.multiworld self.player = world.player self.world: 'SC2World' = world @@ -567,9 +567,6 @@ def __init__(self, world: 'SC2World', self.item_pool.append(item) # Drop any item belonging to a race not used in the campaign continue - if item.name in nova_equipment and not nova_equipment_used: - # Drop Nova equipment if there's no NCO mission generated - continue if item_info.type in upgrade_item_types: # Locking upgrades based on mission duration if item.name not in item_quantities: @@ -596,9 +593,8 @@ def filter_items(world: 'SC2World', mission_req_table: Dict[SC2Campaign, Dict[st open_locations = [location for location in location_cache if location.item is None] inventory_size = len(open_locations) used_races = get_used_races(mission_req_table, world) - nova_equipment_used = is_nova_equipment_used(mission_req_table) mission_requirements = [(location.name, location.access_rule) for location in location_cache] - valid_inventory = ValidInventory(world, item_pool, existing_items, locked_items, used_races, nova_equipment_used) + valid_inventory = ValidInventory(world, item_pool, existing_items, locked_items, used_races) valid_items = valid_inventory.generate_reduced_inventory(inventory_size, mission_requirements) return valid_items @@ -634,10 +630,6 @@ def get_used_races(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], return races -def is_nova_equipment_used(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> bool: - missions = missions_in_mission_table(mission_req_table) - return any([MissionFlag.Nova in mission.flags for mission in missions]) - def missions_in_mission_table(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> Set[SC2Mission]: return set([mission.mission for campaign_missions in mission_req_table.values() for mission in diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index a9b3a2c1f660..f045fe58d43a 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -324,7 +324,7 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem for item in item_list: # Filter Nova equipment if you never get Nova - if not nova_missions and item.data.type == Items.TerranItemType.Nova_Gear: + if not nova_missions and (item.data.type == Items.TerranItemType.Nova_Gear or item.name == ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE): item.flags |= ItemFilterFlags.Excluded # Todo(mm): How should no-build only / grant_story_tech affect excluding Kerrigan items? diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index 8fb332141d47..e9016af09b3f 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -96,11 +96,12 @@ def test_excluding_campaigns_excludes_campaign_specific_items(self) -> None: } self.generate_world(options) self.assertTrue(self.multiworld.itempool) - item_data_entries = [Items.item_table[item.name] for item in self.multiworld.itempool] - for item_data in item_data_entries: + items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] + for item_name, item_data in items: self.assertNotIn(item_data.type, Items.ProtossItemType) self.assertNotIn(item_data.type, Items.ZergItemType) self.assertNotEqual(item_data.type, Items.TerranItemType.Nova_Gear) + self.assertNotEqual(item_name, ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE) def test_excluding_all_terran_missions_excludes_all_terran_items(self) -> None: options = { From 3fc85ed638d309cbedeec7cfe2e95a8ca793f161 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 27 Apr 2024 23:09:05 -0700 Subject: [PATCH 11/48] sc2: Created more item groups; filtering Nova items now uses nova equipment item group --- worlds/sc2/ItemGroups.py | 27 ++++++++++++++++++++++++++- worlds/sc2/__init__.py | 2 +- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index b98093f8d1a0..f60d8f5cb087 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -71,9 +71,14 @@ # Hand-made groups class ItemGroupNames: + TERRAN_UNITS = "Terran Units" + ZERG_UNITS = "Zerg Units" + PROTOSS_UNITS = "Protoss Units" + BARRACKS_UNITS = "Barracks Units" FACTORY_UNITS = "Factory Units" STARPORT_UNITS = "Starport Units" + NOVA_EQUIPMENT = "Nova Equipment" AIUR = "Aiur" NERAZIM = "Nerazim" @@ -81,6 +86,19 @@ class ItemGroupNames: PURIFIER = "PURIFIER" +# Terran +item_name_groups[ItemGroupNames.TERRAN_UNITS] = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type in (Items.TerranItemType.Unit, Items.TerranItemType.Mercenary, Items.TerranItemType.Building) +] +item_name_groups[ItemGroupNames.ZERG_UNITS] = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type in (Items.ZergItemType.Unit, Items.ZergItemType.Mercenary, Items.ZergItemType.Morph) +] +item_name_groups[ItemGroupNames.PROTOSS_UNITS] = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type in (Items.ProtossItemType.Unit, Items.ProtossItemType.Unit_2, Items.ProtossItemType.Building) +] item_name_groups[ItemGroupNames.BARRACKS_UNITS] = barracks_units = [ ItemNames.MARINE, ItemNames.MEDIC, ItemNames.FIREBAT, ItemNames.MARAUDER, ItemNames.REAPER, ItemNames.GHOST, ItemNames.SPECTRE, ItemNames.HERC, @@ -95,6 +113,13 @@ class ItemGroupNames: ItemNames.BATTLECRUISER, ItemNames.HERCULES, ItemNames.SCIENCE_VESSEL, ItemNames.RAVEN, ItemNames.LIBERATOR, ItemNames.VALKYRIE, ] +item_name_groups[ItemGroupNames.NOVA_EQUIPMENT] = nova_equipment = [ + *[item_name for item_name, item_data in Items.item_table.items() + if item_data.type == Items.TerranItemType.Nova_Gear], + ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, +] + +# Protoss item_name_groups[ItemGroupNames.AIUR] = [ ItemNames.ZEALOT, ItemNames.DRAGOON, ItemNames.SENTRY, ItemNames.AVENGER, ItemNames.HIGH_TEMPLAR, ItemNames.IMMORTAL, ItemNames.REAVER, @@ -115,4 +140,4 @@ class ItemGroupNames: ItemNames.SENTINEL, ItemNames.ADEPT, ItemNames.INSTIGATOR, ItemNames.ENERGIZER, ItemNames.COLOSSUS, ItemNames.DISRUPTOR, ItemNames.MIRAGE, ItemNames.TEMPEST, -] \ No newline at end of file +] diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index f045fe58d43a..174c778b5658 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -324,7 +324,7 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem for item in item_list: # Filter Nova equipment if you never get Nova - if not nova_missions and (item.data.type == Items.TerranItemType.Nova_Gear or item.name == ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE): + if not nova_missions and (item.name in ItemGroups.nova_equipment): item.flags |= ItemFilterFlags.Excluded # Todo(mm): How should no-build only / grant_story_tech affect excluding Kerrigan items? From bb58dd76e55ec1498ae2c2b485c054c5591fd84c Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 27 Apr 2024 23:10:51 -0700 Subject: [PATCH 12/48] sc2: Fixed accidental all-caps on purifier item group --- worlds/sc2/ItemGroups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index f60d8f5cb087..db96b1df4db1 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -83,7 +83,7 @@ class ItemGroupNames: AIUR = "Aiur" NERAZIM = "Nerazim" TAL_DARIM = "Tal'Darim" - PURIFIER = "PURIFIER" + PURIFIER = "Purifier" # Terran From 4792a7f7c859bc201da9874d15189c8d309536df Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 27 Apr 2024 23:19:17 -0700 Subject: [PATCH 13/48] sc2: Added Protoss unit per building item groups --- worlds/sc2/ItemGroups.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index db96b1df4db1..2f4dd22154a2 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -80,6 +80,9 @@ class ItemGroupNames: STARPORT_UNITS = "Starport Units" NOVA_EQUIPMENT = "Nova Equipment" + GATEWAY_UNITS = "Gateway Units" + ROBO_UNITS = "Robo Units" + STARGATE_UNITS = "Stargate Units" AIUR = "Aiur" NERAZIM = "Nerazim" TAL_DARIM = "Tal'Darim" @@ -120,6 +123,27 @@ class ItemGroupNames: ] # Protoss +item_name_groups[ItemGroupNames.GATEWAY_UNITS] = [ + ItemNames.ZEALOT, ItemNames.CENTURION, ItemNames.SENTINEL, ItemNames.SUPPLICANT, + ItemNames.STALKER, ItemNames.INSTIGATOR, ItemNames.SLAYER, + ItemNames.SENTRY, ItemNames.HAVOC, ItemNames.ENERGIZER, + ItemNames.DRAGOON, ItemNames.ADEPT, ItemNames.DARK_ARCHON, + ItemNames.HIGH_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.ASCENDANT, + ItemNames.DARK_TEMPLAR, ItemNames.AVENGER, ItemNames.BLOOD_HUNTER, +] +item_name_groups[ItemGroupNames.ROBO_UNITS] = [ + ItemNames.WARP_PRISM, ItemNames.OBSERVER, + ItemNames.IMMORTAL, ItemNames.ANNIHILATOR, ItemNames.VANGUARD, + ItemNames.COLOSSUS, ItemNames.WRATHWALKER, + ItemNames.REAVER, ItemNames.DISRUPTOR, +] +item_name_groups[ItemGroupNames.STARGATE_UNITS] = [ + ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, + ItemNames.VOID_RAY, ItemNames.DESTROYER, + ItemNames.SCOUT, ItemNames.TEMPEST, + ItemNames.CARRIER, ItemNames.MOTHERSHIP, + ItemNames.ARBITER, ItemNames.ORACLE, +] item_name_groups[ItemGroupNames.AIUR] = [ ItemNames.ZEALOT, ItemNames.DRAGOON, ItemNames.SENTRY, ItemNames.AVENGER, ItemNames.HIGH_TEMPLAR, ItemNames.IMMORTAL, ItemNames.REAVER, From 47741cafa701a4d8841d541ef50cf5902ad7016c Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 27 Apr 2024 23:29:53 -0700 Subject: [PATCH 14/48] sc2: Added a few more protoss / terran item groups and some sanity check asserts --- worlds/sc2/ItemGroups.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index 2f4dd22154a2..9b6951064b17 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -79,29 +79,33 @@ class ItemGroupNames: FACTORY_UNITS = "Factory Units" STARPORT_UNITS = "Starport Units" NOVA_EQUIPMENT = "Nova Equipment" + TERRAN_BUILDINGS = "Terran Buildings" + TERRAN_MERCENARIES = "Terran Mercenaries" GATEWAY_UNITS = "Gateway Units" ROBO_UNITS = "Robo Units" STARGATE_UNITS = "Stargate Units" + PROTOSS_BUILDINGS = "Protoss Buildings" AIUR = "Aiur" NERAZIM = "Nerazim" TAL_DARIM = "Tal'Darim" PURIFIER = "Purifier" -# Terran -item_name_groups[ItemGroupNames.TERRAN_UNITS] = [ +item_name_groups[ItemGroupNames.TERRAN_UNITS] = terran_units = [ item_name for item_name, item_data in Items.item_table.items() if item_data.type in (Items.TerranItemType.Unit, Items.TerranItemType.Mercenary, Items.TerranItemType.Building) ] -item_name_groups[ItemGroupNames.ZERG_UNITS] = [ +item_name_groups[ItemGroupNames.ZERG_UNITS] = zerg_units = [ item_name for item_name, item_data in Items.item_table.items() if item_data.type in (Items.ZergItemType.Unit, Items.ZergItemType.Mercenary, Items.ZergItemType.Morph) ] -item_name_groups[ItemGroupNames.PROTOSS_UNITS] = [ +item_name_groups[ItemGroupNames.PROTOSS_UNITS] = protoss_units = [ item_name for item_name, item_data in Items.item_table.items() if item_data.type in (Items.ProtossItemType.Unit, Items.ProtossItemType.Unit_2, Items.ProtossItemType.Building) ] + +# Terran item_name_groups[ItemGroupNames.BARRACKS_UNITS] = barracks_units = [ ItemNames.MARINE, ItemNames.MEDIC, ItemNames.FIREBAT, ItemNames.MARAUDER, ItemNames.REAPER, ItemNames.GHOST, ItemNames.SPECTRE, ItemNames.HERC, @@ -116,6 +120,14 @@ class ItemGroupNames: ItemNames.BATTLECRUISER, ItemNames.HERCULES, ItemNames.SCIENCE_VESSEL, ItemNames.RAVEN, ItemNames.LIBERATOR, ItemNames.VALKYRIE, ] +item_name_groups[ItemGroupNames.TERRAN_BUILDINGS] = terran_buildings = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type == Items.TerranItemType.Building +] +item_name_groups[ItemGroupNames.TERRAN_MERCENARIES] = terran_mercenaries = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type == Items.TerranItemType.Mercenary +] item_name_groups[ItemGroupNames.NOVA_EQUIPMENT] = nova_equipment = [ *[item_name for item_name, item_data in Items.item_table.items() if item_data.type == Items.TerranItemType.Nova_Gear], @@ -123,7 +135,7 @@ class ItemGroupNames: ] # Protoss -item_name_groups[ItemGroupNames.GATEWAY_UNITS] = [ +item_name_groups[ItemGroupNames.GATEWAY_UNITS] = gateway_units = [ ItemNames.ZEALOT, ItemNames.CENTURION, ItemNames.SENTINEL, ItemNames.SUPPLICANT, ItemNames.STALKER, ItemNames.INSTIGATOR, ItemNames.SLAYER, ItemNames.SENTRY, ItemNames.HAVOC, ItemNames.ENERGIZER, @@ -131,19 +143,23 @@ class ItemGroupNames: ItemNames.HIGH_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.ASCENDANT, ItemNames.DARK_TEMPLAR, ItemNames.AVENGER, ItemNames.BLOOD_HUNTER, ] -item_name_groups[ItemGroupNames.ROBO_UNITS] = [ +item_name_groups[ItemGroupNames.ROBO_UNITS] = robo_units = [ ItemNames.WARP_PRISM, ItemNames.OBSERVER, ItemNames.IMMORTAL, ItemNames.ANNIHILATOR, ItemNames.VANGUARD, ItemNames.COLOSSUS, ItemNames.WRATHWALKER, ItemNames.REAVER, ItemNames.DISRUPTOR, ] -item_name_groups[ItemGroupNames.STARGATE_UNITS] = [ +item_name_groups[ItemGroupNames.STARGATE_UNITS] = stargate_units = [ ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, ItemNames.VOID_RAY, ItemNames.DESTROYER, ItemNames.SCOUT, ItemNames.TEMPEST, ItemNames.CARRIER, ItemNames.MOTHERSHIP, ItemNames.ARBITER, ItemNames.ORACLE, ] +item_name_groups[ItemGroupNames.PROTOSS_BUILDINGS] = protoss_buildings = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type == Items.ProtossItemType.Building +] item_name_groups[ItemGroupNames.AIUR] = [ ItemNames.ZEALOT, ItemNames.DRAGOON, ItemNames.SENTRY, ItemNames.AVENGER, ItemNames.HIGH_TEMPLAR, ItemNames.IMMORTAL, ItemNames.REAVER, @@ -165,3 +181,7 @@ class ItemGroupNames: ItemNames.COLOSSUS, ItemNames.DISRUPTOR, ItemNames.MIRAGE, ItemNames.TEMPEST, ] + +# Sanity checks +assert len(terran_units) == len(barracks_units) + len(factory_units) + len(starport_units) + len(terran_buildings) + len(terran_mercenaries) +assert len(protoss_units) == len(gateway_units) + len(robo_units) + len(stargate_units) + len(protoss_buildings) From 214eeb210d2fddfa0cf5ba99f3fedbb52f3ad554 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 29 Apr 2024 18:19:07 -0700 Subject: [PATCH 15/48] sc2: set orphan upgrades to only be flagged when corresponding no-builds are in the item pool --- worlds/sc2/__init__.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 174c778b5658..06d5ba4c5610 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -357,22 +357,17 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem def flag_allowed_orphan_items(world: SC2World, item_list: List[FilterItem]) -> None: """Adds the `Allowed_Orphan` flag to items that shouldn't be filtered with their parents, like combat shield""" missions = get_all_missions(world.mission_req_table) - terran_missions = any(MissionFlag.Terran in mission.flags for mission in missions) - zerg_missions = any(MissionFlag.Terran in mission.flags for mission in missions) - protoss_missions = any(MissionFlag.Terran in mission.flags for mission in missions) + terran_nobuild_missions = any((MissionFlag.Terran|MissionFlag.NoBuild) in mission.flags for mission in missions) + evil_awoken_enabled = SC2Mission.EVIL_AWOKEN in missions for item in item_list: if item.name in ( ItemNames.MARINE_COMBAT_SHIELD, ItemNames.MARINE_PROGRESSIVE_STIMPACK, ItemNames.MARINE_MAGRAIL_MUNITIONS, ItemNames.MEDIC_STABILIZER_MEDPACKS, ItemNames.MEDIC_NANO_PROJECTOR, - ) and terran_missions: - item.flags |= ItemFilterFlags.AllowedOrphan - if item.name in ( - ItemNames.ZERGLING_ADRENAL_OVERLOAD, ItemNames.ZERGLING_METABOLIC_BOOST, - ) and zerg_missions: + ) and terran_nobuild_missions: item.flags |= ItemFilterFlags.AllowedOrphan if item.name in ( ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES, ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION, - ) and protoss_missions: + ) and evil_awoken_enabled: item.flags |= ItemFilterFlags.AllowedOrphan From 6c7144b1243bd8248f5ba8ffe37308219c5c6dda Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 29 Apr 2024 20:31:26 -0700 Subject: [PATCH 16/48] sc2: Replaced bw_items, nco_items, ext_items options with vanilla_items_only option; added many more item groups --- worlds/sc2/ItemGroups.py | 298 ++++++++++++++++++++++++++++++++++++--- worlds/sc2/Items.py | 17 --- worlds/sc2/Options.py | 28 +--- worlds/sc2/PoolFilter.py | 18 +-- worlds/sc2/__init__.py | 41 ++---- 5 files changed, 298 insertions(+), 104 deletions(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index 9b6951064b17..60078781b2e5 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -78,34 +78,60 @@ class ItemGroupNames: BARRACKS_UNITS = "Barracks Units" FACTORY_UNITS = "Factory Units" STARPORT_UNITS = "Starport Units" + WOL_UNITS = "WoL Units" + WOL_MERCS = "WoL Mercenaries" + WOL_BUILDINGS = "WoL Buildings" + WOL_UPGRADES = "WoL Upgrades" + WOL_ITEMS = "WoL Items" + """All items from vanilla WoL. Note some items are progressive where level 2 is not vanilla.""" + NCO_UNITS = "NCO Units" NOVA_EQUIPMENT = "Nova Equipment" TERRAN_BUILDINGS = "Terran Buildings" TERRAN_MERCENARIES = "Terran Mercenaries" + TERRAN_STIMPACKS = "Terran Stimpacks" + TERRAN_PROGRESSIVE_UPGRADES = "Terran Progressive Upgrades" + TERRAN_ORIGINAL_PROGRESSIVE_UPGRADES = "Terran Original Progressive Upgrades" + """Progressive items where level 1 appeared in WoL""" + + HOTS_UNITS = "HotS Units" + HOTS_STRAINS = "HotS Strains" + """Vanilla HotS strains (the upgrades you play a mini-mission for)""" + HOTS_MUTATIONS = "HotS Mutations" + """Vanilla HotS Mutations (basic toggleable unit upgrades)""" + HOTS_GLOBAL_UPGRADES = "HotS Global Upgrades" + HOTS_MORPHS = "HotS Morphs" + KERRIGAN_ABILITIES = "Kerrigan Abilities" + KERRIGAN_PASSIVES = "Kerrigan Passives" + HOTS_ITEMS = "HotS Items" + """All items from vanilla HotS""" + ZERG_MORPHS = "Zerg Morphs" + ZERG_MERCS = "Zerg Mercenaries" + ZERG_BUILDINGS = "Zerg Buildings" GATEWAY_UNITS = "Gateway Units" ROBO_UNITS = "Robo Units" STARGATE_UNITS = "Stargate Units" + PROPHECY_UNITS = "Prophecy Units" + PROPHECY_BUILDINGS = "Prophecy Buildings" + LOTV_UNITS = "LotV Units" + LOTV_ITEMS = "LotV Items" + LOTV_GLOBAL_UPGRADES = "LotV Global Upgrades" + SOA_ITEMS = "SOA" + PROTOSS_GLOBAL_UPGRADES = "Protoss Global Upgrades" PROTOSS_BUILDINGS = "Protoss Buildings" - AIUR = "Aiur" - NERAZIM = "Nerazim" - TAL_DARIM = "Tal'Darim" - PURIFIER = "Purifier" + AIUR_UNITS = "Aiur" + NERAZIM_UNITS = "Nerazim" + TAL_DARIM_UNITS = "Tal'Darim" + PURIFIER_UNITS = "Purifier" + + VANILLA_ITEMS = "Vanilla Items" +# Terran item_name_groups[ItemGroupNames.TERRAN_UNITS] = terran_units = [ item_name for item_name, item_data in Items.item_table.items() - if item_data.type in (Items.TerranItemType.Unit, Items.TerranItemType.Mercenary, Items.TerranItemType.Building) -] -item_name_groups[ItemGroupNames.ZERG_UNITS] = zerg_units = [ - item_name for item_name, item_data in Items.item_table.items() - if item_data.type in (Items.ZergItemType.Unit, Items.ZergItemType.Mercenary, Items.ZergItemType.Morph) -] -item_name_groups[ItemGroupNames.PROTOSS_UNITS] = protoss_units = [ - item_name for item_name, item_data in Items.item_table.items() - if item_data.type in (Items.ProtossItemType.Unit, Items.ProtossItemType.Unit_2, Items.ProtossItemType.Building) + if item_data.type in (Items.TerranItemType.Unit, Items.TerranItemType.Mercenary) ] - -# Terran item_name_groups[ItemGroupNames.BARRACKS_UNITS] = barracks_units = [ ItemNames.MARINE, ItemNames.MEDIC, ItemNames.FIREBAT, ItemNames.MARAUDER, ItemNames.REAPER, ItemNames.GHOST, ItemNames.SPECTRE, ItemNames.HERC, @@ -128,13 +154,200 @@ class ItemGroupNames: item_name for item_name, item_data in Items.item_table.items() if item_data.type == Items.TerranItemType.Mercenary ] +item_name_groups[ItemGroupNames.NCO_UNITS] = nco_units = [ + ItemNames.MARINE, ItemNames.MARAUDER, ItemNames.REAPER, + ItemNames.HELLION, ItemNames.GOLIATH, ItemNames.SIEGE_TANK, + ItemNames.RAVEN, ItemNames.LIBERATOR, ItemNames.BANSHEE, ItemNames.BATTLECRUISER, +] item_name_groups[ItemGroupNames.NOVA_EQUIPMENT] = nova_equipment = [ *[item_name for item_name, item_data in Items.item_table.items() if item_data.type == Items.TerranItemType.Nova_Gear], ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, ] +item_name_groups[ItemGroupNames.WOL_UNITS] = wol_units = [ + ItemNames.MARINE, ItemNames.MEDIC, ItemNames.FIREBAT, ItemNames.MARAUDER, ItemNames.REAPER, + ItemNames.HELLION, ItemNames.VULTURE, ItemNames.GOLIATH, ItemNames.DIAMONDBACK, ItemNames.SIEGE_TANK, + ItemNames.MEDIVAC, ItemNames.WRAITH, ItemNames.VIKING, ItemNames.BANSHEE, ItemNames.BATTLECRUISER, + ItemNames.GHOST, ItemNames.SPECTRE, ItemNames.THOR, + ItemNames.PREDATOR, ItemNames.HERCULES, + ItemNames.SCIENCE_VESSEL, ItemNames.RAVEN, +] +item_name_groups[ItemGroupNames.WOL_MERCS] = wol_mercs = [ + ItemNames.WAR_PIGS, ItemNames.DEVIL_DOGS, ItemNames.HAMMER_SECURITIES, + ItemNames.SPARTAN_COMPANY, ItemNames.SIEGE_BREAKERS, + ItemNames.HELS_ANGELS, ItemNames.DUSK_WINGS, ItemNames.JACKSONS_REVENGE, +] +item_name_groups[ItemGroupNames.WOL_BUILDINGS] = wol_buildings = [ + ItemNames.BUNKER, ItemNames.SENSOR_TOWER, ItemNames.PROGRESSIVE_ORBITAL_COMMAND, + ItemNames.PERDITION_TURRET, ItemNames.PLANETARY_FORTRESS, + ItemNames.HIVE_MIND_EMULATOR, ItemNames.PSI_DISRUPTER, +] + +# Terran Upgrades +item_name_groups[ItemGroupNames.WOL_UPGRADES] = wol_upgrades = [ + # Armory Base + ItemNames.BUNKER_PROJECTILE_ACCELERATOR, ItemNames.BUNKER_NEOSTEEL_BUNKER, + ItemNames.MISSILE_TURRET_TITANIUM_HOUSING, ItemNames.MISSILE_TURRET_HELLSTORM_BATTERIES, + ItemNames.SCV_ADVANCED_CONSTRUCTION, ItemNames.SCV_DUAL_FUSION_WELDERS, + ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM, ItemNames.PROGRESSIVE_ORBITAL_COMMAND, + # Armory Infantry + ItemNames.MARINE_PROGRESSIVE_STIMPACK, ItemNames.MARINE_COMBAT_SHIELD, + ItemNames.MEDIC_ADVANCED_MEDIC_FACILITIES, ItemNames.MEDIC_STABILIZER_MEDPACKS, + ItemNames.FIREBAT_INCINERATOR_GAUNTLETS, ItemNames.FIREBAT_JUGGERNAUT_PLATING, + ItemNames.MARAUDER_CONCUSSIVE_SHELLS, ItemNames.MARAUDER_KINETIC_FOAM, + ItemNames.REAPER_U238_ROUNDS, ItemNames.REAPER_G4_CLUSTERBOMB, + # Armory Vehicles + ItemNames.HELLION_TWIN_LINKED_FLAMETHROWER, ItemNames.HELLION_THERMITE_FILAMENTS, + ItemNames.SPIDER_MINE_CERBERUS_MINE, ItemNames.VULTURE_PROGRESSIVE_REPLENISHABLE_MAGAZINE, + ItemNames.GOLIATH_MULTI_LOCK_WEAPONS_SYSTEM, ItemNames.GOLIATH_ARES_CLASS_TARGETING_SYSTEM, + ItemNames.DIAMONDBACK_PROGRESSIVE_TRI_LITHIUM_POWER_CELL, ItemNames.DIAMONDBACK_SHAPED_HULL, + ItemNames.SIEGE_TANK_MAELSTROM_ROUNDS, ItemNames.SIEGE_TANK_SHAPED_BLAST, + # Armory Starships + ItemNames.MEDIVAC_RAPID_DEPLOYMENT_TUBE, ItemNames.MEDIVAC_ADVANCED_HEALING_AI, + ItemNames.WRAITH_PROGRESSIVE_TOMAHAWK_POWER_CELLS, ItemNames.WRAITH_DISPLACEMENT_FIELD, + ItemNames.VIKING_RIPWAVE_MISSILES, ItemNames.VIKING_PHOBOS_CLASS_WEAPONS_SYSTEM, + ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS, ItemNames.BANSHEE_SHOCKWAVE_MISSILE_BATTERY, + ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS, ItemNames.BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX, + # Armory Dominion + ItemNames.GHOST_OCULAR_IMPLANTS, ItemNames.GHOST_CRIUS_SUIT, + ItemNames.SPECTRE_PSIONIC_LASH, ItemNames.SPECTRE_NYX_CLASS_CLOAKING_MODULE, + ItemNames.THOR_330MM_BARRAGE_CANNON, ItemNames.THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL, + # Lab Zerg + ItemNames.BUNKER_FORTIFIED_BUNKER, ItemNames.BUNKER_SHRIKE_TURRET, + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, ItemNames.CELLULAR_REACTOR, + # Other 3 levels are units/buildings (Perdition, PF, Hercules, Predator, HME, Psi Disrupter) + # Lab Protoss + ItemNames.VANADIUM_PLATING, ItemNames.ULTRA_CAPACITORS, + ItemNames.AUTOMATED_REFINERY, ItemNames.MICRO_FILTERING, + ItemNames.ORBITAL_DEPOTS, ItemNames.COMMAND_CENTER_REACTOR, + ItemNames.ORBITAL_STRIKE, ItemNames.TECH_REACTOR, + # Other level is units (Raven, Science Vessel) +] +item_name_groups[ItemGroupNames.TERRAN_STIMPACKS] = terran_stimpacks = [ + ItemNames.MARINE_PROGRESSIVE_STIMPACK, + ItemNames.MARAUDER_PROGRESSIVE_STIMPACK, + ItemNames.REAPER_PROGRESSIVE_STIMPACK, + ItemNames.FIREBAT_PROGRESSIVE_STIMPACK, + ItemNames.HELLION_PROGRESSIVE_STIMPACK, +] +item_name_groups[ItemGroupNames.TERRAN_ORIGINAL_PROGRESSIVE_UPGRADES] = terran_original_progressive_upgrades = [ + ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM, + ItemNames.PROGRESSIVE_ORBITAL_COMMAND, + ItemNames.MARINE_PROGRESSIVE_STIMPACK, + ItemNames.VULTURE_PROGRESSIVE_REPLENISHABLE_MAGAZINE, + ItemNames.DIAMONDBACK_PROGRESSIVE_TRI_LITHIUM_POWER_CELL, + ItemNames.WRAITH_PROGRESSIVE_TOMAHAWK_POWER_CELLS, + ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS, + ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS, + ItemNames.BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX, + ItemNames.THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL, + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, +] +item_name_groups[ItemGroupNames.TERRAN_PROGRESSIVE_UPGRADES] = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type in (Items.TerranItemType.Progressive, Items.TerranItemType.Progressive_2) +] +item_name_groups[ItemGroupNames.WOL_ITEMS] = vanilla_wol_items = ( + wol_units + + wol_buildings + + wol_mercs + + wol_upgrades +) + +# Zerg +item_name_groups[ItemGroupNames.ZERG_BUILDINGS] = zerg_buildings = [ItemNames.SPINE_CRAWLER, ItemNames.SPORE_CRAWLER] +item_name_groups[ItemGroupNames.ZERG_UNITS] = zerg_units = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type in (Items.ZergItemType.Unit, Items.ZergItemType.Mercenary, Items.ZergItemType.Morph) + and item_name not in zerg_buildings +] +item_name_groups[ItemGroupNames.HOTS_UNITS] = hots_units = [ + ItemNames.ZERGLING, ItemNames.SWARM_QUEEN, ItemNames.ROACH, ItemNames.HYDRALISK, + ItemNames.ABERRATION, ItemNames.SWARM_HOST, ItemNames.MUTALISK, + ItemNames.INFESTOR, ItemNames.ULTRALISK, + ItemNames.ZERGLING_BANELING_ASPECT, + ItemNames.HYDRALISK_LURKER_ASPECT, + ItemNames.HYDRALISK_IMPALER_ASPECT, + ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, + ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, +] +item_name_groups[ItemGroupNames.HOTS_MORPHS] = hots_morphs = [ + ItemNames.ZERGLING_BANELING_ASPECT, + ItemNames.HYDRALISK_IMPALER_ASPECT, + ItemNames.HYDRALISK_LURKER_ASPECT, + ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, + ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, +] +item_name_groups[ItemGroupNames.ZERG_MORPHS] = zerg_morphs = [ + item_name for item_name, item_data in Items.item_table.items() if item_data.type == Items.ZergItemType.Morph +] +item_name_groups[ItemGroupNames.ZERG_MERCS] = zerg_mercs = [ + item_name for item_name, item_data in Items.item_table.items() if item_data.type == Items.ZergItemType.Mercenary +] +item_name_groups[ItemGroupNames.KERRIGAN_ABILITIES] = kerrigan_abilities = [ + item_name for item_name, item_data in Items.item_table.items() if item_data.type == Items.ZergItemType.Ability +] +item_name_groups[ItemGroupNames.KERRIGAN_PASSIVES] = kerrigan_passives = [ + ItemNames.KERRIGAN_HEROIC_FORTITUDE, ItemNames.KERRIGAN_CHAIN_REACTION, + ItemNames.KERRIGAN_INFEST_BROODLINGS, ItemNames.KERRIGAN_FURY, ItemNames.KERRIGAN_ABILITY_EFFICIENCY, +] + +# Zerg Upgrades +item_name_groups[ItemGroupNames.HOTS_STRAINS] = hots_strains = [ + item_name for item_name, item_data in Items.item_table.items() if item_data.type == Items.ZergItemType.Strain +] +item_name_groups[ItemGroupNames.HOTS_MUTATIONS] = hots_mutations = [ + ItemNames.ZERGLING_HARDENED_CARAPACE, ItemNames.ZERGLING_ADRENAL_OVERLOAD, ItemNames.ZERGLING_METABOLIC_BOOST, + ItemNames.BANELING_CORROSIVE_ACID, ItemNames.BANELING_RUPTURE, ItemNames.BANELING_REGENERATIVE_ACID, + ItemNames.ROACH_HYDRIODIC_BILE, ItemNames.ROACH_ADAPTIVE_PLATING, ItemNames.ROACH_TUNNELING_CLAWS, + ItemNames.HYDRALISK_FRENZY, ItemNames.HYDRALISK_ANCILLARY_CARAPACE, ItemNames.HYDRALISK_GROOVED_SPINES, + ItemNames.SWARM_HOST_BURROW, ItemNames.SWARM_HOST_RAPID_INCUBATION, ItemNames.SWARM_HOST_PRESSURIZED_GLANDS, + ItemNames.MUTALISK_VICIOUS_GLAIVE, ItemNames.MUTALISK_RAPID_REGENERATION, ItemNames.MUTALISK_SUNDERING_GLAIVE, + ItemNames.ULTRALISK_BURROW_CHARGE, ItemNames.ULTRALISK_TISSUE_ASSIMILATION, ItemNames.ULTRALISK_MONARCH_BLADES, +] +item_name_groups[ItemGroupNames.HOTS_GLOBAL_UPGRADES] = hots_global_upgrades = [ + ItemNames.KERRIGAN_ZERGLING_RECONSTITUTION, + ItemNames.KERRIGAN_IMPROVED_OVERLORDS, + ItemNames.KERRIGAN_AUTOMATED_EXTRACTORS, + ItemNames.KERRIGAN_TWIN_DRONES, + ItemNames.KERRIGAN_MALIGNANT_CREEP, + ItemNames.KERRIGAN_VESPENE_EFFICIENCY, +] +item_name_groups[ItemGroupNames.HOTS_ITEMS] = vanilla_hots_items = ( + hots_units + + zerg_buildings + + kerrigan_abilities + + hots_mutations + + hots_strains + + hots_global_upgrades +) + # Protoss +item_name_groups[ItemGroupNames.PROTOSS_UNITS] = protoss_units = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type in (Items.ProtossItemType.Unit, Items.ProtossItemType.Unit_2) +] +item_name_groups[ItemGroupNames.LOTV_UNITS] = lotv_units = [ + ItemNames.ZEALOT, ItemNames.CENTURION, ItemNames.SENTINEL, + ItemNames.STALKER, ItemNames.DRAGOON, ItemNames.ADEPT, + ItemNames.SENTRY, ItemNames.HAVOC, ItemNames.ENERGIZER, + ItemNames.HIGH_TEMPLAR, ItemNames.DARK_ARCHON, ItemNames.ASCENDANT, + ItemNames.DARK_TEMPLAR, ItemNames.AVENGER, ItemNames.BLOOD_HUNTER, + ItemNames.IMMORTAL, ItemNames.ANNIHILATOR, ItemNames.VANGUARD, + ItemNames.COLOSSUS, ItemNames.WRATHWALKER, ItemNames.REAVER, + ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, + ItemNames.VOID_RAY, ItemNames.DESTROYER, ItemNames.ARBITER, + ItemNames.CARRIER, ItemNames.TEMPEST, ItemNames.MOTHERSHIP, +] +item_name_groups[ItemGroupNames.PROPHECY_UNITS] = prophecy_units = [ + ItemNames.ZEALOT, ItemNames.STALKER, ItemNames.HIGH_TEMPLAR, ItemNames.DARK_TEMPLAR, + ItemNames.OBSERVER, ItemNames.IMMORTAL, ItemNames.COLOSSUS, + ItemNames.PHOENIX, ItemNames.VOID_RAY, ItemNames.CARRIER, +] +item_name_groups[ItemGroupNames.PROPHECY_BUILDINGS] = prophecy_buildings = [ + ItemNames.PHOTON_CANNON, +] item_name_groups[ItemGroupNames.GATEWAY_UNITS] = gateway_units = [ ItemNames.ZEALOT, ItemNames.CENTURION, ItemNames.SENTINEL, ItemNames.SUPPLICANT, ItemNames.STALKER, ItemNames.INSTIGATOR, ItemNames.SLAYER, @@ -160,28 +373,69 @@ class ItemGroupNames: item_name for item_name, item_data in Items.item_table.items() if item_data.type == Items.ProtossItemType.Building ] -item_name_groups[ItemGroupNames.AIUR] = [ +item_name_groups[ItemGroupNames.AIUR_UNITS] = [ ItemNames.ZEALOT, ItemNames.DRAGOON, ItemNames.SENTRY, ItemNames.AVENGER, ItemNames.HIGH_TEMPLAR, ItemNames.IMMORTAL, ItemNames.REAVER, ItemNames.PHOENIX, ItemNames.SCOUT, ItemNames.ARBITER, ItemNames.CARRIER, ] -item_name_groups[ItemGroupNames.NERAZIM] = [ +item_name_groups[ItemGroupNames.NERAZIM_UNITS] = [ ItemNames.CENTURION, ItemNames.STALKER, ItemNames.DARK_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.DARK_ARCHON, ItemNames.ANNIHILATOR, ItemNames.CORSAIR, ItemNames.ORACLE, ItemNames.VOID_RAY, ] -item_name_groups[ItemGroupNames.TAL_DARIM] = [ +item_name_groups[ItemGroupNames.TAL_DARIM_UNITS] = [ ItemNames.SUPPLICANT, ItemNames.SLAYER, ItemNames.HAVOC, ItemNames.BLOOD_HUNTER, ItemNames.ASCENDANT, ItemNames.VANGUARD, ItemNames.WRATHWALKER, ItemNames.DESTROYER, ItemNames.MOTHERSHIP, - ItemNames.WARP_PRISM_PHASE_BLASTER, ] -item_name_groups[ItemGroupNames.PURIFIER] = [ +item_name_groups[ItemGroupNames.PURIFIER_UNITS] = [ ItemNames.SENTINEL, ItemNames.ADEPT, ItemNames.INSTIGATOR, ItemNames.ENERGIZER, ItemNames.COLOSSUS, ItemNames.DISRUPTOR, ItemNames.MIRAGE, ItemNames.TEMPEST, ] +item_name_groups[ItemGroupNames.SOA_ITEMS] = soa_items = [ + *[item_name for item_name, item_data in Items.item_table.items() if item_data.type == Items.ProtossItemType.Spear_Of_Adun], + ItemNames.SOA_PROGRESSIVE_PROXY_PYLON, +] +lotv_soa_items = [item_name for item_name in soa_items if item_name != ItemNames.SOA_PYLON_OVERCHARGE] +item_name_groups[ItemGroupNames.PROTOSS_GLOBAL_UPGRADES] = [ + item_name for item_name, item_data in Items.item_table.items() if item_data.type == Items.ProtossItemType.Solarite_Core +] +item_name_groups[ItemGroupNames.LOTV_GLOBAL_UPGRADES] = lotv_global_upgrades = [ + ItemNames.NEXUS_OVERCHARGE, + ItemNames.ORBITAL_ASSIMILATORS, + ItemNames.WARP_HARMONIZATION, + ItemNames.MATRIX_OVERLOAD, + ItemNames.GUARDIAN_SHELL, + ItemNames.RECONSTRUCTION_BEAM, +] +item_name_groups[ItemGroupNames.LOTV_ITEMS] = vanilla_lotv_items = ( + lotv_units + + protoss_buildings + + lotv_soa_items + + lotv_global_upgrades +) + +item_name_groups[ItemGroupNames.VANILLA_ITEMS] = vanilla_items = ( + vanilla_wol_items + vanilla_hots_items + vanilla_lotv_items +) + # Sanity checks -assert len(terran_units) == len(barracks_units) + len(factory_units) + len(starport_units) + len(terran_buildings) + len(terran_mercenaries) -assert len(protoss_units) == len(gateway_units) + len(robo_units) + len(stargate_units) + len(protoss_buildings) +def _sanity_check() -> None: + all_progressive_upgrades = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type.display_name == Items.TerranItemType.Progressive.display_name + ] + assert len(terran_units) == len(barracks_units) + len(factory_units) + len(starport_units) + len(terran_mercenaries) + assert len(protoss_units) == len(gateway_units) + len(robo_units) + len(stargate_units) + for item_name in terran_original_progressive_upgrades: + assert item_name in all_progressive_upgrades + assert item_name in wol_upgrades + for item_name in terran_stimpacks: + assert "Stimpack" in item_name + for var_name, display_name in ItemGroupNames.__dict__.items(): + if var_name.startswith("_"): + continue + assert display_name in item_name_groups +_sanity_check() diff --git a/worlds/sc2/Items.py b/worlds/sc2/Items.py index 35bd358ac189..f5a4976c2063 100644 --- a/worlds/sc2/Items.py +++ b/worlds/sc2/Items.py @@ -1819,23 +1819,6 @@ def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: ItemNames.RAVEN_SPIDER_MINES, } -progressive_if_nco = { - ItemNames.MARINE_PROGRESSIVE_STIMPACK, - ItemNames.FIREBAT_PROGRESSIVE_STIMPACK, - ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS, - ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, -} - -progressive_if_ext = { - ItemNames.VULTURE_PROGRESSIVE_REPLENISHABLE_MAGAZINE, - ItemNames.WRAITH_PROGRESSIVE_TOMAHAWK_POWER_CELLS, - ItemNames.BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX, - ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS, - ItemNames.THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL, - ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM, - ItemNames.DIAMONDBACK_PROGRESSIVE_TRI_LITHIUM_POWER_CELL -} - kerrigan_actives: typing.List[typing.Set[str]] = [ {ItemNames.KERRIGAN_KINETIC_BLAST, ItemNames.KERRIGAN_LEAPING_STRIKE}, {ItemNames.KERRIGAN_CRUSHING_GRIP, ItemNames.KERRIGAN_PSIONIC_SHIFT}, diff --git a/worlds/sc2/Options.py b/worlds/sc2/Options.py index 2ca2af00288d..b0cfb6bc729d 100644 --- a/worlds/sc2/Options.py +++ b/worlds/sc2/Options.py @@ -303,26 +303,10 @@ class GenericUpgradeItems(Choice): option_bundle_all = 3 -class NovaCovertOpsItems(Toggle): - """ - If turned on, the equipment upgrades from Nova Covert Ops may be present in the world. - - If Nova Covert Ops campaign is enabled, this option is locked to be turned on. - """ - display_name = "Nova Covert Ops Items" - default = Toggle.option_true - - -class BroodWarItems(Toggle): - """If turned on, returning items from StarCraft: Brood War may appear in the world.""" - display_name = "Brood War Items" - default = Toggle.option_true - - -class ExtendedItems(Toggle): - """If turned on, original items that did not appear in Campaign mode may appear in the world.""" - display_name = "Extended Items" - default = Toggle.option_true +class VanillaItemsOnly(Toggle): + """If turned on, the item pool is limited only to items that appear in the main 3 vanilla campaigns. + locked_items may override these exclusions.""" + display_name = "Vanilla Items Only" # Current maximum number of upgrades for a unit @@ -877,9 +861,7 @@ class Starcraft2Options(PerGameCommonOptions): excluded_items: ExcludedItems excluded_missions: ExcludedMissions exclude_very_hard_missions: ExcludeVeryHardMissions - nco_items: NovaCovertOpsItems - bw_items: BroodWarItems - ext_items: ExtendedItems + vanilla_items_only: VanillaItemsOnly vanilla_locations: VanillaLocations extra_locations: ExtraLocations challenge_locations: ChallengeLocations diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py index 44f5fd42aad4..4dda13916536 100644 --- a/worlds/sc2/PoolFilter.py +++ b/worlds/sc2/PoolFilter.py @@ -1,7 +1,7 @@ from typing import Callable, Dict, List, Set, Union, Tuple, Optional, TYPE_CHECKING from BaseClasses import Item, Location -from .Items import (get_full_item_list, spider_mine_sources, second_pass_placeable_items, progressive_if_nco, - progressive_if_ext, spear_of_adun_calldowns, spear_of_adun_castable_passives, nova_equipment, +from .Items import (get_full_item_list, spider_mine_sources, second_pass_placeable_items, + spear_of_adun_calldowns, spear_of_adun_castable_passives, upgrade_item_types, ) from .MissionTables import (mission_orders, MissionInfo, MissionPools, MissionFlag, @@ -203,17 +203,6 @@ def get_item_upgrades(inventory: List[Item], parent_item: Union[Item, str]) -> L ] -def get_item_quantity(item: Item, world: 'SC2World'): - if (not get_option_value(world, "nco_items")) \ - and SC2Campaign.NCO in get_disabled_campaigns(world) \ - and item.name in progressive_if_nco: - return 1 - if (not get_option_value(world, "ext_items")) \ - and item.name in progressive_if_ext: - return 1 - return get_full_item_list()[item.name].quantity - - def copy_item(item: Item): return Item(item.name, item.classification, item.code, item.player) @@ -265,8 +254,7 @@ def attempt_removal(item: Item) -> bool: if not all(requirement(self) for (_, requirement) in mission_requirements): # If item cannot be removed, lock or revert self.logical_inventory.append(item.name) - for _ in range(get_item_quantity(item, self.world)): - locked_items.append(copy_item(item)) + locked_items.append(item) return False return True diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 06d5ba4c5610..e6bee65382f5 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -9,8 +9,8 @@ from worlds.AutoWorld import WebWorld, World from . import ItemNames from .Items import (StarcraftItem, filler_items, get_full_item_list, - get_basic_units, ItemData, upgrade_included_names, progressive_if_nco, kerrigan_actives, kerrigan_passives, - progressive_if_ext, not_balanced_starting_units, + get_basic_units, ItemData, upgrade_included_names, kerrigan_actives, kerrigan_passives, + not_balanced_starting_units, ) from . import Items from .ItemGroups import item_name_groups @@ -521,36 +521,23 @@ def flag_unused_upgrade_types(world: SC2World, item_list: List[FilterItem]) -> N def flag_user_excluded_item_sets(world: SC2World, item_list: List[FilterItem]) -> None: - """Excludes items based on item set options (`nco_items`, `bw_items`, `ext_items`)""" - # Note(mm): These options should just be removed in favour of better item sets and a single "vanilla only" switch - item_sets = {'wol', 'hots', 'lotv'} - if get_option_value(world, 'nco_items') or SC2Campaign.NCO in get_enabled_campaigns(world): - item_sets.add('nco') - if get_option_value(world, 'bw_items'): - item_sets.add('bw') - if get_option_value(world, 'ext_items'): - item_sets.add('ext') - - def allowed_quantity(name: str, data: ItemData) -> int: - if not data.origin.intersection(item_sets): - return 0 - elif name in progressive_if_nco and 'nco' not in item_sets: - return 1 - elif name in progressive_if_ext and 'ext' not in item_sets: - return 1 - else: - return data.quantity + """Excludes items based on item set options (`only_vanilla_items`)""" + if not world.options.vanilla_items_only.value: + return + + vanilla_nonprogressive_count = { + item_name: 0 for item_name in ItemGroups.terran_original_progressive_upgrades + } - amounts: Dict[str, int] = {} for item in item_list: if ItemFilterFlags.Excluded in item.flags: continue - max_quantity = allowed_quantity(item.name, item.data) - amount_in_pool = amounts.get(item.name, 0) - if max_quantity > amount_in_pool: - amounts[item.name] = amount_in_pool + 1 - else: + if item.name not in ItemGroups.vanilla_items: item.flags |= ItemFilterFlags.Excluded + if item.name in ItemGroups.terran_original_progressive_upgrades: + if vanilla_nonprogressive_count[item.name]: + item.flags |= ItemFilterFlags.Excluded + vanilla_nonprogressive_count[item.name] += 1 def flag_and_add_resource_locations(world: SC2World, item_list: List[FilterItem]) -> None: From d0642b526dd6cef6f88d6fefec1c436765fc9645 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 29 Apr 2024 20:51:04 -0700 Subject: [PATCH 17/48] sc2: Made vanilla_items_only ignore Nova equipment; added unit tests --- worlds/sc2/ItemGroups.py | 2 +- worlds/sc2/__init__.py | 4 +-- worlds/sc2/test/test_generation.py | 45 ++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index 60078781b2e5..18af5f71555d 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -243,7 +243,7 @@ class ItemGroupNames: ItemNames.THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL, ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, ] -item_name_groups[ItemGroupNames.TERRAN_PROGRESSIVE_UPGRADES] = [ +item_name_groups[ItemGroupNames.TERRAN_PROGRESSIVE_UPGRADES] = terran_progressive_items = [ item_name for item_name, item_data in Items.item_table.items() if item_data.type in (Items.TerranItemType.Progressive, Items.TerranItemType.Progressive_2) ] diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index e6bee65382f5..b583bfa1a918 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -528,11 +528,11 @@ def flag_user_excluded_item_sets(world: SC2World, item_list: List[FilterItem]) - vanilla_nonprogressive_count = { item_name: 0 for item_name in ItemGroups.terran_original_progressive_upgrades } - + vanilla_items = ItemGroups.vanilla_items + ItemGroups.nova_equipment for item in item_list: if ItemFilterFlags.Excluded in item.flags: continue - if item.name not in ItemGroups.vanilla_items: + if item.name not in vanilla_items: item.flags |= ItemFilterFlags.Excluded if item.name in ItemGroups.terran_original_progressive_upgrades: if vanilla_nonprogressive_count[item.name]: diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index e9016af09b3f..f4674d4d1e95 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -208,4 +208,49 @@ def test_excluding_all_protoss_build_missions_excludes_protoss_units(self) -> No self.assertNotEqual(item_data.type, Items.ProtossItemType.Unit_2, f"Item '{item_name}' included when all protoss build missions are excluded") self.assertNotEqual(item_data.type, Items.ProtossItemType.Building, f"Item '{item_name}' included when all protoss build missions are excluded") + def test_vanilla_items_only_excludes_terran_progressives(self) -> None: + options = { + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'accessibility': 'locations', + 'vanilla_items_only': True, + } + self.generate_world(options) + items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] + self.assertTrue(items) + occurrences: Dict[str, int] = {} + for item_name, _ in items: + if item_name in ItemGroups.terran_progressive_items: + if item_name in ItemGroups.nova_equipment: + # The option imposes no contraint on Nova equipment + continue + occurrences.setdefault(item_name, 0) + occurrences[item_name] += 1 + self.assertLessEqual(occurrences[item_name], 1, f"'{item_name}' unexpectedly appeared multiple times in the pool") + + def test_nco_and_2_wol_missions_only_can_generate_with_vanilla_items_only(self) -> None: + options = { + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'excluded_missions': [ + mission.mission_name for mission in MissionTables.SC2Mission + if mission.campaign == MissionTables.SC2Campaign.WOL + and mission.mission_name not in (MissionTables.SC2Mission.LIBERATION_DAY.mission_name, MissionTables.SC2Mission.THE_OUTLAWS.mission_name) + ], + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'accessibility': 'locations', + 'vanilla_items_only': True, + } + self.generate_world(options) + items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] + self.assertTrue(items) From a0c64400461db9c0c41a7f5b7b5b0beafc27e78f Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 29 Apr 2024 20:53:01 -0700 Subject: [PATCH 18/48] sc2: Added more asserts to unit tests to verify option is doing what it says it does --- worlds/sc2/test/test_generation.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index f4674d4d1e95..c08da29c60fa 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -251,6 +251,10 @@ def test_nco_and_2_wol_missions_only_can_generate_with_vanilla_items_only(self) 'vanilla_items_only': True, } self.generate_world(options) - items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] - self.assertTrue(items) + item_names = [item.name for item in self.multiworld.itempool] + self.assertTrue(item_names) + self.assertNotIn(ItemNames.LIBERATOR, item_names) + self.assertNotIn(ItemNames.MARAUDER_PROGRESSIVE_STIMPACK, item_names) + self.assertNotIn(ItemNames.HELLION_HELLBAT_ASPECT, item_names) + self.assertNotIn(ItemNames.BATTLECRUISER_CLOAK, item_names) From b6fd3a45231acf00979cb9a4556dffae0a97eb2b Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 29 Apr 2024 21:23:20 -0700 Subject: [PATCH 19/48] sc2: added unexcluded_items option --- worlds/sc2/Options.py | 6 +++++ worlds/sc2/__init__.py | 26 ++++++++++++-------- worlds/sc2/test/test_generation.py | 38 ++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/worlds/sc2/Options.py b/worlds/sc2/Options.py index b0cfb6bc729d..a50da662597a 100644 --- a/worlds/sc2/Options.py +++ b/worlds/sc2/Options.py @@ -677,6 +677,11 @@ class ExcludedItems(Sc2ItemDict): display_name = "Excluded Items" +class UnexcludedItems(Sc2ItemDict): + """Undoes an item exclusion; useful for whitelisting or fine-tuning a category.""" + display_name = "Unexcluded Items" + + class ExcludedMissions(OptionSet): """Guarantees that these missions will not appear in the campaign Doesn't apply to vanilla mission order. @@ -859,6 +864,7 @@ class Starcraft2Options(PerGameCommonOptions): take_over_ai_allies: TakeOverAIAllies locked_items: LockedItems excluded_items: ExcludedItems + unexcluded_items: UnexcludedItems excluded_missions: ExcludedMissions exclude_very_hard_missions: ExcludeVeryHardMissions vanilla_items_only: VanillaItemsOnly diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index b583bfa1a918..b874bc07a403 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -179,29 +179,35 @@ def create_and_flag_explicit_item_locks_and_excludes(world: SC2World) -> List[Fi Returns a list of all possible non-filler items that can be added, with an accompanying flags bitfield. """ excluded_items = world.options.excluded_items + unexcluded_items = world.options.unexcluded_items locked_items = world.options.locked_items start_inventory = world.options.start_inventory + + def resolve_count(count: Optional[int], max_count: int) -> int: + if count == 0: + return max_count + if count is None: + return 0 + return min(count, max_count) + result: List[FilterItem] = [] for item_name, item_data in Items.item_table.items(): if not item_data.quantity: continue max_count = item_data.quantity excluded_count = excluded_items.get(item_name) + unexcluded_count = unexcluded_items.get(item_name) locked_count = locked_items.get(item_name) start_count: Optional[int] = start_inventory.get(item_name) # specifying 0 in the yaml means exclude / lock all # start_inventory doesn't allow specifying 0 # not specifying means don't exclude/lock/start - if excluded_count == 0: - excluded_count = max_count - elif excluded_count is None: - excluded_count = 0 - if locked_count == 0: - locked_count = max_count - elif locked_count is None: - locked_count = 0 - if start_count is None: - start_count = 0 + excluded_count = resolve_count(excluded_count, max_count) + unexcluded_count = resolve_count(unexcluded_count, max_count) + locked_count = resolve_count(locked_count, max_count) + start_count = resolve_count(start_count, max_count) + + excluded_count = max(0, excluded_count - unexcluded_count) # Priority: start_inventory >> locked_items >> excluded_items >> unspecified if start_count > max_count: diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index c08da29c60fa..9253ca07efac 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -72,6 +72,44 @@ def test_explicit_locks_excludes_interact_and_set_flags(self): regen_biosteel_items = [x for x in item_names if x == ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL] self.assertEqual(len(regen_biosteel_items), 2) + def test_unexcludes_cancel_out_excludes(self): + options = { + 'grant_story_tech': True, + 'excluded_items': { + ItemGroups.ItemGroupNames.NOVA_EQUIPMENT: 15, + ItemNames.MARINE_PROGRESSIVE_STIMPACK: 1, + ItemNames.MARAUDER_PROGRESSIVE_STIMPACK: 2, + ItemNames.MARINE: 0, + ItemNames.MARAUDER: 0, + ItemNames.REAPER: 1, + ItemNames.DIAMONDBACK: 0, + ItemNames.HELLION: 1, + }, + 'unexcluded_items': { + ItemNames.NOVA_PLASMA_RIFLE: 1, # Necessary to pass logic + ItemNames.NOVA_PULSE_GRENADES: 0, # Necessary to pass logic + ItemNames.NOVA_JUMP_SUIT_MODULE: 0, # Necessary to pass logic + ItemGroups.ItemGroupNames.BARRACKS_UNITS: 0, + ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE: 1, + ItemNames.HELLION: 1, + ItemNames.MARINE_PROGRESSIVE_STIMPACK: 1, + ItemNames.MARAUDER_PROGRESSIVE_STIMPACK: 0, + }, + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + item_names = [item.name for item in self.multiworld.itempool] + self.assertIn(ItemNames.MARINE, item_names) + self.assertIn(ItemNames.MARAUDER, item_names) + self.assertIn(ItemNames.REAPER, item_names) + self.assertEqual(item_names.count(ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE), 1, f"Stealth suit occurred the wrong number of times") + self.assertIn(ItemNames.HELLION, item_names) + self.assertEqual(item_names.count(ItemNames.MARINE_PROGRESSIVE_STIMPACK), 2, "Marine stimpacks weren't unexcluded") + self.assertEqual(item_names.count(ItemNames.MARAUDER_PROGRESSIVE_STIMPACK), 2, "Marauder stimpacks weren't unexcluded") + self.assertNotIn(ItemNames.DIAMONDBACK, item_names) + self.assertNotIn(ItemNames.NOVA_BLAZEFIRE_GUNBLADE, item_names) + self.assertNotIn(ItemNames.NOVA_ENERGY_SUIT_MODULE, item_names) + def test_excluding_groups_excludes_all_items_in_group(self): options = { 'excluded_items': [ From f8b260feaeb2ac1dcfa47a65b6a1cc40ff7379bf Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 29 Apr 2024 21:26:35 -0700 Subject: [PATCH 20/48] sc2: Removed ItemOrigin enum --- worlds/sc2/Items.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/worlds/sc2/Items.py b/worlds/sc2/Items.py index f5a4976c2063..ea294220057e 100644 --- a/worlds/sc2/Items.py +++ b/worlds/sc2/Items.py @@ -92,17 +92,6 @@ class FactionlessItemType(ItemTypeEnum): } -class ItemOrigin(enum.IntFlag): - WoL = enum.auto() - HotS = enum.auto() - LotV = enum.auto() - NCO = enum.auto() - Melee = enum.auto() - Coop = enum.auto() - BW = enum.auto() - AP = enum.auto() - - class ItemData(typing.NamedTuple): code: int type: ItemType From 9eec4f0c5bc1468b779f28cccfefc4379c0d172d Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 29 Apr 2024 21:42:46 -0700 Subject: [PATCH 21/48] sc2: Fixed mypy issues in init.py --- worlds/sc2/__init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index b874bc07a403..7e41e4d7c02d 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -13,7 +13,7 @@ not_balanced_starting_units, ) from . import Items -from .ItemGroups import item_name_groups +from . import ItemGroups from .Locations import get_locations, get_location_types, get_plando_locations from .Regions import create_regions from .Options import (get_option_value, LocationInclusion, KerriganLevelItemDistribution, @@ -77,7 +77,7 @@ class SC2World(World): options_dataclass = Starcraft2Options options: Starcraft2Options - item_name_groups = item_name_groups + item_name_groups = ItemGroups.item_name_groups locked_locations: List[str] """Locations locked to contain specific items, such as victory events or forced resources""" location_cache: List[Location] @@ -638,11 +638,13 @@ def create_item_with_correct_settings(player: int, name: str) -> Item: return item -def fill_pool_with_kerrigan_levels(world: World, item_pool: List[Item]): - total_levels = get_option_value(world, "kerrigan_level_item_sum") - if get_option_value(world, "kerrigan_presence") not in kerrigan_unit_available \ - or total_levels == 0 \ - or SC2Campaign.HOTS not in get_enabled_campaigns(world): +def fill_pool_with_kerrigan_levels(world: SC2World, item_pool: List[Item]): + total_levels = world.options.kerrigan_level_item_sum.value + missions = get_all_missions(world.mission_req_table) + if (world.options.kerrigan_presence.value not in kerrigan_unit_available + or total_levels == 0 + or not [mission for mission in missions if MissionFlag.Kerrigan in mission.flags] + ): return def add_kerrigan_level_items(level_amount: int, item_amount: int): @@ -653,7 +655,7 @@ def add_kerrigan_level_items(level_amount: int, item_amount: int): item_pool.append(create_item_with_correct_settings(world.player, name)) sizes = [70, 35, 14, 10, 7, 5, 2, 1] - option = get_option_value(world, "kerrigan_level_item_distribution") + option = world.options.kerrigan_level_item_distribution.value assert isinstance(option, int) assert isinstance(total_levels, int) From eba422182ef586a36fb25ae8feb37da9d2633a47 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 29 Apr 2024 21:51:04 -0700 Subject: [PATCH 22/48] sc2: Fixed more mypy issues in regions.py and items.py --- worlds/sc2/Items.py | 6 ++++-- worlds/sc2/Regions.py | 18 +++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/worlds/sc2/Items.py b/worlds/sc2/Items.py index ea294220057e..a3351477d217 100644 --- a/worlds/sc2/Items.py +++ b/worlds/sc2/Items.py @@ -1,5 +1,4 @@ from typing import * -from pydoc import describe from BaseClasses import Item, ItemClassification, MultiWorld import typing @@ -10,6 +9,9 @@ from . import ItemNames from worlds.AutoWorld import World +if TYPE_CHECKING: + from . import SC2World + class ItemTypeEnum(enum.Enum): def __new__(cls, *args, **kwargs): @@ -1669,7 +1671,7 @@ def get_item_table(): } -def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: +def get_basic_units(world: 'SC2World', race: SC2Race) -> typing.Set[str]: logic_level = get_option_value(world, 'required_tactics') if logic_level == RequiredTactics.option_no_logic: return no_logic_starting_units[race] diff --git a/worlds/sc2/Regions.py b/worlds/sc2/Regions.py index 8045e819876c..c0cdd4ef6632 100644 --- a/worlds/sc2/Regions.py +++ b/worlds/sc2/Regions.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Tuple, Optional, Callable, NamedTuple, Union +from typing import List, Dict, Tuple, Optional, Callable, NamedTuple, Union, TYPE_CHECKING import math from BaseClasses import MultiWorld, Region, Entrance, Location, CollectionState @@ -11,13 +11,17 @@ from worlds.AutoWorld import World +if TYPE_CHECKING: + from . import SC2World + + class SC2MissionSlot(NamedTuple): campaign: SC2Campaign slot: Union[MissionPools, SC2Mission, None] def create_regions( - world: World, locations: Tuple[LocationData, ...], location_cache: List[Location] + world: 'SC2World', locations: Tuple[LocationData, ...], location_cache: List[Location] ) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: """ Creates region connections by calling the multiworld's `connect()` methods @@ -36,7 +40,7 @@ def create_regions( return create_structured_regions(world, locations, location_cache, mission_order_type) def create_vanilla_regions( - world: World, + world: 'SC2World', locations: Tuple[LocationData, ...], location_cache: List[Location], ) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: @@ -268,7 +272,7 @@ def wol_cleared_missions(state: CollectionState, mission_count: int) -> bool: def create_grid_regions( - world: World, + world: 'SC2World', locations: Tuple[LocationData, ...], location_cache: List[Location], ) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: @@ -377,7 +381,7 @@ def make_grid_connect_rule( def create_structured_regions( - world: World, + world: 'SC2World', locations: Tuple[LocationData, ...], location_cache: List[Location], mission_order_type: int, @@ -622,7 +626,7 @@ def create_location(player: int, location_data: LocationData, region: Region, return location -def create_region(world: World, locations_per_region: Dict[str, List[LocationData]], +def create_region(world: 'SC2World', locations_per_region: Dict[str, List[LocationData]], location_cache: List[Location], name: str) -> Region: region = Region(name, world.player, world.multiworld) @@ -634,7 +638,7 @@ def create_region(world: World, locations_per_region: Dict[str, List[LocationDat return region -def connect(world: World, used_names: Dict[str, int], source: str, target: str, +def connect(world: 'SC2World', used_names: Dict[str, int], source: str, target: str, rule: Optional[Callable] = None): source_region = world.get_region(source) target_region = world.get_region(target) From dd4a7c57b0fb3ccdafc7fec8a00ef98768cc62ff Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 29 Apr 2024 21:58:53 -0700 Subject: [PATCH 23/48] sc2: Added a usecase test for playing wol with NCO units only --- worlds/sc2/test/test_generation.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index 9253ca07efac..cc5dbfe27ef0 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -110,6 +110,31 @@ def test_unexcludes_cancel_out_excludes(self): self.assertNotIn(ItemNames.NOVA_BLAZEFIRE_GUNBLADE, item_names) self.assertNotIn(ItemNames.NOVA_ENERGY_SUIT_MODULE, item_names) + def test_usecase_terran_with_nco_units_only(self): + options = { + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'excluded_items': { + ItemGroups.ItemGroupNames.TERRAN_UNITS: 0, + }, + 'unexcluded_items': { + ItemGroups.ItemGroupNames.NCO_UNITS: 0, + }, + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + item_names = [item.name for item in self.multiworld.itempool] + self.assertIn(ItemNames.MARINE, item_names) + self.assertIn(ItemNames.RAVEN, item_names) + self.assertIn(ItemNames.LIBERATOR, item_names) + self.assertIn(ItemNames.BATTLECRUISER, item_names) + self.assertNotIn(ItemNames.DIAMONDBACK, item_names) + self.assertNotIn(ItemNames.DIAMONDBACK_BURST_CAPACITORS, item_names) + self.assertNotIn(ItemNames.VIKING, item_names) + def test_excluding_groups_excludes_all_items_in_group(self): options = { 'excluded_items': [ From 4221b81cb420471a3e52f6bfda7468148fcdd6f2 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 29 Apr 2024 23:47:14 -0700 Subject: [PATCH 24/48] sc2: Fixed case-handling on /received --- worlds/sc2/Client.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/worlds/sc2/Client.py b/worlds/sc2/Client.py index 1bb7fb4f719c..846af6582924 100644 --- a/worlds/sc2/Client.py +++ b/worlds/sc2/Client.py @@ -215,15 +215,21 @@ def _cmd_received(self, filter_search: str = "") -> bool: # Groups must be matched case-sensitively, so we properly capitalize the search term # eg. "Spear of Adun" over "Spear Of Adun" or "spear of adun" # This fails a lot of item name matches, but those should be found by partial name match - formatted_filter_search = " ".join([(part.lower() if len(part) <= 3 else part.lower().capitalize()) for part in filter_search.split()]) + group_filter = '' + for group_name in item_name_groups: + if group_name in unlisted_item_name_groups: + continue + if filter_search.casefold() == group_name.casefold(): + group_filter = group_name + break def item_matches_filter(item_name: str) -> bool: # The filter can be an exact group name or a partial item name # Partial item name can be matched case-insensitively - if filter_search.lower() in item_name.lower(): + if filter_search.casefold() in item_name.casefold(): return True # The search term should already be formatted as a group name - if formatted_filter_search in item_name_groups and item_name in item_name_groups[formatted_filter_search]: + if group_filter and item_name in item_name_groups[group_filter]: return True return False From 1db913c8e67f1bc8bec3e29651c81916b6aab4d0 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 1 May 2024 23:58:25 -0700 Subject: [PATCH 25/48] sc2: Loosening logic on Evil Awoken when vanilla_items_only is true; adding unit tests --- worlds/sc2/Locations.py | 34 +++++++++++++--------- worlds/sc2/test/test_generation.py | 45 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/worlds/sc2/Locations.py b/worlds/sc2/Locations.py index e29ef1480aab..2f36446d66c3 100644 --- a/worlds/sc2/Locations.py +++ b/worlds/sc2/Locations.py @@ -1,8 +1,9 @@ from enum import IntEnum from typing import List, Tuple, Optional, Callable, NamedTuple, Set, Any, TYPE_CHECKING from . import ItemNames -from .Options import get_option_value, kerrigan_unit_available, RequiredTactics, GrantStoryTech, LocationInclusion, \ - EnableHotsMissions +from .Options import (VanillaItemsOnly, get_option_value, RequiredTactics, + LocationInclusion, KerriganPresence, +) from .Rules import SC2Logic from BaseClasses import Location @@ -38,7 +39,7 @@ class LocationData(NamedTuple): rule: Callable[['CollectionState'], bool] = Location.access_rule -def get_location_types(world: World, inclusion_type: int) -> Set[LocationType]: +def get_location_types(world: 'SC2World', inclusion_type: int) -> Set[LocationType]: """ :param multiworld: @@ -80,13 +81,20 @@ def get_plando_locations(world: World) -> List[str]: def get_locations(world: Optional['SC2World']) -> Tuple[LocationData, ...]: # Note: rules which are ended with or True are rules identified as needed later when restricted units is an option - logic_level = get_option_value(world, 'required_tactics') + if world is None: + logic_level = int(RequiredTactics.default) + vanilla_items_only = not not VanillaItemsOnly.default + kerriganless = False + else: + logic_level = world.options.required_tactics.value + vanilla_items_only = not not world.options.vanilla_items_only.value + kerriganless = ( + world.options.kerrigan_presence.value != KerriganPresence.option_vanilla + or not world.options.enable_hots_missions.value + ) adv_tactics = logic_level != RequiredTactics.option_standard - kerriganless = get_option_value(world, 'kerrigan_presence') not in kerrigan_unit_available \ - or get_option_value(world, "enable_hots_missions") == EnableHotsMissions.option_false - story_tech_granted = get_option_value(world, "grant_story_tech") == GrantStoryTech.option_true logic = SC2Logic(world) - player = None if world is None else world.player + player = 1 if world is None else world.player location_table: List[LocationData] = [ # WoL LocationData("Liberation Day", "Liberation Day: Victory", SC2WOL_LOC_ID_OFFSET + 100, LocationType.VICTORY), @@ -460,8 +468,8 @@ def get_locations(world: Optional['SC2World']) -> Tuple[LocationData, ...]: lambda state: logic.great_train_robbery_train_stopper(state) and logic.terran_basic_anti_air(state)), LocationData("Cutthroat", "Cutthroat: Victory", SC2WOL_LOC_ID_OFFSET + 1800, LocationType.VICTORY, - lambda state: logic.terran_common_unit(state) and - (adv_tactics or logic.terran_basic_anti_air)), + lambda state: logic.terran_common_unit(state) + and (adv_tactics or logic.terran_basic_anti_air(state))), LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801, LocationType.EXTRA, lambda state: logic.terran_common_unit(state)), LocationData("Cutthroat", "Cutthroat: North Relic", SC2WOL_LOC_ID_OFFSET + 1802, LocationType.VANILLA, @@ -1091,7 +1099,7 @@ def get_locations(world: Optional['SC2World']) -> Tuple[LocationData, ...]: and logic.protoss_anti_armor_anti_air(state) \ and logic.protoss_can_attack_behind_chasm(state)), LocationData("Evil Awoken", "Evil Awoken: Victory", SC2LOTV_LOC_ID_OFFSET + 300, LocationType.VICTORY, - lambda state: adv_tactics or logic.protoss_stalker_upgrade(state)), + lambda state: adv_tactics or vanilla_items_only or logic.protoss_stalker_upgrade(state)), LocationData("Evil Awoken", "Evil Awoken: Temple Investigated", SC2LOTV_LOC_ID_OFFSET + 301, LocationType.EXTRA), LocationData("Evil Awoken", "Evil Awoken: Void Catalyst", SC2LOTV_LOC_ID_OFFSET + 302, LocationType.EXTRA), LocationData("Evil Awoken", "Evil Awoken: First Particle Cannon", SC2LOTV_LOC_ID_OFFSET + 303, LocationType.VANILLA), @@ -1581,7 +1589,7 @@ def get_locations(world: Optional['SC2World']) -> Tuple[LocationData, ...]: lambda state: logic.enemy_shadow_door_controls(state)), LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Blazefire Gunblade", SC2NCO_LOC_ID_OFFSET + 706, LocationType.VANILLA, lambda state: logic.enemy_shadow_second_stage(state) - and (story_tech_granted + and (logic.story_tech_granted or state.has(ItemNames.NOVA_BLINK, player) or (adv_tactics and state.has_all({ItemNames.NOVA_DOMINATION, ItemNames.NOVA_HOLO_DECOY, ItemNames.NOVA_JUMP_SUIT_MODULE}, player)) ) @@ -1621,7 +1629,7 @@ def get_locations(world: Optional['SC2World']) -> Tuple[LocationData, ...]: if world is not None: excluded_location_types = get_location_types(world, LocationInclusion.option_disabled) plando_locations = get_plando_locations(world) - exclude_locations = get_option_value(world, "exclude_locations") + exclude_locations = world.options.exclude_locations.value location_table = [location for location in location_table if (location.type is LocationType.VICTORY or location.name not in exclude_locations) and location.type not in excluded_location_types diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index cc5dbfe27ef0..c66db258151d 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -321,3 +321,48 @@ def test_nco_and_2_wol_missions_only_can_generate_with_vanilla_items_only(self) self.assertNotIn(ItemNames.HELLION_HELLBAT_ASPECT, item_names) self.assertNotIn(ItemNames.BATTLECRUISER_CLOAK, item_names) + def test_evil_awoken_with_vanilla_items_only_generates(self) -> None: + options = { + 'enable_wol_missions': False, + 'enable_nco_missions': False, + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_epilogue_missions': False, + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'accessibility': 'locations', + 'vanilla_items_only': True, + } + self.generate_world(options) + item_names = [item.name for item in self.multiworld.itempool] + self.assertTrue(item_names) + self.assertTrue(self.world.get_region(MissionTables.SC2Mission.EVIL_AWOKEN.mission_name)) + + def test_enemy_within_and_no_zerg_build_missions_generates(self) -> None: + options = { + # including WoL to allow for valid goal missions + 'enable_nco_missions': False, + 'enable_prophecy_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'excluded_missions': [ + mission.mission_name for mission in MissionTables.SC2Mission + if MissionTables.MissionFlag.Zerg in mission.flags + and MissionTables.MissionFlag.NoBuild not in mission.flags + ], + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'accessibility': 'locations', + 'vanilla_items_only': True, + } + self.generate_world(options) + item_names = [item.name for item in self.multiworld.itempool] + self.assertTrue(item_names) + self.assertTrue(self.world.get_region(MissionTables.SC2Mission.ENEMY_WITHIN.mission_name)) + self.assertNotIn(ItemNames.ULTRALISK, item_names) + self.assertNotIn(ItemNames.SWARM_QUEEN, item_names) + self.assertNotIn(ItemNames.MUTALISK, item_names) + self.assertNotIn(ItemNames.CORRUPTOR, item_names) + self.assertNotIn(ItemNames.SCOURGE, item_names) + From 29adee5f28209a0ab581c464a4d148bcfbd1f269 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 00:22:18 -0700 Subject: [PATCH 26/48] sc2: Removed raid liberator start-inventory under esoteric conditions; added NCO-only usecase unit test --- worlds/sc2/Options.py | 2 +- worlds/sc2/__init__.py | 6 --- worlds/sc2/test/test_generation.py | 63 +++++++++++++++++++----------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/worlds/sc2/Options.py b/worlds/sc2/Options.py index a50da662597a..c973a578c84f 100644 --- a/worlds/sc2/Options.py +++ b/worlds/sc2/Options.py @@ -917,7 +917,7 @@ def get_disabled_campaigns(world: 'SC2World') -> Set[SC2Campaign]: def get_excluded_missions(world: 'SC2World') -> Set[SC2Mission]: mission_order_type = world.options.mission_order.value excluded_mission_names = world.options.excluded_missions.value - shuffle_no_build = world.options.shuffle_no_build + shuffle_no_build = world.options.shuffle_no_build.value disabled_campaigns = get_disabled_campaigns(world) excluded_missions: Set[SC2Mission] = set([lookup_name_to_mission[name] for name in excluded_mission_names]) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 7e41e4d7c02d..6863f6f2d992 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -476,12 +476,6 @@ def flag_start_unit(world: SC2World, item_list: List[FilterItem], starter_unit: starter_weapon_options = [item for item in possible_starter_items.values() if item.name in possible_starter_weapons] starter_weapon = world.random.choice(starter_weapon_options) starter_weapon.flags |= ItemFilterFlags.StartInventory - mission_count = sum(len(campaign) for campaign in world.mission_req_table.values()) - if mission_count <= 10 and world.final_mission_id == SC2Mission.END_GAME.id: - if ItemNames.LIBERATOR_RAID_ARTILLERY in possible_starter_items: - possible_starter_items[ItemNames.LIBERATOR_RAID_ARTILLERY].flags |= ItemFilterFlags.StartInventory - else: - logger.warning(f"Tried and failed to add {ItemNames.LIBERATOR_RAID_ARTILLERY} to the start inventory because it was excluded or plando'd") def flag_start_abilities(world: SC2World, item_list: List[FilterItem]) -> None: diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index c66db258151d..fbded63373fc 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -6,7 +6,7 @@ import random from .. import Options, MissionTables, ItemNames, Items, ItemGroups -from .. import FilterItem, ItemFilterFlags, create_and_flag_explicit_item_locks_and_excludes, SC2World +from .. import get_all_missions, SC2World from BaseClasses import MultiWorld, CollectionState, PlandoOptions from argparse import Namespace @@ -110,6 +110,37 @@ def test_unexcludes_cancel_out_excludes(self): self.assertNotIn(ItemNames.NOVA_BLAZEFIRE_GUNBLADE, item_names) self.assertNotIn(ItemNames.NOVA_ENERGY_SUIT_MODULE, item_names) + def test_excluding_groups_excludes_all_items_in_group(self): + options = { + 'excluded_items': [ + ItemGroups.ItemGroupNames.BARRACKS_UNITS, + ] + } + self.generate_world(options) + item_names = [item.name for item in self.multiworld.itempool] + self.assertIn(ItemNames.MARINE, self.world.options.excluded_items) + for item_name in ItemGroups.barracks_units: + self.assertNotIn(item_name, item_names) + + def test_excluding_campaigns_excludes_campaign_specific_items(self) -> None: + options = { + 'enable_wol_missions': True, + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'enable_nco_missions': False, + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] + for item_name, item_data in items: + self.assertNotIn(item_data.type, Items.ProtossItemType) + self.assertNotIn(item_data.type, Items.ZergItemType) + self.assertNotEqual(item_data.type, Items.TerranItemType.Nova_Gear) + self.assertNotEqual(item_name, ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE) + def test_usecase_terran_with_nco_units_only(self): options = { 'enable_prophecy_missions': False, @@ -135,36 +166,24 @@ def test_usecase_terran_with_nco_units_only(self): self.assertNotIn(ItemNames.DIAMONDBACK_BURST_CAPACITORS, item_names) self.assertNotIn(ItemNames.VIKING, item_names) - def test_excluding_groups_excludes_all_items_in_group(self): - options = { - 'excluded_items': [ - ItemGroups.ItemGroupNames.BARRACKS_UNITS, - ] - } - self.generate_world(options) - item_names = [item.name for item in self.multiworld.itempool] - self.assertIn(ItemNames.MARINE, self.world.options.excluded_items) - for item_name in ItemGroups.barracks_units: - self.assertNotIn(item_name, item_names) - - def test_excluding_campaigns_excludes_campaign_specific_items(self) -> None: + def test_usecase_nco_with_nobuilds_excluded(self): options = { - 'enable_wol_missions': True, + 'enable_wol_missions': False, 'enable_prophecy_missions': False, 'enable_hots_missions': False, 'enable_lotv_prologue_missions': False, 'enable_lotv_missions': False, 'enable_epilogue_missions': False, - 'enable_nco_missions': False, + 'shuffle_no_build': Options.ShuffleNoBuild.option_false, + 'mission_order': Options.MissionOrder.option_mini_campaign, } self.generate_world(options) self.assertTrue(self.multiworld.itempool) - items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] - for item_name, item_data in items: - self.assertNotIn(item_data.type, Items.ProtossItemType) - self.assertNotIn(item_data.type, Items.ZergItemType) - self.assertNotEqual(item_data.type, Items.TerranItemType.Nova_Gear) - self.assertNotEqual(item_name, ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE) + missions = get_all_missions(self.world.mission_req_table) + self.assertNotIn(MissionTables.SC2Mission.THE_ESCAPE, missions) + self.assertNotIn(MissionTables.SC2Mission.IN_THE_ENEMY_S_SHADOW, missions) + for mission in missions: + self.assertEqual(MissionTables.SC2Campaign.NCO, mission.campaign) def test_excluding_all_terran_missions_excludes_all_terran_items(self) -> None: options = { From 6aba9cfe7ee3401c74e2fca6288175e630d4f0d6 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 01:09:24 -0700 Subject: [PATCH 27/48] sc2: Fixed an issue where indirectly-flagged start inventory wasn't properly pushed to the multiworld --- worlds/sc2/__init__.py | 17 +++++++++++++++++ worlds/sc2/test/test_generation.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 6863f6f2d992..8b07cd26677c 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -121,6 +121,8 @@ def create_items(self): pool: List[Item] = prune_item_pool(self, item_list) pad_item_pool_with_filler(self, len(self.location_cache) - len(self.locked_locations) - len(pool), pool) + push_precollected_items_to_multiworld(self, item_list) + self.multiworld.itempool += pool def set_rules(self): @@ -193,6 +195,7 @@ def resolve_count(count: Optional[int], max_count: int) -> int: result: List[FilterItem] = [] for item_name, item_data in Items.item_table.items(): if not item_data.quantity: + result.append(FilterItem(item_name, item_data, 0, ItemFilterFlags.StartInventory)) continue max_count = item_data.quantity excluded_count = excluded_items.get(item_name) @@ -670,3 +673,17 @@ def add_kerrigan_level_items(level_amount: int, item_amount: int): else: round_func = ceil add_kerrigan_level_items(size, round_func(float(total_levels) / size)) + +def push_precollected_items_to_multiworld(world: SC2World, item_list: List[FilterItem]) -> None: + # world.multiworld.push_precollected() has side-effects, so we can't just clear + # world.multiworld.precollected_items[world.player] + precollected_amounts: Dict[str, int] = {} + for ap_item in world.multiworld.precollected_items[world.player]: + precollected_amounts[ap_item.name] = precollected_amounts.get(ap_item.name, 0) + 1 + for item in item_list: + if ItemFilterFlags.StartInventory not in item.flags: + continue + if precollected_amounts.get(item.name, 0) <= 0: + world.multiworld.push_precollected(create_item_with_correct_settings(world.player, item.name)) + else: + precollected_amounts[item.name] -= 1 diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index fbded63373fc..d36cf59651c7 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -185,6 +185,23 @@ def test_usecase_nco_with_nobuilds_excluded(self): for mission in missions: self.assertEqual(MissionTables.SC2Campaign.NCO, mission.campaign) + def test_starter_unit_populates_start_inventory(self): + options = { + 'enable_wol_missions': True, + 'enable_nco_missions': False, + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'shuffle_no_build': Options.ShuffleNoBuild.option_false, + 'mission_order': Options.MissionOrder.option_grid, + 'starter_unit': Options.StarterUnit.option_any_starter_unit, + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + self.assertTrue(self.multiworld.precollected_items[self.player]) + def test_excluding_all_terran_missions_excludes_all_terran_items(self) -> None: options = { 'mission_order': Options.MissionOrder.option_grid, From 7c1ef79e62188dd6f876a82f6546d84bcb468868 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 01:19:35 -0700 Subject: [PATCH 28/48] sc2: Zerg and Protoss units are now fully excluded when Enemy Within/Templar's Return are included but grant story tech is on --- worlds/sc2/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 8b07cd26677c..b51d5eea9337 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -18,7 +18,8 @@ from .Regions import create_regions from .Options import (get_option_value, LocationInclusion, KerriganLevelItemDistribution, KerriganPresence, KerriganPrimalStatus, kerrigan_unit_available, StarterUnit, SpearOfAdunPresence, - get_enabled_campaigns, SpearOfAdunAutonomouslyCastAbilityPresence, Starcraft2Options + get_enabled_campaigns, SpearOfAdunAutonomouslyCastAbilityPresence, Starcraft2Options, + GrantStoryTech ) from .PoolFilter import filter_items, get_used_races from .MissionTables import ( @@ -261,6 +262,7 @@ def flag_faction_unit_excludes_based_on_mission_excludes(world: SC2World, item_l and item.data.type in (Items.ZergItemType.Unit, Items.ZergItemType.Mercenary, Items.ZergItemType.Evolution_Pit) ): if (SC2Mission.ENEMY_WITHIN not in missions + or world.options.grant_story_tech.value == GrantStoryTech.option_true or item.name not in (ItemNames.ZERGLING, ItemNames.ROACH, ItemNames.HYDRALISK, ItemNames.INFESTOR) ): item.flags |= ItemFilterFlags.Excluded @@ -274,6 +276,7 @@ def flag_faction_unit_excludes_based_on_mission_excludes(world: SC2World, item_l # Note(mm): This doesn't exclude things like automated assimilators or warp gate improvements # because that item type is mixed in with e.g. Reconstruction Beam and Overwatch if (SC2Mission.TEMPLAR_S_RETURN not in missions + or world.options.grant_story_tech.value == GrantStoryTech.option_true or item.name not in ( ItemNames.IMMORTAL, ItemNames.ANNIHILATOR, ItemNames.COLOSSUS, ItemNames.VANGUARD, ItemNames.REAVER, ItemNames.DARK_TEMPLAR, From daac893078ef5aaeda3bd1f63edadd408ec991d5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 01:31:51 -0700 Subject: [PATCH 29/48] sc2: Fixed an issue where SOA items wouldn't ever be included --- worlds/sc2/__init__.py | 5 ++--- worlds/sc2/test/test_generation.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index b51d5eea9337..784ea736808e 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -286,7 +286,6 @@ def flag_faction_unit_excludes_based_on_mission_excludes(world: SC2World, item_l item.flags |= ItemFilterFlags.Excluded - def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem]) -> None: """ Excludes items based on mission / campaign presence: Nova Gear, Kerrigan abilities, SOA @@ -307,7 +306,7 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem tvz_build_missions = [mission for mission in missions if tvz_build_mask & mission.flags == tvz_expect_value] # Check if SOA actives should be present - if world.options.spear_of_adun_presence == SpearOfAdunPresence.option_not_present: + if world.options.spear_of_adun_presence != SpearOfAdunPresence.option_not_present: soa_missions = missions if not world.options.spear_of_adun_present_in_no_build: soa_missions = [m for m in soa_missions if MissionFlag.NoBuild not in m.flags] @@ -321,7 +320,7 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem soa_presence = False # Check if SOA passives should be present - if world.options.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present: + if world.options.spear_of_adun_autonomously_cast_ability_presence != SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present: soa_missions = missions if not world.options.spear_of_adun_autonomously_cast_present_in_no_build: soa_missions = [m for m in soa_missions if MissionFlag.NoBuild not in m.flags] diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index d36cf59651c7..8ed3c833973f 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -402,3 +402,23 @@ def test_enemy_within_and_no_zerg_build_missions_generates(self) -> None: self.assertNotIn(ItemNames.CORRUPTOR, item_names) self.assertNotIn(ItemNames.SCOURGE, item_names) + def test_soa_items_are_included_in_wol_when_presence_set_to_everywhere(self) -> None: + options = { + 'enable_nco_missions': False, + 'enable_prophecy_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_hots_missions': False, + 'enable_epilogue_missions': False, + 'spear_of_adun_presence': Options.SpearOfAdunPresence.option_everywhere, + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'accessibility': 'locations', + 'excluded_items': {ItemGroups.ItemGroupNames.BARRACKS_UNITS: 0}, + } + self.generate_world(options) + item_names = [item.name for item in self.multiworld.itempool] + self.assertTrue(item_names) + self.assertIn(ItemNames.SOA_SOLAR_LANCE, item_names) + self.assertIn(ItemNames.SOA_SOLAR_BOMBARDMENT, item_names) + From 67b911ea0856f02bbe3fd38e79da904f9ee42175 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 02:17:23 -0700 Subject: [PATCH 30/48] sc2: Moved faction-based item exclude out of PoolFilter.py --- worlds/sc2/PoolFilter.py | 48 ++----------------------------- worlds/sc2/__init__.py | 61 ++++++++++++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 54 deletions(-) diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py index 4dda13916536..b2992282f699 100644 --- a/worlds/sc2/PoolFilter.py +++ b/worlds/sc2/PoolFilter.py @@ -527,15 +527,13 @@ def attempt_removal(item: Item) -> bool: def __init__(self, world: 'SC2World', item_pool: List[Item], existing_items: List[Item], locked_items: List[Item], - used_races: Set[SC2Race]): + ): self.multiworld = world.multiworld self.player = world.player self.world: 'SC2World' = world self.logical_inventory = list() self.locked_items = locked_items[:] self.existing_items = existing_items - soa_presence = get_option_value(world, "spear_of_adun_presence") - soa_autocast_presence = get_option_value(world, "spear_of_adun_autonomously_cast_ability_presence") # Initial filter of item pool self.item_pool = [] item_quantities: dict[str, int] = dict() @@ -545,16 +543,6 @@ def __init__(self, world: 'SC2World', min_upgrades = 1 if mission_count < 10 else 2 for item in item_pool: item_info = get_full_item_list()[item.name] - if item_info.race != SC2Race.ANY and item_info.race not in used_races: - if soa_presence == SpearOfAdunPresence.option_everywhere \ - and item.name in spear_of_adun_calldowns: - # Add SoA powers regardless of used races as it's present everywhere - self.item_pool.append(item) - if soa_autocast_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_everywhere \ - and item.name in spear_of_adun_castable_passives: - self.item_pool.append(item) - # Drop any item belonging to a race not used in the campaign - continue if item_info.type in upgrade_item_types: # Locking upgrades based on mission duration if item.name not in item_quantities: @@ -580,45 +568,13 @@ def filter_items(world: 'SC2World', mission_req_table: Dict[SC2Campaign, Dict[st """ open_locations = [location for location in location_cache if location.item is None] inventory_size = len(open_locations) - used_races = get_used_races(mission_req_table, world) mission_requirements = [(location.name, location.access_rule) for location in location_cache] - valid_inventory = ValidInventory(world, item_pool, existing_items, locked_items, used_races) + valid_inventory = ValidInventory(world, item_pool, existing_items, locked_items) valid_items = valid_inventory.generate_reduced_inventory(inventory_size, mission_requirements) return valid_items -def get_used_races(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], world: 'SC2World') -> Set[SC2Race]: - grant_story_tech = get_option_value(world, "grant_story_tech") - take_over_ai_allies = get_option_value(world, "take_over_ai_allies") - missions = missions_in_mission_table(mission_req_table) - kerrigan_presence = ( - world.options.kerrigan_presence in kerrigan_unit_available - and any([MissionFlag.Kerrigan in mission.flags for mission in missions]) - ) - - # By missions - races = set([mission.race for mission in missions]) - - # Conditionally logic-less no-builds (They're set to SC2Race.ANY): - if grant_story_tech == GrantStoryTech.option_false: - if SC2Mission.ENEMY_WITHIN in missions: - # Zerg units need to be unlocked - races.add(SC2Race.ZERG) - if kerrigan_presence \ - and not missions.isdisjoint({SC2Mission.BACK_IN_THE_SADDLE, SC2Mission.SUPREME, SC2Mission.CONVICTION, SC2Mission.THE_INFINITE_CYCLE}): - # You need some Kerrigan abilities (they're granted if Kerriganless or story tech granted) - races.add(SC2Race.ZERG) - - # If you take over the AI Ally, you need to have its race stuff - if take_over_ai_allies == TakeOverAIAllies.option_true \ - and not missions.isdisjoint({SC2Mission.THE_RECKONING}): - # Jimmy in The Reckoning - races.add(SC2Race.TERRAN) - - return races - - def missions_in_mission_table(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> Set[SC2Mission]: return set([mission.mission for campaign_missions in mission_req_table.values() for mission in campaign_missions.values()]) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 784ea736808e..8104136293a2 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -19,9 +19,9 @@ from .Options import (get_option_value, LocationInclusion, KerriganLevelItemDistribution, KerriganPresence, KerriganPrimalStatus, kerrigan_unit_available, StarterUnit, SpearOfAdunPresence, get_enabled_campaigns, SpearOfAdunAutonomouslyCastAbilityPresence, Starcraft2Options, - GrantStoryTech + GrantStoryTech, GenericUpgradeResearch, ) -from .PoolFilter import filter_items, get_used_races +from .PoolFilter import filter_items from .MissionTables import ( MissionInfo, SC2Campaign, SC2Mission, SC2Race, MissionFlag ) @@ -114,6 +114,7 @@ def create_items(self): item_list: List[FilterItem] = create_and_flag_explicit_item_locks_and_excludes(self) flag_faction_unit_excludes_based_on_mission_excludes(self, item_list) flag_mission_based_item_excludes(self, item_list) + flag_faction_excludes_based_on_faction_presence(self, item_list) flag_allowed_orphan_items(self, item_list) flag_start_inventory(self, item_list) flag_unused_upgrade_types(self, item_list) @@ -286,6 +287,52 @@ def flag_faction_unit_excludes_based_on_mission_excludes(world: SC2World, item_l item.flags |= ItemFilterFlags.Excluded +def flag_faction_excludes_based_on_faction_presence(world: SC2World, item_list: List[FilterItem]) -> None: + """ + Excludes global upgrade / progressive items based on mission presence + """ + missions = get_all_missions(world.mission_req_table) + if world.options.take_over_ai_allies.value: + terran_missions = [mission for mission in missions if (MissionFlag.Terran|MissionFlag.AiTerranAlly) & mission.flags] + zerg_missions = [mission for mission in missions if (MissionFlag.Zerg|MissionFlag.AiZergAlly) & mission.flags] + protoss_missions = [mission for mission in missions if (MissionFlag.Protoss|MissionFlag.AiProtossAlly) & mission.flags] + else: + terran_missions = [mission for mission in missions if MissionFlag.Terran in mission.flags] + zerg_missions = [mission for mission in missions if MissionFlag.Zerg in mission.flags] + protoss_missions = [mission for mission in missions if MissionFlag.Protoss in mission.flags] + terran_build_missions = [mission for mission in terran_missions if MissionFlag.NoBuild not in mission.flags] + zerg_build_missions = [mission for mission in zerg_missions if MissionFlag.NoBuild not in mission.flags] + protoss_build_missions = [mission for mission in protoss_missions if MissionFlag.NoBuild not in mission.flags] + auto_upgrades_in_nobuilds = ( + world.options.generic_upgrade_research.value + in (GenericUpgradeResearch.option_always_auto, GenericUpgradeResearch.option_auto_in_no_build) + ) + + for item in item_list: + if (not terran_missions and item.data.race == SC2Race.TERRAN): + item.flags |= ItemFilterFlags.Excluded + if (not zerg_missions and item.data.race == SC2Race.ZERG): + item.flags |= ItemFilterFlags.Excluded + if (not protoss_missions and item.data.race == SC2Race.PROTOSS): + if item.name not in ItemGroups.soa_items: + item.flags |= ItemFilterFlags.Excluded + if (item.data.type == Items.TerranItemType.Upgrade + and not terran_build_missions + and not auto_upgrades_in_nobuilds + ): + item.flags |= ItemFilterFlags.Excluded + if (item.data.type == Items.ZergItemType.Upgrade + and not zerg_build_missions + and not auto_upgrades_in_nobuilds + ): + item.flags |= ItemFilterFlags.Excluded + if (item.data.type == Items.ProtossItemType.Upgrade + and not protoss_build_missions + and not auto_upgrades_in_nobuilds + ): + item.flags |= ItemFilterFlags.Excluded + + def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem]) -> None: """ Excludes items based on mission / campaign presence: Nova Gear, Kerrigan abilities, SOA @@ -410,13 +457,11 @@ def flag_start_unit(world: SC2World, item_list: List[FilterItem], starter_unit: if first_race == SC2Race.ANY: # If the first mission is a logic-less no-build - races = get_used_races(world.mission_req_table, world) + missions = get_all_missions(world.mission_req_table) + build_missions = [mission for mission in missions if MissionFlag.NoBuild not in mission.flags] + races = set(mission.race for mission in build_missions) races.remove(SC2Race.ANY) - if first_mission.race in races: - # The campaign's race is in (At least one mission that's not logic-less no-build exists) - first_race = first_mission.campaign.race - elif len(races) > 0: - # The campaign only has logic-less no-build missions. Find any other valid race + if races: first_race = world.random.choice(list(races)) if first_race != SC2Race.ANY: From 152d00da944254fb515f209a0a75fe3a657cb999 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 02:35:13 -0700 Subject: [PATCH 31/48] sc2: Combined two like functions in init.py --- worlds/sc2/__init__.py | 78 ++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 8104136293a2..ec65888e1bc0 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -112,9 +112,8 @@ def create_items(self): setup_events(self.player, self.locked_locations, self.location_cache) item_list: List[FilterItem] = create_and_flag_explicit_item_locks_and_excludes(self) - flag_faction_unit_excludes_based_on_mission_excludes(self, item_list) + flag_excludes_by_faction_presence(self, item_list) flag_mission_based_item_excludes(self, item_list) - flag_faction_excludes_based_on_faction_presence(self, item_list) flag_allowed_orphan_items(self, item_list) flag_start_inventory(self, item_list) flag_unused_upgrade_types(self, item_list) @@ -240,21 +239,39 @@ def resolve_count(count: Optional[int], max_count: int) -> int: return result -def flag_faction_unit_excludes_based_on_mission_excludes(world: SC2World, item_list: List[FilterItem]) -> None: - """ - Excludes units based on whether a mission they can be built exists in the mission table - """ +def flag_excludes_by_faction_presence(world: SC2World, item_list: List[FilterItem]) -> None: + """Excludes items based on if their faction has a mission present where they can be used""" missions = get_all_missions(world.mission_req_table) - build_missions = [mission for mission in missions if MissionFlag.NoBuild not in mission.flags] - if world.options.take_over_ai_allies: - terran_build_missions = [mission for mission in build_missions if MissionFlag.Terran|MissionFlag.AiZergAlly & mission.flags] - zerg_build_missions = [mission for mission in build_missions if MissionFlag.Zerg|MissionFlag.AiTerranAlly & mission.flags] - protoss_build_missions = [mission for mission in build_missions if MissionFlag.Protoss|MissionFlag.AiProtossAlly & mission.flags] + if world.options.take_over_ai_allies.value: + terran_missions = [mission for mission in missions if (MissionFlag.Terran|MissionFlag.AiTerranAlly) & mission.flags] + zerg_missions = [mission for mission in missions if (MissionFlag.Zerg|MissionFlag.AiZergAlly) & mission.flags] + protoss_missions = [mission for mission in missions if (MissionFlag.Protoss|MissionFlag.AiProtossAlly) & mission.flags] else: - terran_build_missions = [mission for mission in build_missions if MissionFlag.Terran & mission.flags] - zerg_build_missions = [mission for mission in build_missions if MissionFlag.Zerg & mission.flags] - protoss_build_missions = [mission for mission in build_missions if MissionFlag.Protoss & mission.flags] + terran_missions = [mission for mission in missions if MissionFlag.Terran in mission.flags] + zerg_missions = [mission for mission in missions if MissionFlag.Zerg in mission.flags] + protoss_missions = [mission for mission in missions if MissionFlag.Protoss in mission.flags] + terran_build_missions = [mission for mission in terran_missions if MissionFlag.NoBuild not in mission.flags] + zerg_build_missions = [mission for mission in zerg_missions if MissionFlag.NoBuild not in mission.flags] + protoss_build_missions = [mission for mission in protoss_missions if MissionFlag.NoBuild not in mission.flags] + auto_upgrades_in_nobuilds = ( + world.options.generic_upgrade_research.value + in (GenericUpgradeResearch.option_always_auto, GenericUpgradeResearch.option_auto_in_no_build) + ) + for item in item_list: + # Catch-all for all of a faction's items + if (not terran_missions and item.data.race == SC2Race.TERRAN): + item.flags |= ItemFilterFlags.Excluded + continue + if (not zerg_missions and item.data.race == SC2Race.ZERG): + item.flags |= ItemFilterFlags.Excluded + continue + if (not protoss_missions and item.data.race == SC2Race.PROTOSS): + if item.name not in ItemGroups.soa_items: + item.flags |= ItemFilterFlags.Excluded + continue + + # Faction units if (not terran_build_missions and item.data.type in (Items.TerranItemType.Unit, Items.TerranItemType.Building, Items.TerranItemType.Mercenary) ): @@ -285,37 +302,8 @@ def flag_faction_unit_excludes_based_on_mission_excludes(world: SC2World, item_l ) ): item.flags |= ItemFilterFlags.Excluded - - -def flag_faction_excludes_based_on_faction_presence(world: SC2World, item_list: List[FilterItem]) -> None: - """ - Excludes global upgrade / progressive items based on mission presence - """ - missions = get_all_missions(world.mission_req_table) - if world.options.take_over_ai_allies.value: - terran_missions = [mission for mission in missions if (MissionFlag.Terran|MissionFlag.AiTerranAlly) & mission.flags] - zerg_missions = [mission for mission in missions if (MissionFlag.Zerg|MissionFlag.AiZergAlly) & mission.flags] - protoss_missions = [mission for mission in missions if (MissionFlag.Protoss|MissionFlag.AiProtossAlly) & mission.flags] - else: - terran_missions = [mission for mission in missions if MissionFlag.Terran in mission.flags] - zerg_missions = [mission for mission in missions if MissionFlag.Zerg in mission.flags] - protoss_missions = [mission for mission in missions if MissionFlag.Protoss in mission.flags] - terran_build_missions = [mission for mission in terran_missions if MissionFlag.NoBuild not in mission.flags] - zerg_build_missions = [mission for mission in zerg_missions if MissionFlag.NoBuild not in mission.flags] - protoss_build_missions = [mission for mission in protoss_missions if MissionFlag.NoBuild not in mission.flags] - auto_upgrades_in_nobuilds = ( - world.options.generic_upgrade_research.value - in (GenericUpgradeResearch.option_always_auto, GenericUpgradeResearch.option_auto_in_no_build) - ) - - for item in item_list: - if (not terran_missions and item.data.race == SC2Race.TERRAN): - item.flags |= ItemFilterFlags.Excluded - if (not zerg_missions and item.data.race == SC2Race.ZERG): - item.flags |= ItemFilterFlags.Excluded - if (not protoss_missions and item.data.race == SC2Race.PROTOSS): - if item.name not in ItemGroups.soa_items: - item.flags |= ItemFilterFlags.Excluded + + # Faction +attack/armour upgrades if (item.data.type == Items.TerranItemType.Upgrade and not terran_build_missions and not auto_upgrades_in_nobuilds From fccc3b0265fab5f8b91a4afb449768221fcd6f63 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 02:44:22 -0700 Subject: [PATCH 32/48] sc2: Loosened the asserts on a test to make it less flaky --- worlds/sc2/test/test_generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index 8ed3c833973f..e794a08fd5a4 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -419,6 +419,6 @@ def test_soa_items_are_included_in_wol_when_presence_set_to_everywhere(self) -> self.generate_world(options) item_names = [item.name for item in self.multiworld.itempool] self.assertTrue(item_names) - self.assertIn(ItemNames.SOA_SOLAR_LANCE, item_names) - self.assertIn(ItemNames.SOA_SOLAR_BOMBARDMENT, item_names) + soa_items_in_pool = [item_name for item_name in item_names if Items.item_table[item_name].type == Items.ProtossItemType.Spear_Of_Adun] + self.assertGreater(len(soa_items_in_pool), 5) From 6bc3ce9fb47dc0dfc465e5d2f3fadbfb47948232 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 03:21:03 -0700 Subject: [PATCH 33/48] sc2: Filter out Kerrigan items if only Kerrigan nobuilds are present and grant story tech/levels is on --- worlds/sc2/PoolFilter.py | 2 +- worlds/sc2/__init__.py | 17 ++++++++++++----- worlds/sc2/test/test_generation.py | 24 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py index b2992282f699..8e2732ec069f 100644 --- a/worlds/sc2/PoolFilter.py +++ b/worlds/sc2/PoolFilter.py @@ -538,7 +538,7 @@ def __init__(self, world: 'SC2World', self.item_pool = [] item_quantities: dict[str, int] = dict() # Inventory restrictiveness based on number of missions with checks - mission_count = mission_count = sum(len(campaign) for campaign in world.mission_req_table.values()) + mission_count = sum(len(campaign) for campaign in world.mission_req_table.values()) self.min_units_per_structure = int(mission_count / 7) min_upgrades = 1 if mission_count < 10 else 2 for item in item_pool: diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index ec65888e1bc0..9ef7edc8d226 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -327,10 +327,11 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem """ missions = get_all_missions(world.mission_req_table) - kerrigan_missions = len([mission for mission in missions if MissionFlag.Kerrigan in mission.flags]) > 0 + kerrigan_missions = [mission for mission in missions if MissionFlag.Kerrigan in mission.flags] + kerrigan_build_missions = [mission for mission in kerrigan_missions if MissionFlag.NoBuild not in mission.flags] nova_missions = [mission for mission in missions if MissionFlag.Nova in mission.flags] - kerrigan_is_present = kerrigan_missions and world.options.kerrigan_presence == KerriganPresence.option_vanilla + kerrigan_is_present = len(kerrigan_missions) > 0 and world.options.kerrigan_presence == KerriganPresence.option_vanilla # TvZ build missions -- check flags Terran and VsZerg are true and NoBuild is false tvz_build_mask = MissionFlag.Terran|MissionFlag.VsZerg|MissionFlag.NoBuild @@ -381,8 +382,11 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem item.flags |= ItemFilterFlags.Excluded # Remove Kerrigan abilities if there's no kerrigan - if (item.data.type == Items.ZergItemType.Ability and not kerrigan_is_present): - item.flags |= ItemFilterFlags.Excluded + if item.data.type == Items.ZergItemType.Ability: + if not kerrigan_is_present: + item.flags |= ItemFilterFlags.Excluded + elif world.options.grant_story_tech and not kerrigan_build_missions: + item.flags |= ItemFilterFlags.Excluded # Remove Spear of Adun if it's off if item.name in Items.spear_of_adun_calldowns and not soa_presence: @@ -673,9 +677,12 @@ def create_item_with_correct_settings(player: int, name: str) -> Item: def fill_pool_with_kerrigan_levels(world: SC2World, item_pool: List[Item]): total_levels = world.options.kerrigan_level_item_sum.value missions = get_all_missions(world.mission_req_table) + kerrigan_missions = [mission for mission in missions if MissionFlag.Kerrigan in mission.flags] + kerrigan_build_missions = [mission for mission in missions if MissionFlag.NoBuild not in mission.flags] if (world.options.kerrigan_presence.value not in kerrigan_unit_available or total_levels == 0 - or not [mission for mission in missions if MissionFlag.Kerrigan in mission.flags] + or not kerrigan_missions + or (world.options.grant_story_levels and not kerrigan_build_missions) ): return diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index e794a08fd5a4..2458fa1acd70 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -422,3 +422,27 @@ def test_soa_items_are_included_in_wol_when_presence_set_to_everywhere(self) -> soa_items_in_pool = [item_name for item_name in item_names if Items.item_table[item_name].type == Items.ProtossItemType.Spear_Of_Adun] self.assertGreater(len(soa_items_in_pool), 5) + def test_lotv_only_doesnt_include_kerrigan_items_with_grant_story_tech(self) -> None: + options = { + 'enable_wol_missions': False, + 'enable_nco_missions': False, + 'enable_prophecy_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': True, + 'enable_hots_missions': False, + 'enable_epilogue_missions': False, + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'accessibility': 'locations', + 'grant_story_tech': Options.GrantStoryTech.option_true, + } + self.generate_world(options) + missions = get_all_missions(self.world.mission_req_table) + self.assertIn(MissionTables.SC2Mission.TEMPLE_OF_UNIFICATION, missions) + item_names = [item.name for item in self.multiworld.itempool] + self.assertTrue(item_names) + kerrigan_items_in_pool = set(ItemGroups.kerrigan_abilities).intersection(item_names) + self.assertFalse(kerrigan_items_in_pool) + kerrigan_passives_in_pool = set(ItemGroups.kerrigan_passives).intersection(item_names) + self.assertFalse(kerrigan_passives_in_pool) + From c1035e32b3a5b5c9e4882422cf1784bde0855cae Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 04:11:44 -0700 Subject: [PATCH 34/48] sc2: Fixing a sporadic generation issue caused by accidental duplicate item objects in the pool --- worlds/sc2/PoolFilter.py | 8 ++++++-- worlds/sc2/test/test_generation.py | 9 ++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py index 8e2732ec069f..a88b582094ee 100644 --- a/worlds/sc2/PoolFilter.py +++ b/worlds/sc2/PoolFilter.py @@ -247,14 +247,18 @@ def generate_reduced_inventory(self, inventory_size: int, mission_requirements: minimum_upgrades = get_option_value(self.world, "min_number_of_upgrades") def attempt_removal(item: Item) -> bool: - inventory.remove(item) + removed_item = inventory.pop(inventory.index(item)) # Only run logic checks when removing logic items if item.name in self.logical_inventory: self.logical_inventory.remove(item.name) if not all(requirement(self) for (_, requirement) in mission_requirements): # If item cannot be removed, lock or revert self.logical_inventory.append(item.name) - locked_items.append(item) + # Note(mm): Be sure to re-add the _exact_ item we removed. + # Removing from `inventory` searches based on == checks, but AutoWorld.py + # checks using `is` to make sure there are no duplicate item objects in the pool. + # Hence, appending `item` could cause sporadic generation failures. + locked_items.append(removed_item) return False return True diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index 2458fa1acd70..49fd771d53ef 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -36,9 +36,12 @@ def generate_world(self, options: Dict[str, Any]) -> None: }) self.multiworld.set_options(args) self.world: SC2World = cast(SC2World, self.multiworld.worlds[self.player]) - for step in gen_steps: - call_all(self.multiworld, step) - + try: + for step in gen_steps: + call_all(self.multiworld, step) + except Exception as ex: + ex.add_note(f"Seed: {self.multiworld.seed}") + raise class TestItemFiltering(Sc2SetupTestBase): def test_explicit_locks_excludes_interact_and_set_flags(self): From cff8f3fa3a28e4eef7cc1c5cec9005a1c9bcf5a9 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 12:42:44 -0700 Subject: [PATCH 35/48] sc2: Updated logic on Evil Awoken so vanilla composition is in-logic --- worlds/sc2/Locations.py | 5 +---- worlds/sc2/__init__.py | 5 ----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/worlds/sc2/Locations.py b/worlds/sc2/Locations.py index 2f36446d66c3..d476ff243e08 100644 --- a/worlds/sc2/Locations.py +++ b/worlds/sc2/Locations.py @@ -83,11 +83,9 @@ def get_locations(world: Optional['SC2World']) -> Tuple[LocationData, ...]: # Note: rules which are ended with or True are rules identified as needed later when restricted units is an option if world is None: logic_level = int(RequiredTactics.default) - vanilla_items_only = not not VanillaItemsOnly.default kerriganless = False else: logic_level = world.options.required_tactics.value - vanilla_items_only = not not world.options.vanilla_items_only.value kerriganless = ( world.options.kerrigan_presence.value != KerriganPresence.option_vanilla or not world.options.enable_hots_missions.value @@ -1098,8 +1096,7 @@ def get_locations(world: Optional['SC2World']) -> Tuple[LocationData, ...]: lambda state: logic.protoss_common_unit(state) \ and logic.protoss_anti_armor_anti_air(state) \ and logic.protoss_can_attack_behind_chasm(state)), - LocationData("Evil Awoken", "Evil Awoken: Victory", SC2LOTV_LOC_ID_OFFSET + 300, LocationType.VICTORY, - lambda state: adv_tactics or vanilla_items_only or logic.protoss_stalker_upgrade(state)), + LocationData("Evil Awoken", "Evil Awoken: Victory", SC2LOTV_LOC_ID_OFFSET + 300, LocationType.VICTORY), LocationData("Evil Awoken", "Evil Awoken: Temple Investigated", SC2LOTV_LOC_ID_OFFSET + 301, LocationType.EXTRA), LocationData("Evil Awoken", "Evil Awoken: Void Catalyst", SC2LOTV_LOC_ID_OFFSET + 302, LocationType.EXTRA), LocationData("Evil Awoken", "Evil Awoken: First Particle Cannon", SC2LOTV_LOC_ID_OFFSET + 303, LocationType.VANILLA), diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 9ef7edc8d226..8644f8c2c7e8 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -408,17 +408,12 @@ def flag_allowed_orphan_items(world: SC2World, item_list: List[FilterItem]) -> N """Adds the `Allowed_Orphan` flag to items that shouldn't be filtered with their parents, like combat shield""" missions = get_all_missions(world.mission_req_table) terran_nobuild_missions = any((MissionFlag.Terran|MissionFlag.NoBuild) in mission.flags for mission in missions) - evil_awoken_enabled = SC2Mission.EVIL_AWOKEN in missions for item in item_list: if item.name in ( ItemNames.MARINE_COMBAT_SHIELD, ItemNames.MARINE_PROGRESSIVE_STIMPACK, ItemNames.MARINE_MAGRAIL_MUNITIONS, ItemNames.MEDIC_STABILIZER_MEDPACKS, ItemNames.MEDIC_NANO_PROJECTOR, ) and terran_nobuild_missions: item.flags |= ItemFilterFlags.AllowedOrphan - if item.name in ( - ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES, ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION, - ) and evil_awoken_enabled: - item.flags |= ItemFilterFlags.AllowedOrphan def flag_start_inventory(world: SC2World, item_list: List[FilterItem]) -> None: From 4ea2614e1c94d26a0b634453f527589a5b9a7568 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 15:31:29 -0700 Subject: [PATCH 36/48] sc2: Added NCO item groups; added usecase test for NCO item pool only --- worlds/sc2/ItemGroups.py | 122 ++++++++++++++++++++++++++++- worlds/sc2/test/test_generation.py | 39 +++++++++ 2 files changed, 157 insertions(+), 4 deletions(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index 18af5f71555d..8e900f1c57d8 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -1,6 +1,6 @@ import typing from . import Items, ItemNames -from .MissionTables import campaign_mission_table, SC2Campaign, SC2Mission +from .MissionTables import campaign_mission_table, SC2Campaign, SC2Mission, SC2Race """ Item name groups, given to Archipelago and used in YAMLs and /received filtering. @@ -71,10 +71,11 @@ # Hand-made groups class ItemGroupNames: + TERRAN_ITEMS = "Terran Items" + """All Terran items""" TERRAN_UNITS = "Terran Units" - ZERG_UNITS = "Zerg Units" - PROTOSS_UNITS = "Protoss Units" - + TERRAN_GENERIC_UPGRADES = "Terran Generic Upgrades" + """+attack/armour upgrades""" BARRACKS_UNITS = "Barracks Units" FACTORY_UNITS = "Factory Units" STARPORT_UNITS = "Starport Units" @@ -85,7 +86,15 @@ class ItemGroupNames: WOL_ITEMS = "WoL Items" """All items from vanilla WoL. Note some items are progressive where level 2 is not vanilla.""" NCO_UNITS = "NCO Units" + NCO_BUILDINGS = "NCO Buildings" + NCO_UNIT_TECHNOLOGY = "NCO Unit Technology" + NCO_BASELINE_UPGRADES = "NCO Baseline Upgrades" + NCO_UPGRADES = "NCO Upgrades" NOVA_EQUIPMENT = "Nova Equipment" + NCO_MAX_PROGRESSIVE_UPGRADES = "NCO +Items" + """NCO item groups that should be set to maximum progressive amounts""" + NCO_MIN_PROGRESSIVE_UPGRADES = "NCO -Items" + """NCO item groups that should be set to minimum progressive amounts (1)""" TERRAN_BUILDINGS = "Terran Buildings" TERRAN_MERCENARIES = "Terran Mercenaries" TERRAN_STIMPACKS = "Terran Stimpacks" @@ -93,6 +102,10 @@ class ItemGroupNames: TERRAN_ORIGINAL_PROGRESSIVE_UPGRADES = "Terran Original Progressive Upgrades" """Progressive items where level 1 appeared in WoL""" + ZERG_ITEMS = "Zerg Items" + ZERG_UNITS = "Zerg Units" + ZERG_GENERIC_UPGRADES = "Zerg Generic Upgrades" + """+attack/armour upgrades""" HOTS_UNITS = "HotS Units" HOTS_STRAINS = "HotS Strains" """Vanilla HotS strains (the upgrades you play a mini-mission for)""" @@ -108,6 +121,10 @@ class ItemGroupNames: ZERG_MERCS = "Zerg Mercenaries" ZERG_BUILDINGS = "Zerg Buildings" + PROTOSS_ITEMS = "Protoss Items" + PROTOSS_UNITS = "Protoss Units" + PROTOSS_GENERIC_UPGRADES = "Protoss Generic Upgrades" + """+attack/armour upgrades""" GATEWAY_UNITS = "Gateway Units" ROBO_UNITS = "Robo Units" STARGATE_UNITS = "Stargate Units" @@ -128,10 +145,18 @@ class ItemGroupNames: # Terran +item_name_groups[ItemGroupNames.TERRAN_ITEMS] = terran_items = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.race == SC2Race.TERRAN +] item_name_groups[ItemGroupNames.TERRAN_UNITS] = terran_units = [ item_name for item_name, item_data in Items.item_table.items() if item_data.type in (Items.TerranItemType.Unit, Items.TerranItemType.Mercenary) ] +item_name_groups[ItemGroupNames.TERRAN_GENERIC_UPGRADES] = terran_generic_upgrades = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type == Items.TerranItemType.Upgrade +] item_name_groups[ItemGroupNames.BARRACKS_UNITS] = barracks_units = [ ItemNames.MARINE, ItemNames.MEDIC, ItemNames.FIREBAT, ItemNames.MARAUDER, ItemNames.REAPER, ItemNames.GHOST, ItemNames.SPECTRE, ItemNames.HERC, @@ -158,6 +183,10 @@ class ItemGroupNames: ItemNames.MARINE, ItemNames.MARAUDER, ItemNames.REAPER, ItemNames.HELLION, ItemNames.GOLIATH, ItemNames.SIEGE_TANK, ItemNames.RAVEN, ItemNames.LIBERATOR, ItemNames.BANSHEE, ItemNames.BATTLECRUISER, + ItemNames.HERC, # From that one bonus objective in mission 5 +] +item_name_groups[ItemGroupNames.NCO_BUILDINGS] = nco_buildings = [ + ItemNames.BUNKER, ItemNames.MISSILE_TURRET, ItemNames.PLANETARY_FORTRESS, ] item_name_groups[ItemGroupNames.NOVA_EQUIPMENT] = nova_equipment = [ *[item_name for item_name, item_data in Items.item_table.items() @@ -243,6 +272,72 @@ class ItemGroupNames: ItemNames.THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL, ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, ] +item_name_groups[ItemGroupNames.NCO_BASELINE_UPGRADES] = nco_baseline_upgrades = [ + ItemNames.BUNKER_NEOSTEEL_BUNKER, # Baseline from mission 2 + ItemNames.BUNKER_FORTIFIED_BUNKER, # Baseline from mission 2 + ItemNames.MARINE_COMBAT_SHIELD, # Baseline from mission 2 + ItemNames.MARAUDER_KINETIC_FOAM, # Baseline outside WOL + ItemNames.MARAUDER_CONCUSSIVE_SHELLS, # Baseline from mission 2 + ItemNames.HELLION_HELLBAT_ASPECT, # Baseline from mission 3 + ItemNames.GOLIATH_INTERNAL_TECH_MODULE, # Baseline from mission 4 + ItemNames.GOLIATH_SHAPED_HULL, + # ItemNames.GOLIATH_RESOURCE_EFFICIENCY, # Supply savings baseline in NCO, mineral savings is non-NCO + ItemNames.SIEGE_TANK_SHAPED_HULL, # Baseline NCO gives +10; this upgrade gives +25 + ItemNames.SIEGE_TANK_SHAPED_BLAST, # Baseline from mission 3 + ItemNames.LIBERATOR_RAID_ARTILLERY, # Baseline in mission 5 + ItemNames.RAVEN_BIO_MECHANICAL_REPAIR_DRONE, # Baseline in mission 5 + ItemNames.BATTLECRUISER_TACTICAL_JUMP, + ItemNames.BATTLECRUISER_COVERT_OPS_ENGINES, + ItemNames.PROGRESSIVE_ORBITAL_COMMAND, # Can be upgraded from mission 2 + ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM, # Baseline from mission 2 + ItemNames.ORBITAL_DEPOTS, # Baseline from mission 2 +] + nco_buildings +item_name_groups[ItemGroupNames.NCO_UNIT_TECHNOLOGY] = nco_unit_technology = [ + ItemNames.MARINE_LASER_TARGETING_SYSTEM, + ItemNames.MARINE_PROGRESSIVE_STIMPACK, + ItemNames.MARINE_MAGRAIL_MUNITIONS, + ItemNames.MARINE_OPTIMIZED_LOGISTICS, + ItemNames.MARAUDER_LASER_TARGETING_SYSTEM, + ItemNames.MARAUDER_INTERNAL_TECH_MODULE, + ItemNames.MARAUDER_PROGRESSIVE_STIMPACK, + ItemNames.MARAUDER_MAGRAIL_MUNITIONS, + ItemNames.REAPER_SPIDER_MINES, + ItemNames.REAPER_LASER_TARGETING_SYSTEM, + ItemNames.REAPER_PROGRESSIVE_STIMPACK, + ItemNames.REAPER_ADVANCED_CLOAKING_FIELD, + # Reaper special ordnance gives anti-building attack, which is baseline in AP + ItemNames.HELLION_JUMP_JETS, + ItemNames.HELLION_PROGRESSIVE_STIMPACK, + ItemNames.HELLION_SMART_SERVOS, + ItemNames.HELLION_OPTIMIZED_LOGISTICS, + ItemNames.HELLION_THERMITE_FILAMENTS, # Called Infernal Pre-Igniter in NCO + ItemNames.GOLIATH_ARES_CLASS_TARGETING_SYSTEM, # Called Laser Targeting System in NCO + ItemNames.GOLIATH_JUMP_JETS, + ItemNames.GOLIATH_OPTIMIZED_LOGISTICS, + ItemNames.GOLIATH_MULTI_LOCK_WEAPONS_SYSTEM, + ItemNames.SIEGE_TANK_SPIDER_MINES, ItemNames.SIEGE_TANK_JUMP_JETS, + ItemNames.SIEGE_TANK_INTERNAL_TECH_MODULE, ItemNames.SIEGE_TANK_SMART_SERVOS, + # Tanks can't get Laser targeting system in NCO + ItemNames.BANSHEE_INTERNAL_TECH_MODULE, + ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS, + ItemNames.BANSHEE_SHOCKWAVE_MISSILE_BATTERY, # Banshee Special Ordnance + # Banshees can't get laser targeting systems in NCO + ItemNames.LIBERATOR_CLOAK, + ItemNames.LIBERATOR_SMART_SERVOS, + ItemNames.LIBERATOR_OPTIMIZED_LOGISTICS, + # Liberators can't get laser targeting system in NCO + ItemNames.RAVEN_SPIDER_MINES, + ItemNames.RAVEN_INTERNAL_TECH_MODULE, + ItemNames.RAVEN_RAILGUN_TURRET, # Raven Magrail Munitions + ItemNames.RAVEN_HUNTER_SEEKER_WEAPON, # Raven Special Ordnance + ItemNames.BATTLECRUISER_INTERNAL_TECH_MODULE, + ItemNames.BATTLECRUISER_CLOAK, + ItemNames.BATTLECRUISER_ATX_LASER_BATTERY, # Battlecruiser Special Ordnance + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, +] +item_name_groups[ItemGroupNames.NCO_UPGRADES] = nco_upgrades = nco_baseline_upgrades + nco_unit_technology +item_name_groups[ItemGroupNames.NCO_MAX_PROGRESSIVE_UPGRADES] = nco_unit_technology + nova_equipment + terran_generic_upgrades +item_name_groups[ItemGroupNames.NCO_MIN_PROGRESSIVE_UPGRADES] = nco_units + nco_baseline_upgrades item_name_groups[ItemGroupNames.TERRAN_PROGRESSIVE_UPGRADES] = terran_progressive_items = [ item_name for item_name, item_data in Items.item_table.items() if item_data.type in (Items.TerranItemType.Progressive, Items.TerranItemType.Progressive_2) @@ -252,15 +347,24 @@ class ItemGroupNames: + wol_buildings + wol_mercs + wol_upgrades + + terran_generic_upgrades ) # Zerg +item_name_groups[ItemGroupNames.ZERG_ITEMS] = zerg_items = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.race == SC2Race.ZERG +] item_name_groups[ItemGroupNames.ZERG_BUILDINGS] = zerg_buildings = [ItemNames.SPINE_CRAWLER, ItemNames.SPORE_CRAWLER] item_name_groups[ItemGroupNames.ZERG_UNITS] = zerg_units = [ item_name for item_name, item_data in Items.item_table.items() if item_data.type in (Items.ZergItemType.Unit, Items.ZergItemType.Mercenary, Items.ZergItemType.Morph) and item_name not in zerg_buildings ] +item_name_groups[ItemGroupNames.ZERG_GENERIC_UPGRADES] = zerg_generic_upgrades = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type == Items.ZergItemType.Upgrade +] item_name_groups[ItemGroupNames.HOTS_UNITS] = hots_units = [ ItemNames.ZERGLING, ItemNames.SWARM_QUEEN, ItemNames.ROACH, ItemNames.HYDRALISK, ItemNames.ABERRATION, ItemNames.SWARM_HOST, ItemNames.MUTALISK, @@ -320,14 +424,23 @@ class ItemGroupNames: + hots_mutations + hots_strains + hots_global_upgrades + + zerg_generic_upgrades ) # Protoss +item_name_groups[ItemGroupNames.PROTOSS_ITEMS] = protoss_items = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.race == SC2Race.PROTOSS +] item_name_groups[ItemGroupNames.PROTOSS_UNITS] = protoss_units = [ item_name for item_name, item_data in Items.item_table.items() if item_data.type in (Items.ProtossItemType.Unit, Items.ProtossItemType.Unit_2) ] +item_name_groups[ItemGroupNames.PROTOSS_GENERIC_UPGRADES] = protoss_generic_upgrades = [ + item_name for item_name, item_data in Items.item_table.items() + if item_data.type == Items.ProtossItemType.Upgrade +] item_name_groups[ItemGroupNames.LOTV_UNITS] = lotv_units = [ ItemNames.ZEALOT, ItemNames.CENTURION, ItemNames.SENTINEL, ItemNames.STALKER, ItemNames.DRAGOON, ItemNames.ADEPT, @@ -414,6 +527,7 @@ class ItemGroupNames: + protoss_buildings + lotv_soa_items + lotv_global_upgrades + + protoss_generic_upgrades ) item_name_groups[ItemGroupNames.VANILLA_ITEMS] = vanilla_items = ( diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index 49fd771d53ef..8dfd3c5331d4 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -188,6 +188,45 @@ def test_usecase_nco_with_nobuilds_excluded(self): for mission in missions: self.assertEqual(MissionTables.SC2Campaign.NCO, mission.campaign) + def test_usecase_terran_with_nco_upgrades_units_only(self): + options = { + 'enable_wol_missions': True, + 'enable_nco_missions': True, + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'mission_order': Options.MissionOrder.option_mini_campaign, + 'excluded_items': { + ItemGroups.ItemGroupNames.TERRAN_ITEMS: 0, + }, + 'unexcluded_items': { + ItemGroups.ItemGroupNames.NCO_MAX_PROGRESSIVE_UPGRADES: 0, + ItemGroups.ItemGroupNames.NCO_MIN_PROGRESSIVE_UPGRADES: 1, + }, + } + self.generate_world(options) + item_names = [item.name for item in self.multiworld.itempool] + self.assertTrue(item_names) + missions = get_all_missions(self.world.mission_req_table) + for mission in missions: + self.assertIn(MissionTables.MissionFlag.Terran, mission.flags) + self.assertIn(ItemNames.MARINE, item_names) + self.assertIn(ItemNames.MARAUDER, item_names) + self.assertIn(ItemNames.BUNKER, item_names) + self.assertIn(ItemNames.BANSHEE, item_names) + self.assertIn(ItemNames.BATTLECRUISER_ATX_LASER_BATTERY, item_names) + self.assertIn(ItemNames.NOVA_C20A_CANISTER_RIFLE, item_names) + self.assertGreaterEqual(item_names.count(ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS), 2) + self.assertGreaterEqual(item_names.count(ItemNames.PROGRESSIVE_TERRAN_SHIP_WEAPON), 3) + self.assertNotIn(ItemNames.MEDIC, item_names) + self.assertNotIn(ItemNames.PSI_DISRUPTER, item_names) + self.assertNotIn(ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS, item_names) + self.assertNotIn(ItemNames.HELLION_INFERNAL_PLATING, item_names) + self.assertNotIn(ItemNames.CELLULAR_REACTOR, item_names) + self.assertNotIn(ItemNames.TECH_REACTOR, item_names) + def test_starter_unit_populates_start_inventory(self): options = { 'enable_wol_missions': True, From b95f509dd02681a22ccd6f2b1d2510d33590fa1c Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 15:46:12 -0700 Subject: [PATCH 37/48] sc2: Adding '0 means select all' explanation to item dict option descriptions --- worlds/sc2/Options.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/worlds/sc2/Options.py b/worlds/sc2/Options.py index c973a578c84f..e549ef92d8c7 100644 --- a/worlds/sc2/Options.py +++ b/worlds/sc2/Options.py @@ -668,17 +668,20 @@ def __len__(self) -> int: class LockedItems(Sc2ItemDict): - """Guarantees that these items will be unlockable""" + """Guarantees that these items will be unlockable, in the amount specified. + Specify an amount of 0 to lock all copies of an item.""" display_name = "Locked Items" class ExcludedItems(Sc2ItemDict): - """Guarantees that these items will not be unlockable""" + """Guarantees that these items will not be unlockable, in the amount specified. + Specify an amount of 0 to exclude all copies of an item.""" display_name = "Excluded Items" class UnexcludedItems(Sc2ItemDict): - """Undoes an item exclusion; useful for whitelisting or fine-tuning a category.""" + """Undoes an item exclusion; useful for whitelisting or fine-tuning a category. + Specify an amount of 0 to unexclude all copies of an item.""" display_name = "Unexcluded Items" From 99383d171e75eb31cff161ae790a94fe79a1fd1c Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 15:47:12 -0700 Subject: [PATCH 38/48] sc2: Fixing variable name for NCO all item groups --- worlds/sc2/ItemGroups.py | 8 ++++---- worlds/sc2/test/test_generation.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index 8e900f1c57d8..f616dbc89808 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -91,9 +91,9 @@ class ItemGroupNames: NCO_BASELINE_UPGRADES = "NCO Baseline Upgrades" NCO_UPGRADES = "NCO Upgrades" NOVA_EQUIPMENT = "Nova Equipment" - NCO_MAX_PROGRESSIVE_UPGRADES = "NCO +Items" + NCO_MAX_PROGRESSIVE_ITEMS = "NCO +Items" """NCO item groups that should be set to maximum progressive amounts""" - NCO_MIN_PROGRESSIVE_UPGRADES = "NCO -Items" + NCO_MIN_PROGRESSIVE_ITEMS = "NCO -Items" """NCO item groups that should be set to minimum progressive amounts (1)""" TERRAN_BUILDINGS = "Terran Buildings" TERRAN_MERCENARIES = "Terran Mercenaries" @@ -336,8 +336,8 @@ class ItemGroupNames: ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, ] item_name_groups[ItemGroupNames.NCO_UPGRADES] = nco_upgrades = nco_baseline_upgrades + nco_unit_technology -item_name_groups[ItemGroupNames.NCO_MAX_PROGRESSIVE_UPGRADES] = nco_unit_technology + nova_equipment + terran_generic_upgrades -item_name_groups[ItemGroupNames.NCO_MIN_PROGRESSIVE_UPGRADES] = nco_units + nco_baseline_upgrades +item_name_groups[ItemGroupNames.NCO_MAX_PROGRESSIVE_ITEMS] = nco_unit_technology + nova_equipment + terran_generic_upgrades +item_name_groups[ItemGroupNames.NCO_MIN_PROGRESSIVE_ITEMS] = nco_units + nco_baseline_upgrades item_name_groups[ItemGroupNames.TERRAN_PROGRESSIVE_UPGRADES] = terran_progressive_items = [ item_name for item_name, item_data in Items.item_table.items() if item_data.type in (Items.TerranItemType.Progressive, Items.TerranItemType.Progressive_2) diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index 8dfd3c5331d4..8ddccdfa360c 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -202,8 +202,8 @@ def test_usecase_terran_with_nco_upgrades_units_only(self): ItemGroups.ItemGroupNames.TERRAN_ITEMS: 0, }, 'unexcluded_items': { - ItemGroups.ItemGroupNames.NCO_MAX_PROGRESSIVE_UPGRADES: 0, - ItemGroups.ItemGroupNames.NCO_MIN_PROGRESSIVE_UPGRADES: 1, + ItemGroups.ItemGroupNames.NCO_MAX_PROGRESSIVE_ITEMS: 0, + ItemGroups.ItemGroupNames.NCO_MIN_PROGRESSIVE_ITEMS: 1, }, } self.generate_world(options) From 3eacf9ca4bd9a14ff5f847336e1809f4900ee7b2 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 16:18:12 -0700 Subject: [PATCH 39/48] sc2: Consolidated display names for item groups ending with numbers; settings some duplicate groups to unlisted --- worlds/sc2/ItemGroups.py | 8 +++++++- worlds/sc2/Items.py | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index f616dbc89808..1dd559feee82 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -28,7 +28,13 @@ # These item name groups should not show up in documentation unlisted_item_name_groups = { - "Missions", "WoL Missions" + "Missions", "WoL Missions", + Items.TerranItemType.Progressive.display_name, + Items.TerranItemType.Nova_Gear.display_name, + Items.TerranItemType.Mercenary.display_name, + Items.ZergItemType.Ability.display_name, + Items.ZergItemType.Morph.display_name, + Items.ZergItemType.Strain.display_name, } # Some item names only differ in bracketed parts diff --git a/worlds/sc2/Items.py b/worlds/sc2/Items.py index a3351477d217..d399dc504108 100644 --- a/worlds/sc2/Items.py +++ b/worlds/sc2/Items.py @@ -63,18 +63,18 @@ class ZergItemType(ItemTypeEnum): class ProtossItemType(ItemTypeEnum): Unit = "Unit", 0 - Unit_2 = "Unit 2", 1 + Unit_2 = "Unit", 1 Upgrade = "Upgrade", 2 Building = "Building", 3 Progressive = "Progressive Upgrade", 4 Spear_Of_Adun = "Spear of Adun", 5 Solarite_Core = "Solarite Core", 6 """Protoss global effects, such as reconstruction beam or automated assimilators""" - Forge_1 = "Forge 1", 7 + Forge_1 = "Forge", 7 """General Protoss unit upgrades""" - Forge_2 = "Forge 2", 8 + Forge_2 = "Forge", 8 """General Protoss unit upgrades""" - Forge_3 = "Forge 3", 9 + Forge_3 = "Forge", 9 """General Protoss unit upgrades""" From 67e7c4792622a78648e045be0ff79c731b06cd6b Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 16:23:48 -0700 Subject: [PATCH 40/48] sc2: Made item group substitution case-insensitive --- worlds/sc2/Options.py | 4 +++- worlds/sc2/test/test_generation.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/worlds/sc2/Options.py b/worlds/sc2/Options.py index e549ef92d8c7..c340cefe034e 100644 --- a/worlds/sc2/Options.py +++ b/worlds/sc2/Options.py @@ -642,8 +642,10 @@ def from_any(cls, data: Union[List[str], Dict[str, int]]) -> 'Sc2ItemDict': def verify(self, world: Type['World'], player_name: str, plando_options: PlandoOptions) -> None: """Overridden version of function from Options.VerifyKeys for a better error message""" new_value: dict[str, int] = {} + case_insensitive_group_mapping = { + group_name.casefold(): group_value for group_name, group_value in world.item_name_groups.items()} for group_name in self.value: - item_names = world.item_name_groups.get(group_name, {group_name}) + item_names = case_insensitive_group_mapping.get(group_name.casefold(), {group_name}) for item_name in item_names: new_value[item_name] = new_value.get(item_name, 0) + self.value[group_name] self.value = new_value diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index 8ddccdfa360c..ab8e5a0079f6 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -116,7 +116,7 @@ def test_unexcludes_cancel_out_excludes(self): def test_excluding_groups_excludes_all_items_in_group(self): options = { 'excluded_items': [ - ItemGroups.ItemGroupNames.BARRACKS_UNITS, + ItemGroups.ItemGroupNames.BARRACKS_UNITS.lower(), ] } self.generate_world(options) From 8ea189242a9732a8047b9812028be03760308bb8 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 18:40:24 -0700 Subject: [PATCH 41/48] sc2: Split usecase unit tests from item filtering tests --- worlds/sc2/test/test_base.py | 41 ++++++++ worlds/sc2/test/test_generation.py | 148 +---------------------------- worlds/sc2/test/test_usecases.py | 116 ++++++++++++++++++++++ 3 files changed, 159 insertions(+), 146 deletions(-) create mode 100644 worlds/sc2/test/test_usecases.py diff --git a/worlds/sc2/test/test_base.py b/worlds/sc2/test/test_base.py index ceabfc2b95d3..894b126183d1 100644 --- a/worlds/sc2/test/test_base.py +++ b/worlds/sc2/test/test_base.py @@ -1,4 +1,11 @@ from typing import * +import unittest +import random +from argparse import Namespace +from BaseClasses import MultiWorld, CollectionState, PlandoOptions +from Generate import get_seed_name +from worlds import AutoWorld +from test.general import gen_steps, call_all from test.bases import WorldTestBase from .. import SC2World @@ -9,3 +16,37 @@ class Sc2TestBase(WorldTestBase): world: SC2World player: ClassVar[int] = 1 skip_long_tests: bool = True + + +class Sc2SetupTestBase(unittest.TestCase): + """ + A custom sc2-specific test base class that provides an explicit function to generate the world from options. + This allows potentially generating multiple worlds in one test case, useful for tracking down a rare / sporadic + crash. + """ + seed: Optional[int] = None + game = SC2World.game + player = 1 + def generate_world(self, options: Dict[str, Any]) -> None: + self.multiworld = MultiWorld(1) + self.multiworld.game[self.player] = self.game + self.multiworld.player_name = {self.player: "Tester"} + self.multiworld.set_seed(self.seed) + self.multiworld.state = CollectionState(self.multiworld) + random.seed(self.multiworld.seed) + self.multiworld.seed_name = get_seed_name(random) # only called to get same RNG progression as Generate.py + args = Namespace() + for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items(): + new_option = option.from_any(options.get(name, option.default)) + new_option.verify(SC2World, "Tester", PlandoOptions.items|PlandoOptions.connections|PlandoOptions.texts|PlandoOptions.bosses) + setattr(args, name, { + 1: new_option + }) + self.multiworld.set_options(args) + self.world: SC2World = cast(SC2World, self.multiworld.worlds[self.player]) + try: + for step in gen_steps: + call_all(self.multiworld, step) + except Exception as ex: + ex.add_note(f"Seed: {self.multiworld.seed}") + raise diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index ab8e5a0079f6..0ee11daa23a6 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -2,46 +2,11 @@ Unit tests for world generation """ from typing import * -import unittest -import random +from .test_base import Sc2SetupTestBase from .. import Options, MissionTables, ItemNames, Items, ItemGroups -from .. import get_all_missions, SC2World +from .. import get_all_missions -from BaseClasses import MultiWorld, CollectionState, PlandoOptions -from argparse import Namespace -from worlds import AutoWorld -from Generate import get_seed_name -from test.general import gen_steps, call_all - - -class Sc2SetupTestBase(unittest.TestCase): - seed: Optional[int] = None - game = SC2World.game - player = 1 - def generate_world(self, options: Dict[str, Any]) -> None: - self.multiworld = MultiWorld(1) - self.multiworld.game[self.player] = self.game - self.multiworld.player_name = {self.player: "Tester"} - self.multiworld.set_seed(self.seed) - self.multiworld.state = CollectionState(self.multiworld) - random.seed(self.multiworld.seed) - self.multiworld.seed_name = get_seed_name(random) # only called to get same RNG progression as Generate.py - args = Namespace() - for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items(): - new_option = option.from_any(options.get(name, option.default)) - new_option.verify(SC2World, "Tester", PlandoOptions.items|PlandoOptions.connections|PlandoOptions.texts|PlandoOptions.bosses) - setattr(args, name, { - 1: new_option - }) - self.multiworld.set_options(args) - self.world: SC2World = cast(SC2World, self.multiworld.worlds[self.player]) - try: - for step in gen_steps: - call_all(self.multiworld, step) - except Exception as ex: - ex.add_note(f"Seed: {self.multiworld.seed}") - raise class TestItemFiltering(Sc2SetupTestBase): def test_explicit_locks_excludes_interact_and_set_flags(self): @@ -144,89 +109,6 @@ def test_excluding_campaigns_excludes_campaign_specific_items(self) -> None: self.assertNotEqual(item_data.type, Items.TerranItemType.Nova_Gear) self.assertNotEqual(item_name, ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE) - def test_usecase_terran_with_nco_units_only(self): - options = { - 'enable_prophecy_missions': False, - 'enable_hots_missions': False, - 'enable_lotv_prologue_missions': False, - 'enable_lotv_missions': False, - 'enable_epilogue_missions': False, - 'excluded_items': { - ItemGroups.ItemGroupNames.TERRAN_UNITS: 0, - }, - 'unexcluded_items': { - ItemGroups.ItemGroupNames.NCO_UNITS: 0, - }, - } - self.generate_world(options) - self.assertTrue(self.multiworld.itempool) - item_names = [item.name for item in self.multiworld.itempool] - self.assertIn(ItemNames.MARINE, item_names) - self.assertIn(ItemNames.RAVEN, item_names) - self.assertIn(ItemNames.LIBERATOR, item_names) - self.assertIn(ItemNames.BATTLECRUISER, item_names) - self.assertNotIn(ItemNames.DIAMONDBACK, item_names) - self.assertNotIn(ItemNames.DIAMONDBACK_BURST_CAPACITORS, item_names) - self.assertNotIn(ItemNames.VIKING, item_names) - - def test_usecase_nco_with_nobuilds_excluded(self): - options = { - 'enable_wol_missions': False, - 'enable_prophecy_missions': False, - 'enable_hots_missions': False, - 'enable_lotv_prologue_missions': False, - 'enable_lotv_missions': False, - 'enable_epilogue_missions': False, - 'shuffle_no_build': Options.ShuffleNoBuild.option_false, - 'mission_order': Options.MissionOrder.option_mini_campaign, - } - self.generate_world(options) - self.assertTrue(self.multiworld.itempool) - missions = get_all_missions(self.world.mission_req_table) - self.assertNotIn(MissionTables.SC2Mission.THE_ESCAPE, missions) - self.assertNotIn(MissionTables.SC2Mission.IN_THE_ENEMY_S_SHADOW, missions) - for mission in missions: - self.assertEqual(MissionTables.SC2Campaign.NCO, mission.campaign) - - def test_usecase_terran_with_nco_upgrades_units_only(self): - options = { - 'enable_wol_missions': True, - 'enable_nco_missions': True, - 'enable_prophecy_missions': False, - 'enable_hots_missions': False, - 'enable_lotv_prologue_missions': False, - 'enable_lotv_missions': False, - 'enable_epilogue_missions': False, - 'mission_order': Options.MissionOrder.option_mini_campaign, - 'excluded_items': { - ItemGroups.ItemGroupNames.TERRAN_ITEMS: 0, - }, - 'unexcluded_items': { - ItemGroups.ItemGroupNames.NCO_MAX_PROGRESSIVE_ITEMS: 0, - ItemGroups.ItemGroupNames.NCO_MIN_PROGRESSIVE_ITEMS: 1, - }, - } - self.generate_world(options) - item_names = [item.name for item in self.multiworld.itempool] - self.assertTrue(item_names) - missions = get_all_missions(self.world.mission_req_table) - for mission in missions: - self.assertIn(MissionTables.MissionFlag.Terran, mission.flags) - self.assertIn(ItemNames.MARINE, item_names) - self.assertIn(ItemNames.MARAUDER, item_names) - self.assertIn(ItemNames.BUNKER, item_names) - self.assertIn(ItemNames.BANSHEE, item_names) - self.assertIn(ItemNames.BATTLECRUISER_ATX_LASER_BATTERY, item_names) - self.assertIn(ItemNames.NOVA_C20A_CANISTER_RIFLE, item_names) - self.assertGreaterEqual(item_names.count(ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS), 2) - self.assertGreaterEqual(item_names.count(ItemNames.PROGRESSIVE_TERRAN_SHIP_WEAPON), 3) - self.assertNotIn(ItemNames.MEDIC, item_names) - self.assertNotIn(ItemNames.PSI_DISRUPTER, item_names) - self.assertNotIn(ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS, item_names) - self.assertNotIn(ItemNames.HELLION_INFERNAL_PLATING, item_names) - self.assertNotIn(ItemNames.CELLULAR_REACTOR, item_names) - self.assertNotIn(ItemNames.TECH_REACTOR, item_names) - def test_starter_unit_populates_start_inventory(self): options = { 'enable_wol_missions': True, @@ -374,31 +256,6 @@ def test_vanilla_items_only_excludes_terran_progressives(self) -> None: occurrences[item_name] += 1 self.assertLessEqual(occurrences[item_name], 1, f"'{item_name}' unexpectedly appeared multiple times in the pool") - def test_nco_and_2_wol_missions_only_can_generate_with_vanilla_items_only(self) -> None: - options = { - 'enable_prophecy_missions': False, - 'enable_hots_missions': False, - 'enable_lotv_prologue_missions': False, - 'enable_lotv_missions': False, - 'enable_epilogue_missions': False, - 'excluded_missions': [ - mission.mission_name for mission in MissionTables.SC2Mission - if mission.campaign == MissionTables.SC2Campaign.WOL - and mission.mission_name not in (MissionTables.SC2Mission.LIBERATION_DAY.mission_name, MissionTables.SC2Mission.THE_OUTLAWS.mission_name) - ], - 'mission_order': Options.MissionOrder.option_grid, - 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, - 'accessibility': 'locations', - 'vanilla_items_only': True, - } - self.generate_world(options) - item_names = [item.name for item in self.multiworld.itempool] - self.assertTrue(item_names) - self.assertNotIn(ItemNames.LIBERATOR, item_names) - self.assertNotIn(ItemNames.MARAUDER_PROGRESSIVE_STIMPACK, item_names) - self.assertNotIn(ItemNames.HELLION_HELLBAT_ASPECT, item_names) - self.assertNotIn(ItemNames.BATTLECRUISER_CLOAK, item_names) - def test_evil_awoken_with_vanilla_items_only_generates(self) -> None: options = { 'enable_wol_missions': False, @@ -487,4 +344,3 @@ def test_lotv_only_doesnt_include_kerrigan_items_with_grant_story_tech(self) -> self.assertFalse(kerrigan_items_in_pool) kerrigan_passives_in_pool = set(ItemGroups.kerrigan_passives).intersection(item_names) self.assertFalse(kerrigan_passives_in_pool) - diff --git a/worlds/sc2/test/test_usecases.py b/worlds/sc2/test/test_usecases.py new file mode 100644 index 000000000000..627b0db224b5 --- /dev/null +++ b/worlds/sc2/test/test_usecases.py @@ -0,0 +1,116 @@ +""" +Unit tests for yaml usecases we want to support +""" + +from .test_base import Sc2SetupTestBase +from .. import ItemGroups, ItemNames, Options, MissionTables, get_all_missions + + +class TestSupportedUseCases(Sc2SetupTestBase): + def test_terran_with_nco_units_only_generates(self): + options = { + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'excluded_items': { + ItemGroups.ItemGroupNames.TERRAN_UNITS: 0, + }, + 'unexcluded_items': { + ItemGroups.ItemGroupNames.NCO_UNITS: 0, + }, + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + item_names = [item.name for item in self.multiworld.itempool] + self.assertIn(ItemNames.MARINE, item_names) + self.assertIn(ItemNames.RAVEN, item_names) + self.assertIn(ItemNames.LIBERATOR, item_names) + self.assertIn(ItemNames.BATTLECRUISER, item_names) + self.assertNotIn(ItemNames.DIAMONDBACK, item_names) + self.assertNotIn(ItemNames.DIAMONDBACK_BURST_CAPACITORS, item_names) + self.assertNotIn(ItemNames.VIKING, item_names) + + def test_nco_with_nobuilds_excluded_generates(self): + options = { + 'enable_wol_missions': False, + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'shuffle_no_build': Options.ShuffleNoBuild.option_false, + 'mission_order': Options.MissionOrder.option_mini_campaign, + } + self.generate_world(options) + self.assertTrue(self.multiworld.itempool) + missions = get_all_missions(self.world.mission_req_table) + self.assertNotIn(MissionTables.SC2Mission.THE_ESCAPE, missions) + self.assertNotIn(MissionTables.SC2Mission.IN_THE_ENEMY_S_SHADOW, missions) + for mission in missions: + self.assertEqual(MissionTables.SC2Campaign.NCO, mission.campaign) + + def test_terran_with_nco_upgrades_units_only_generates(self): + options = { + 'enable_wol_missions': True, + 'enable_nco_missions': True, + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'mission_order': Options.MissionOrder.option_mini_campaign, + 'excluded_items': { + ItemGroups.ItemGroupNames.TERRAN_ITEMS: 0, + }, + 'unexcluded_items': { + ItemGroups.ItemGroupNames.NCO_MAX_PROGRESSIVE_ITEMS: 0, + ItemGroups.ItemGroupNames.NCO_MIN_PROGRESSIVE_ITEMS: 1, + }, + } + self.generate_world(options) + item_names = [item.name for item in self.multiworld.itempool] + self.assertTrue(item_names) + missions = get_all_missions(self.world.mission_req_table) + for mission in missions: + self.assertIn(MissionTables.MissionFlag.Terran, mission.flags) + self.assertIn(ItemNames.MARINE, item_names) + self.assertIn(ItemNames.MARAUDER, item_names) + self.assertIn(ItemNames.BUNKER, item_names) + self.assertIn(ItemNames.BANSHEE, item_names) + self.assertIn(ItemNames.BATTLECRUISER_ATX_LASER_BATTERY, item_names) + self.assertIn(ItemNames.NOVA_C20A_CANISTER_RIFLE, item_names) + self.assertGreaterEqual(item_names.count(ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS), 2) + self.assertGreaterEqual(item_names.count(ItemNames.PROGRESSIVE_TERRAN_SHIP_WEAPON), 3) + self.assertNotIn(ItemNames.MEDIC, item_names) + self.assertNotIn(ItemNames.PSI_DISRUPTER, item_names) + self.assertNotIn(ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS, item_names) + self.assertNotIn(ItemNames.HELLION_INFERNAL_PLATING, item_names) + self.assertNotIn(ItemNames.CELLULAR_REACTOR, item_names) + self.assertNotIn(ItemNames.TECH_REACTOR, item_names) + + def test_nco_and_2_wol_missions_only_can_generate_with_vanilla_items_only(self) -> None: + options = { + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'excluded_missions': [ + mission.mission_name for mission in MissionTables.SC2Mission + if mission.campaign == MissionTables.SC2Campaign.WOL + and mission.mission_name not in (MissionTables.SC2Mission.LIBERATION_DAY.mission_name, MissionTables.SC2Mission.THE_OUTLAWS.mission_name) + ], + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'accessibility': 'locations', + 'vanilla_items_only': True, + } + self.generate_world(options) + item_names = [item.name for item in self.multiworld.itempool] + self.assertTrue(item_names) + self.assertNotIn(ItemNames.LIBERATOR, item_names) + self.assertNotIn(ItemNames.MARAUDER_PROGRESSIVE_STIMPACK, item_names) + self.assertNotIn(ItemNames.HELLION_HELLBAT_ASPECT, item_names) + self.assertNotIn(ItemNames.BATTLECRUISER_CLOAK, item_names) From 52fda6f9c948bfae71aa64cdf516fea06a290f97 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 19:13:20 -0700 Subject: [PATCH 42/48] sc2: Added more usecase tests; fixed an issue that was spuriously putting filler items in start inventory --- worlds/sc2/__init__.py | 12 ++++++--- worlds/sc2/test/test_usecases.py | 42 +++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index 8644f8c2c7e8..5b443a701b28 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -191,13 +191,12 @@ def resolve_count(count: Optional[int], max_count: int) -> int: return max_count if count is None: return 0 + if max_count == 0: + return count return min(count, max_count) result: List[FilterItem] = [] for item_name, item_data in Items.item_table.items(): - if not item_data.quantity: - result.append(FilterItem(item_name, item_data, 0, ItemFilterFlags.StartInventory)) - continue max_count = item_data.quantity excluded_count = excluded_items.get(item_name) unexcluded_count = unexcluded_items.get(item_name) @@ -214,7 +213,12 @@ def resolve_count(count: Optional[int], max_count: int) -> int: excluded_count = max(0, excluded_count - unexcluded_count) # Priority: start_inventory >> locked_items >> excluded_items >> unspecified - if start_count > max_count: + if max_count == 0: + if excluded_count: + logger.warning(f"Item {item_name} was listed as excluded, but as a filler item, it cannot be explicitly excluded.") + excluded_count = 0 + max_count = start_count + locked_count + elif start_count > max_count: logger.warning(f"Item {item_name} had start amount greater than maximum amount ({start_count} > {max_count}). Capping start amount to max.") start_count = max_count locked_count = 0 diff --git a/worlds/sc2/test/test_usecases.py b/worlds/sc2/test/test_usecases.py index 627b0db224b5..b3a7cda11753 100644 --- a/worlds/sc2/test/test_usecases.py +++ b/worlds/sc2/test/test_usecases.py @@ -3,7 +3,7 @@ """ from .test_base import Sc2SetupTestBase -from .. import ItemGroups, ItemNames, Options, MissionTables, get_all_missions +from .. import ItemGroups, ItemNames, Items, Options, MissionTables, get_all_missions class TestSupportedUseCases(Sc2SetupTestBase): @@ -114,3 +114,43 @@ def test_nco_and_2_wol_missions_only_can_generate_with_vanilla_items_only(self) self.assertNotIn(ItemNames.MARAUDER_PROGRESSIVE_STIMPACK, item_names) self.assertNotIn(ItemNames.HELLION_HELLBAT_ASPECT, item_names) self.assertNotIn(ItemNames.BATTLECRUISER_CLOAK, item_names) + + def test_free_protoss_only_generates(self) -> None: + options = { + 'enable_wol_missions': False, + 'enable_nco_missions': False, + 'enable_prophecy_missions': True, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': True, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + # todo(mm): Currently, these settings don't generate on grid because there are not enough EASY missions + 'mission_order': Options.MissionOrder.option_vanilla_shuffled, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'accessibility': 'locations', + } + self.generate_world(options) + item_names = [item.name for item in self.multiworld.itempool] + self.assertTrue(item_names) + missions = get_all_missions(self.world.mission_req_table) + self.assertEqual(len(missions), 7, "Wrong number of missions in free protoss seed") + for mission in missions: + self.assertIn(mission.campaign, (MissionTables.SC2Campaign.PROLOGUE, MissionTables.SC2Campaign.PROPHECY)) + for item_name in item_names: + self.assertIn(Items.item_table[item_name].race, (MissionTables.SC2Race.ANY, MissionTables.SC2Race.PROTOSS)) + + def test_resource_filler_items_may_be_put_in_start_inventory(self) -> None: + NUM_RESOURCE_ITEMS = 10 + options = { + 'start_inventory': { + ItemNames.STARTING_MINERALS: NUM_RESOURCE_ITEMS, + ItemNames.STARTING_VESPENE: NUM_RESOURCE_ITEMS, + ItemNames.STARTING_SUPPLY: NUM_RESOURCE_ITEMS, + }, + } + self.generate_world(options) + start_item_names = [item.name for item in self.multiworld.precollected_items[self.player]] + self.assertEqual(start_item_names.count(ItemNames.STARTING_MINERALS), NUM_RESOURCE_ITEMS, "Wrong number of starting minerals in starting inventory") + self.assertEqual(start_item_names.count(ItemNames.STARTING_VESPENE), NUM_RESOURCE_ITEMS, "Wrong number of starting vespene in starting inventory") + self.assertEqual(start_item_names.count(ItemNames.STARTING_SUPPLY), NUM_RESOURCE_ITEMS, "Wrong number of starting supply in starting inventory") + From eff8650e1e7ea3ddad7267165ae1559f1aeb1bd7 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 2 May 2024 20:01:44 -0700 Subject: [PATCH 43/48] sc2: Added a unit test specifying the design constraints of vanilla_items_only --- worlds/sc2/test/test_generation.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index 0ee11daa23a6..4960d0f80ed5 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -256,6 +256,21 @@ def test_vanilla_items_only_excludes_terran_progressives(self) -> None: occurrences[item_name] += 1 self.assertLessEqual(occurrences[item_name], 1, f"'{item_name}' unexpectedly appeared multiple times in the pool") + def test_vanilla_items_only_includes_only_nova_equipment_and_vanilla_and_filler_items(self) -> None: + options = { + 'mission_order': Options.MissionOrder.option_grid, + 'maximum_campaign_size': Options.MaximumCampaignSize.range_end, + 'accessibility': 'locations', + 'vanilla_items_only': True, + } + self.generate_world(options) + items = [(item.name, Items.item_table[item.name]) for item in self.multiworld.itempool] + self.assertTrue(items) + for item_name, item_data in items: + if item_data.quantity == 0: + continue + self.assertIn(item_name, ItemGroups.vanilla_items + ItemGroups.nova_equipment) + def test_evil_awoken_with_vanilla_items_only_generates(self) -> None: options = { 'enable_wol_missions': False, From 0828205d80bc394c5d7cc220f414e774c0ab9ff1 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 5 May 2024 20:49:09 -0700 Subject: [PATCH 44/48] sc2: Set stalker upgrades to useful instead of progression; removed unused imports --- worlds/sc2/Items.py | 4 ++-- worlds/sc2/PoolFilter.py | 9 ++++----- worlds/sc2/Regions.py | 3 +-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/worlds/sc2/Items.py b/worlds/sc2/Items.py index d399dc504108..1e193dd80473 100644 --- a/worlds/sc2/Items.py +++ b/worlds/sc2/Items.py @@ -1443,8 +1443,8 @@ def get_full_item_list(): ItemNames.ADEPT_SHOCKWAVE: ItemData(303 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 3, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), ItemNames.ADEPT_RESONATING_GLAIVES: ItemData(304 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 4, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), ItemNames.ADEPT_PHASE_BULWARK: ItemData(305 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), - ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES: ItemData(306 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 6, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), - ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION: ItemData(307 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 7, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), + ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES: ItemData(306 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 6, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.useful), + ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION: ItemData(307 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 7, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.useful), ItemNames.DRAGOON_HIGH_IMPACT_PHASE_DISRUPTORS: ItemData(308 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 8, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), ItemNames.DRAGOON_TRILLIC_COMPRESSION_SYSTEM: ItemData(309 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), ItemNames.DRAGOON_SINGULARITY_CHARGE: ItemData(310 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_1, 10, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.DRAGOON), diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py index a88b582094ee..1a88599966fa 100644 --- a/worlds/sc2/PoolFilter.py +++ b/worlds/sc2/PoolFilter.py @@ -1,16 +1,15 @@ from typing import Callable, Dict, List, Set, Union, Tuple, Optional, TYPE_CHECKING from BaseClasses import Item, Location from .Items import (get_full_item_list, spider_mine_sources, second_pass_placeable_items, - spear_of_adun_calldowns, spear_of_adun_castable_passives, upgrade_item_types, ) -from .MissionTables import (mission_orders, MissionInfo, MissionPools, MissionFlag, +from .MissionTables import (MissionInfo, MissionPools, get_campaign_goal_priority, campaign_final_mission_locations, campaign_alt_final_mission_locations, - SC2Campaign, SC2Race, SC2CampaignGoalPriority, SC2Mission, + SC2Campaign, SC2CampaignGoalPriority, SC2Mission, ) from .Options import (get_option_value, MissionOrder, - get_enabled_campaigns, get_disabled_campaigns, RequiredTactics, kerrigan_unit_available, GrantStoryTech, - TakeOverAIAllies, SpearOfAdunPresence, SpearOfAdunAutonomouslyCastAbilityPresence, campaign_depending_orders, + get_enabled_campaigns, RequiredTactics, kerrigan_unit_available, GrantStoryTech, + TakeOverAIAllies, campaign_depending_orders, ShuffleCampaigns, get_excluded_missions, ShuffleNoBuild, ExtraLocations, GrantStoryLevels, ) from . import ItemNames, ItemGroups diff --git a/worlds/sc2/Regions.py b/worlds/sc2/Regions.py index c0cdd4ef6632..db22c7deccfa 100644 --- a/worlds/sc2/Regions.py +++ b/worlds/sc2/Regions.py @@ -1,14 +1,13 @@ from typing import List, Dict, Tuple, Optional, Callable, NamedTuple, Union, TYPE_CHECKING import math -from BaseClasses import MultiWorld, Region, Entrance, Location, CollectionState +from BaseClasses import Region, Entrance, Location, CollectionState from .Locations import LocationData from .Options import get_option_value, MissionOrder, get_enabled_campaigns, campaign_depending_orders, \ GridTwoStartPositions from .MissionTables import MissionInfo, mission_orders, vanilla_mission_req_table, \ MissionPools, SC2Campaign, get_goal_location, SC2Mission, MissionConnection from .PoolFilter import filter_missions -from worlds.AutoWorld import World if TYPE_CHECKING: From 8b3465b358d78d2a8d91cf4b6e871a47a6cba1b4 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 6 May 2024 20:54:55 -0700 Subject: [PATCH 45/48] sc2: Split off item groups sanity asserts into a unit test file --- worlds/sc2/ItemGroups.py | 20 ------------------- worlds/sc2/test/test_itemgroups.py | 32 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 20 deletions(-) create mode 100644 worlds/sc2/test/test_itemgroups.py diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index 1dd559feee82..93cfbba4cd43 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -539,23 +539,3 @@ class ItemGroupNames: item_name_groups[ItemGroupNames.VANILLA_ITEMS] = vanilla_items = ( vanilla_wol_items + vanilla_hots_items + vanilla_lotv_items ) - - -# Sanity checks -def _sanity_check() -> None: - all_progressive_upgrades = [ - item_name for item_name, item_data in Items.item_table.items() - if item_data.type.display_name == Items.TerranItemType.Progressive.display_name - ] - assert len(terran_units) == len(barracks_units) + len(factory_units) + len(starport_units) + len(terran_mercenaries) - assert len(protoss_units) == len(gateway_units) + len(robo_units) + len(stargate_units) - for item_name in terran_original_progressive_upgrades: - assert item_name in all_progressive_upgrades - assert item_name in wol_upgrades - for item_name in terran_stimpacks: - assert "Stimpack" in item_name - for var_name, display_name in ItemGroupNames.__dict__.items(): - if var_name.startswith("_"): - continue - assert display_name in item_name_groups -_sanity_check() diff --git a/worlds/sc2/test/test_itemgroups.py b/worlds/sc2/test/test_itemgroups.py new file mode 100644 index 000000000000..ba1181bd929b --- /dev/null +++ b/worlds/sc2/test/test_itemgroups.py @@ -0,0 +1,32 @@ +""" +Unit tests for ItemGroups.py +""" + +import unittest +from .. import ItemGroups, Items + +class ItemGroupsUnitTests(unittest.TestCase): + def test_all_production_structure_groups_capture_all_units(self) -> None: + self.assertCountEqual( + ItemGroups.terran_units, + ItemGroups.barracks_units + ItemGroups.factory_units + ItemGroups.starport_units + ItemGroups.terran_mercenaries + ) + self.assertCountEqual( + ItemGroups.protoss_units, + ItemGroups.gateway_units + ItemGroups.robo_units + ItemGroups.stargate_units + ) + + def test_terran_original_progressive_group_fully_contained_in_wol_upgrades(self) -> None: + for item_name in ItemGroups.terran_original_progressive_upgrades: + self.assertIn(Items.item_table[item_name].type, (Items.TerranItemType.Progressive, Items.TerranItemType.Progressive_2), f"{item_name} is not progressive") + self.assertIn(item_name, ItemGroups.wol_upgrades) + + def test_all_items_in_stimpack_group_are_stimpacks(self) -> None: + for item_name in ItemGroups.terran_stimpacks: + self.assertIn("Stimpack", item_name) + + def test_all_item_group_names_have_a_group_defined(self) -> None: + for var_name, display_name in ItemGroups.ItemGroupNames.__dict__.items(): + if var_name.startswith("_"): + continue + assert display_name in ItemGroups.item_name_groups From 63542ce4dc7eb11571b4ea81a87bf12d24d0cc31 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 6 May 2024 20:56:34 -0700 Subject: [PATCH 46/48] sc2: PR review comments; formatting cleanups --- worlds/sc2/ItemGroups.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index 93cfbba4cd43..58a6408a1574 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -321,8 +321,10 @@ class ItemGroupNames: ItemNames.GOLIATH_JUMP_JETS, ItemNames.GOLIATH_OPTIMIZED_LOGISTICS, ItemNames.GOLIATH_MULTI_LOCK_WEAPONS_SYSTEM, - ItemNames.SIEGE_TANK_SPIDER_MINES, ItemNames.SIEGE_TANK_JUMP_JETS, - ItemNames.SIEGE_TANK_INTERNAL_TECH_MODULE, ItemNames.SIEGE_TANK_SMART_SERVOS, + ItemNames.SIEGE_TANK_SPIDER_MINES, + ItemNames.SIEGE_TANK_JUMP_JETS, + ItemNames.SIEGE_TANK_INTERNAL_TECH_MODULE, + ItemNames.SIEGE_TANK_SMART_SERVOS, # Tanks can't get Laser targeting system in NCO ItemNames.BANSHEE_INTERNAL_TECH_MODULE, ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS, @@ -484,8 +486,8 @@ class ItemGroupNames: item_name_groups[ItemGroupNames.STARGATE_UNITS] = stargate_units = [ ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, ItemNames.VOID_RAY, ItemNames.DESTROYER, - ItemNames.SCOUT, ItemNames.TEMPEST, - ItemNames.CARRIER, ItemNames.MOTHERSHIP, + ItemNames.SCOUT, ItemNames.MOTHERSHIP, + ItemNames.CARRIER, ItemNames.TEMPEST, ItemNames.ARBITER, ItemNames.ORACLE, ] item_name_groups[ItemGroupNames.PROTOSS_BUILDINGS] = protoss_buildings = [ From 82c285147e842ee4741a8752c474a307bd3397c9 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 7 May 2024 18:53:09 -0700 Subject: [PATCH 47/48] sc2: Reformatting stargate unit group in itemgroups --- worlds/sc2/ItemGroups.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index 58a6408a1574..34f93599ebad 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -486,8 +486,10 @@ class ItemGroupNames: item_name_groups[ItemGroupNames.STARGATE_UNITS] = stargate_units = [ ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, ItemNames.VOID_RAY, ItemNames.DESTROYER, - ItemNames.SCOUT, ItemNames.MOTHERSHIP, - ItemNames.CARRIER, ItemNames.TEMPEST, + ItemNames.SCOUT, + ItemNames.CARRIER, + ItemNames.TEMPEST, + ItemNames.MOTHERSHIP, ItemNames.ARBITER, ItemNames.ORACLE, ] item_name_groups[ItemGroupNames.PROTOSS_BUILDINGS] = protoss_buildings = [ From 0e48aa6c9db02ea9897b41bf25e021459fbaa8c0 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 9 May 2024 19:55:29 -0700 Subject: [PATCH 48/48] sc2: re-grouped stargate units --- worlds/sc2/ItemGroups.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py index 34f93599ebad..c9ca50c27753 100644 --- a/worlds/sc2/ItemGroups.py +++ b/worlds/sc2/ItemGroups.py @@ -486,10 +486,8 @@ class ItemGroupNames: item_name_groups[ItemGroupNames.STARGATE_UNITS] = stargate_units = [ ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, ItemNames.VOID_RAY, ItemNames.DESTROYER, - ItemNames.SCOUT, ItemNames.CARRIER, - ItemNames.TEMPEST, - ItemNames.MOTHERSHIP, + ItemNames.TEMPEST, ItemNames.SCOUT, ItemNames.MOTHERSHIP, ItemNames.ARBITER, ItemNames.ORACLE, ] item_name_groups[ItemGroupNames.PROTOSS_BUILDINGS] = protoss_buildings = [