Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sc2: Adding an option to enable war council nerfs #224

Merged
merged 2 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion worlds/sc2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from worlds.AutoWorld import WebWorld, World
from . import item_names
from .items import (
StarcraftItem, filler_items, get_full_item_list,
StarcraftItem, filler_items, get_full_item_list, ProtossItemType,
get_basic_units, ItemData, upgrade_included_names, kerrigan_actives, kerrigan_passives,
not_balanced_starting_units,
)
Expand Down Expand Up @@ -129,6 +129,7 @@ def create_items(self):
flag_start_inventory(self, item_list)
flag_unused_upgrade_types(self, item_list)
flag_user_excluded_item_sets(self, item_list)
flag_war_council_excludes(self, item_list)
flag_and_add_resource_locations(self, item_list)
pool: List[Item] = prune_item_pool(self, item_list)
pad_item_pool_with_filler(self, len(self.location_cache) - len(self.locked_locations) - len(pool), pool)
Expand Down Expand Up @@ -590,6 +591,16 @@ def flag_user_excluded_item_sets(world: SC2World, item_list: List[FilterItem]) -
item.flags |= ItemFilterFlags.Excluded
vanilla_nonprogressive_count[item.name] += 1

def flag_war_council_excludes(world: SC2World, item_list: List[FilterItem]) -> None:
"""Excludes items based on item set options (`only_vanilla_items`)"""
if world.options.allow_unit_nerfs:
return

for item in item_list:
if item.data.type != ProtossItemType.War_Council:
continue
item.flags |= ItemFilterFlags.Excluded


