diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index c6c73431d67d..3adc18482550 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -1,23 +1,19 @@ from dataclasses import fields -import enum import logging from typing import * from math import floor, ceil -from dataclasses import dataclass -from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification, CollectionState, Region -from Fill import fill_restrictive, FillError +from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification, CollectionState from Options import Accessibility from worlds.AutoWorld import WebWorld, World -from . import item_names -from .items import ( - StarcraftItem, filler_items, get_full_item_list, ProtossItemType, +from .item.item_tables import ( + filler_items, get_full_item_list, ProtossItemType, ItemData, kerrigan_actives, kerrigan_passives, - not_balanced_starting_units, WEAPON_ARMOR_UPGRADE_MAX_LEVEL, + not_balanced_starting_units, WEAPON_ARMOR_UPGRADE_MAX_LEVEL, ZergItemType, ) -from . import items -from . import item_groups +from . import item from . import location_groups +from .item import FilterItem, ItemFilterFlags, StarcraftItem, item_groups, item_names, item_tables from .locations import get_locations, DEFAULT_LOCATION_LIST, get_location_types, get_location_flags, get_plando_locations from .mission_order.layout_types import LayoutType, Gauntlet from .options import ( @@ -35,39 +31,9 @@ ) from .regions import create_mission_order from .mission_order.structs import SC2MissionOrder -from ..stardew_valley import true_ -from ..v6 import location_table 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""" - ForceProgression = enum.auto() - """Used to flag items that aren't classified as progression by default""" - Necessary = enum.auto() - """Used to flag items that are never allowed to be culled. - This differs from `Locked` in that locked items may still be culled if there's space issues or in some circumstances when a parent item is culled.""" - - Unremovable = Locked|StartInventory|Plando|Necessary - - -@dataclass -class FilterItem: - name: str - data: ItemData - index: int = 0 - flags: ItemFilterFlags = ItemFilterFlags.Available - - class Starcraft2WebWorld(WebWorld): setup_en = Tutorial( "Multiworld Setup Guide", @@ -330,7 +296,7 @@ def resolve_count(count: Optional[int], max_count: int) -> int: return min(count, max_count) result: List[FilterItem] = [] - for item_name, item_data in items.item_table.items(): + for item_name, item_data in item_tables.item_table.items(): max_count = item_data.quantity excluded_count = excluded_items.get(item_name) unexcluded_count = unexcluded_items.get(item_name) @@ -368,7 +334,7 @@ def resolve_count(count: Optional[int], max_count: int) -> int: f"({excluded_count} + {locked_count} + {start_count} > {max_count}). Decreasing excluded amount.") excluded_count = max_count - start_count - locked_count # Make sure the final count creates enough items to satisfy key requirements - final_count = max(max_count - excluded_count, key_count) + final_count = max(max_count, key_count) for index in range(final_count): result.append(FilterItem(item_name, item_data, index)) if index < start_count: @@ -377,6 +343,8 @@ def resolve_count(count: Optional[int], max_count: int) -> int: result[-1].flags |= ItemFilterFlags.Locked if item_name in world.options.non_local_items: result[-1].flags |= ItemFilterFlags.NonLocal + if index >= max(max_count - excluded_count, key_count): + result[-1].flags |= ItemFilterFlags.Excluded return result @@ -401,35 +369,38 @@ def flag_excludes_by_faction_presence(world: SC2World, item_list: List[FilterIte 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 not terran_missions and item.data.race == SC2Race.TERRAN: + if item.name not in item_groups.nova_equipment: + item.flags |= ItemFilterFlags.Removed + continue + if not zerg_missions and item.data.race == SC2Race.ZERG: + if item.data.type != item_tables.ZergItemType.Ability \ + and item.data.type != ZergItemType.Level: + item.flags |= ItemFilterFlags.Removed + continue + if not protoss_missions and item.data.race == SC2Race.PROTOSS: if item.name not in item_groups.soa_items: - item.flags |= ItemFilterFlags.Excluded + item.flags |= ItemFilterFlags.Removed continue # Faction units if (not terran_build_missions - and item.data.type in (items.TerranItemType.Unit, items.TerranItemType.Building, items.TerranItemType.Mercenary) + and item.data.type in (item_tables.TerranItemType.Unit, item_tables.TerranItemType.Building, item_tables.TerranItemType.Mercenary) ): - item.flags |= ItemFilterFlags.Excluded + item.flags |= ItemFilterFlags.Removed if (not zerg_build_missions - and item.data.type in (items.ZergItemType.Unit, items.ZergItemType.Mercenary, items.ZergItemType.Evolution_Pit) + and item.data.type in (item_tables.ZergItemType.Unit, item_tables.ZergItemType.Mercenary, item_tables.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 (item_names.ZERGLING, item_names.ROACH, item_names.HYDRALISK, item_names.INFESTOR) ): - item.flags |= ItemFilterFlags.Excluded + item.flags |= ItemFilterFlags.Removed if (not protoss_build_missions and item.data.type in ( - items.ProtossItemType.Unit, - items.ProtossItemType.Unit_2, - items.ProtossItemType.Building, + item_tables.ProtossItemType.Unit, + item_tables.ProtossItemType.Unit_2, + item_tables.ProtossItemType.Building, ) ): # Note(mm): This doesn't exclude things like automated assimilators or warp gate improvements @@ -437,29 +408,29 @@ def flag_excludes_by_faction_presence(world: SC2World, item_list: List[FilterIte if (SC2Mission.TEMPLAR_S_RETURN not in missions or world.options.grant_story_tech.value == GrantStoryTech.option_true or item.name not in ( - item_names.IMMORTAL, item_names.ANNIHILATOR, - item_names.COLOSSUS, item_names.VANGUARD, item_names.REAVER, item_names.DARK_TEMPLAR, - item_names.SENTRY, item_names.HIGH_TEMPLAR, + item_names.IMMORTAL, item_names.ANNIHILATOR, + item_names.COLOSSUS, item_names.VANGUARD, item_names.REAVER, item_names.DARK_TEMPLAR, + item_names.SENTRY, item_names.HIGH_TEMPLAR, ) ): - item.flags |= ItemFilterFlags.Excluded + item.flags |= ItemFilterFlags.Removed # Faction +attack/armour upgrades - if (item.data.type == items.TerranItemType.Upgrade + if (item.data.type == item_tables.TerranItemType.Upgrade and not terran_build_missions and not auto_upgrades_in_nobuilds ): - item.flags |= ItemFilterFlags.Excluded - if (item.data.type == items.ZergItemType.Upgrade + item.flags |= ItemFilterFlags.Removed + if (item.data.type == item_tables.ZergItemType.Upgrade and not zerg_build_missions and not auto_upgrades_in_nobuilds ): - item.flags |= ItemFilterFlags.Excluded - if (item.data.type == items.ProtossItemType.Upgrade + item.flags |= ItemFilterFlags.Removed + if (item.data.type == item_tables.ProtossItemType.Upgrade and not protoss_build_missions and not auto_upgrades_in_nobuilds ): - item.flags |= ItemFilterFlags.Excluded + item.flags |= ItemFilterFlags.Removed def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem]) -> None: @@ -472,7 +443,11 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem 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 + kerrigan_is_present = ( + len(kerrigan_missions) > 0 + and world.options.kerrigan_presence == KerriganPresence.option_vanilla + and SC2Campaign.HOTS in options.get_enabled_campaigns(world) # TODO: Kerrigan available all Zerg/Everywhere + ) # TvZ build missions -- check flags Terran and VsZerg are true and NoBuild is false tvz_build_mask = MissionFlag.Terran|MissionFlag.VsZerg|MissionFlag.NoBuild @@ -513,35 +488,36 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem for item in item_list: # Filter Nova equipment if you never get Nova if not nova_missions and (item.name in item_groups.nova_equipment): - item.flags |= ItemFilterFlags.Excluded + item.flags |= ItemFilterFlags.Removed # 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 + if (item.data.type == item_tables.ZergItemType.Primal_Form and ((not kerrigan_is_present) or world.options.kerrigan_primal_status != KerriganPrimalStatus.option_item) ): - item.flags |= ItemFilterFlags.Excluded + item.flags |= ItemFilterFlags.Removed # Remove Kerrigan abilities if there's no kerrigan - if item.data.type == items.ZergItemType.Ability: + if item.data.type == item_tables.ZergItemType.Ability: if not kerrigan_is_present: - item.flags |= ItemFilterFlags.Excluded + # TODO: Kerrigan presence Zerg/Everywhere + item.flags |= ItemFilterFlags.Removed elif world.options.grant_story_tech and not kerrigan_build_missions: - item.flags |= ItemFilterFlags.Excluded + item.flags |= ItemFilterFlags.Removed # 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 + if item.name in item_tables.spear_of_adun_calldowns and not soa_presence: + item.flags |= ItemFilterFlags.Removed # Remove Spear of Adun passives - if item.name in items.spear_of_adun_castable_passives and not soa_passive_presence: - item.flags |= ItemFilterFlags.Excluded + if item.name in item_tables.spear_of_adun_castable_passives and not soa_passive_presence: + item.flags |= ItemFilterFlags.Removed # Remove Psi Disrupter and Hive Mind Emulator if you never play a build TvZ if (item.name in (item_names.HIVE_MIND_EMULATOR, item_names.PSI_DISRUPTER) and not tvz_build_missions ): - item.flags |= ItemFilterFlags.Excluded + item.flags |= ItemFilterFlags.Removed return @@ -551,8 +527,8 @@ def flag_allowed_orphan_items(world: SC2World, item_list: List[FilterItem]) -> N terran_nobuild_missions = any((MissionFlag.Terran|MissionFlag.NoBuild) in mission.flags for mission in missions) for item in item_list: if item.name in ( - item_names.MARINE_COMBAT_SHIELD, item_names.MARINE_PROGRESSIVE_STIMPACK, item_names.MARINE_MAGRAIL_MUNITIONS, - item_names.MEDIC_STABILIZER_MEDPACKS, item_names.MEDIC_NANO_PROJECTOR, + item_names.MARINE_COMBAT_SHIELD, item_names.MARINE_PROGRESSIVE_STIMPACK, item_names.MARINE_MAGRAIL_MUNITIONS, + item_names.MEDIC_STABILIZER_MEDPACKS, item_names.MEDIC_NANO_PROJECTOR, ) and terran_nobuild_missions: item.flags |= ItemFilterFlags.AllowedOrphan @@ -594,7 +570,7 @@ def flag_start_unit(world: SC2World, item_list: List[FilterItem], starter_unit: if first_race != SC2Race.ANY: possible_starter_items = { - item.name: item for item in item_list if (ItemFilterFlags.Plando|ItemFilterFlags.Excluded) & item.flags == 0 + item.name: item for item in item_list if (ItemFilterFlags.Plando|ItemFilterFlags.Excluded|ItemFilterFlags.Removed) & item.flags == 0 } # The race of the early unit has been chosen @@ -605,7 +581,7 @@ def flag_start_unit(world: SC2World, item_list: List[FilterItem], starter_unit: # Special case - you don't have a logicless location but need an AA basic_units = basic_units.difference( {item_names.ZEALOT, item_names.CENTURION, item_names.SENTINEL, item_names.BLOOD_HUNTER, - item_names.AVENGER, item_names.IMMORTAL, item_names.ANNIHILATOR, item_names.VANGUARD}) + item_names.AVENGER, item_names.IMMORTAL, item_names.ANNIHILATOR, item_names.VANGUARD}) if first_mission == SC2Mission.SUDDEN_STRIKE: # Special case - cliffjumpers basic_units = {item_names.REAPER, item_names.GOLIATH, item_names.SIEGE_TANK, item_names.VIKING, item_names.BANSHEE} @@ -627,7 +603,7 @@ def flag_start_unit(world: SC2World, item_list: List[FilterItem], starter_unit: 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 + and ((ItemFilterFlags.Plando|ItemFilterFlags.Excluded|ItemFilterFlags.Removed) & 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") @@ -693,9 +669,9 @@ def flag_unused_upgrade_types(world: SC2World, item_list: List[FilterItem]) -> N include_upgrades = world.options.generic_upgrade_missions == 0 upgrade_items: GenericUpgradeItems = world.options.generic_upgrade_items for item in item_list: - if item.data.type in items.upgrade_item_types: + if item.data.type in item_tables.upgrade_item_types: if not include_upgrades or (item.name not in upgrade_included_names[upgrade_items]): - item.flags |= ItemFilterFlags.Excluded + item.flags |= ItemFilterFlags.Removed def flag_user_excluded_item_sets(world: SC2World, item_list: List[FilterItem]) -> None: @@ -766,7 +742,7 @@ def flag_mission_order_required_items(world: SC2World, item_list: List[FilterIte 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] + item_list = [item for item in item_list if ItemFilterFlags.Unremovable & item.flags or ItemFilterFlags.Removed not in item.flags] num_items = len(item_list) last_num_items = -1 while num_items != last_num_items: @@ -777,12 +753,12 @@ def prune_item_pool(world: SC2World, item_list: List[FilterItem]) -> List[Item]: last_num_items = num_items num_items = len(item_list) - pool: List[Item] = [] - locked_items: List[Item] = [] - existing_items: List[Item] = [] - necessary_items: List[Item] = [] + pool: List[StarcraftItem] = [] + locked_items: List[StarcraftItem] = [] + existing_items: List[StarcraftItem] = [] + necessary_items: List[StarcraftItem] = [] for item in item_list: - ap_item = create_item_with_correct_settings(world.player, item.name) + ap_item = create_item_with_correct_settings(world.player, item.name, item.flags) if ItemFilterFlags.ForceProgression in item.flags: ap_item.classification = ItemClassification.progression if ItemFilterFlags.StartInventory in item.flags: @@ -827,10 +803,10 @@ def get_all_missions(mission_order: SC2MissionOrder) -> List[SC2Mission]: return mission_order.get_used_missions() -def create_item_with_correct_settings(player: int, name: str) -> Item: - data = items.item_table[name] +def create_item_with_correct_settings(player: int, name: str, filter_flags: ItemFilterFlags = ItemFilterFlags.Available) -> StarcraftItem: + data = item_tables.item_table[name] - item = Item(name, data.classification, data.code, player) + item = StarcraftItem(name, data.classification, data.code, player, filter_flags) return item diff --git a/worlds/sc2/client.py b/worlds/sc2/client.py index 378a9c6fad2e..f3b041866d97 100644 --- a/worlds/sc2/client.py +++ b/worlds/sc2/client.py @@ -23,8 +23,8 @@ # CommonClient import first to trigger ModuleUpdater from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser from Utils import init_logging, is_windows, async_start -from . import item_names -from .item_groups import item_name_groups, unlisted_item_name_groups +from worlds.sc2.item import item_names +from worlds.sc2.item.item_groups import item_name_groups, unlisted_item_name_groups from . import options from .options import ( MissionOrder, KerriganPrimalStatus, kerrigan_unit_available, KerriganPresence, EnableMorphling, @@ -52,7 +52,7 @@ from worlds._sc2common.bot.data import Race from worlds._sc2common.bot.main import run_game from worlds._sc2common.bot.player import Bot -from .items import ( +from worlds.sc2.item.item_tables import ( lookup_id_to_name, get_full_item_list, ItemData, race_to_item_type, ZergItemType, ProtossItemType, upgrade_bundles, WEAPON_ARMOR_UPGRADE_MAX_LEVEL, @@ -931,8 +931,8 @@ async def main(): CompatItemHolder(item_name) for item_name, item_data in get_full_item_list().items() if item_data.type in (ProtossItemType.War_Council, ProtossItemType.War_Council_2) - and item_name != item_names.DESTROYER_REFORGED_BLOODSHARD_CORE - and item_name != item_names.OBSERVER_INDUCE_SCOPOPHOBIA + and item_name != item_names.DESTROYER_REFORGED_BLOODSHARD_CORE + and item_name != item_names.OBSERVER_INDUCE_SCOPOPHOBIA } diff --git a/worlds/sc2/client_gui.py b/worlds/sc2/client_gui.py index edde53f2321d..3e135f173ed8 100644 --- a/worlds/sc2/client_gui.py +++ b/worlds/sc2/client_gui.py @@ -7,7 +7,6 @@ from kvui import GameManager, HoverBehavior, ServerToolTip, KivyJSONtoTextParser from kivy.app import App from kivy.clock import Clock -from kivy.uix.tabbedpanel import TabbedPanelItem from kivy.uix.gridlayout import GridLayout from kivy.lang import Builder from kivy.uix.label import Label @@ -16,8 +15,8 @@ from kivy.uix.scrollview import ScrollView from kivy.properties import StringProperty, BooleanProperty -from worlds.sc2.client import SC2Context, calc_unfinished_nodes, force_settings_save_on_close -from worlds.sc2.item_descriptions import item_descriptions +from worlds.sc2.client import SC2Context, calc_unfinished_nodes +from worlds.sc2.item.item_descriptions import item_descriptions from worlds.sc2.mission_tables import lookup_id_to_mission, campaign_race_exceptions, \ SC2Mission, SC2Race from worlds.sc2.locations import LocationType, lookup_location_id_to_type, lookup_location_id_to_flags diff --git a/worlds/sc2/item/__init__.py b/worlds/sc2/item/__init__.py new file mode 100644 index 000000000000..b177381d21d6 --- /dev/null +++ b/worlds/sc2/item/__init__.py @@ -0,0 +1,42 @@ +import enum +from dataclasses import dataclass +from typing import Optional + +from BaseClasses import Item, ItemClassification +from worlds.sc2.item.item_tables import ItemData + + +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""" + ForceProgression = enum.auto() + """Used to flag items that aren't classified as progression by default""" + Necessary = enum.auto() + """Used to flag items that are never allowed to be culled. + This differs from `Locked` in that locked items may still be culled if there's space issues or in some circumstances when a parent item is culled.""" + + Unremovable = Locked|StartInventory|Plando|Necessary + + +@dataclass +class FilterItem: + name: str + data: ItemData + index: int = 0 + flags: ItemFilterFlags = ItemFilterFlags.Available + + +class StarcraftItem(Item): + game: str = "Starcraft 2" + filter_flags: ItemFilterFlags = ItemFilterFlags.Available + + def __init__(self, name: str, classification: ItemClassification, code: Optional[int], player: int, filter_flags: ItemFilterFlags = ItemFilterFlags.Available): + super().__init__(name, classification, code, player) + self.filter_flags = filter_flags diff --git a/worlds/sc2/item_descriptions.py b/worlds/sc2/item/item_descriptions.py similarity index 98% rename from worlds/sc2/item_descriptions.py rename to worlds/sc2/item/item_descriptions.py index bfa085e38612..50d4ed326b09 100644 --- a/worlds/sc2/item_descriptions.py +++ b/worlds/sc2/item/item_descriptions.py @@ -3,7 +3,7 @@ """ import inspect -from . import item_names, items +from worlds.sc2.item import item_tables, item_names WEAPON_ARMOR_UPGRADE_NOTE = inspect.cleandoc(""" Must be researched during the mission if the mission type isn't set to auto-unlock generic upgrades. @@ -751,7 +751,8 @@ def _ability_desc(unit_name_plural: str, ability_name: str, ability_description: item_names.INFESTED_LIBERATOR_CLOUD_DISPERSAL: "Infested Liberators instantly transform into a cloud of microscopic organisms while attacking, reducing the damage they take by 85%.", item_names.INFESTED_LIBERATOR_VIRAL_CONTAMINATION: "Increases the damage Infested Liberators deal to their primary target by 100%.", item_names.FRIGHTFUL_FLESHWELDER_INFESTED_SIEGE_TANK: _get_resource_efficiency_desc(item_names.INFESTED_SIEGE_TANK), - item_names.FRIGHTFUL_FLESHWELDER_INFESTED_DIAMONDBACK: _get_resource_efficiency_desc(item_names.INFESTED_DIAMONDBACK), + item_names.FRIGHTFUL_FLESHWELDER_INFESTED_DIAMONDBACK: _get_resource_efficiency_desc( + item_names.INFESTED_DIAMONDBACK), item_names.FRIGHTFUL_FLESHWELDER_INFESTED_BANSHEE: _get_resource_efficiency_desc(item_names.INFESTED_BANSHEE), item_names.FRIGHTFUL_FLESHWELDER_INFESTED_LIBERATOR: _get_resource_efficiency_desc(item_names.INFESTED_LIBERATOR), item_names.ZERG_EXCAVATING_CLAWS: "Increases movement speed of uprooted Zerg structures, especially off creep. Also increases root speed.", @@ -969,8 +970,10 @@ def _ability_desc(unit_name_plural: str, ability_name: str, ability_description: item_names.ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS: "Zealots, Sentinels, and Centurions gain increased movement speed.", item_names.ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY: "Zealots, Sentinels, and Centurions gain +30 maximum shields.", item_names.ZEALOT_WHIRLWIND: "Zealot War Council upgrade. Gives Zealots the whirlwind ability, dealing damage in an area over 3 seconds.", - item_names.CENTURION_RESOURCE_EFFICIENCY: "Centurion War Council upgrade. " + _get_resource_efficiency_desc(item_names.CENTURION), - item_names.SENTINEL_RESOURCE_EFFICIENCY: "Sentinel War Council upgrade. " + _get_resource_efficiency_desc(item_names.SENTINEL), + item_names.CENTURION_RESOURCE_EFFICIENCY: "Centurion War Council upgrade. " + _get_resource_efficiency_desc( + item_names.CENTURION), + item_names.SENTINEL_RESOURCE_EFFICIENCY: "Sentinel War Council upgrade. " + _get_resource_efficiency_desc( + item_names.SENTINEL), item_names.STALKER_PHASE_REACTOR: "Stalker War Council upgrade. Stalkers restore 80 shields over 5 seconds after they Blink.", item_names.DRAGOON_PHALANX_SUIT: "Dragoon War Council upgrade. Dragoons gain +1 range, move slightly faster, and can form tighter formations.", item_names.INSTIGATOR_RESOURCE_EFFICIENCY: f"Instigator War Council upgrade. {_get_resource_efficiency_desc(item_names.INSTIGATOR)}", @@ -1051,6 +1054,6 @@ def _ability_desc(unit_name_plural: str, ability_name: str, ability_description: # Key descriptions key_descriptions = { key: GENERIC_KEY_DESC - for key in items.key_item_table.keys() + for key in item_tables.key_item_table.keys() } item_descriptions.update(key_descriptions) diff --git a/worlds/sc2/item_groups.py b/worlds/sc2/item/item_groups.py similarity index 90% rename from worlds/sc2/item_groups.py rename to worlds/sc2/item/item_groups.py index d34adbbb7ab3..a31eecaac9b5 100644 --- a/worlds/sc2/item_groups.py +++ b/worlds/sc2/item/item_groups.py @@ -1,6 +1,6 @@ import typing -from . import item_names, items -from .mission_tables import campaign_mission_table, SC2Campaign, SC2Mission, SC2Race +from worlds.sc2.item import item_tables, item_names +from worlds.sc2.mission_tables import campaign_mission_table, SC2Campaign, SC2Mission, SC2Race """ Item name groups, given to Archipelago and used in YAMLs and /received filtering. @@ -29,12 +29,12 @@ # These item name groups should not show up in documentation unlisted_item_name_groups = { "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, + item_tables.TerranItemType.Progressive.display_name, + item_tables.TerranItemType.Nova_Gear.display_name, + item_tables.TerranItemType.Mercenary.display_name, + item_tables.ZergItemType.Ability.display_name, + item_tables.ZergItemType.Morph.display_name, + item_tables.ZergItemType.Strain.display_name, } # Some item names only differ in bracketed parts @@ -42,7 +42,7 @@ bracketless_duplicates: typing.Set[str] # This is a list of names in ItemNames with bracketed parts removed, for internal use _shortened_names = [(name[:name.find(' (')] if '(' in name else name) - for name in [item_names.__dict__[name] for name in item_names.__dir__() if not name.startswith('_')]] + for name in [item_names.__dict__[name] for name in item_names.__dir__() if not name.startswith('_')]] # Remove the first instance of every short-name from the full item list bracketless_duplicates = set(_shortened_names) for name in bracketless_duplicates: @@ -52,7 +52,7 @@ del _shortened_names # All items get sorted into their data type -for item, data in items.get_full_item_list().items(): +for item, data in item_tables.get_full_item_list().items(): # 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 @@ -178,16 +178,17 @@ def get_all_group_names(cls) -> typing.Set[str]: # Terran item_name_groups[ItemGroupNames.TERRAN_ITEMS] = terran_items = [ - item_name for item_name, item_data in items.item_table.items() + item_name for item_name, item_data in item_tables.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.Unit_2, items.TerranItemType.Mercenary) + item_name for item_name, item_data in item_tables.item_table.items() + if item_data.type in ( + item_tables.TerranItemType.Unit, item_tables.TerranItemType.Unit_2, item_tables.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 for item_name, item_data in item_tables.item_table.items() + if item_data.type == item_tables.TerranItemType.Upgrade ] barracks_wa_group = [ item_names.MARINE, item_names.FIREBAT, item_names.MARAUDER, @@ -220,12 +221,12 @@ def get_all_group_names(cls) -> typing.Set[str]: item_names.EMPERORS_GUARDIAN, item_names.NIGHT_HAWK, item_names.NIGHT_WOLF, ] 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 for item_name, item_data in item_tables.item_table.items() + if item_data.type == item_tables.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 for item_name, item_data in item_tables.item_table.items() + if item_data.type == item_tables.TerranItemType.Mercenary ] item_name_groups[ItemGroupNames.NCO_UNITS] = nco_units = [ item_names.MARINE, item_names.MARAUDER, item_names.REAPER, @@ -237,8 +238,8 @@ def get_all_group_names(cls) -> typing.Set[str]: item_names.BUNKER, item_names.MISSILE_TURRET, item_names.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], + *[item_name for item_name, item_data in item_tables.item_table.items() + if item_data.type == item_tables.TerranItemType.Nova_Gear], item_names.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, ] item_name_groups[ItemGroupNames.WOL_UNITS] = wol_units = [ @@ -401,8 +402,8 @@ def get_all_group_names(cls) -> typing.Set[str]: 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 for item_name, item_data in item_tables.item_table.items() + if item_data.type in (item_tables.TerranItemType.Progressive, item_tables.TerranItemType.Progressive_2) ] item_name_groups[ItemGroupNames.WOL_ITEMS] = vanilla_wol_items = ( wol_units @@ -414,7 +415,7 @@ def get_all_group_names(cls) -> typing.Set[str]: # Zerg item_name_groups[ItemGroupNames.ZERG_ITEMS] = zerg_items = [ - item_name for item_name, item_data in items.item_table.items() + item_name for item_name, item_data in item_tables.item_table.items() if item_data.race == SC2Race.ZERG ] item_name_groups[ItemGroupNames.ZERG_BUILDINGS] = zerg_buildings = [ @@ -424,9 +425,11 @@ def get_all_group_names(cls) -> typing.Set[str]: item_names.NYDUS_WORM, item_names.OMEGA_WORM] 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 for item_name, item_data in item_tables.item_table.items() + if item_data.type in ( + item_tables.ZergItemType.Unit, item_tables.ZergItemType.Mercenary, item_tables.ZergItemType.Morph + ) + and item_name not in zerg_buildings ] # For W/A upgrades zerg_ground_units = [ @@ -453,8 +456,8 @@ def get_all_group_names(cls) -> typing.Set[str]: item_names.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, item_names.INFESTED_BANSHEE, item_names.INFESTED_LIBERATOR, ] 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 for item_name, item_data in item_tables.item_table.items() + if item_data.type == item_tables.ZergItemType.Upgrade ] item_name_groups[ItemGroupNames.HOTS_UNITS] = hots_units = [ item_names.ZERGLING, item_names.SWARM_QUEEN, item_names.ROACH, item_names.HYDRALISK, @@ -478,13 +481,13 @@ def get_all_group_names(cls) -> typing.Set[str]: item_names.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 for item_name, item_data in item_tables.item_table.items() if item_data.type == item_tables.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 for item_name, item_data in item_tables.item_table.items() if item_data.type == item_tables.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 for item_name, item_data in item_tables.item_table.items() if item_data.type == item_tables.ZergItemType.Ability ] item_name_groups[ItemGroupNames.KERRIGAN_PASSIVES] = kerrigan_passives = [ item_names.KERRIGAN_HEROIC_FORTITUDE, item_names.KERRIGAN_CHAIN_REACTION, @@ -528,7 +531,7 @@ def get_all_group_names(cls) -> typing.Set[str]: # 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 for item_name, item_data in item_tables.item_table.items() if item_data.type == item_tables.ZergItemType.Strain ] item_name_groups[ItemGroupNames.HOTS_MUTATIONS] = hots_mutations = [ item_names.ZERGLING_HARDENED_CARAPACE, item_names.ZERGLING_ADRENAL_OVERLOAD, item_names.ZERGLING_METABOLIC_BOOST, @@ -576,12 +579,12 @@ def get_all_group_names(cls) -> typing.Set[str]: # Protoss item_name_groups[ItemGroupNames.PROTOSS_ITEMS] = protoss_items = [ - item_name for item_name, item_data in items.item_table.items() + item_name for item_name, item_data in item_tables.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 for item_name, item_data in item_tables.item_table.items() + if item_data.type in (item_tables.ProtossItemType.Unit, item_tables.ProtossItemType.Unit_2) ] protoss_ground_wa = [ item_names.ZEALOT, item_names.CENTURION, item_names.SENTINEL, item_names.SUPPLICANT, @@ -603,8 +606,8 @@ def get_all_group_names(cls) -> typing.Set[str]: item_names.ARBITER, item_names.ORACLE, ] 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 for item_name, item_data in item_tables.item_table.items() + if item_data.type == item_tables.ProtossItemType.Upgrade ] item_name_groups[ItemGroupNames.LOTV_UNITS] = lotv_units = [ item_names.ZEALOT, item_names.CENTURION, item_names.SENTINEL, @@ -648,8 +651,8 @@ def get_all_group_names(cls) -> typing.Set[str]: item_names.ARBITER, item_names.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 for item_name, item_data in item_tables.item_table.items() + if item_data.type == item_tables.ProtossItemType.Building ] item_name_groups[ItemGroupNames.AIUR_UNITS] = [ item_names.ZEALOT, item_names.DRAGOON, item_names.SENTRY, item_names.AVENGER, item_names.HIGH_TEMPLAR, @@ -672,12 +675,12 @@ def get_all_group_names(cls) -> typing.Set[str]: item_names.MIRAGE, item_names.DAWNBRINGER, item_names.TRIREME, item_names.TEMPEST, ] item_name_groups[ItemGroupNames.SOA_ITEMS] = soa_items = [ - *[item_name for item_name, item_data in items.item_table.items() if item_data.type == items.ProtossItemType.Spear_Of_Adun], + *[item_name for item_name, item_data in item_tables.item_table.items() if item_data.type == item_tables.ProtossItemType.Spear_Of_Adun], item_names.SOA_PROGRESSIVE_PROXY_PYLON, ] lotv_soa_items = [item_name for item_name in soa_items if item_name != item_names.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 for item_name, item_data in item_tables.item_table.items() if item_data.type == item_tables.ProtossItemType.Solarite_Core ] item_name_groups[ItemGroupNames.LOTV_GLOBAL_UPGRADES] = lotv_global_upgrades = [ item_names.NEXUS_OVERCHARGE, @@ -700,8 +703,8 @@ def get_all_group_names(cls) -> typing.Set[str]: ) item_name_groups[ItemGroupNames.WAR_COUNCIL] = [ - item_name for item_name, item_data in items.item_table.items() - if item_data.type in (items.ProtossItemType.War_Council, items.ProtossItemType.War_Council_2) + item_name for item_name, item_data in item_tables.item_table.items() + if item_data.type in (item_tables.ProtossItemType.War_Council, item_tables.ProtossItemType.War_Council_2) ] item_name_groups[ItemGroupNames.OVERPOWERED_ITEMS] = [ diff --git a/worlds/sc2/item_names.py b/worlds/sc2/item/item_names.py similarity index 100% rename from worlds/sc2/item_names.py rename to worlds/sc2/item/item_names.py diff --git a/worlds/sc2/items.py b/worlds/sc2/item/item_tables.py similarity index 98% rename from worlds/sc2/items.py rename to worlds/sc2/item/item_tables.py index 2846df6bf206..dcf36bd9595b 100644 --- a/worlds/sc2/items.py +++ b/worlds/sc2/item/item_tables.py @@ -1,12 +1,12 @@ from typing import * -from BaseClasses import Item, ItemClassification +from BaseClasses import ItemClassification import typing import enum -from .mission_tables import SC2Mission, SC2Race, SC2Campaign -from . import item_names -from .mission_order.presets_static import get_used_layout_names +from ..mission_tables import SC2Mission, SC2Race, SC2Campaign +from ..item import item_names +from ..mission_order.presets_static import get_used_layout_names class ItemTypeEnum(enum.Enum): @@ -117,10 +117,6 @@ def is_important_for_filtering(self): or self.classification == ItemClassification.progression_skip_balancing -class StarcraftItem(Item): - game: str = "Starcraft 2" - - def get_full_item_list(): return item_table @@ -251,19 +247,19 @@ def get_full_item_list(): ItemData(61 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit_2, 10, SC2Race.TERRAN), # Some other items are moved to Upgrade group because of the way how the bot message is parsed - item_names.PROGRESSIVE_TERRAN_INFANTRY_WEAPON: ItemData(100 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 0, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_TERRAN_INFANTRY_ARMOR: ItemData(102 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 4, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_TERRAN_VEHICLE_WEAPON: ItemData(103 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 8, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_TERRAN_VEHICLE_ARMOR: ItemData(104 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 12, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON: ItemData(105 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 16, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_TERRAN_SHIP_ARMOR: ItemData(106 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 20, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_INFANTRY_WEAPON: ItemData(100 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 0, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_INFANTRY_ARMOR: ItemData(102 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 4, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_VEHICLE_WEAPON: ItemData(103 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 8, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_VEHICLE_ARMOR: ItemData(104 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 12, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON: ItemData(105 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 16, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_SHIP_ARMOR: ItemData(106 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 20, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), # Bundles - item_names.PROGRESSIVE_TERRAN_WEAPON_UPGRADE: ItemData(107 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_TERRAN_ARMOR_UPGRADE: ItemData(108 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE: ItemData(109 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE: ItemData(110 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_TERRAN_SHIP_UPGRADE: ItemData(111 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE: ItemData(112 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_WEAPON_UPGRADE: ItemData(107 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_ARMOR_UPGRADE: ItemData(108 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE: ItemData(109 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE: ItemData(110 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_SHIP_UPGRADE: ItemData(111 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE: ItemData(112 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, -1, SC2Race.TERRAN, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), # Unit and structure upgrades item_names.BUNKER_PROJECTILE_ACCELERATOR: @@ -528,7 +524,7 @@ def get_full_item_list(): parent_item=item_names.HERC), item_names.REAPER_RESOURCE_EFFICIENCY: ItemData(287 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 18, SC2Race.TERRAN, - classification=ItemClassification.progression, parent_item=item_names.REAPER,), + classification=ItemClassification.progression, parent_item=item_names.REAPER), item_names.REAPER_KINETIC_FOAM: ItemData(288 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Armory_6, 19, SC2Race.TERRAN, classification=ItemClassification.filler, parent_item=item_names.REAPER), @@ -1200,17 +1196,17 @@ def get_full_item_list(): ItemData(24 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Unit, 23, SC2Race.ZERG, classification=ItemClassification.progression), - item_names.PROGRESSIVE_ZERG_MELEE_ATTACK: ItemData(100 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 0, SC2Race.ZERG, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_ZERG_MISSILE_ATTACK: ItemData(101 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 4, SC2Race.ZERG, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_ZERG_GROUND_CARAPACE: ItemData(102 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 8, SC2Race.ZERG, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_ZERG_FLYER_ATTACK: ItemData(103 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 12, SC2Race.ZERG, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_ZERG_FLYER_CARAPACE: ItemData(104 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 16, SC2Race.ZERG, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_ZERG_MELEE_ATTACK: ItemData(100 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 0, SC2Race.ZERG, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_ZERG_MISSILE_ATTACK: ItemData(101 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 4, SC2Race.ZERG, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_ZERG_GROUND_CARAPACE: ItemData(102 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 8, SC2Race.ZERG, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_ZERG_FLYER_ATTACK: ItemData(103 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 12, SC2Race.ZERG, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_ZERG_FLYER_CARAPACE: ItemData(104 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, 16, SC2Race.ZERG, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), # Bundles - item_names.PROGRESSIVE_ZERG_WEAPON_UPGRADE: ItemData(105 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, -1, SC2Race.ZERG, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_ZERG_ARMOR_UPGRADE: ItemData(106 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, -1, SC2Race.ZERG, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_ZERG_GROUND_UPGRADE: ItemData(107 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, -1, SC2Race.ZERG, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_ZERG_FLYER_UPGRADE: ItemData(108 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, -1, SC2Race.ZERG, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, -1, SC2Race.ZERG, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_ZERG_WEAPON_UPGRADE: ItemData(105 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, -1, SC2Race.ZERG, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_ZERG_ARMOR_UPGRADE: ItemData(106 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, -1, SC2Race.ZERG, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_ZERG_GROUND_UPGRADE: ItemData(107 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, -1, SC2Race.ZERG, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_ZERG_FLYER_UPGRADE: ItemData(108 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, -1, SC2Race.ZERG, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Upgrade, -1, SC2Race.ZERG, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), item_names.ZERGLING_HARDENED_CARAPACE: ItemData(200 + SC2HOTS_ITEM_ID_OFFSET, ZergItemType.Mutation_1, 0, SC2Race.ZERG, parent_item=item_names.ZERGLING), @@ -1722,17 +1718,17 @@ def get_full_item_list(): classification=ItemClassification.progression), # Protoss Upgrades - item_names.PROGRESSIVE_PROTOSS_GROUND_WEAPON: ItemData(100 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 0, SC2Race.PROTOSS, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_PROTOSS_GROUND_ARMOR: ItemData(101 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 4, SC2Race.PROTOSS, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_PROTOSS_SHIELDS: ItemData(102 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 8, SC2Race.PROTOSS, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_PROTOSS_AIR_WEAPON: ItemData(103 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 12, SC2Race.PROTOSS, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_PROTOSS_AIR_ARMOR: ItemData(104 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 16, SC2Race.PROTOSS, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_PROTOSS_GROUND_WEAPON: ItemData(100 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 0, SC2Race.PROTOSS, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_PROTOSS_GROUND_ARMOR: ItemData(101 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 4, SC2Race.PROTOSS, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_PROTOSS_SHIELDS: ItemData(102 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 8, SC2Race.PROTOSS, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_PROTOSS_AIR_WEAPON: ItemData(103 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 12, SC2Race.PROTOSS, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_PROTOSS_AIR_ARMOR: ItemData(104 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, 16, SC2Race.PROTOSS, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), # Bundles - item_names.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE: ItemData(105 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, -1, SC2Race.PROTOSS, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE: ItemData(106 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, -1, SC2Race.PROTOSS, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_PROTOSS_GROUND_UPGRADE: ItemData(107 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, -1, SC2Race.PROTOSS, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_PROTOSS_AIR_UPGRADE: ItemData(108 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, -1, SC2Race.PROTOSS, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), - item_names.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, -1, SC2Race.PROTOSS, classification=ItemClassification.progression , quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE: ItemData(105 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, -1, SC2Race.PROTOSS, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE: ItemData(106 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, -1, SC2Race.PROTOSS, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_PROTOSS_GROUND_UPGRADE: ItemData(107 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, -1, SC2Race.PROTOSS, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_PROTOSS_AIR_UPGRADE: ItemData(108 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, -1, SC2Race.PROTOSS, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), + item_names.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Upgrade, -1, SC2Race.PROTOSS, classification=ItemClassification.progression, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), # Protoss Buildings item_names.PHOTON_CANNON: ItemData(200 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Building, 0, SC2Race.PROTOSS, classification=ItemClassification.progression), diff --git a/worlds/sc2/locations.py b/worlds/sc2/locations.py index b87365ca7aad..15486c1b33b0 100644 --- a/worlds/sc2/locations.py +++ b/worlds/sc2/locations.py @@ -1,6 +1,6 @@ import enum -from typing import List, Tuple, Optional, Callable, NamedTuple, Set, Any, TYPE_CHECKING -from . import item_names +from typing import List, Tuple, Optional, Callable, NamedTuple, Set, TYPE_CHECKING +from .item import item_names from .options import (get_option_value, RequiredTactics, LocationInclusion, KerriganPresence, ) @@ -574,14 +574,14 @@ def get_locations(world: Optional['SC2World']) -> Tuple[LocationData, ...]: lambda state: ( logic.terran_basic_anti_air(state) and (adv_tactics - or logic.terran_common_unit(state) - or state.has(item_names.REAPER, player))) + or logic.terran_common_unit(state) + or state.has(item_names.REAPER, player))) ), make_location_data(SC2Mission.DEVILS_PLAYGROUND.mission_name, "Zerg Cleared", SC2WOL_LOC_ID_OFFSET + 1308, LocationType.CHALLENGE, lambda state: ( logic.terran_competent_anti_air(state) and (logic.terran_common_unit(state) - or state.has(item_names.REAPER, player))) + or state.has(item_names.REAPER, player))) ), make_location_data(SC2Mission.WELCOME_TO_THE_JUNGLE.mission_name, "Victory", SC2WOL_LOC_ID_OFFSET + 1400, LocationType.VICTORY, logic.welcome_to_the_jungle_requirement @@ -2796,8 +2796,8 @@ def get_locations(world: Optional['SC2World']) -> Tuple[LocationData, ...]: lambda state: ( logic.zerg_basic_kerriganless_anti_air(state) and (adv_tactics - or logic.zerg_common_unit(state) - or state.has(item_names.HUNTERLING, player))) + or logic.zerg_common_unit(state) + or state.has(item_names.HUNTERLING, player))) ), make_location_data(SC2Mission.DEVILS_PLAYGROUND_Z.mission_name, "Zerg Cleared", SC2_RACESWAP_LOC_ID_OFFSET + 2508, LocationType.CHALLENGE, lambda state: ( diff --git a/worlds/sc2/mission_order/entry_rules.py b/worlds/sc2/mission_order/entry_rules.py index 25c3f631f185..221123d0bc24 100644 --- a/worlds/sc2/mission_order/entry_rules.py +++ b/worlds/sc2/mission_order/entry_rules.py @@ -1,10 +1,10 @@ from __future__ import annotations -from typing import Set, Callable, Dict, List, Union, TYPE_CHECKING, Any, Tuple +from typing import Set, Callable, Dict, List, Union, TYPE_CHECKING, Any from abc import ABC, abstractmethod from dataclasses import dataclass from ..mission_tables import SC2Mission -from ..items import item_table +from ..item.item_tables import item_table from BaseClasses import CollectionState if TYPE_CHECKING: diff --git a/worlds/sc2/mission_order/options.py b/worlds/sc2/mission_order/options.py index a2c136911ccb..4d5262d35ac9 100644 --- a/worlds/sc2/mission_order/options.py +++ b/worlds/sc2/mission_order/options.py @@ -9,8 +9,8 @@ from ..mission_tables import lookup_name_to_mission from ..mission_groups import mission_groups -from ..items import item_table -from ..item_groups import item_name_groups +from ..item.item_tables import item_table +from ..item.item_groups import item_name_groups from .structs import Difficulty, LayoutType, GENERIC_KEY_NAME from .layout_types import Column, Grid, Hopscotch, Gauntlet, Blitz from .presets_static import ( diff --git a/worlds/sc2/mission_order/structs.py b/worlds/sc2/mission_order/structs.py index cad8f7b58fb2..675b31324d28 100644 --- a/worlds/sc2/mission_order/structs.py +++ b/worlds/sc2/mission_order/structs.py @@ -7,8 +7,8 @@ from BaseClasses import Region, Location, CollectionState, Entrance from ..mission_tables import SC2Mission, lookup_name_to_mission, MissionFlag, lookup_id_to_mission, get_goal_location -from ..items import named_layout_key_item_table, named_campaign_key_item_table -from .. import item_names +from ..item.item_tables import named_layout_key_item_table, named_campaign_key_item_table +from ..item import item_names from .layout_types import LayoutType from .entry_rules import EntryRule, SubRuleEntryRule, CountMissionsEntryRule, BeatMissionsEntryRule, SubRuleRuleData, ItemEntryRule from .mission_pools import SC2MOGenMissionPools, Difficulty, modified_difficulty_thresholds diff --git a/worlds/sc2/options.py b/worlds/sc2/options.py index d16433bf6b16..51b959ecb48f 100644 --- a/worlds/sc2/options.py +++ b/worlds/sc2/options.py @@ -1,7 +1,6 @@ -from dataclasses import dataclass, fields, Field +from dataclasses import fields, Field from typing import * -from Utils import is_iterable_except_str from Options import * from Utils import get_fuzzy_results from BaseClasses import PlandoOptions @@ -9,7 +8,7 @@ campaign_mission_table, SC2Race, MissionFlag from .mission_groups import mission_groups, MissionGroupNames from .mission_order.options import CustomMissionOrder -from . import item_names +from .item import item_names if TYPE_CHECKING: from worlds.AutoWorld import World @@ -770,7 +769,7 @@ def verify(self, world: Type['World'], player_name: str, plando_options: PlandoO self.value = new_value for item_name in self.value: if item_name not in world.item_names: - from . import item_groups + from .item import item_groups picks = get_fuzzy_results( item_name, list(world.item_names) + list(item_groups.ItemGroupNames.get_all_group_names()), diff --git a/worlds/sc2/pool_filter.py b/worlds/sc2/pool_filter.py index 2ee42ab23026..99c2ae80197a 100644 --- a/worlds/sc2/pool_filter.py +++ b/worlds/sc2/pool_filter.py @@ -1,10 +1,12 @@ -from typing import Callable, Dict, List, Set, Union, Tuple, Optional, TYPE_CHECKING, Iterable -from BaseClasses import Item, Location -from .items import (get_full_item_list, spider_mine_sources, second_pass_placeable_items, - upgrade_item_types, +import copy +from typing import Callable, Dict, List, Set, Union, Tuple, TYPE_CHECKING, Iterable + +from BaseClasses import Item, Location +from worlds.sc2.item.item_tables import ( + get_full_item_list, spider_mine_sources, second_pass_placeable_items, ) +from .item import StarcraftItem, ItemFilterFlags, item_groups, item_names from .options import get_option_value, EnableMorphling, RequiredTactics -from . import item_groups, item_names if TYPE_CHECKING: from . import SC2World @@ -18,16 +20,16 @@ INF_TERRAN_UNITS = set(item_groups.infterr_units) -def get_item_upgrades(inventory: List[Item], parent_item: Union[Item, str]) -> List[Item]: - item_name = parent_item.name if isinstance(parent_item, Item) else parent_item +def get_item_upgrades(inventory: List[StarcraftItem], parent_item: Union[Item, str]) -> List[StarcraftItem]: + item_name = parent_item.name if isinstance(parent_item, StarcraftItem) else parent_item return [ inv_item for inv_item in inventory if get_full_item_list()[inv_item.name].parent_item == item_name ] -def copy_item(item: Item): - return Item(item.name, item.classification, item.code, item.player) +def copy_item(item: StarcraftItem) -> StarcraftItem: + return StarcraftItem(item.name, item.classification, item.code, item.player, item.filter_flags) class ValidInventory: @@ -60,9 +62,9 @@ def has_units_per_structure(self) -> bool: def generate_reduced_inventory(self, inventory_size: int, mission_requirements: List[Tuple[str, Callable]]) -> List[Item]: """Attempts to generate a reduced inventory that can fulfill the mission requirements.""" - inventory: List[Item] = list(self.item_pool) - locked_items: List[Item] = list(self.locked_items) - necessary_items: List[Item] = list(self.necessary_items) + inventory: List[StarcraftItem] = list(self.item_pool) + locked_items: List[StarcraftItem] = list(self.locked_items) + necessary_items: List[StarcraftItem] = list(self.necessary_items) enable_morphling = self.world.options.enable_morphling == EnableMorphling.option_true item_list = get_full_item_list() self.logical_inventory = [ @@ -71,10 +73,10 @@ def generate_reduced_inventory(self, inventory_size: int, mission_requirements: ] requirements = mission_requirements parent_items = self.item_children.keys() - parent_lookup = {child: parent for parent, children in self.item_children.items() for child in children} + parent_lookup: Dict[StarcraftItem, StarcraftItem] = {child: parent for parent, children in self.item_children.items() for child in children} minimum_upgrades = get_option_value(self.world, "min_number_of_upgrades") - def attempt_removal(item: Item) -> bool: + def attempt_removal(item: StarcraftItem) -> bool: removed_item = inventory.pop(inventory.index(item)) # Only run logic checks when removing logic items if item.name in self.logical_inventory: @@ -90,6 +92,29 @@ def attempt_removal(item: Item) -> bool: return False return True + # Process Excluded items, validate if the item can get actually excluded + excluded_items: List[StarcraftItem] = [starcraft_item for starcraft_item in inventory if ItemFilterFlags.Excluded in starcraft_item.filter_flags] + self.world.random.shuffle(excluded_items) + for excluded_item in excluded_items: + logical_inventory_copy = copy.copy(self.logical_inventory) + if ( + excluded_item in inventory + and attempt_removal(excluded_item) + and excluded_item in parent_items + ): + for child in self.item_children[excluded_item]: + if ( + child in inventory + and not attempt_removal(child) + and ItemFilterFlags.AllowedOrphan not in child.filter_flags + ): + if excluded_item.name in logical_inventory_copy: + self.logical_inventory.append(excluded_item.name) + if excluded_item not in self.logical_inventory: + self.logical_inventory.append(excluded_item.name) + if not excluded_item in locked_items: + self.locked_items.append(excluded_item) + # Limit the maximum number of upgrades maxNbUpgrade = get_option_value(self.world, "max_number_of_upgrades") if maxNbUpgrade != -1: @@ -139,7 +164,7 @@ def attempt_removal(item: Item) -> bool: known_parents = [item for item in known_items if item in parent_items] for parent in known_parents: child_items = self.item_children[parent] - removable_upgrades = [item for item in inventory if item in child_items] + removable_upgrades = [starcraft_item for starcraft_item in inventory if starcraft_item in child_items] locked_upgrade_count = sum(1 if item in child_items else 0 for item in known_items) self.world.random.shuffle(removable_upgrades) while len(removable_upgrades) > 0 and locked_upgrade_count < minimum_upgrades: @@ -157,16 +182,16 @@ def attempt_removal(item: Item) -> bool: raise Exception(f"Too many items excluded - couldn't satisfy access rules for the following locations:\n{failed_locations}") # Optionally locking generic items - generic_items = [item for item in inventory if item.name in second_pass_placeable_items] + generic_items: List[StarcraftItem] = [starcraft_item for starcraft_item in inventory if starcraft_item.name in second_pass_placeable_items] reserved_generic_percent = get_option_value(self.world, "ensure_generic_items") / 100 reserved_generic_amount = int(len(generic_items) * reserved_generic_percent) removable_generic_items = [] self.world.random.shuffle(generic_items) - for item in generic_items[:reserved_generic_amount]: - locked_items.append(copy_item(item)) - inventory.remove(item) - if item.name not in self.logical_inventory: - removable_generic_items.append(item) + for starcraft_item in generic_items[:reserved_generic_amount]: + locked_items.append(copy_item(starcraft_item)) + inventory.remove(starcraft_item) + if starcraft_item.name not in self.logical_inventory: + removable_generic_items.append(starcraft_item) # Main cull process unused_items: List[str] = [] # Reusable items for the second pass @@ -180,12 +205,12 @@ def attempt_removal(item: Item) -> bool: # If there still isn't enough space, push locked items into start inventory self.world.random.shuffle(locked_items) while len(locked_items) > 0 and len(locked_items) + len(necessary_items) > inventory_size: - item: Item = locked_items.pop() + item: StarcraftItem = locked_items.pop() self.multiworld.push_precollected(item) # If locked items weren't enough either, push necessary items into start inventory too self.world.random.shuffle(necessary_items) while len(necessary_items) > inventory_size: - item: Item = necessary_items.pop() + item: StarcraftItem = necessary_items.pop() self.multiworld.push_precollected(item) break # Select random item from removable items @@ -246,11 +271,14 @@ def attempt_removal(item: Item) -> bool: item_names.TERRAN_INFANTRY_UPGRADE_PREFIX) or item_name == item_names.ORBITAL_STRIKE)] if not FACTORY_UNITS & logical_inventory_set: - inventory = [item for item in inventory if not item.name.startswith(item_names.TERRAN_VEHICLE_UPGRADE_PREFIX)] - unused_items = [item_name for item_name in unused_items if not item_name.startswith(item_names.TERRAN_VEHICLE_UPGRADE_PREFIX)] + inventory = [item for item in inventory if not item.name.startswith( + item_names.TERRAN_VEHICLE_UPGRADE_PREFIX)] + unused_items = [item_name for item_name in unused_items if not item_name.startswith( + item_names.TERRAN_VEHICLE_UPGRADE_PREFIX)] if not STARPORT_UNITS & logical_inventory_set: inventory = [item for item in inventory if not item.name.startswith(item_names.TERRAN_SHIP_UPGRADE_PREFIX)] - unused_items = [item_name for item_name in unused_items if not item_name.startswith(item_names.TERRAN_SHIP_UPGRADE_PREFIX)] + unused_items = [item_name for item_name in unused_items if not item_name.startswith( + item_names.TERRAN_SHIP_UPGRADE_PREFIX)] if not {item_names.MEDIVAC, item_names.HERCULES} & logical_inventory_set: inventory = [item for item in inventory if item.name != item_names.SIEGE_TANK_PROGRESSIVE_TRANSPORT_HOOK] unused_items = [item_name for item_name in unused_items if item_name != item_names.SIEGE_TANK_PROGRESSIVE_TRANSPORT_HOOK] @@ -275,7 +303,7 @@ def attempt_removal(item: Item) -> bool: if not {item_names.COMMAND_CENTER_SCANNER_SWEEP, item_names.COMMAND_CENTER_MULE, item_names.COMMAND_CENTER_EXTRA_SUPPLIES} & logical_inventory_set: # No orbital Command Spells inventory = [item for item in inventory if item.name != item_names.PLANETARY_FORTRESS_ORBITAL_MODULE] - unused_items = [item_name for item_name in unused_items if item_name !=item_names.PLANETARY_FORTRESS_ORBITAL_MODULE] + unused_items = [item_name for item_name in unused_items if item_name != item_names.PLANETARY_FORTRESS_ORBITAL_MODULE] locked_items = [item for item in locked_items if item.name != item_names.PLANETARY_FORTRESS_ORBITAL_MODULE] # No weapon upgrades for Dominion Trooper -> drop weapon is useless if not { @@ -310,8 +338,10 @@ def attempt_removal(item: Item) -> bool: unused_items = [item_name for item_name in unused_items if item_name != item_names.BANELING_RAPID_METAMORPH] if not {item_names.MUTALISK, item_names.CORRUPTOR, item_names.SCOURGE} & logical_inventory_set: inventory = [item for item in inventory if not item.name.startswith(item_names.ZERG_FLYER_UPGRADE_PREFIX)] - locked_items = [item for item in locked_items if not item.name.startswith(item_names.ZERG_FLYER_UPGRADE_PREFIX)] - unused_items = [item_name for item_name in unused_items if not item_name.startswith(item_names.ZERG_FLYER_UPGRADE_PREFIX)] + locked_items = [item for item in locked_items if not item.name.startswith( + item_names.ZERG_FLYER_UPGRADE_PREFIX)] + unused_items = [item_name for item_name in unused_items if not item_name.startswith( + item_names.ZERG_FLYER_UPGRADE_PREFIX)] # T3 items removal rules - remove morph and its upgrades if the basic unit isn't in and morphling is unavailable if not {item_names.MUTALISK, item_names.CORRUPTOR} & logical_inventory_set and not enable_morphling: inventory = [item for item in inventory if not item.name.endswith("(Mutalisk/Corruptor)")] @@ -407,12 +437,18 @@ def attempt_removal(item: Item) -> bool: inventory += necessary_items # Replacing empty space with generically useful items - replacement_items = [item for item in self.item_pool - if (item not in inventory - and item not in self.locked_items - and ( - item.name in second_pass_placeable_items - or item.name in unused_items))] + replacement_items = [ + item for item in self.item_pool + if ( + item not in inventory + and item not in self.locked_items + and item not in excluded_items + and ( + item.name in second_pass_placeable_items + or item.name in unused_items + ) + ) + ] self.world.random.shuffle(replacement_items) while len(inventory) < inventory_size and len(replacement_items) > 0: item = replacement_items.pop() @@ -421,7 +457,7 @@ def attempt_removal(item: Item) -> bool: return inventory def __init__(self, world: 'SC2World', - item_pool: List[Item], existing_items: List[Item], locked_items: List[Item], necessary_items: List[Item] + item_pool: List[StarcraftItem], existing_items: List[StarcraftItem], locked_items: List[StarcraftItem], necessary_items: List[StarcraftItem] ): self.multiworld = world.multiworld self.player = world.player @@ -431,33 +467,18 @@ def __init__(self, world: 'SC2World', self.necessary_items = necessary_items[:] self.existing_items = existing_items # Initial filter of item pool - self.item_pool = [] - item_quantities: dict[str, int] = dict() + self.item_pool = item_pool # Inventory restrictiveness based on number of missions with checks mission_count = world.custom_mission_order.get_mission_count() 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.type in upgrade_item_types: - # Locking upgrades based on mission duration - if item.name not in item_quantities: - item_quantities[item.name] = 0 - item_quantities[item.name] += 1 - if item_quantities[item.name] <= min_upgrades: - self.locked_items.append(item) - else: - self.item_pool.append(item) - else: - self.item_pool.append(item) - self.item_children: Dict[Item, List[Item]] = dict() + self.item_children: Dict[StarcraftItem, List[StarcraftItem]] = dict() for item in self.item_pool + locked_items + existing_items: if item.name in UPGRADABLE_ITEMS: self.item_children[item] = get_item_upgrades(self.item_pool, item) def filter_items(world: 'SC2World', location_cache: List[Location], - item_pool: List[Item], existing_items: List[Item], locked_items: List[Item], necessary_items: List[Item]) -> List[Item]: + item_pool: List[StarcraftItem], existing_items: List[StarcraftItem], locked_items: List[StarcraftItem], necessary_items: List[StarcraftItem]) -> List[Item]: """ Returns a semi-randomly pruned set of items based on number of available locations. The returned inventory must be capable of logically accessing every location in the world. diff --git a/worlds/sc2/rules.py b/worlds/sc2/rules.py index ff26acdcce63..e9e50c4ca9e6 100644 --- a/worlds/sc2/rules.py +++ b/worlds/sc2/rules.py @@ -7,14 +7,13 @@ GrantStoryTech, GrantStoryLevels, TakeOverAIAllies, SpearOfAdunAutonomouslyCastAbilityPresence, get_enabled_campaigns, MissionOrder, EnableMorphling, get_enabled_races ) -from .items import ( +from worlds.sc2.item.item_tables import ( tvx_defense_ratings, tvz_defense_ratings, kerrigan_actives, tvx_air_defense_ratings, kerrigan_levels, get_full_item_list, zvx_air_defense_ratings, zvx_defense_ratings, pvx_defense_ratings, - pvz_defense_ratings, no_logic_basic_units, advanced_basic_units, basic_units, upgrade_bundles, - upgrade_bundle_inverted_lookup, WEAPON_ARMOR_UPGRADE_MAX_LEVEL + pvz_defense_ratings, no_logic_basic_units, advanced_basic_units, basic_units, upgrade_bundle_inverted_lookup, WEAPON_ARMOR_UPGRADE_MAX_LEVEL ) from .mission_tables import SC2Race, SC2Campaign -from . import item_names, item_groups +from .item import item_groups, item_names if TYPE_CHECKING: from . import SC2World @@ -146,8 +145,15 @@ def terran_air(self, state: CollectionState) -> bool: :param state: :return: """ - return (state.has_any({item_names.VIKING, item_names.WRAITH, item_names.BANSHEE, item_names.BATTLECRUISER}, self.player) or self.advanced_tactics - and state.has_any({item_names.HERCULES, item_names.MEDIVAC}, self.player) and self.terran_common_unit(state) + return ( + state.has_any({ + item_names.VIKING, item_names.WRAITH, item_names.BANSHEE, item_names.BATTLECRUISER + }, self.player) + or ( + self.advanced_tactics + and state.has_any({item_names.HERCULES, item_names.MEDIVAC}, self.player) + and self.terran_common_unit(state) + ) ) def terran_air_anti_air(self, state: CollectionState) -> bool: @@ -284,9 +290,9 @@ def terran_basic_anti_air(self, state: CollectionState) -> bool: item_names.EMPERORS_GUARDIAN, item_names.NIGHT_HAWK, ), self.player) or ( - state.has(item_names.MEDIVAC, self.player) - and state.has_any((item_names.SIEGE_TANK, item_names.SHOCK_DIVISION), self.player) - and state.count(item_names.SIEGE_TANK_PROGRESSIVE_TRANSPORT_HOOK, self.player) >= 2 + state.has(item_names.MEDIVAC, self.player) + and state.has_any((item_names.SIEGE_TANK, item_names.SHOCK_DIVISION), self.player) + and state.count(item_names.SIEGE_TANK_PROGRESSIVE_TRANSPORT_HOOK, self.player) >= 2 ) ) ) @@ -301,7 +307,8 @@ def terran_defense_rating(self, state: CollectionState, zerg_enemy: bool, air_en """ defense_score = sum((tvx_defense_ratings[item] for item in tvx_defense_ratings if state.has(item, self.player))) # Manned Bunker - if state.has_any({item_names.MARINE, item_names.DOMINION_TROOPER, item_names.MARAUDER}, self.player) and state.has(item_names.BUNKER, self.player): + if state.has_any({item_names.MARINE, item_names.DOMINION_TROOPER, item_names.MARAUDER}, self.player) and state.has( + item_names.BUNKER, self.player): defense_score += 3 elif zerg_enemy and state.has(item_names.FIREBAT, self.player) and state.has(item_names.BUNKER, self.player): defense_score += 2 @@ -351,8 +358,8 @@ def terran_competent_comp(self, state: CollectionState) -> bool: state.has(item_names.BATTLECRUISER, self.player) and self.terran_common_unit(state) and ( - self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, state) >= 2 - or state.has(item_names.BATTLECRUISER_ATX_LASER_BATTERY, self.player) + self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, state) >= 2 + or state.has(item_names.BATTLECRUISER_ATX_LASER_BATTERY, self.player) ) ) @@ -446,7 +453,7 @@ def marine_medic_upgrade(self, state: CollectionState) -> bool: }, self.player) or (state.count(item_names.MARINE_PROGRESSIVE_STIMPACK, self.player) >= 2 and state.has_group("Missions", self.player, 1) - ) + ) ) def terran_survives_rip_field(self, state: CollectionState) -> bool: @@ -475,16 +482,16 @@ def terran_sustainable_mech_heal(self, state: CollectionState) -> bool: :return: """ return ( - state.has(item_names.SCIENCE_VESSEL, self.player) - or ( + state.has(item_names.SCIENCE_VESSEL, self.player) + or ( state.has_any({item_names.MEDIC, item_names.FIELD_RESPONSE_THETA}, self.player) and state.has(item_names.MEDIC_ADAPTIVE_MEDPACKS, self.player) ) - or state.count(item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL, self.player) >= 3 - or (self.advanced_tactics + or state.count(item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL, self.player) >= 3 + or (self.advanced_tactics and ( - state.has_all({item_names.RAVEN, item_names.RAVEN_BIO_MECHANICAL_REPAIR_DRONE}, self.player) - or state.count(item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL, self.player) >= 2 + state.has_all({item_names.RAVEN, item_names.RAVEN_BIO_MECHANICAL_REPAIR_DRONE}, self.player) + or state.count(item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL, self.player) >= 2 ) ) ) @@ -549,8 +556,8 @@ def terran_respond_to_colony_infestations(self, state: CollectionState) -> bool: and ( self.terran_air_anti_air(state) or ( - state.has_any({item_names.BATTLECRUISER, item_names.VALKYRIE}, self.player) - and self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, state) >= 2 + state.has_any({item_names.BATTLECRUISER, item_names.VALKYRIE}, self.player) + and self.weapon_armor_upgrade_count(item_names.PROGRESSIVE_TERRAN_SHIP_WEAPON, state) >= 2 ) ) and self.terran_defense_rating(state, True) >= 3 @@ -930,7 +937,7 @@ def zerg_competent_defense(self, state: CollectionState) -> bool: ) or ( self.advanced_tactics and (self.morph_viper(state) - or state.has(item_names.SPINE_CRAWLER, self.player)) + or state.has(item_names.SPINE_CRAWLER, self.player)) ) ) ) @@ -966,9 +973,9 @@ def basic_kerrigan(self, state: CollectionState) -> bool: # Two non-ultimate abilities count = 0 for item in ( - item_names.KERRIGAN_KINETIC_BLAST, item_names.KERRIGAN_LEAPING_STRIKE, item_names.KERRIGAN_HEROIC_FORTITUDE, - item_names.KERRIGAN_CHAIN_REACTION, item_names.KERRIGAN_CRUSHING_GRIP, item_names.KERRIGAN_PSIONIC_SHIFT, - item_names.KERRIGAN_SPAWN_BANELINGS, item_names.KERRIGAN_INFEST_BROODLINGS, item_names.KERRIGAN_FURY + item_names.KERRIGAN_KINETIC_BLAST, item_names.KERRIGAN_LEAPING_STRIKE, item_names.KERRIGAN_HEROIC_FORTITUDE, + item_names.KERRIGAN_CHAIN_REACTION, item_names.KERRIGAN_CRUSHING_GRIP, item_names.KERRIGAN_PSIONIC_SHIFT, + item_names.KERRIGAN_SPAWN_BANELINGS, item_names.KERRIGAN_INFEST_BROODLINGS, item_names.KERRIGAN_FURY ): if state.has(item, self.player): count += 1 diff --git a/worlds/sc2/test/test_custom_mission_orders.py b/worlds/sc2/test/test_custom_mission_orders.py index e9b2421cacd7..11244e99141e 100644 --- a/worlds/sc2/test/test_custom_mission_orders.py +++ b/worlds/sc2/test/test_custom_mission_orders.py @@ -4,8 +4,7 @@ from .test_base import Sc2SetupTestBase from .. import MissionFlag -from .. import item_names -from .. import items +from ..item import item_tables, item_names from BaseClasses import ItemClassification class TestCustomMissionOrders(Sc2SetupTestBase): @@ -126,7 +125,7 @@ def test_locked_and_necessary_item_appears_once(self): } } - self.assertNotEqual(items.item_table[test_item].classification, ItemClassification.progression, f"Test item {test_item} won't change classification") + self.assertNotEqual(item_tables.item_table[test_item].classification, ItemClassification.progression, f"Test item {test_item} won't change classification") self.generate_world(world_options) test_items_in_pool = [item for item in self.multiworld.itempool if item.name == test_item] diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index e941c10e57c9..579338c4dc95 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -4,7 +4,8 @@ from typing import * from .test_base import Sc2SetupTestBase -from .. import item_groups, item_names, items, mission_groups, mission_tables, options, locations +from .. import mission_groups, mission_tables, options, locations, SC2Mission +from ..item import item_groups, item_tables, item_names from .. import get_all_missions, get_first_mission @@ -136,11 +137,11 @@ def test_excluding_campaigns_excludes_campaign_specific_items(self) -> None: } self.generate_world(world_options) self.assertTrue(self.multiworld.itempool) - world_items = [(item.name, items.item_table[item.name]) for item in self.multiworld.itempool] + world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool] for item_name, item_data in world_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.assertNotIn(item_data.type, item_tables.ProtossItemType) + self.assertNotIn(item_data.type, item_tables.ZergItemType) + self.assertNotEqual(item_data.type, item_tables.TerranItemType.Nova_Gear) self.assertNotEqual(item_name, item_names.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE) def test_starter_unit_populates_start_inventory(self): @@ -171,9 +172,9 @@ def test_excluding_all_terran_missions_excludes_all_terran_items(self) -> None: } self.generate_world(world_options) self.assertTrue(self.multiworld.itempool) - world_items = [(item.name, items.item_table[item.name]) for item in self.multiworld.itempool] + world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool] for item_name, item_data in world_items: - self.assertNotIn(item_data.type, items.TerranItemType, f"Item '{item_name}' included when all terran missions are excluded") + self.assertNotIn(item_data.type, item_tables.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: world_options = { @@ -187,11 +188,11 @@ def test_excluding_all_terran_build_missions_excludes_all_terran_units(self) -> } self.generate_world(world_options) self.assertTrue(self.multiworld.itempool) - world_items = [(item.name, items.item_table[item.name]) for item in self.multiworld.itempool] + world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool] for item_name, item_data in world_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") + self.assertNotEqual(item_data.type, item_tables.TerranItemType.Unit, f"Item '{item_name}' included when all terran build missions are excluded") + self.assertNotEqual(item_data.type, item_tables.TerranItemType.Mercenary, f"Item '{item_name}' included when all terran build missions are excluded") + self.assertNotEqual(item_data.type, item_tables.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: world_options = { @@ -204,9 +205,9 @@ def test_excluding_all_zerg_and_kerrigan_missions_excludes_all_zerg_items(self) } self.generate_world(world_options) self.assertTrue(self.multiworld.itempool) - world_items = [(item.name, items.item_table[item.name]) for item in self.multiworld.itempool] + world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool] for item_name, item_data in world_items: - self.assertNotIn(item_data.type, items.ZergItemType, f"Item '{item_name}' included when all zerg missions are excluded") + self.assertNotIn(item_data.type, item_tables.ZergItemType, f"Item '{item_name}' included when all zerg missions are excluded") def test_excluding_all_zerg_build_missions_excludes_zerg_units(self) -> None: world_options = { @@ -222,10 +223,10 @@ def test_excluding_all_zerg_build_missions_excludes_zerg_units(self) -> None: } self.generate_world(world_options) self.assertTrue(self.multiworld.itempool) - world_items = [(item.name, items.item_table[item.name]) for item in self.multiworld.itempool] + world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool] for item_name, item_data in world_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") + self.assertNotEqual(item_data.type, item_tables.ZergItemType.Unit, f"Item '{item_name}' included when all zerg build missions are excluded") + self.assertNotEqual(item_data.type, item_tables.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: world_options = { @@ -240,9 +241,9 @@ def test_excluding_all_protoss_missions_excludes_all_protoss_items(self) -> None } self.generate_world(world_options) self.assertTrue(self.multiworld.itempool) - world_items = [(item.name, items.item_table[item.name]) for item in self.multiworld.itempool] + world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool] for item_name, item_data in world_items: - self.assertNotIn(item_data.type, items.ProtossItemType, f"Item '{item_name}' included when all protoss missions are excluded") + self.assertNotIn(item_data.type, item_tables.ProtossItemType, f"Item '{item_name}' included when all protoss missions are excluded") def test_excluding_all_protoss_build_missions_excludes_protoss_units(self) -> None: world_options = { @@ -259,11 +260,11 @@ def test_excluding_all_protoss_build_missions_excludes_protoss_units(self) -> No } self.generate_world(world_options) self.assertTrue(self.multiworld.itempool) - world_items = [(item.name, items.item_table[item.name]) for item in self.multiworld.itempool] + world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool] for item_name, item_data in world_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") + self.assertNotEqual(item_data.type, item_tables.ProtossItemType.Unit, f"Item '{item_name}' included when all protoss build missions are excluded") + self.assertNotEqual(item_data.type, item_tables.ProtossItemType.Unit_2, f"Item '{item_name}' included when all protoss build missions are excluded") + self.assertNotEqual(item_data.type, item_tables.ProtossItemType.Building, f"Item '{item_name}' included when all protoss build missions are excluded") def test_vanilla_items_only_excludes_terran_progressives(self) -> None: world_options = { @@ -278,8 +279,8 @@ def test_vanilla_items_only_excludes_terran_progressives(self) -> None: 'vanilla_items_only': True, } self.generate_world(world_options) - world_items = [(item.name, items.item_table[item.name]) for item in self.multiworld.itempool] - self.assertTrue(items) + world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool] + self.assertTrue(world_items) occurrences: Dict[str, int] = {} for item_name, _ in world_items: if item_name in item_groups.terran_progressive_items: @@ -298,8 +299,8 @@ def test_vanilla_items_only_includes_only_nova_equipment_and_vanilla_and_filler_ 'vanilla_items_only': True, } self.generate_world(world_options) - world_items = [(item.name, items.item_table[item.name]) for item in self.multiworld.itempool] - self.assertTrue(items) + world_items = [(item.name, item_tables.item_table[item.name]) for item in self.multiworld.itempool] + self.assertTrue(world_items) for item_name, item_data in world_items: if item_data.quantity == 0: continue @@ -367,7 +368,7 @@ def test_soa_items_are_included_in_wol_when_presence_set_to_everywhere(self) -> self.generate_world(world_options) itempool = [item.name for item in self.multiworld.itempool] self.assertTrue(itempool) - soa_items_in_pool = [item_name for item_name in itempool if items.item_table[item_name].type == items.ProtossItemType.Spear_Of_Adun] + soa_items_in_pool = [item_name for item_name in itempool if item_tables.item_table[item_name].type == item_tables.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: @@ -779,3 +780,39 @@ def test_weapon_armor_upgrades_generic_upgrade_missions_no_countermeasure_needed # No additional starting inventory item placement is needed self.assertEqual(len(upgrade_items), 0) + + + def test_locking_required_items(self): + world_options = { + 'mission_order': options.MissionOrder.option_custom, + 'custom_mission_order': { + 'campaign': { + 'goal': True, + 'layout': { + 'type': 'column', + 'size': 2, + 'missions': [ + { + 'index': 0, + 'mission_pool': [SC2Mission.LIBERATION_DAY.mission_name] + }, + { + 'index': 1, + 'mission_pool': [SC2Mission.SUPREME.mission_name] + }, + ] + } + } + }, + 'grant_story_levels': options.GrantStoryLevels.option_additive, + 'excluded_items': [ + item_names.KERRIGAN_LEAPING_STRIKE, + item_names.KERRIGAN_MEND, + ] + } + self.generate_world(world_options) + itempool = [item.name for item in self.multiworld.itempool] + + # These items will be in the pool despite exclusions + self.assertIn(item_names.KERRIGAN_LEAPING_STRIKE, itempool) + self.assertIn(item_names.KERRIGAN_MEND, itempool) \ No newline at end of file diff --git a/worlds/sc2/test/test_itemdescriptions.py b/worlds/sc2/test/test_itemdescriptions.py index f4b65b2c7924..a4fd6d5c2846 100644 --- a/worlds/sc2/test/test_itemdescriptions.py +++ b/worlds/sc2/test/test_itemdescriptions.py @@ -1,17 +1,16 @@ import unittest -from .. import items -from .. import item_descriptions +from ..item import item_descriptions, item_tables class TestItemDescriptions(unittest.TestCase): def test_all_items_have_description(self) -> None: - for item_name in items.item_table: + for item_name in item_tables.item_table: self.assertIn(item_name, item_descriptions.item_descriptions) def test_all_descriptions_refer_to_item_and_end_in_dot(self) -> None: for item_name, item_desc in item_descriptions.item_descriptions.items(): - self.assertIn(item_name, items.item_table) + self.assertIn(item_name, item_tables.item_table) self.assertEqual(item_desc.strip()[-1], '.', msg=f"{item_name}'s item description does not end in a '.': '{item_desc}'") def test_item_descriptions_follow_single_space_after_period_style(self) -> None: diff --git a/worlds/sc2/test/test_itemgroups.py b/worlds/sc2/test/test_itemgroups.py index 407c6170bbb6..8b4a0939b905 100644 --- a/worlds/sc2/test/test_itemgroups.py +++ b/worlds/sc2/test/test_itemgroups.py @@ -3,7 +3,7 @@ """ import unittest -from .. import item_groups, items +from ..item import item_groups, item_tables class ItemGroupsUnitTests(unittest.TestCase): @@ -19,7 +19,8 @@ def test_all_production_structure_groups_capture_all_units(self) -> None: def test_terran_original_progressive_group_fully_contained_in_wol_upgrades(self) -> None: for item_name in item_groups.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_tables.item_table[item_name].type, ( + item_tables.TerranItemType.Progressive, item_tables.TerranItemType.Progressive_2), f"{item_name} is not progressive") self.assertIn(item_name, item_groups.wol_upgrades) def test_all_items_in_stimpack_group_are_stimpacks(self) -> None: diff --git a/worlds/sc2/test/test_items.py b/worlds/sc2/test/test_items.py index 26d970634c16..49ec698c3bc6 100644 --- a/worlds/sc2/test/test_items.py +++ b/worlds/sc2/test/test_items.py @@ -1,7 +1,7 @@ import unittest from typing import List, Set -from worlds.sc2 import items +from worlds.sc2.item import item_tables class TestItems(unittest.TestCase): @@ -10,8 +10,8 @@ def test_grouped_upgrades_number(self) -> None: Tests if grouped upgrades have set number correctly :return: """ - bundled_items = items.upgrade_bundles.keys() - bundled_item_data = [items.get_full_item_list()[item_name] for item_name in bundled_items] + bundled_items = item_tables.upgrade_bundles.keys() + bundled_item_data = [item_tables.get_full_item_list()[item_name] for item_name in bundled_items] bundled_item_numbers = [item_data.number for item_data in bundled_item_data] check_numbers = [number == -1 for number in bundled_item_numbers] @@ -24,13 +24,13 @@ def test_non_grouped_upgrades_number(self) -> None: :return: """ check_modulo = 4 - bundled_items = items.upgrade_bundles.keys() + bundled_items = item_tables.upgrade_bundles.keys() non_bundled_upgrades = [ - item_name for item_name in items.get_full_item_list().keys() + item_name for item_name in item_tables.get_full_item_list().keys() if (item_name not in bundled_items - and items.get_full_item_list()[item_name].type in items.upgrade_item_types) + and item_tables.get_full_item_list()[item_name].type in item_tables.upgrade_item_types) ] - non_bundled_upgrade_data = [items.get_full_item_list()[item_name] for item_name in non_bundled_upgrades] + non_bundled_upgrade_data = [item_tables.get_full_item_list()[item_name] for item_name in non_bundled_upgrades] non_bundled_upgrade_numbers = [item_data.number for item_data in non_bundled_upgrade_data] check_numbers = [number % check_modulo == 0 for number in non_bundled_upgrade_numbers] @@ -42,8 +42,8 @@ def test_bundles_contain_only_basic_elements(self) -> None: Checks if there are no bundles within bundles. :return: """ - bundled_items = items.upgrade_bundles.keys() - bundle_elements: List[str] = [item_name for values in items.upgrade_bundles.values() for item_name in values] + bundled_items = item_tables.upgrade_bundles.keys() + bundle_elements: List[str] = [item_name for values in item_tables.upgrade_bundles.values() for item_name in values] for element in bundle_elements: self.assertNotIn(element, bundled_items) @@ -53,41 +53,41 @@ def test_weapon_armor_level(self) -> None: Checks if Weapon/Armor upgrade level is correctly set to all Weapon/Armor upgrade items. :return: """ - weapon_armor_upgrades = [item for item in items.get_full_item_list() if items.get_item_table()[item].type in items.upgrade_item_types] + weapon_armor_upgrades = [item for item in item_tables.get_full_item_list() if item_tables.get_item_table()[item].type in item_tables.upgrade_item_types] for weapon_armor_upgrade in weapon_armor_upgrades: - self.assertEqual(items.get_full_item_list()[weapon_armor_upgrade].quantity, items.WEAPON_ARMOR_UPGRADE_MAX_LEVEL) + self.assertEqual(item_tables.get_full_item_list()[weapon_armor_upgrade].quantity, item_tables.WEAPON_ARMOR_UPGRADE_MAX_LEVEL) def test_item_ids_distinct(self) -> None: """ Verifies if there are no duplicates of item ID. :return: """ - item_ids: Set[int] = {items.get_full_item_list()[item_name].code for item_name in items.get_full_item_list()} + item_ids: Set[int] = {item_tables.get_full_item_list()[item_name].code for item_name in item_tables.get_full_item_list()} - self.assertEqual(len(item_ids), len(items.get_full_item_list())) + self.assertEqual(len(item_ids), len(item_tables.get_full_item_list())) def test_number_distinct_in_item_type(self) -> None: """ Tests if each item is distinct for sending into the mod. :return: """ - item_types: List[items.ItemTypeEnum] = [ - *[item.value for item in items.TerranItemType], - *[item.value for item in items.ZergItemType], - *[item.value for item in items.ProtossItemType], - *[item.value for item in items.FactionlessItemType] + item_types: List[item_tables.ItemTypeEnum] = [ + *[item.value for item in item_tables.TerranItemType], + *[item.value for item in item_tables.ZergItemType], + *[item.value for item in item_tables.ProtossItemType], + *[item.value for item in item_tables.FactionlessItemType] ] self.assertGreater(len(item_types), 0) for item_type in item_types: item_names: List[str] = [ - item_name for item_name in items.get_full_item_list() - if items.get_full_item_list()[item_name].number >= 0 # Negative numbers have special meaning - and items.get_full_item_list()[item_name].type == item_type + item_name for item_name in item_tables.get_full_item_list() + if item_tables.get_full_item_list()[item_name].number >= 0 # Negative numbers have special meaning + and item_tables.get_full_item_list()[item_name].type == item_type ] - item_numbers: Set[int] = {items.get_full_item_list()[item_name] for item_name in item_names} + item_numbers: Set[int] = {item_tables.get_full_item_list()[item_name] for item_name in item_names} self.assertEqual(len(item_names), len(item_numbers)) @@ -96,16 +96,16 @@ def test_progressive_has_quantity(self) -> None: Checks if the quantity attribute has been set for progressive items. :return: """ - progressive_groups: List[items.ItemTypeEnum] = [ - items.TerranItemType.Progressive, - items.TerranItemType.Progressive_2, - items.ProtossItemType.Progressive, - items.ZergItemType.Progressive + progressive_groups: List[item_tables.ItemTypeEnum] = [ + item_tables.TerranItemType.Progressive, + item_tables.TerranItemType.Progressive_2, + item_tables.ProtossItemType.Progressive, + item_tables.ZergItemType.Progressive ] quantities: List[int] = [ - items.get_full_item_list()[item].quantity for item in items.get_full_item_list() - if items.get_full_item_list()[item].type in progressive_groups + item_tables.get_full_item_list()[item].quantity for item in item_tables.get_full_item_list() + if item_tables.get_full_item_list()[item].type in progressive_groups ] self.assertNotIn(1, quantities) @@ -115,46 +115,46 @@ def test_non_progressive_quantity(self) -> None: Check if non-progressive items have quantity at most 1. :return: """ - non_progressive_single_entity_groups: List[items.ItemTypeEnum] = [ + non_progressive_single_entity_groups: List[item_tables.ItemTypeEnum] = [ # Terran - items.TerranItemType.Unit, - items.TerranItemType.Unit_2, - items.TerranItemType.Mercenary, - items.TerranItemType.Armory_1, - items.TerranItemType.Armory_2, - items.TerranItemType.Armory_3, - items.TerranItemType.Armory_4, - items.TerranItemType.Armory_5, - items.TerranItemType.Armory_6, - items.TerranItemType.Armory_7, - items.TerranItemType.Building, - items.TerranItemType.Laboratory, - items.TerranItemType.Nova_Gear, + item_tables.TerranItemType.Unit, + item_tables.TerranItemType.Unit_2, + item_tables.TerranItemType.Mercenary, + item_tables.TerranItemType.Armory_1, + item_tables.TerranItemType.Armory_2, + item_tables.TerranItemType.Armory_3, + item_tables.TerranItemType.Armory_4, + item_tables.TerranItemType.Armory_5, + item_tables.TerranItemType.Armory_6, + item_tables.TerranItemType.Armory_7, + item_tables.TerranItemType.Building, + item_tables.TerranItemType.Laboratory, + item_tables.TerranItemType.Nova_Gear, # Zerg - items.ZergItemType.Unit, - items.ZergItemType.Mercenary, - items.ZergItemType.Morph, - items.ZergItemType.Strain, - items.ZergItemType.Mutation_1, - items.ZergItemType.Mutation_2, - items.ZergItemType.Mutation_3, - items.ZergItemType.Evolution_Pit, - items.ZergItemType.Ability, + item_tables.ZergItemType.Unit, + item_tables.ZergItemType.Mercenary, + item_tables.ZergItemType.Morph, + item_tables.ZergItemType.Strain, + item_tables.ZergItemType.Mutation_1, + item_tables.ZergItemType.Mutation_2, + item_tables.ZergItemType.Mutation_3, + item_tables.ZergItemType.Evolution_Pit, + item_tables.ZergItemType.Ability, # Protoss - items.ProtossItemType.Unit, - items.ProtossItemType.Unit_2, - items.ProtossItemType.Building, - items.ProtossItemType.Forge_1, - items.ProtossItemType.Forge_2, - items.ProtossItemType.Forge_3, - items.ProtossItemType.Forge_4, - items.ProtossItemType.Solarite_Core, - items.ProtossItemType.Spear_Of_Adun + item_tables.ProtossItemType.Unit, + item_tables.ProtossItemType.Unit_2, + item_tables.ProtossItemType.Building, + item_tables.ProtossItemType.Forge_1, + item_tables.ProtossItemType.Forge_2, + item_tables.ProtossItemType.Forge_3, + item_tables.ProtossItemType.Forge_4, + item_tables.ProtossItemType.Solarite_Core, + item_tables.ProtossItemType.Spear_Of_Adun ] quantities: List[int] = [ - items.get_full_item_list()[item].quantity for item in items.get_full_item_list() - if items.get_full_item_list()[item].type in non_progressive_single_entity_groups + item_tables.get_full_item_list()[item].quantity for item in item_tables.get_full_item_list() + if item_tables.get_full_item_list()[item].type in non_progressive_single_entity_groups ] for quantity in quantities: @@ -165,14 +165,14 @@ def test_item_number_less_than_30(self) -> None: Checks if all item numbers are within bounds supported by game mod. :return: """ - not_checked_item_types: List[items.ItemTypeEnum] = [ - items.ZergItemType.Level + not_checked_item_types: List[item_tables.ItemTypeEnum] = [ + item_tables.ZergItemType.Level ] items_to_check: List[str] = [ - item for item in items.get_full_item_list() - if items.get_full_item_list()[item].type not in not_checked_item_types + item for item in item_tables.get_full_item_list() + if item_tables.get_full_item_list()[item].type not in not_checked_item_types ] for item in items_to_check: - item_number = items.get_full_item_list()[item].number + item_number = item_tables.get_full_item_list()[item].number self.assertLess(item_number, 30) diff --git a/worlds/sc2/test/test_options.py b/worlds/sc2/test/test_options.py index 793a6b5313bb..fb10f020c084 100644 --- a/worlds/sc2/test/test_options.py +++ b/worlds/sc2/test/test_options.py @@ -1,8 +1,8 @@ import unittest from typing import Set, Dict, List -from .test_base import Sc2TestBase -from .. import mission_tables, options, items +from .. import mission_tables, options +from ..item import item_tables class TestOptions(unittest.TestCase): @@ -11,19 +11,19 @@ def test_campaign_size_option_max_matches_number_of_missions(self) -> None: def test_unit_max_upgrades_matching_items(self) -> None: base_items: Set[str] = { - items.get_full_item_list()[item].parent_item for item in items.get_full_item_list() - if items.get_full_item_list()[item].parent_item is not None + item_tables.get_full_item_list()[item].parent_item for item in item_tables.get_full_item_list() + if item_tables.get_full_item_list()[item].parent_item is not None } upgrade_items: Dict[str, List[str]] = dict() for item in base_items: upgrade_items[item] = [ - upgrade_item for upgrade_item in items.get_full_item_list() - if items.get_full_item_list()[upgrade_item].parent_item == item + upgrade_item for upgrade_item in item_tables.get_full_item_list() + if item_tables.get_full_item_list()[upgrade_item].parent_item == item ] upgrade_counter: List[int] = list() for item in base_items: - quantities: List[int] = [items.get_full_item_list()[upgrade_item].quantity for upgrade_item in upgrade_items[item]] + quantities: List[int] = [item_tables.get_full_item_list()[upgrade_item].quantity for upgrade_item in upgrade_items[item]] upgrade_counter.append(sum(quantities)) self.assertEqual(options.MAX_UPGRADES_OPTION, max(upgrade_counter)) diff --git a/worlds/sc2/test/test_rules.py b/worlds/sc2/test/test_rules.py index 17824f81e604..a52cc4aad61f 100644 --- a/worlds/sc2/test/test_rules.py +++ b/worlds/sc2/test/test_rules.py @@ -6,7 +6,8 @@ from BaseClasses import ItemClassification, MultiWorld from Options import * # Mandatory -from worlds.sc2 import items, options, locations +from worlds.sc2 import options, locations +from worlds.sc2.item import item_tables class TestInventory: @@ -18,7 +19,7 @@ def __init__(self): self.progression_types: Set[ItemClassification] = {ItemClassification.progression, ItemClassification.progression_skip_balancing} def is_item_progression(self, item: str) -> bool: - return items.get_full_item_list()[item].classification in self.progression_types + return item_tables.get_full_item_list()[item].classification in self.progression_types def random_boolean(self): return self.random.choice([True, False]) diff --git a/worlds/sc2/test/test_usecases.py b/worlds/sc2/test/test_usecases.py index 8a3886e5264f..c2a2dbb8dd23 100644 --- a/worlds/sc2/test/test_usecases.py +++ b/worlds/sc2/test/test_usecases.py @@ -3,7 +3,8 @@ """ from .test_base import Sc2SetupTestBase -from .. import get_all_missions, item_groups, item_names, items, mission_tables, options +from .. import get_all_missions, mission_tables, options +from ..item import item_groups, item_tables, item_names from ..mission_tables import SC2Race, SC2Mission, SC2Campaign, MissionFlag @@ -153,7 +154,7 @@ def test_free_protoss_only_generates(self) -> None: for mission in missions: self.assertIn(mission.campaign, (mission_tables.SC2Campaign.PROLOGUE, mission_tables.SC2Campaign.PROPHECY)) for item_name in world_item_names: - self.assertIn(items.item_table[item_name].race, (mission_tables.SC2Race.ANY, mission_tables.SC2Race.PROTOSS)) + self.assertIn(item_tables.item_table[item_name].race, (mission_tables.SC2Race.ANY, mission_tables.SC2Race.PROTOSS)) def test_resource_filler_items_may_be_put_in_start_inventory(self) -> None: NUM_RESOURCE_ITEMS = 10 @@ -187,7 +188,7 @@ def test_excluding_protoss_excludes_campaigns_and_items(self) -> None: world_regions = [region.name for region in self.multiworld.regions] world_regions.remove('Menu') for item_name in world_item_names: - self.assertNotEqual(items.item_table[item_name].race, mission_tables.SC2Race.PROTOSS, f"{item_name} is a PROTOSS item!") + self.assertNotEqual(item_tables.item_table[item_name].race, mission_tables.SC2Race.PROTOSS, f"{item_name} is a PROTOSS item!") for region in world_regions: self.assertNotIn(mission_tables.lookup_name_to_mission[region].campaign, (mission_tables.SC2Campaign.LOTV, mission_tables.SC2Campaign.PROPHECY, mission_tables.SC2Campaign.PROLOGUE), @@ -210,7 +211,7 @@ def test_excluding_terran_excludes_campaigns_and_items(self) -> None: world_regions = [region.name for region in self.multiworld.regions] world_regions.remove('Menu') for item_name in world_item_names: - self.assertNotEqual(items.item_table[item_name].race, mission_tables.SC2Race.TERRAN, + self.assertNotEqual(item_tables.item_table[item_name].race, mission_tables.SC2Race.TERRAN, f"{item_name} is a TERRAN item!") for region in world_regions: self.assertNotIn(mission_tables.lookup_name_to_mission[region].campaign, @@ -228,13 +229,16 @@ def test_excluding_zerg_excludes_campaigns_and_items(self) -> None: 'enable_lotv_missions': True, 'enable_epilogue_missions': True, 'mission_order': options.MissionOrder.option_grid, + 'excluded_missions': [ + SC2Mission.THE_INFINITE_CYCLE.mission_name + ] } self.generate_world(world_options) world_item_names = [item.name for item in self.multiworld.itempool] world_regions = [region.name for region in self.multiworld.regions] world_regions.remove('Menu') for item_name in world_item_names: - self.assertNotEqual(items.item_table[item_name].race, mission_tables.SC2Race.ZERG, + self.assertNotEqual(item_tables.item_table[item_name].race, mission_tables.SC2Race.ZERG, f"{item_name} is a ZERG item!") # have to manually exclude the only non-zerg HotS mission... for region in filter(lambda region: region != "With Friends Like These", world_regions):