diff --git a/worlds/sc2/Client.py b/worlds/sc2/Client.py index 78ce7902a5c7..846af6582924 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,10 +46,13 @@ 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, 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, \ - 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 @@ -212,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 @@ -801,7 +810,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 +826,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 +852,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, @@ -894,7 +906,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) 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/ItemGroups.py b/worlds/sc2/ItemGroups.py index a77fb920f64d..c9ca50c27753 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. @@ -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 @@ -47,15 +53,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(' (')] @@ -77,24 +76,468 @@ # Hand-made groups -item_name_groups["Aiur"] = [ +class ItemGroupNames: + TERRAN_ITEMS = "Terran Items" + """All Terran items""" + TERRAN_UNITS = "Terran Units" + TERRAN_GENERIC_UPGRADES = "Terran Generic Upgrades" + """+attack/armour upgrades""" + 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" + 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_ITEMS = "NCO +Items" + """NCO item groups that should be set to maximum progressive amounts""" + 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" + 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""" + + 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)""" + 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" + + 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" + 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_UNITS = "Aiur" + NERAZIM_UNITS = "Nerazim" + TAL_DARIM_UNITS = "Tal'Darim" + PURIFIER_UNITS = "Purifier" + + VANILLA_ITEMS = "Vanilla Items" + + +# 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, +] +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.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.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, + 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() + 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.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_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) +] +item_name_groups[ItemGroupNames.WOL_ITEMS] = vanilla_wol_items = ( + wol_units + + 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, + 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 + + 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, + 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, + 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] = 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] = stargate_units = [ + ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, + ItemNames.VOID_RAY, ItemNames.DESTROYER, + ItemNames.CARRIER, + ItemNames.TEMPEST, ItemNames.SCOUT, 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_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["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["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["Purifier"] = [ +item_name_groups[ItemGroupNames.PURIFIER_UNITS] = [ ItemNames.SENTINEL, ItemNames.ADEPT, ItemNames.INSTIGATOR, ItemNames.ENERGIZER, ItemNames.COLOSSUS, ItemNames.DISRUPTOR, ItemNames.MIRAGE, ItemNames.TEMPEST, -] \ No newline at end of file +] +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 + + protoss_generic_upgrades +) + +item_name_groups[ItemGroupNames.VANILLA_ITEMS] = vanilla_items = ( + vanilla_wol_items + vanilla_hots_items + vanilla_lotv_items +) diff --git a/worlds/sc2/Items.py b/worlds/sc2/Items.py index cc0670ed92cf..1e193dd80473 100644 --- a/worlds/sc2/Items.py +++ b/worlds/sc2/Items.py @@ -1,18 +1,102 @@ -import inspect -from pydoc import describe +from typing import * 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 from . import ItemNames from worlds.AutoWorld import World +if TYPE_CHECKING: + from . import SC2World + + +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 + """General Terran unit upgrades""" + 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 + """Kerrigan abilities""" + Mutation_1 = "Mutation", 1 + Strain = "Strain", 2 + Morph = "Morph", 3 + Upgrade = "Upgrade", 4 + 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 + + +class ProtossItemType(ItemTypeEnum): + Unit = "Unit", 0 + 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", 7 + """General Protoss unit upgrades""" + Forge_2 = "Forge", 8 + """General Protoss unit upgrades""" + Forge_3 = "Forge", 9 + """General Protoss unit upgrades""" + + +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 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 +128,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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, TerranItemType.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, 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, "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, 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, "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, 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, "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, "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, "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, "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, "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, "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, "Progressive Upgrade", 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, "Progressive Upgrade", 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, "Progressive Upgrade", 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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "Progressive Upgrade", 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, "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, "Progressive Upgrade", 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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "Progressive Upgrade", 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, "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, "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, "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, "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, "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, "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, "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, "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, "Progressive Upgrade", 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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "Progressive Upgrade", 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, "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, "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, "Progressive Upgrade 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, "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, "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, "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, "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, "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, "Progressive Upgrade", 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, "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, "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, "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, "Progressive Upgrade", 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, "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, "Progressive Upgrade 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, "Progressive Upgrade", 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, "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, "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, "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, "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, "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, "Progressive Upgrade", 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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "Progressive Upgrade", 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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "Progressive Upgrade", 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, "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, "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, "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, "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, "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, "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, "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, "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, "Building", 2, SC2Race.TERRAN), + ItemData(402 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Building, 2, SC2Race.TERRAN), ItemNames.WAR_PIGS: - ItemData(500 + SC2WOL_ITEM_ID_OFFSET, "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, "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, "Mercenary", 2, SC2Race.TERRAN), + ItemData(502 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 2, SC2Race.TERRAN), ItemNames.SPARTAN_COMPANY: - ItemData(503 + SC2WOL_ITEM_ID_OFFSET, "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, "Mercenary", 4, SC2Race.TERRAN), + ItemData(504 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 4, SC2Race.TERRAN), ItemNames.HELS_ANGELS: - ItemData(505 + SC2WOL_ITEM_ID_OFFSET, "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, "Mercenary", 6, SC2Race.TERRAN), + ItemData(506 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 6, SC2Race.TERRAN), ItemNames.JACKSONS_REVENGE: - ItemData(507 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 7, SC2Race.TERRAN), + ItemData(507 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Mercenary, 7, SC2Race.TERRAN), ItemNames.SKIBIS_ANGELS: - ItemData(508 + SC2WOL_ITEM_ID_OFFSET, "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, "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, "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, "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, "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, "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, "Laboratory", 0, SC2Race.TERRAN), + ItemData(600 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 0, SC2Race.TERRAN), ItemNames.VANADIUM_PLATING: - ItemData(601 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 1, SC2Race.TERRAN), + ItemData(601 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 1, SC2Race.TERRAN), ItemNames.ORBITAL_DEPOTS: - ItemData(602 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 2, SC2Race.TERRAN), + ItemData(602 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 2, SC2Race.TERRAN), ItemNames.MICRO_FILTERING: - ItemData(603 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 3, SC2Race.TERRAN), + ItemData(603 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 3, SC2Race.TERRAN), ItemNames.AUTOMATED_REFINERY: - ItemData(604 + SC2WOL_ITEM_ID_OFFSET, "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, "Laboratory", 5, SC2Race.TERRAN), + ItemData(605 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 5, SC2Race.TERRAN), ItemNames.RAVEN: - ItemData(606 + SC2WOL_ITEM_ID_OFFSET, "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, "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, "Laboratory", 6, SC2Race.TERRAN), + ItemData(608 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 6, SC2Race.TERRAN), ItemNames.ORBITAL_STRIKE: - ItemData(609 + SC2WOL_ITEM_ID_OFFSET, "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, "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, "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, "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, "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, "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, "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, "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, "Progressive Upgrade", 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, "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, "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, "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, "Laboratory", 10, SC2Race.TERRAN), + ItemData(621 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Laboratory, 10, SC2Race.TERRAN), ItemNames.ADVANCED_OPTICS: - ItemData(622 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 11, SC2Race.TERRAN), + ItemData(622 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.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, TerranItemType.Laboratory, 12, SC2Race.TERRAN, origin={"ext"}), ItemNames.ZEALOT: - ItemData(700 + SC2WOL_ITEM_ID_OFFSET, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "Nothing Group", 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, "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, "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, "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, "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, "Progressive Upgrade 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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, 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, "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, 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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, 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, "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, 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, "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, 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, "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, "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, 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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, 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, "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, 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, "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, 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, "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, 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.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), + 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, "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, "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, "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, "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, "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, "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, "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, 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, "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, 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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "Solarite Core", 12, SC2Race.PROTOSS, origin={"ext"}), + ItemData(812 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Solarite_Core, 12, SC2Race.PROTOSS, origin={"ext"}), } @@ -1587,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] @@ -1638,11 +1722,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 == "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 ("Level", "Ability", "Evolution Pit", "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, @@ -1714,8 +1798,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 == ZergItemType.Level and item_data.race == SC2Race.ZERG +] spider_mine_sources = { ItemNames.VULTURE, @@ -1724,23 +1810,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}, @@ -1788,7 +1857,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 == TerranItemType.Nova_Gear], ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE } @@ -1868,55 +1937,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: { - "Minerals": 0, - "Vespene": 1, - "Supply": 2, - "Goal": 3, - "Nothing Group": 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, - }, - 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 - }, - 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, - } -} +upgrade_item_types = (TerranItemType.Upgrade, ZergItemType.Upgrade, ProtossItemType.Upgrade) diff --git a/worlds/sc2/Locations.py b/worlds/sc2/Locations.py index bf9c06fa3f78..d476ff243e08 100644 --- a/worlds/sc2/Locations.py +++ b/worlds/sc2/Locations.py @@ -1,14 +1,18 @@ from enum import IntEnum -from typing import List, Tuple, Optional, Callable, NamedTuple, Set, Any -from BaseClasses import MultiWorld +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 from worlds.AutoWorld import World +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 SC2LOTV_LOC_ID_OFFSET = SC2HOTS_LOC_ID_OFFSET + 2000 @@ -30,12 +34,12 @@ class LocationType(IntEnum): class LocationData(NamedTuple): region: str name: str - code: Optional[int] + code: int type: LocationType - rule: Optional[Callable[[Any], bool]] = Location.access_rule + rule: Callable[['CollectionState'], bool] = Location.access_rule -def get_location_types(world: World, inclusion_type: LocationInclusion) -> Set[LocationType]: +def get_location_types(world: 'SC2World', inclusion_type: int) -> Set[LocationType]: """ :param multiworld: @@ -75,15 +79,20 @@ 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') + if world is None: + logic_level = int(RequiredTactics.default) + kerriganless = False + else: + logic_level = world.options.required_tactics.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), @@ -457,8 +466,8 @@ def get_locations(world: Optional[World]) -> 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, @@ -1087,8 +1096,7 @@ def get_locations(world: Optional[World]) -> 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 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), @@ -1578,7 +1586,7 @@ def get_locations(world: Optional[World]) -> 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)) ) @@ -1618,7 +1626,7 @@ def get_locations(world: Optional[World]) -> 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/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/Options.py b/worlds/sc2/Options.py index e599512d894d..c340cefe034e 100644 --- a/worlds/sc2/Options.py +++ b/worlds/sc2/Options.py @@ -1,10 +1,16 @@ 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 + +if TYPE_CHECKING: + from worlds.AutoWorld import World + from . import SC2World class GameDifficulty(Choice): @@ -297,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 @@ -380,7 +370,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,16 +610,83 @@ class TakeOverAIAllies(Toggle): display_name = "Take Over AI Allies" -class LockedItems(ItemSet): - """Guarantees that these items will be unlockable""" +class Sc2ItemDict(Option[Dict[str, int]], VerifyKeys, Mapping[str, int]): + """A branch of ItemDict that supports item counts of 0""" + default = {} + 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""" + 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 = 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 + for item_name in self.value: + if item_name not in world.item_names: + 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)") + + 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, in the amount specified. + Specify an amount of 0 to lock all copies of an item.""" display_name = "Locked Items" -class ExcludedItems(ItemSet): - """Guarantees that these items will not be unlockable""" +class ExcludedItems(Sc2ItemDict): + """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. + Specify an amount of 0 to unexclude all copies of an item.""" + display_name = "Unexcluded Items" + + class ExcludedMissions(OptionSet): """Guarantees that these missions will not appear in the campaign Doesn't apply to vanilla mission order. @@ -813,11 +869,10 @@ 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 - nco_items: NovaCovertOpsItems - bw_items: BroodWarItems - ext_items: ExtendedItems + vanilla_items_only: VanillaItemsOnly vanilla_locations: VanillaLocations extra_locations: ExtraLocations challenge_locations: ChallengeLocations @@ -827,7 +882,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 @@ -837,7 +892,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) @@ -856,7 +911,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) @@ -864,23 +919,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.value 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 ) ) ): @@ -906,4 +961,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/PoolFilter.py b/worlds/sc2/PoolFilter.py index c2cda5c542e8..1a88599966fa 100644 --- a/worlds/sc2/PoolFilter.py +++ b/worlds/sc2/PoolFilter.py @@ -1,42 +1,35 @@ -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 -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 .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 +from .Items import (get_full_item_list, spider_mine_sources, second_pass_placeable_items, + upgrade_item_types, +) +from .MissionTables import (MissionInfo, MissionPools, + get_campaign_goal_priority, campaign_final_mission_locations, campaign_alt_final_mission_locations, + SC2Campaign, SC2CampaignGoalPriority, SC2Mission, +) +from .Options import (get_option_value, MissionOrder, + get_enabled_campaigns, RequiredTactics, kerrigan_unit_available, GrantStoryTech, + TakeOverAIAllies, campaign_depending_orders, + ShuffleCampaigns, get_excluded_missions, ShuffleNoBuild, ExtraLocations, GrantStoryLevels, +) +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 = { - 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, -} +BARRACKS_UNITS = set(ItemGroups.barracks_units) +FACTORY_UNITS = set(ItemGroups.factory_units) +STARPORT_UNITS = set(ItemGroups.starport_units) -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 """ - 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) @@ -75,7 +68,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) @@ -210,32 +202,10 @@ def get_item_upgrades(inventory: List[Item], parent_item: Union[Item, str]) -> L ] -def get_item_quantity(item: Item, world: World): - 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) -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): @@ -276,15 +246,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) - for _ in range(get_item_quantity(item, self.world)): - locked_items.append(copy_item(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 @@ -555,40 +528,25 @@ 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 - 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() # Inventory restrictiveness based on number of missions with checks - mission_count = num_missions(world) + 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: 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.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 @@ -597,8 +555,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() @@ -607,7 +563,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. @@ -615,48 +571,13 @@ def filter_items(world: World, mission_req_table: Dict[SC2Campaign, Dict[str, Mi """ 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) 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: World) -> 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) - - # 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 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]) - - 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/Regions.py b/worlds/sc2/Regions.py index 84830a9a32bd..db22c7deccfa 100644 --- a/worlds/sc2/Regions.py +++ b/worlds/sc2/Regions.py @@ -1,14 +1,17 @@ -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 +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 . import SC2World class SC2MissionSlot(NamedTuple): @@ -17,7 +20,7 @@ class SC2MissionSlot(NamedTuple): 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 +39,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 +271,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]: @@ -362,6 +365,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 @@ -376,7 +380,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, @@ -405,20 +409,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 +430,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 = [] @@ -597,6 +601,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 @@ -620,7 +625,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) @@ -632,7 +637,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) 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 59c6fe900197..5b443a701b28 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -1,24 +1,54 @@ -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, \ - 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 .ItemGroups import item_name_groups -from .Locations import get_locations, LocationType, get_location_types, get_plando_locations +from .Items import (StarcraftItem, filler_items, get_full_item_list, + get_basic_units, ItemData, upgrade_included_names, kerrigan_actives, kerrigan_passives, + not_balanced_starting_units, +) +from . import Items +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, \ - 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, kerrigan_unit_available, StarterUnit, SpearOfAdunPresence, + get_enabled_campaigns, SpearOfAdunAutonomouslyCastAbilityPresence, Starcraft2Options, + GrantStoryTech, GenericUpgradeResearch, +) +from .PoolFilter import filter_items +from .MissionTables import ( + MissionInfo, SC2Campaign, 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() + AllowedOrphan = enum.auto() + """Used to flag items that shouldn't be filtered out with their parents""" + + Unremovable = Locked|StartInventory|Plando + + +@dataclass +class FilterItem: + name: str + data: ItemData + index: int = 0 + flags: ItemFilterFlags = ItemFilterFlags.Available class Starcraft2WebWorld(WebWorld): @@ -48,9 +78,10 @@ class SC2World(World): options_dataclass = Starcraft2Options options: Starcraft2Options - item_name_groups = item_name_groups - locked_locations: typing.List[str] - location_cache: typing.List[Location] + 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] mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = {} final_mission_id: int victory_item: str @@ -71,17 +102,27 @@ def create_regions(self): ) def create_items(self): - setup_events(self.player, self.locked_locations, self.location_cache) - - excluded_items = get_excluded_items(self) - - starter_items = assign_starter_items(self, excluded_items, self.locked_locations, self.location_cache) + # 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 - fill_resource_locations(self, self.locked_locations, self.location_cache) + setup_events(self.player, self.locked_locations, self.location_cache) - pool = get_item_pool(self, self.mission_req_table, starter_items, excluded_items, self.location_cache) + item_list: List[FilterItem] = create_and_flag_explicit_item_locks_and_excludes(self) + flag_excludes_by_faction_presence(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_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) - fill_item_pool_with_dummy_items(self, self.locked_locations, self.location_cache, pool) + push_precollected_items_to_multiworld(self, item_list) self.multiworld.itempool += pool @@ -125,7 +166,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,319 +176,513 @@ def setup_events(player: int, locked_locations: typing.List[str], location_cache location.place_locked_item(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 in {KerriganPresence.option_not_present, KerriganPresence.option_not_present_and_no_passives}): - 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: - for tier in range(7): - smart_exclude(kerrigan_actives[tier].union(kerrigan_passives[tier]), 0) +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 + 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 + if max_count == 0: + return count + return min(count, max_count) + + result: List[FilterItem] = [] + for item_name, item_data in Items.item_table.items(): + 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 + 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 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 + 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_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) + 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: + # 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) + ): + 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 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 + 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 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, + ItemNames.SENTRY, ItemNames.HIGH_TEMPLAR, + ) + ): + 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 + ): + 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 + """ + 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 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 = 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 + 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: - # 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: World, excluded_items: Set[str], locked_locations: List[str], location_cache: typing.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) - # Ensuring that first mission is completable + 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.name in ItemGroups.nova_equipment): + 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.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.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: + 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_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_nobuild_missions = any((MissionFlag.Terran|MissionFlag.NoBuild) 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_nobuild_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 + 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: - starter_mission_locations = [location.name for location in location_cache - if location.parent_region.name == first_mission - and location.access_rule == Location.access_rule] + 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: - 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])) + flag_start_unit(world, item_list, starter_unit) - 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]]) -> str: - # 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 + flag_start_abilities(world, item_list) -def add_starter_item(world: World, excluded_items: Set[str], item_list: Sequence[str]) -> Item: +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 + + if first_race == SC2Race.ANY: + # If the first mission is a logic-less no-build + 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 races: + 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 + + +def flag_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 in Items.upgrade_item_types: + if not include_upgrades or (item.name not in upgrade_included_names[upgrade_items]): + item.flags |= ItemFilterFlags.Excluded + + +def flag_user_excluded_item_sets(world: SC2World, item_list: List[FilterItem]) -> None: + """Excludes items based on item set options (`only_vanilla_items`)""" + if not world.options.vanilla_items_only.value: + return - item_name = world.random.choice(sorted(item_list)) + 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 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 - excluded_items.add(item_name) - item = create_item_with_correct_settings(world.player, item_name) +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) - world.multiworld.push_precollected(item) - return item +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|ItemFilterFlags.AllowedOrphan) & item.flags + or item_list_contains_parent(item.data, item_list)] + last_num_items = num_items + num_items = len(item_list) -def get_item_pool(world: World, 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 + 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: - return data.quantity - - for name, data in get_item_table().items(): - for _ 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) + pool.append(ap_item) fill_pool_with_kerrigan_levels(world, pool) - filtered_pool = filter_items(world, mission_req_table, location_cache, pool, existing_items, locked_items) + filtered_pool = filter_items(world, world.mission_req_table, world.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)): +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 create_item_with_correct_settings(player: int, name: str) -> Item: - data = get_full_item_list()[name] +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] + return list(mission_req_table[first_campaign].values())[0].mission - item = Item(name, data.classification, data.code, player) - return item +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 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 create_item_with_correct_settings(player: int, name: str) -> Item: + data = Items.item_table[name] + item = Item(name, data.classification, data.code, player) -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) + 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) + 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 kerrigan_missions + or (world.options.grant_story_levels and not kerrigan_build_missions) + ): return def add_kerrigan_level_items(level_amount: int, item_amount: int): @@ -458,7 +693,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) @@ -479,3 +714,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_base.py b/worlds/sc2/test/test_base.py index 28529e37edd5..894b126183d1 100644 --- a/worlds/sc2/test/test_base.py +++ b/worlds/sc2/test/test_base.py @@ -1,6 +1,13 @@ 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.TestBase import WorldTestBase +from test.bases import WorldTestBase from .. import SC2World from .. import Client @@ -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 new file mode 100644 index 000000000000..4960d0f80ed5 --- /dev/null +++ b/worlds/sc2/test/test_generation.py @@ -0,0 +1,361 @@ +""" +Unit tests for world generation +""" +from typing import * +from .test_base import Sc2SetupTestBase + +from .. import Options, MissionTables, ItemNames, Items, ItemGroups +from .. import get_all_missions + + +class TestItemFiltering(Sc2SetupTestBase): + 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) + 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_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': [ + ItemGroups.ItemGroupNames.BARRACKS_UNITS.lower(), + ] + } + 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_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, + '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") + + 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_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, + '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) + + 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) + 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) 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 diff --git a/worlds/sc2/test/test_usecases.py b/worlds/sc2/test/test_usecases.py new file mode 100644 index 000000000000..b3a7cda11753 --- /dev/null +++ b/worlds/sc2/test/test_usecases.py @@ -0,0 +1,156 @@ +""" +Unit tests for yaml usecases we want to support +""" + +from .test_base import Sc2SetupTestBase +from .. import ItemGroups, ItemNames, Items, 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) + + 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") +