def flag_and_add_resource_locations(world: SC2World, item_list: List[FilterItem]) -> None:
"""
Expand Down
60 changes: 32 additions & 28 deletions worlds/sc2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
LocationInclusion, ExtraLocations, MasteryLocations, ChallengeLocations, VanillaLocations,
DisableForcedCamera, SkipCutscenes, GrantStoryTech, GrantStoryLevels, TakeOverAIAllies, RequiredTactics,
SpearOfAdunPresence, SpearOfAdunPresentInNoBuild, SpearOfAdunAutonomouslyCastAbilityPresence,
SpearOfAdunAutonomouslyCastPresentInNoBuild, LEGACY_GRID_ORDERS,
SpearOfAdunAutonomouslyCastPresentInNoBuild, AllowUnitNerfs, LEGACY_GRID_ORDERS,
)


Expand All @@ -49,12 +49,14 @@
from worlds._sc2common.bot.player import Bot
from .items import (
lookup_id_to_name, get_full_item_list, ItemData,
race_to_item_type, upgrade_item_types, ZergItemType, upgrade_bundles, upgrade_included_names,
race_to_item_type, ZergItemType, ProtossItemType, upgrade_bundles, upgrade_included_names,
WEAPON_ARMOR_UPGRADE_MAX_LEVEL,
)
from .locations import SC2WOL_LOC_ID_OFFSET, LocationType, SC2HOTS_LOC_ID_OFFSET
from .mission_tables import lookup_id_to_mission, SC2Campaign, lookup_name_to_mission, \
from .mission_tables import (
lookup_id_to_mission, SC2Campaign, lookup_name_to_mission,
lookup_id_to_campaign, MissionConnection, SC2Mission, campaign_mission_table, SC2Race
)
from .regions import MissionInfo

import colorama
Expand Down Expand Up @@ -327,6 +329,7 @@ def _cmd_option(self, option_name: str = "", option_value: str = "") -> None:
ConfigurableOptionInfo('no_forced_camera', 'disable_forced_camera', options.DisableForcedCamera),
ConfigurableOptionInfo('skip_cutscenes', 'skip_cutscenes', options.SkipCutscenes),
ConfigurableOptionInfo('enable_morphling', 'enable_morphling', options.EnableMorphling, can_break_logic=True),
ConfigurableOptionInfo('unit_nerfs', 'allow_unit_nerfs', options.AllowUnitNerfs, can_break_logic=True),
)

WARNING_COLOUR = "salmon"
Expand Down Expand Up @@ -540,6 +543,7 @@ def __init__(self, *args, **kwargs) -> None:
self.kerrigan_presence: int = KerriganPresence.default
self.kerrigan_primal_status = 0
self.enable_morphling = EnableMorphling.default
self.allow_unit_nerfs: int = AllowUnitNerfs.default
self.mission_req_table: typing.Dict[SC2Campaign, typing.Dict[str, MissionInfo]] = {}
self.final_mission: int = 29
self.announcements: queue.Queue = queue.Queue()
Expand Down Expand Up @@ -647,6 +651,7 @@ def on_package(self, cmd: str, args: dict) -> None:
self.vespene_per_item = args["slot_data"].get("vespene_per_item", 15)
self.starting_supply_per_item = args["slot_data"].get("starting_supply_per_item", 2)
self.nova_covert_ops_only = args["slot_data"].get("nova_covert_ops_only", False)
self.allow_unit_nerfs = args["slot_data"].get("allow_unit_nerfs", AllowUnitNerfs.default)

if self.required_tactics == RequiredTactics.option_no_logic:
# Locking Grant Story Tech/Levels if no logic
Expand Down Expand Up @@ -890,6 +895,10 @@ def calculate_items(ctx: SC2Context) -> typing.Dict[SC2Race, typing.List[int]]:
shield_upgrade_item = item_list[item_names.PROGRESSIVE_PROTOSS_SHIELDS]
for _ in range(0, shield_upgrade_level):
accumulators[shield_upgrade_item.race][shield_upgrade_item.type.flag_word] += 1 << shield_upgrade_item.number

# War council option
if not ctx.allow_unit_nerfs:
accumulators[SC2Race.PROTOSS][ProtossItemType.War_Council.flag_word] = (1 << 30) - 1

# Deprecated Orbital Command handling (Backwards compatibility):
if orbital_command_count > 0:
Expand Down Expand Up @@ -1103,23 +1112,24 @@ async def on_step(self, iteration: int):
game_speed = self.ctx.game_speed_override
else:
game_speed = self.ctx.game_speed
await self.chat_send("?SetOptions {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}".format(
difficulty,
self.ctx.generic_upgrade_research,
self.ctx.all_in_choice,
game_speed,
self.ctx.disable_forced_camera,
self.ctx.skip_cutscenes,
kerrigan_options,
self.ctx.grant_story_tech,
self.ctx.take_over_ai_allies,
soa_options,
self.ctx.mission_order,
1 if self.ctx.nova_covert_ops_only else 0,
self.ctx.grant_story_levels,
self.ctx.enable_morphling,
mission_variant
))
await self.chat_send(
"?SetOptions"
f" {difficulty}"
f" {self.ctx.generic_upgrade_research}"
f" {self.ctx.all_in_choice}"
f" {game_speed}"
f" {self.ctx.disable_forced_camera}"
f" {self.ctx.skip_cutscenes}"
f" {kerrigan_options}"
f" {self.ctx.grant_story_tech}"
f" {self.ctx.take_over_ai_allies}"
f" {soa_options}"
f" {self.ctx.mission_order}"
f" {int(self.ctx.nova_covert_ops_only)}"
f" {self.ctx.grant_story_levels}"
f" {self.ctx.enable_morphling}"
f" {mission_variant}"
)
await self.chat_send("?GiveResources {} {} {}".format(
start_items[SC2Race.ANY][0],
start_items[SC2Race.ANY][1],
Expand Down Expand Up @@ -1220,17 +1230,11 @@ async def updateZergTech(self, current_items, kerrigan_level):
zerg_items = current_items[SC2Race.ZERG]
kerrigan_primal_by_items = kerrigan_primal(self.ctx, kerrigan_level)
kerrigan_primal_bot_value = 1 if kerrigan_primal_by_items else 0
await self.chat_send("?GiveZergTech {} {} {} {} {} {} {} {} {} {} {} {}".format(
kerrigan_level, kerrigan_primal_bot_value, zerg_items[0], zerg_items[1], zerg_items[2],
zerg_items[3], zerg_items[4], zerg_items[5], zerg_items[6], zerg_items[9], zerg_items[10], zerg_items[11]
))
await self.chat_send(f"?GiveZergTech {kerrigan_level} {kerrigan_primal_bot_value} " + ' '.join(map(str, zerg_items)))

async def updateProtossTech(self, current_items):
protoss_items = current_items[SC2Race.PROTOSS]
await self.chat_send("?GiveProtossTech {} {} {} {} {} {} {} {} {} {}".format(
protoss_items[0], protoss_items[1], protoss_items[2], protoss_items[3], protoss_items[4],
protoss_items[5], protoss_items[6], protoss_items[7], protoss_items[8], protoss_items[9]
))
await self.chat_send("?GiveProtossTech " + " ".join(map(str, protoss_items)))


def request_unfinished_missions(ctx: SC2Context) -> None:
Expand Down
8 changes: 8 additions & 0 deletions worlds/sc2/item_descriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
item_names.REAVER: (100, 100, 2),
DISPLAY_NAME_CLOAKED_ASSASSIN: (0, 50, 0),
item_names.SCOUT: (125, 25, 1),

# War Council
item_names.CENTURION: (0, 50, 0),
item_names.SENTINEL: (60, 0, 1),
}


Expand Down Expand Up @@ -831,6 +835,10 @@ def _ability_desc(unit_name_plural: str, ability_name: str, ability_description:
item_names.HAVOC_BLOODSHARD_RESONANCE: "Havoc gain increased range for Squad Sight, Target Lock, and Force Field.",
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: _get_resource_efficiency_desc(item_names.CENTURION),
item_names.SENTINEL_RESOURCE_EFFICIENCY: _get_resource_efficiency_desc(item_names.SENTINEL),
item_names.STALKER_PHASE_REACTOR: "Stalkers restore 80 shields over 5 seconds after they Blink.",
item_names.SOA_CHRONO_SURGE: "The Spear of Adun increases a target structure's unit warp in and research speeds by +1000% for 20 seconds.",
item_names.SOA_PROGRESSIVE_PROXY_PYLON: inspect.cleandoc("""
Level 1: The Spear of Adun quickly warps in a Pylon to a target location.
Expand Down
5 changes: 5 additions & 0 deletions worlds/sc2/item_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class ItemGroupNames:
SOA_ITEMS = "SOA"
PROTOSS_GLOBAL_UPGRADES = "Protoss Global Upgrades"
PROTOSS_BUILDINGS = "Protoss Buildings"
WAR_COUNCIL = "Protoss War Council Upgrades"
AIUR_UNITS = "Aiur"
NERAZIM_UNITS = "Nerazim"
TAL_DARIM_UNITS = "Tal'Darim"
Expand Down Expand Up @@ -587,3 +588,7 @@ def get_all_group_names(cls) -> typing.Set[str]:
item_name_groups[ItemGroupNames.VANILLA_ITEMS] = vanilla_items = (
vanilla_wol_items + vanilla_hots_items + vanilla_lotv_items
)

