From 154e17f4ff161e833816c857939cc7115c20ae10 Mon Sep 17 00:00:00 2001 From: Ziktofel Date: Wed, 8 Nov 2023 19:00:55 +0100 Subject: [PATCH] SC2: 0.4.3 bugfixes (#2273) Co-authored-by: Matthew --- worlds/sc2wol/Client.py | 9 +++++++-- worlds/sc2wol/Locations.py | 8 ++++---- worlds/sc2wol/Options.py | 8 ++++---- worlds/sc2wol/PoolFilter.py | 35 +++++++++++++++++++++++------------ worlds/sc2wol/Starcraft2.kv | 2 +- worlds/sc2wol/__init__.py | 4 ++-- 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/worlds/sc2wol/Client.py b/worlds/sc2wol/Client.py index a9bb826b..3dbd2047 100644 --- a/worlds/sc2wol/Client.py +++ b/worlds/sc2wol/Client.py @@ -9,6 +9,7 @@ import multiprocessing import os.path import re import sys +import tempfile import typing import queue import zipfile @@ -286,6 +287,8 @@ class SC2Context(CommonContext): await super(SC2Context, self).server_auth(password_requested) await self.get_username() await self.send_connect() + if self.ui: + self.ui.first_check = True def on_package(self, cmd: str, args: dict): if cmd in {"Connected"}: @@ -1166,10 +1169,12 @@ def download_latest_release_zip(owner: str, repo: str, api_version: str, metadat r2 = requests.get(download_url, headers=headers) if r2.status_code == 200 and zipfile.is_zipfile(io.BytesIO(r2.content)): - with open(f"{repo}.zip", "wb") as fh: + tempdir = tempfile.gettempdir() + file = tempdir + os.sep + f"{repo}.zip" + with open(file, "wb") as fh: fh.write(r2.content) sc2_logger.info(f"Successfully downloaded {repo}.zip.") - return f"{repo}.zip", latest_metadata + return file, latest_metadata else: sc2_logger.warning(f"Status code: {r2.status_code}") sc2_logger.warning("Download failed.") diff --git a/worlds/sc2wol/Locations.py b/worlds/sc2wol/Locations.py index ae31fa8e..fba70513 100644 --- a/worlds/sc2wol/Locations.py +++ b/worlds/sc2wol/Locations.py @@ -68,10 +68,10 @@ def get_locations(multiworld: Optional[MultiWorld], player: Optional[int]) -> Tu lambda state: state._sc2wol_has_common_unit(multiworld, player) and (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Evacuation", "Evacuation: First Chrysalis", SC2WOL_LOC_ID_OFFSET + 401, LocationType.BONUS), - LocationData("Evacuation", "Evacuation: Second Chrysalis", SC2WOL_LOC_ID_OFFSET + 402, LocationType.BONUS, + LocationData("Evacuation", "Evacuation: North Chrysalis", SC2WOL_LOC_ID_OFFSET + 401, LocationType.BONUS), + LocationData("Evacuation", "Evacuation: West Chrysalis", SC2WOL_LOC_ID_OFFSET + 402, LocationType.BONUS, lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Evacuation", "Evacuation: Third Chrysalis", SC2WOL_LOC_ID_OFFSET + 403, LocationType.BONUS, + LocationData("Evacuation", "Evacuation: East Chrysalis", SC2WOL_LOC_ID_OFFSET + 403, LocationType.BONUS, lambda state: state._sc2wol_has_common_unit(multiworld, player)), LocationData("Evacuation", "Evacuation: Reach Hanson", SC2WOL_LOC_ID_OFFSET + 404, LocationType.MISSION_PROGRESS), LocationData("Evacuation", "Evacuation: Secret Resource Stash", SC2WOL_LOC_ID_OFFSET + 405, LocationType.BONUS), @@ -419,7 +419,7 @@ def get_locations(multiworld: Optional[MultiWorld], player: Optional[int]) -> Tu lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), LocationData("A Sinister Turn", "A Sinister Turn: Northeast Base", SC2WOL_LOC_ID_OFFSET + 2304, LocationType.MISSION_PROGRESS, lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Southeast Base", SC2WOL_LOC_ID_OFFSET + 2305, LocationType.MISSION_PROGRESS, + LocationData("A Sinister Turn", "A Sinister Turn: Southwest Base", SC2WOL_LOC_ID_OFFSET + 2305, LocationType.MISSION_PROGRESS, lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), LocationData("A Sinister Turn", "A Sinister Turn: Maar", SC2WOL_LOC_ID_OFFSET + 2306, LocationType.MISSION_PROGRESS, lambda state: logic_level > 0 or state._sc2wol_has_protoss_medium_units(multiworld, player)), diff --git a/worlds/sc2wol/Options.py b/worlds/sc2wol/Options.py index 13b01c42..e4b6a740 100644 --- a/worlds/sc2wol/Options.py +++ b/worlds/sc2wol/Options.py @@ -41,6 +41,10 @@ class FinalMap(Choice): Vanilla mission order always ends with All in mission! + Warning: Using All-in with a short mission order (7 or fewer missions) is not recommended, + as there might not be enough locations to place all the required items, + any excess required items will be placed into the player's starting inventory! + This option is short-lived. It may be changed in the future """ display_name = "Final Map" @@ -265,7 +269,6 @@ class MissionProgressLocations(LocationInclusion): Nothing: No rewards for this type of tasks, effectively disabling such locations Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - Warning: The generation may fail if too many locations are excluded by this way. See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) """ display_name = "Mission Progress Locations" @@ -282,7 +285,6 @@ class BonusLocations(LocationInclusion): Nothing: No rewards for this type of tasks, effectively disabling such locations Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - Warning: The generation may fail if too many locations are excluded by this way. See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) """ display_name = "Bonus Locations" @@ -300,7 +302,6 @@ class ChallengeLocations(LocationInclusion): Nothing: No rewards for this type of tasks, effectively disabling such locations Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - Warning: The generation may fail if too many locations are excluded by this way. See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) """ display_name = "Challenge Locations" @@ -317,7 +318,6 @@ class OptionalBossLocations(LocationInclusion): Nothing: No rewards for this type of tasks, effectively disabling such locations Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - Warning: The generation may fail if too many locations are excluded by this way. See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) """ display_name = "Optional Boss Locations" diff --git a/worlds/sc2wol/PoolFilter.py b/worlds/sc2wol/PoolFilter.py index 4a19e2db..23422a3d 100644 --- a/worlds/sc2wol/PoolFilter.py +++ b/worlds/sc2wol/PoolFilter.py @@ -1,6 +1,7 @@ from typing import Callable, Dict, List, Set from BaseClasses import MultiWorld, ItemClassification, Item, Location -from .Items import get_full_item_list, spider_mine_sources, second_pass_placeable_items, filler_items +from .Items import get_full_item_list, spider_mine_sources, second_pass_placeable_items, filler_items, \ + progressive_if_nco from .MissionTables import no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list,\ mission_orders, MissionInfo, alt_final_mission_locations, MissionPools from .Options import get_option_value, MissionOrder, FinalMap, MissionProgressLocations, LocationInclusion @@ -15,7 +16,7 @@ UPGRADABLE_ITEMS = [ ] BARRACKS_UNITS = {"Marine", "Medic", "Firebat", "Marauder", "Reaper", "Ghost", "Spectre"} -FACTORY_UNITS = {"Hellion", "Vulture", "Goliath", "Diamondback", "Siege Tank", "Thor", "Predator", "Widow Mine"} +FACTORY_UNITS = {"Hellion", "Vulture", "Goliath", "Diamondback", "Siege Tank", "Thor", "Predator", "Widow Mine", "Cyclone"} STARPORT_UNITS = {"Medivac", "Wraith", "Viking", "Banshee", "Battlecruiser", "Hercules", "Science Vessel", "Raven", "Liberator", "Valkyrie"} PROTOSS_REGIONS = {"A Sinister Turn", "Echoes of the Future", "In Utter Darkness"} @@ -93,7 +94,10 @@ def get_item_upgrades(inventory: List[Item], parent_item: Item or str): ] -def get_item_quantity(item): +def get_item_quantity(item: Item, multiworld: MultiWorld, player: int): + if (not get_option_value(multiworld, player, "nco_items")) \ + and item.name in progressive_if_nco: + return 1 return get_full_item_list()[item.name].quantity @@ -138,13 +142,13 @@ class ValidInventory: if not all(requirement(self) for requirement in requirements): # If item cannot be removed, lock or revert self.logical_inventory.add(item.name) - for _ in range(get_item_quantity(item)): + for _ in range(get_item_quantity(item, self.multiworld, self.player)): locked_items.append(copy_item(item)) return False return True - + # Limit the maximum number of upgrades - maxUpgrad = get_option_value(self.multiworld, self.player, + maxUpgrad = get_option_value(self.multiworld, self.player, "max_number_of_upgrades") if maxUpgrad != -1: unit_avail_upgrades = {} @@ -197,15 +201,16 @@ class ValidInventory: # Don't process general upgrades, they may have been pre-locked per-level for item in items_to_lock: if item in inventory: + item_quantity = inventory.count(item) # Unit upgrades, lock all levels - for _ in range(inventory.count(item)): + for _ in range(item_quantity): inventory.remove(item) if item not in locked_items: # Lock all the associated items if not already locked - for _ in range(get_item_quantity(item)): + for _ in range(item_quantity): locked_items.append(copy_item(item)) - if item in existing_items: - existing_items.remove(item) + if item in existing_items: + existing_items.remove(item) if self.min_units_per_structure > 0 and self.has_units_per_structure(): requirements.append(lambda state: state.has_units_per_structure()) @@ -216,7 +221,13 @@ class ValidInventory: while len(inventory) + len(locked_items) > inventory_size: if len(inventory) == 0: - raise Exception("Reduced item pool generation failed - not enough locations available to place items.") + # There are more items than locations and all of them are already locked due to YAML or logic. + # Random items from locked ones will go to starting items + self.multiworld.random.shuffle(locked_items) + while len(locked_items) > inventory_size: + item: Item = locked_items.pop() + self.multiworld.push_precollected(item) + break # Select random item from removable items item = self.multiworld.random.choice(inventory) # Cascade removals to associated items @@ -245,7 +256,7 @@ class ValidInventory: for _ in range(inventory.count(transient_item)): inventory.remove(transient_item) if transient_item not in locked_items: - for _ in range(get_item_quantity(transient_item)): + for _ in range(get_item_quantity(transient_item, self.multiworld, self.player)): locked_items.append(copy_item(transient_item)) if transient_item.classification in (ItemClassification.progression, ItemClassification.progression_skip_balancing): self.logical_inventory.add(transient_item.name) diff --git a/worlds/sc2wol/Starcraft2.kv b/worlds/sc2wol/Starcraft2.kv index 9c52d64c..f0785b89 100644 --- a/worlds/sc2wol/Starcraft2.kv +++ b/worlds/sc2wol/Starcraft2.kv @@ -11,6 +11,6 @@ markup: True halign: 'center' valign: 'middle' - padding_x: 5 + padding: [5,0,5,0] markup: True outline_width: 1 diff --git a/worlds/sc2wol/__init__.py b/worlds/sc2wol/__init__.py index 93aebb7a..5c487f8f 100644 --- a/worlds/sc2wol/__init__.py +++ b/worlds/sc2wol/__init__.py @@ -34,7 +34,7 @@ class SC2WoLWorld(World): game = "Starcraft 2 Wings of Liberty" web = Starcraft2WoLWebWorld() - data_version = 4 + data_version = 5 item_name_to_id = {name: data.code for name, data in get_full_item_list().items()} location_name_to_id = {location.name: location.code for location in get_locations(None, None)} @@ -46,7 +46,7 @@ class SC2WoLWorld(World): mission_req_table = {} final_mission_id: int victory_item: str - required_client_version = 0, 3, 6 + required_client_version = 0, 4, 3 def __init__(self, multiworld: MultiWorld, player: int): super(SC2WoLWorld, self).__init__(multiworld, player)