diff --git a/worlds/sc2/item_descriptions.py b/worlds/sc2/item_descriptions.py index 1acd48b35641..f8d7d7282d6e 100644 --- a/worlds/sc2/item_descriptions.py +++ b/worlds/sc2/item_descriptions.py @@ -45,8 +45,8 @@ item_names.WARHOUND: (75, 0, 0), item_names.HERC: (25, 25, 1), item_names.WRAITH: (0, 50, 0), - item_names.GHOST: (125, 75, 1), - item_names.SPECTRE: (125, 75, 1), + item_names.GHOST: (25, 25, 1), + item_names.SPECTRE: (25, 25, 1), item_names.RAVEN: (0, 50, 0), item_names.CYCLONE: (25, 50, 1), item_names.WIDOW_MINE: (0, 25, 1), @@ -157,6 +157,7 @@ def _ability_desc(unit_name_plural: str, ability_name: str, ability_description: Durable Royal Guard support flyer. Loaded with strong anti-capital air missiles. Can switch into Assault Mode to attack ground units. """), + item_names.SHOCK_DIVISION: "Royal Guard Heavy tank. Long-range artillery in Siege Mode.", item_names.PROGRESSIVE_TERRAN_INFANTRY_WEAPON: GENERIC_UPGRADE_TEMPLATE.format("damage", TERRAN, "infantry"), item_names.PROGRESSIVE_TERRAN_INFANTRY_ARMOR: GENERIC_UPGRADE_TEMPLATE.format("armor", TERRAN, "infantry"), item_names.PROGRESSIVE_TERRAN_VEHICLE_WEAPON: GENERIC_UPGRADE_TEMPLATE.format("damage", TERRAN, "vehicles"), @@ -513,7 +514,7 @@ def _ability_desc(unit_name_plural: str, ability_name: str, ability_description: item_names.MECHANICAL_KNOW_HOW: "Increases mechanical unit life by 20%.", item_names.MERCENARY_MUNITIONS: "Increases attack speed of all combat units by 15%.", item_names.FAST_DELIVERY: "Mercenary calldowns can be deployed right at the mission start.", - item_names.RAPID_REINFORCEMENT: "Halves cooldowns of mercenary calldowns.", + item_names.RAPID_REINFORCEMENT: "Reduces cooldowns of mercenary calldowns by 60s.", item_names.ZEALOT: "Powerful melee warrior. Can use the charge ability.", item_names.STALKER: "Ranged attack strider. Can use the Blink ability.", item_names.HIGH_TEMPLAR: "Potent psionic master. Can use the Feedback and Psionic Storm abilities. Can merge into an Archon.", @@ -946,7 +947,7 @@ def _ability_desc(unit_name_plural: str, ability_name: str, ability_description: item_names.DAWNBRINGER_SOLARITE_LENS: "Dawnbringer War Council upgrade. Dawnbringers gain +2 range.", item_names.CARRIER_REPAIR_DRONES: "Carrier War Council upgrade. Carriers gain 2 repair drones which heal nearby mechanical units.", item_names.SKYLORD_HYPERJUMP: "Skylord War Council upgrade. " + _ability_desc("Skylords", "Hyperjump", "teleports the skylord to any location on the map."), - # Trireme + item_names.TRIREME_SOLAR_BEAM: "Trireme War Council upgrade. Triremes gain an anti-air laser attack that deals more damage over time.", item_names.TEMPEST_DISINTEGRATION: "Tempest War Council upgrade. " + _ability_desc("Tempests", "Disintegration", "deals 500 damage to a target unit or structure over 20 seconds"), # Scout item_names.ARBITER_ABILITY_EFFICIENCY: "Arbiter War Council upgrade. Reduces the energy cost of Recall by 50 and Stasis Field by 100.", diff --git a/worlds/sc2/item_groups.py b/worlds/sc2/item_groups.py index 7c5d947897cf..df2c8b608a52 100644 --- a/worlds/sc2/item_groups.py +++ b/worlds/sc2/item_groups.py @@ -163,6 +163,7 @@ class ItemGroupNames: PURIFIER_UNITS = "Purifier" VANILLA_ITEMS = "Vanilla Items" + OVERPOWERED_ITEMS = "Overpowered Items" @classmethod def get_all_group_names(cls) -> typing.Set[str]: @@ -180,7 +181,7 @@ def get_all_group_names(cls) -> typing.Set[str]: ] item_name_groups[ItemGroupNames.TERRAN_UNITS] = terran_units = [ item_name for item_name, item_data in items.item_table.items() - if item_data.type in (items.TerranItemType.Unit, items.TerranItemType.Mercenary) + if item_data.type in (items.TerranItemType.Unit, items.TerranItemType.Unit_2, items.TerranItemType.Mercenary) ] item_name_groups[ItemGroupNames.TERRAN_GENERIC_UPGRADES] = terran_generic_upgrades = [ item_name for item_name, item_data in items.item_table.items() @@ -193,7 +194,7 @@ def get_all_group_names(cls) -> typing.Set[str]: item_name_groups[ItemGroupNames.FACTORY_UNITS] = factory_units = [ item_names.HELLION, item_names.VULTURE, item_names.GOLIATH, item_names.DIAMONDBACK, item_names.SIEGE_TANK, item_names.THOR, item_names.PREDATOR, item_names.WIDOW_MINE, - item_names.CYCLONE, item_names.WARHOUND, + item_names.CYCLONE, item_names.WARHOUND, item_names.SHOCK_DIVISION, ] item_name_groups[ItemGroupNames.STARPORT_UNITS] = starport_units = [ item_names.MEDIVAC, item_names.WRAITH, item_names.VIKING, item_names.BANSHEE, @@ -375,7 +376,7 @@ def get_all_group_names(cls) -> typing.Set[str]: if item_data.type in (items.TerranItemType.Progressive, items.TerranItemType.Progressive_2) ] item_name_groups[ItemGroupNames.MENGSK_UNITS] = [ - item_names.PRIDE_OF_AUGUSTRGRAD, item_names.SKY_FURY, + item_names.PRIDE_OF_AUGUSTRGRAD, item_names.SKY_FURY, item_names.SHOCK_DIVISION, ] item_name_groups[ItemGroupNames.WOL_ITEMS] = vanilla_wol_items = ( wol_units @@ -627,3 +628,24 @@ def get_all_group_names(cls) -> typing.Set[str]: 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_groups[ItemGroupNames.OVERPOWERED_ITEMS] = [ + item_names.SIEGE_TANK_GRADUATING_RANGE, + item_names.SIEGE_TANK_RESOURCE_EFFICIENCY, + item_names.BATTLECRUISER_ATX_LASER_BATTERY, + item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL, + item_names.MECHANICAL_KNOW_HOW, + item_names.MERCENARY_MUNITIONS, + + item_names.KERRIGAN_APOCALYPSE, + item_names.KERRIGAN_DROP_PODS, + item_names.KERRIGAN_SPAWN_LEVIATHAN, + + item_names.REAVER_RESOURCE_EFFICIENCY, + item_names.SOA_TIME_STOP, + item_names.SOA_SOLAR_LANCE, + # Note: This is more an issue of having multiple ults at the same time, rather than solar bombardment in particular. + # Can be removed from the list if we get an SOA ult combined cooldown or energy cost on it. + item_names.SOA_SOLAR_BOMBARDMENT, + item_names.MOTHERSHIP, +] diff --git a/worlds/sc2/item_names.py b/worlds/sc2/item_names.py index 1186a65540e6..fa1c2247057c 100644 --- a/worlds/sc2/item_names.py +++ b/worlds/sc2/item_names.py @@ -38,6 +38,7 @@ # Elites PRIDE_OF_AUGUSTRGRAD = "Pride of Augustgrad" SKY_FURY = "Sky Fury" +SHOCK_DIVISION = "Shock Division" # Terran Buildings BUNKER = "Bunker" @@ -770,7 +771,7 @@ DAWNBRINGER_SOLARITE_LENS = "Solarite Lens (Dawnbringer)" CARRIER_REPAIR_DRONES = "Repair Drones (Carrier)" SKYLORD_HYPERJUMP = "Hyperjump (Skylord)" -# Trireme +TRIREME_SOLAR_BEAM = "Solar Beam (Trieme)" TEMPEST_DISINTEGRATION = "Disintegration (Tempest)" # Scout ARBITER_ABILITY_EFFICIENCY = "Ability Efficiency (Arbiter)" diff --git a/worlds/sc2/items.py b/worlds/sc2/items.py index fc8c99d20085..56fdb7f58931 100644 --- a/worlds/sc2/items.py +++ b/worlds/sc2/items.py @@ -42,6 +42,7 @@ class TerranItemType(ItemTypeEnum): Mercenary = "Mercenary", 12 Nova_Gear = "Nova Gear", 13 Progressive_2 = "Progressive Upgrade", 14 + Unit_2 = "Unit", 15 class ZergItemType(ItemTypeEnum): @@ -218,6 +219,9 @@ def get_full_item_list(): item_names.SKY_FURY: ItemData(51 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit, 29, SC2Race.TERRAN, classification=ItemClassification.progression, origin={"ext"}), + item_names.SHOCK_DIVISION: + ItemData(52 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Unit_2, 0, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}), # Some other items are moved to Upgrade group because of the way how the bot message is parsed item_names.PROGRESSIVE_TERRAN_INFANTRY_WEAPON: ItemData(100 + SC2WOL_ITEM_ID_OFFSET, TerranItemType.Upgrade, 0, SC2Race.TERRAN, quantity=WEAPON_ARMOR_UPGRADE_MAX_LEVEL), @@ -1867,7 +1871,7 @@ def get_full_item_list(): item_names.DAWNBRINGER_SOLARITE_LENS: ItemData(537 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.War_Council_2, 7, SC2Race.PROTOSS, classification=ItemClassification.progression, parent_item=item_names.DAWNBRINGER), item_names.CARRIER_REPAIR_DRONES: ItemData(538 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.War_Council_2, 8, SC2Race.PROTOSS, classification=ItemClassification.progression, parent_item=item_names.CARRIER), item_names.SKYLORD_HYPERJUMP: ItemData(539 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.War_Council_2, 9, SC2Race.PROTOSS, parent_item=item_names.SKYLORD), - # 540 reserved for Trireme + item_names.TRIREME_SOLAR_BEAM: ItemData(540 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.War_Council_2, 10, SC2Race.PROTOSS, classification=ItemClassification.progression, parent_item=item_names.TRIREME), item_names.TEMPEST_DISINTEGRATION: ItemData(541 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.War_Council_2, 11, SC2Race.PROTOSS, parent_item=item_names.TEMPEST), # 542 reserved for Scout item_names.ARBITER_ABILITY_EFFICIENCY: ItemData(543 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.War_Council_2, 13, SC2Race.PROTOSS, parent_item=item_names.ARBITER), diff --git a/worlds/sc2/mission_groups.py b/worlds/sc2/mission_groups.py index fb2d46126d92..d6cb6fb2638e 100644 --- a/worlds/sc2/mission_groups.py +++ b/worlds/sc2/mission_groups.py @@ -192,3 +192,9 @@ def get_all_group_names(cls) -> Set[str]: SC2Mission.THE_HOST.mission_name, SC2Mission.SALVATION.mission_name, ] + +for mission in SC2Mission: + if mission.flags & MissionFlag.HasRaceSwap: + short_name = mission.mission_name[:mission.mission_name.find(' (')] + mission_groups[short_name] = [mission_var.mission_name for mission_var in SC2Mission + if short_name in mission_var.mission_name] diff --git a/worlds/sc2/rules.py b/worlds/sc2/rules.py index ebb6f1251124..8b1e23eb1a6d 100644 --- a/worlds/sc2/rules.py +++ b/worlds/sc2/rules.py @@ -146,7 +146,8 @@ def terran_basic_anti_air(self, state: CollectionState) -> bool: item_names.PRIDE_OF_AUGUSTRGRAD, ), self.player) or ( - state.has_all((item_names.SIEGE_TANK, item_names.MEDIVAC), self.player) + 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 ) ) @@ -853,6 +854,7 @@ def protoss_basic_anti_air(self, state: CollectionState) -> bool: item_names.PHOENIX, item_names.MIRAGE, item_names.CORSAIR, item_names.CARRIER, item_names.SKYLORD, item_names.SCOUT, item_names.DARK_ARCHON, item_names.MOTHERSHIP }, self.player) + or state.has_all({item_names.TRIREME, item_names.TRIREME_SOLAR_BEAM}, self.player) or state.has_all({item_names.WRATHWALKER, item_names.WRATHWALKER_AERIAL_TRACKING}, self.player) or state.has_all({item_names.WARP_PRISM, item_names.WARP_PRISM_PHASE_BLASTER}, self.player) or self.advanced_tactics and state.has_any( diff --git a/worlds/sc2/test/test_generation.py b/worlds/sc2/test/test_generation.py index d13aa84ae2c5..0a904a29287d 100644 --- a/worlds/sc2/test/test_generation.py +++ b/worlds/sc2/test/test_generation.py @@ -529,3 +529,46 @@ def test_nco_and_wol_picks_correct_starting_mission(self): self.generate_world(world_options) self.assertEqual(get_first_mission(self.world, self.world.custom_mission_order), mission_tables.SC2Mission.LIBERATION_DAY) + def test_excluding_mission_short_name_excludes_all_variants_of_mission(self): + world_options = { + 'excluded_missions': [ + mission_tables.SC2Mission.ZERO_HOUR.mission_name.split(" (")[0] + ], + 'mission_order': options.MissionOrder.option_grid, + 'selected_races': options.SelectRaces.option_all, + 'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all, + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'enable_nco_missions': False, + } + self.generate_world(world_options) + missions = get_all_missions(self.world.custom_mission_order) + self.assertTrue(missions) + self.assertNotIn(mission_tables.SC2Mission.ZERO_HOUR, missions) + self.assertNotIn(mission_tables.SC2Mission.ZERO_HOUR_Z, missions) + self.assertNotIn(mission_tables.SC2Mission.ZERO_HOUR_P, missions) + + def test_excluding_mission_variant_excludes_just_that_variant(self): + world_options = { + 'excluded_missions': [ + mission_tables.SC2Mission.ZERO_HOUR.mission_name + ], + 'mission_order': options.MissionOrder.option_grid, + 'selected_races': options.SelectRaces.option_all, + 'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all, + 'enable_prophecy_missions': False, + 'enable_hots_missions': False, + 'enable_lotv_prologue_missions': False, + 'enable_lotv_missions': False, + 'enable_epilogue_missions': False, + 'enable_nco_missions': False, + } + self.generate_world(world_options) + missions = get_all_missions(self.world.custom_mission_order) + self.assertTrue(missions) + self.assertNotIn(mission_tables.SC2Mission.ZERO_HOUR, missions) + self.assertIn(mission_tables.SC2Mission.ZERO_HOUR_Z, missions) + self.assertIn(mission_tables.SC2Mission.ZERO_HOUR_P, missions) \ No newline at end of file