item_name_groups[ItemGroupNames.WAR_COUNCIL] = [
item_name for item_name, item_data in items.item_table.items() if item_data.type == items.ProtossItemType.War_Council
]
6 changes: 6 additions & 0 deletions worlds/sc2/item_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,12 @@
ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS = "Leg Enhancements (Zealot/Sentinel/Centurion)"
ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY = "Shield Capacity (Zealot/Sentinel/Centurion)"

# War Council
ZEALOT_WHIRLWIND = "Whirlwind (Zealot)"
CENTURION_RESOURCE_EFFICIENCY = "Resource Efficiency (Centurion)"
SENTINEL_RESOURCE_EFFICIENCY = "Resource Efficiency (Sentinel)"
STALKER_PHASE_REACTOR = "Phase Reactor (Stalker)"

# Spear Of Adun
SOA_CHRONO_SURGE = "Chrono Surge (Spear of Adun Calldown)"
SOA_PROGRESSIVE_PROXY_PYLON = "Progressive Proxy Pylon (Spear of Adun Calldown)"
Expand Down
7 changes: 7 additions & 0 deletions worlds/sc2/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class ProtossItemType(ItemTypeEnum):
"""General Protoss unit upgrades"""
Forge_3 = "Forge", 9
"""General Protoss unit upgrades"""
War_Council = "War Council", 10


class FactionlessItemType(ItemTypeEnum):
Expand Down Expand Up @@ -1651,6 +1652,12 @@ def get_full_item_list():
item_names.ORACLE_BOSONIC_CORE: ItemData(378 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 18, SC2Race.PROTOSS, origin={"ext"}, parent_item=item_names.ORACLE),
item_names.SCOUT_RESOURCE_EFFICIENCY: ItemData(379 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Forge_3, 19, SC2Race.PROTOSS, origin={"ext"}, parent_item=item_names.SCOUT),

# War Council
item_names.ZEALOT_WHIRLWIND: ItemData(500 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.War_Council, 0, SC2Race.PROTOSS, parent_item=item_names.ZEALOT),
item_names.CENTURION_RESOURCE_EFFICIENCY: ItemData(501 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.War_Council, 1, SC2Race.PROTOSS, parent_item=item_names.CENTURION),
item_names.SENTINEL_RESOURCE_EFFICIENCY: ItemData(502 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.War_Council, 2, SC2Race.PROTOSS, parent_item=item_names.SENTINEL),
item_names.STALKER_PHASE_REACTOR: ItemData(503 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.War_Council, 3, SC2Race.PROTOSS, parent_item=item_names.STALKER),

# SoA Calldown powers
item_names.SOA_CHRONO_SURGE: ItemData(700 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Spear_Of_Adun, 0, SC2Race.PROTOSS, origin={"lotv"}),
item_names.SOA_PROGRESSIVE_PROXY_PYLON: ItemData(701 + SC2LOTV_ITEM_ID_OFFSET, ProtossItemType.Progressive, 0, SC2Race.PROTOSS, origin={"lotv"}, quantity=2),
Expand Down
9 changes: 9 additions & 0 deletions worlds/sc2/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,14 @@ class EnableMorphling(Toggle):
display_name = "Enable Morphling"


class AllowUnitNerfs(Toggle):
"""
Controls whether some units can initially be found in a nerfed state, with upgrades restoring their stronger power level.
For example, nerfed Zealots will lack the whirlwind upgrade until it is found as an item.
"""
display_name = "Allow Unit Nerfs"


class SpearOfAdunPresence(Choice):
"""
Determines in which missions Spear of Adun calldowns will be available.
Expand Down Expand Up @@ -929,6 +937,7 @@ class Starcraft2Options(PerGameCommonOptions):
start_primary_abilities: StartPrimaryAbilities
kerrigan_primal_status: KerriganPrimalStatus
enable_morphling: EnableMorphling
allow_unit_nerfs: AllowUnitNerfs
spear_of_adun_presence: SpearOfAdunPresence
spear_of_adun_present_in_no_build: SpearOfAdunPresentInNoBuild
spear_of_adun_autonomously_cast_ability_presence: SpearOfAdunAutonomouslyCastAbilityPresence
Expand Down
21 changes: 21 additions & 0 deletions worlds/sc2/test/test_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,3 +471,24 @@ def test_planetary_orbital_module_not_present_without_cc_spells(self) -> None:
self.assertTrue(itempool)
self.assertIn(item_names.PLANETARY_FORTRESS, itempool)
self.assertNotIn(item_names.PLANETARY_FORTRESS_ORBITAL_MODULE, itempool)

def test_disabling_unit_nerfs_removes_war_council_upgrades(self) -> None:
world_options = {
'enable_wol_missions': False,
'enable_prophecy_missions': True,
'enable_hots_missions': False,
'enable_lotv_prologue_missions': True,
'enable_lotv_missions': True,
'enable_epilogue_missions': False,
'enable_nco_missions': False,
'mission_order': options.MissionOrder.option_grid,
'allow_unit_nerfs': options.AllowUnitNerfs.option_false,
}

self.generate_world(world_options)
itempool = [item.name for item in self.multiworld.itempool]
war_council_item_names = set(item_groups.item_name_groups[item_groups.ItemGroupNames.WAR_COUNCIL])
present_war_council_items = war_council_item_names.intersection(itempool)

self.assertTrue(itempool)
self.assertFalse(present_war_council_items, f'Found war council upgrades when allow_unit_nerfs is false: {present_war_council_items}')
Loading