mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-10 10:28:16 -07:00
Compare commits
1 Commits
0.6.5-rc1
...
NewSoupVi-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
938d6c9bd4 |
@@ -44,19 +44,19 @@ class TestOptions(unittest.TestCase):
|
|||||||
}],
|
}],
|
||||||
[{
|
[{
|
||||||
"name": "ItemLinkGroup",
|
"name": "ItemLinkGroup",
|
||||||
"item_pool": ["Hammer", "Sword"],
|
"item_pool": ["Hammer", "Bow"],
|
||||||
"link_replacement": False,
|
"link_replacement": False,
|
||||||
"replacement_item": None,
|
"replacement_item": None,
|
||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
# we really need some sort of test world but generic doesn't have enough items for this
|
# we really need some sort of test world but generic doesn't have enough items for this
|
||||||
world = AutoWorldRegister.world_types["APQuest"]
|
world = AutoWorldRegister.world_types["A Link to the Past"]
|
||||||
plando_options = PlandoOptions.from_option_string("bosses")
|
plando_options = PlandoOptions.from_option_string("bosses")
|
||||||
item_links = [ItemLinks.from_any(item_link_groups[0]), ItemLinks.from_any(item_link_groups[1])]
|
item_links = [ItemLinks.from_any(item_link_groups[0]), ItemLinks.from_any(item_link_groups[1])]
|
||||||
for link in item_links:
|
for link in item_links:
|
||||||
link.verify(world, "tester", plando_options)
|
link.verify(world, "tester", plando_options)
|
||||||
self.assertIn("Hammer", link.value[0]["item_pool"])
|
self.assertIn("Hammer", link.value[0]["item_pool"])
|
||||||
self.assertIn("Sword", link.value[0]["item_pool"])
|
self.assertIn("Bow", link.value[0]["item_pool"])
|
||||||
|
|
||||||
# TODO test that the group created using these options has the items
|
# TODO test that the group created using these options has the items
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ description: Almost blank test yaml
|
|||||||
name: Player{NUMBER}
|
name: Player{NUMBER}
|
||||||
|
|
||||||
game:
|
game:
|
||||||
APQuest: 1 # what else
|
Timespinner: 1 # what else
|
||||||
requires:
|
requires:
|
||||||
version: 0.2.6
|
version: 0.2.6
|
||||||
APQuest: {}
|
Timespinner: {}
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ def make_data_directory(dir_name: str) -> Path:
|
|||||||
gitignore = specific_data_directory / ".gitignore"
|
gitignore = specific_data_directory / ".gitignore"
|
||||||
|
|
||||||
with open(gitignore, "w") as f:
|
with open(gitignore, "w") as f:
|
||||||
f.write(
|
f.write("*\n")
|
||||||
"""*
|
|
||||||
!.gitignore
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
return specific_data_directory
|
return specific_data_directory
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import binascii
|
import binascii
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import importlib.machinery
|
import importlib.machinery
|
||||||
|
import os
|
||||||
import random
|
import random
|
||||||
import pickle
|
import pickle
|
||||||
import Utils
|
import Utils
|
||||||
|
import settings
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
@@ -63,27 +65,8 @@ from .patches.aesthetics import rgb_to_bin, bin_to_rgb
|
|||||||
|
|
||||||
from .. import Options
|
from .. import Options
|
||||||
|
|
||||||
class VersionError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Function to generate a final rom, this patches the rom with all required patches
|
# Function to generate a final rom, this patches the rom with all required patches
|
||||||
def generateRom(base_rom: bytes, args, patch_data: Dict):
|
def generateRom(base_rom: bytes, args, patch_data: Dict):
|
||||||
from .. import LinksAwakeningWorld
|
|
||||||
patcher_version = LinksAwakeningWorld.world_version
|
|
||||||
generated_version = Utils.tuplize_version(patch_data.get("generated_world_version", "2.0.0"))
|
|
||||||
if generated_version.major != patcher_version.major or generated_version.minor != patcher_version.minor:
|
|
||||||
Utils.messagebox(
|
|
||||||
"Error",
|
|
||||||
"The apworld version that this patch was generated on is incompatible with your installed world.\n\n"
|
|
||||||
f"Generated on {generated_version.as_simple_string()}\n"
|
|
||||||
f"Installed version {patcher_version.as_simple_string()}",
|
|
||||||
True
|
|
||||||
)
|
|
||||||
raise VersionError(
|
|
||||||
f"The installed world ({patcher_version.as_simple_string()}) is incompatible with the world this patch "
|
|
||||||
f"was generated on ({generated_version.as_simple_string()})"
|
|
||||||
)
|
|
||||||
|
|
||||||
random.seed(patch_data["seed"] + patch_data["player"])
|
random.seed(patch_data["seed"] + patch_data["player"])
|
||||||
multi_key = binascii.unhexlify(patch_data["multi_key"].encode())
|
multi_key = binascii.unhexlify(patch_data["multi_key"].encode())
|
||||||
item_list = pickle.loads(binascii.unhexlify(patch_data["item_list"].encode()))
|
item_list = pickle.loads(binascii.unhexlify(patch_data["item_list"].encode()))
|
||||||
@@ -102,8 +85,9 @@ def generateRom(base_rom: bytes, args, patch_data: Dict):
|
|||||||
pymod.prePatch(rom)
|
pymod.prePatch(rom)
|
||||||
|
|
||||||
if options["gfxmod"]:
|
if options["gfxmod"]:
|
||||||
|
user_settings = settings.get_settings()
|
||||||
try:
|
try:
|
||||||
gfx_mod_file = LinksAwakeningWorld.settings.gfx_mod_file
|
gfx_mod_file = user_settings["ladx_options"]["gfx_mod_file"]
|
||||||
patches.aesthetics.gfxMod(rom, gfx_mod_file)
|
patches.aesthetics.gfxMod(rom, gfx_mod_file)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass # if user just doesnt provide gfxmod file, let patching continue
|
pass # if user just doesnt provide gfxmod file, let patching continue
|
||||||
|
|||||||
@@ -47,10 +47,6 @@ class BadRetroArchResponse(GameboyException):
|
|||||||
|
|
||||||
class BadRetroArchResponse(GameboyException):
|
class BadRetroArchResponse(GameboyException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class VersionError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class LAClientConstants:
|
class LAClientConstants:
|
||||||
@@ -522,7 +518,7 @@ class LinksAwakeningContext(CommonContext):
|
|||||||
class LADXManager(GameManager):
|
class LADXManager(GameManager):
|
||||||
logging_pairs = [
|
logging_pairs = [
|
||||||
("Client", "Archipelago"),
|
("Client", "Archipelago"),
|
||||||
("Tracker", "Tracker"),
|
("Tracker", "Tracker"),
|
||||||
]
|
]
|
||||||
base_title = "Archipelago Links Awakening DX Client"
|
base_title = "Archipelago Links Awakening DX Client"
|
||||||
|
|
||||||
@@ -618,20 +614,11 @@ class LinksAwakeningContext(CommonContext):
|
|||||||
|
|
||||||
def on_package(self, cmd: str, args: dict):
|
def on_package(self, cmd: str, args: dict):
|
||||||
if cmd == "Connected":
|
if cmd == "Connected":
|
||||||
self.game = self.slot_info[self.slot].game
|
|
||||||
self.slot_data = args.get("slot_data", {})
|
|
||||||
generated_version = Utils.tuplize_version(self.slot_data.get("world_version", "2.0.0"))
|
|
||||||
client_version = LinksAwakeningWorld.world_version
|
|
||||||
if generated_version.major != client_version.major:
|
|
||||||
self.disconnected_intentionally = True
|
|
||||||
raise VersionError(
|
|
||||||
f"The installed world ({client_version.as_simple_string()}) is incompatible with "
|
|
||||||
self.game = self.slot_info[self.slot].game
|
self.game = self.slot_info[self.slot].game
|
||||||
self.slot_data = args.get("slot_data", {})
|
self.slot_data = args.get("slot_data", {})
|
||||||
# This is sent to magpie over local websocket to make its own connection
|
# This is sent to magpie over local websocket to make its own connection
|
||||||
self.slot_data.update({
|
self.slot_data.update({
|
||||||
"server_address": self.server_address,
|
"server_address": self.server_address,
|
||||||
"slot_name": self.player_names[self.slot],
|
|
||||||
"slot_name": self.player_names[self.slot],
|
"slot_name": self.player_names[self.slot],
|
||||||
"password": self.password,
|
"password": self.password,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import settings
|
||||||
import worlds.Files
|
import worlds.Files
|
||||||
import hashlib
|
import hashlib
|
||||||
import Utils
|
import Utils
|
||||||
@@ -58,7 +59,6 @@ class LADXProcedurePatch(worlds.Files.APProcedurePatch):
|
|||||||
def write_patch_data(world: "LinksAwakeningWorld", patch: LADXProcedurePatch):
|
def write_patch_data(world: "LinksAwakeningWorld", patch: LADXProcedurePatch):
|
||||||
item_list = pickle.dumps([item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)])
|
item_list = pickle.dumps([item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)])
|
||||||
data_dict = {
|
data_dict = {
|
||||||
"generated_world_version": world.world_version.as_simple_string(),
|
|
||||||
"out_base": world.multiworld.get_out_file_name_base(patch.player),
|
"out_base": world.multiworld.get_out_file_name_base(patch.player),
|
||||||
"is_race": world.multiworld.is_race,
|
"is_race": world.multiworld.is_race,
|
||||||
"seed": world.multiworld.seed,
|
"seed": world.multiworld.seed,
|
||||||
@@ -125,9 +125,9 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
|
|||||||
|
|
||||||
|
|
||||||
def get_base_rom_path(file_name: str = "") -> str:
|
def get_base_rom_path(file_name: str = "") -> str:
|
||||||
from . import LinksAwakeningWorld
|
options = settings.get_settings()
|
||||||
if not file_name:
|
if not file_name:
|
||||||
file_name = LinksAwakeningWorld.settings.rom_file
|
file_name = options["ladx_options"]["rom_file"]
|
||||||
if not os.path.exists(file_name):
|
if not os.path.exists(file_name):
|
||||||
file_name = Utils.user_path(file_name)
|
file_name = Utils.user_path(file_name)
|
||||||
return file_name
|
return file_name
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import os
|
|||||||
import typing
|
import typing
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import struct
|
|
||||||
|
|
||||||
import settings
|
import settings
|
||||||
import Utils
|
|
||||||
from BaseClasses import CollectionState, Entrance, Item, ItemClassification, Location, Tutorial
|
from BaseClasses import CollectionState, Entrance, Item, ItemClassification, Location, Tutorial
|
||||||
from Fill import fill_restrictive
|
from Fill import fill_restrictive
|
||||||
from worlds.AutoWorld import WebWorld, World
|
from worlds.AutoWorld import WebWorld, World
|
||||||
@@ -52,17 +50,6 @@ class LinksAwakeningSettings(settings.Group):
|
|||||||
description = "LADX ROM File"
|
description = "LADX ROM File"
|
||||||
md5s = [LADXProcedurePatch.hash]
|
md5s = [LADXProcedurePatch.hash]
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, path: str) -> None:
|
|
||||||
try:
|
|
||||||
super().validate(path)
|
|
||||||
except ValueError:
|
|
||||||
Utils.messagebox(
|
|
||||||
"Error",
|
|
||||||
"Provided rom does not match hash for English 1.0/revision-0 of Link's Awakening DX",
|
|
||||||
True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
class RomStart(str):
|
class RomStart(str):
|
||||||
"""
|
"""
|
||||||
Set this to false to never autostart a rom (such as after patching)
|
Set this to false to never autostart a rom (such as after patching)
|
||||||
@@ -84,24 +71,6 @@ class LinksAwakeningSettings(settings.Group):
|
|||||||
Only .bin or .bdiff files
|
Only .bin or .bdiff files
|
||||||
The same directory will be checked for a matching text modification file
|
The same directory will be checked for a matching text modification file
|
||||||
"""
|
"""
|
||||||
def browse(self, filetypes=None, **kwargs):
|
|
||||||
filetypes = [("Binary / Patch files", [".bin", ".bdiff"])]
|
|
||||||
return super().browse(filetypes=filetypes, **kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, path: str) -> None:
|
|
||||||
with open(path, "rb", buffering=0) as f:
|
|
||||||
header, size = struct.unpack("<II", f.read()[:8])
|
|
||||||
if path.endswith('.bin') and header == 0xDEADBEEF and size < 1024:
|
|
||||||
# detect extended spritesheets from upstream ladxr
|
|
||||||
Utils.messagebox(
|
|
||||||
"Error",
|
|
||||||
"Extended sprite sheets are not supported. Try again with a different gfxmod file, "
|
|
||||||
"or provide no file to continue without modifying graphics.",
|
|
||||||
True)
|
|
||||||
raise ValueError("Provided gfxmod file is an extended sheet, which is not supported")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
rom_file: RomFile = RomFile(RomFile.copy_to)
|
rom_file: RomFile = RomFile(RomFile.copy_to)
|
||||||
rom_start: typing.Union[RomStart, bool] = True
|
rom_start: typing.Union[RomStart, bool] = True
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"game": "Links Awakening DX",
|
"game": "Links Awakening DX",
|
||||||
"authors": [ "zig", "threeandthree" ],
|
"authors": [ "zig", "threeandthree" ],
|
||||||
"minimum_ap_version": "0.6.4",
|
"minimum_ap_version": "0.6.4",
|
||||||
"world_version": "2.0.1"
|
"world_version": "2.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,32 +374,22 @@ def create_and_flag_explicit_item_locks_and_excludes(world: SC2World) -> List[Fi
|
|||||||
Handles `excluded_items`, `locked_items`, and `start_inventory`
|
Handles `excluded_items`, `locked_items`, and `start_inventory`
|
||||||
Returns a list of all possible non-filler items that can be added, with an accompanying flags bitfield.
|
Returns a list of all possible non-filler items that can be added, with an accompanying flags bitfield.
|
||||||
"""
|
"""
|
||||||
excluded_items: dict[str, int] = world.options.excluded_items.value
|
excluded_items = world.options.excluded_items
|
||||||
unexcluded_items: dict[str, int] = world.options.unexcluded_items.value
|
unexcluded_items = world.options.unexcluded_items
|
||||||
locked_items: dict[str, int] = world.options.locked_items.value
|
locked_items = world.options.locked_items
|
||||||
start_inventory: dict[str, int] = world.options.start_inventory.value
|
start_inventory = world.options.start_inventory
|
||||||
key_items = world.custom_mission_order.get_items_to_lock()
|
key_items = world.custom_mission_order.get_items_to_lock()
|
||||||
|
|
||||||
def resolve_exclude(count: int, max_count: int) -> int:
|
def resolve_count(count: Optional[int], max_count: int) -> int:
|
||||||
if count < 0:
|
if count == 0:
|
||||||
return max_count
|
return max_count
|
||||||
return count
|
if count is None:
|
||||||
|
return 0
|
||||||
def resolve_count(count: int, max_count: int, negative_value: int | None = None) -> int:
|
if max_count == 0:
|
||||||
"""
|
return count
|
||||||
Handles `count` being out of range.
|
return min(count, max_count)
|
||||||
* If `count > max_count`, returns `max_count`.
|
|
||||||
* If `count < 0`, returns `negative_value` (returns `max_count` if `negative_value` is unspecified)
|
|
||||||
"""
|
|
||||||
if count < 0:
|
|
||||||
if negative_value is None:
|
|
||||||
return max_count
|
|
||||||
return negative_value
|
|
||||||
if max_count and count > max_count:
|
|
||||||
return max_count
|
|
||||||
return count
|
|
||||||
|
|
||||||
auto_excludes = Counter({item_name: 1 for item_name in item_groups.legacy_items})
|
auto_excludes = {item_name: 1 for item_name in item_groups.legacy_items}
|
||||||
if world.options.exclude_overpowered_items.value == ExcludeOverpoweredItems.option_true:
|
if world.options.exclude_overpowered_items.value == ExcludeOverpoweredItems.option_true:
|
||||||
for item_name in item_groups.overpowered_items:
|
for item_name in item_groups.overpowered_items:
|
||||||
auto_excludes[item_name] = 1
|
auto_excludes[item_name] = 1
|
||||||
@@ -412,29 +402,28 @@ def create_and_flag_explicit_item_locks_and_excludes(world: SC2World) -> List[Fi
|
|||||||
elif item_name in item_groups.nova_equipment:
|
elif item_name in item_groups.nova_equipment:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
auto_excludes[item_name] = item_data.quantity
|
auto_excludes[item_name] = 0
|
||||||
|
|
||||||
|
|
||||||
result: List[FilterItem] = []
|
result: List[FilterItem] = []
|
||||||
for item_name, item_data in item_tables.item_table.items():
|
for item_name, item_data in item_tables.item_table.items():
|
||||||
max_count = item_data.quantity
|
max_count = item_data.quantity
|
||||||
auto_excluded_count = auto_excludes.get(item_name, 0)
|
auto_excluded_count = auto_excludes.get(item_name)
|
||||||
excluded_count = excluded_items.get(item_name, auto_excluded_count)
|
excluded_count = excluded_items.get(item_name, auto_excluded_count)
|
||||||
unexcluded_count = unexcluded_items.get(item_name, 0)
|
unexcluded_count = unexcluded_items.get(item_name)
|
||||||
locked_count = locked_items.get(item_name, 0)
|
locked_count = locked_items.get(item_name)
|
||||||
start_count = start_inventory.get(item_name, 0)
|
start_count: Optional[int] = start_inventory.get(item_name)
|
||||||
key_count = key_items.get(item_name, 0)
|
key_count = key_items.get(item_name, 0)
|
||||||
# Specifying a negative number in the yaml means exclude / lock / start all.
|
# specifying 0 in the yaml means exclude / lock all
|
||||||
# In the case of excluded/unexcluded, resolve negatives to max_count before subtracting them,
|
# start_inventory doesn't allow specifying 0
|
||||||
# and after subtraction resolve negatives to just 0 (when unexcluded > excluded).
|
# not specifying means don't exclude/lock/start
|
||||||
excluded_count = resolve_count(
|
excluded_count = resolve_count(excluded_count, max_count)
|
||||||
resolve_exclude(excluded_count, max_count) - resolve_exclude(unexcluded_count, max_count),
|
unexcluded_count = resolve_count(unexcluded_count, max_count)
|
||||||
max_count,
|
|
||||||
negative_value=0
|
|
||||||
)
|
|
||||||
locked_count = resolve_count(locked_count, max_count)
|
locked_count = resolve_count(locked_count, max_count)
|
||||||
start_count = resolve_count(start_count, max_count)
|
start_count = resolve_count(start_count, max_count)
|
||||||
|
|
||||||
|
excluded_count = max(0, excluded_count - unexcluded_count)
|
||||||
|
|
||||||
# Priority: start_inventory >> locked_items >> excluded_items >> unspecified
|
# Priority: start_inventory >> locked_items >> excluded_items >> unspecified
|
||||||
if max_count == 0:
|
if max_count == 0:
|
||||||
if excluded_count:
|
if excluded_count:
|
||||||
@@ -497,9 +486,8 @@ def flag_excludes_by_faction_presence(world: SC2World, item_list: List[FilterIte
|
|||||||
item.flags |= ItemFilterFlags.FilterExcluded
|
item.flags |= ItemFilterFlags.FilterExcluded
|
||||||
continue
|
continue
|
||||||
if not zerg_missions and item.data.race == SC2Race.ZERG:
|
if not zerg_missions and item.data.race == SC2Race.ZERG:
|
||||||
if (item.data.type != item_tables.ZergItemType.Ability
|
if item.data.type != item_tables.ZergItemType.Ability \
|
||||||
and item.data.type != ZergItemType.Level
|
and item.data.type != ZergItemType.Level:
|
||||||
):
|
|
||||||
item.flags |= ItemFilterFlags.FilterExcluded
|
item.flags |= ItemFilterFlags.FilterExcluded
|
||||||
continue
|
continue
|
||||||
if not protoss_missions and item.data.race == SC2Race.PROTOSS:
|
if not protoss_missions and item.data.race == SC2Race.PROTOSS:
|
||||||
@@ -653,7 +641,7 @@ def flag_mission_based_item_excludes(world: SC2World, item_list: List[FilterItem
|
|||||||
item.flags |= ItemFilterFlags.FilterExcluded
|
item.flags |= ItemFilterFlags.FilterExcluded
|
||||||
|
|
||||||
# Remove Spear of Adun passives
|
# Remove Spear of Adun passives
|
||||||
if item.name in item_groups.spear_of_adun_passives and not soa_passive_presence:
|
if item.name in item_tables.spear_of_adun_castable_passives and not soa_passive_presence:
|
||||||
item.flags |= ItemFilterFlags.FilterExcluded
|
item.flags |= ItemFilterFlags.FilterExcluded
|
||||||
|
|
||||||
# Remove matchup-specific items if you don't play that matchup
|
# Remove matchup-specific items if you don't play that matchup
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ from .options import (
|
|||||||
SpearOfAdunPassivesPresentInNoBuild, EnableVoidTrade, VoidTradeAgeLimit, void_trade_age_limits_ms, VoidTradeWorkers,
|
SpearOfAdunPassivesPresentInNoBuild, EnableVoidTrade, VoidTradeAgeLimit, void_trade_age_limits_ms, VoidTradeWorkers,
|
||||||
DifficultyDamageModifier, MissionOrderScouting, GenericUpgradeResearchSpeedup, MercenaryHighlanders, WarCouncilNerfs,
|
DifficultyDamageModifier, MissionOrderScouting, GenericUpgradeResearchSpeedup, MercenaryHighlanders, WarCouncilNerfs,
|
||||||
is_mission_in_soa_presence,
|
is_mission_in_soa_presence,
|
||||||
upgrade_included_names,
|
|
||||||
)
|
)
|
||||||
from .mission_order.slot_data import CampaignSlotData, LayoutSlotData, MissionSlotData, MissionOrderObjectSlotData
|
from .mission_order.slot_data import CampaignSlotData, LayoutSlotData, MissionSlotData, MissionOrderObjectSlotData
|
||||||
from .mission_order.entry_rules import SubRuleRuleData, CountMissionsRuleData, MissionEntryRules
|
from .mission_order.entry_rules import SubRuleRuleData, CountMissionsRuleData, MissionEntryRules
|
||||||
@@ -72,12 +71,10 @@ from .mission_tables import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
import colorama
|
import colorama
|
||||||
|
from .options import Option, upgrade_included_names
|
||||||
from NetUtils import ClientStatus, NetworkItem, JSONtoTextParser, JSONMessagePart, add_json_item, add_json_location, add_json_text, JSONTypes
|
from NetUtils import ClientStatus, NetworkItem, JSONtoTextParser, JSONMessagePart, add_json_item, add_json_location, add_json_text, JSONTypes
|
||||||
from MultiServer import mark_raw
|
from MultiServer import mark_raw
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
|
||||||
from Options import Option
|
|
||||||
|
|
||||||
pool = concurrent.futures.ThreadPoolExecutor(1)
|
pool = concurrent.futures.ThreadPoolExecutor(1)
|
||||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||||
nest_asyncio.apply(loop)
|
nest_asyncio.apply(loop)
|
||||||
|
|||||||
@@ -167,7 +167,6 @@ class ItemGroupNames:
|
|||||||
LOTV_UNITS = "LotV Units"
|
LOTV_UNITS = "LotV Units"
|
||||||
LOTV_ITEMS = "LotV Items"
|
LOTV_ITEMS = "LotV Items"
|
||||||
LOTV_GLOBAL_UPGRADES = "LotV Global Upgrades"
|
LOTV_GLOBAL_UPGRADES = "LotV Global Upgrades"
|
||||||
SOA_PASSIVES = "SOA Passive Abilities"
|
|
||||||
SOA_ITEMS = "SOA"
|
SOA_ITEMS = "SOA"
|
||||||
PROTOSS_GLOBAL_UPGRADES = "Protoss Global Upgrades"
|
PROTOSS_GLOBAL_UPGRADES = "Protoss Global Upgrades"
|
||||||
PROTOSS_BUILDINGS = "Protoss Buildings"
|
PROTOSS_BUILDINGS = "Protoss Buildings"
|
||||||
@@ -778,21 +777,11 @@ item_name_groups[ItemGroupNames.PURIFIER_UNITS] = [
|
|||||||
item_names.MIRAGE, item_names.DAWNBRINGER, item_names.TRIREME, item_names.TEMPEST,
|
item_names.MIRAGE, item_names.DAWNBRINGER, item_names.TRIREME, item_names.TEMPEST,
|
||||||
item_names.CALADRIUS,
|
item_names.CALADRIUS,
|
||||||
]
|
]
|
||||||
item_name_groups[ItemGroupNames.SOA_PASSIVES] = spear_of_adun_passives = [
|
item_name_groups[ItemGroupNames.SOA_ITEMS] = soa_items = [
|
||||||
item_names.RECONSTRUCTION_BEAM,
|
|
||||||
item_names.OVERWATCH,
|
|
||||||
item_names.GUARDIAN_SHELL,
|
|
||||||
]
|
|
||||||
spear_of_adun_actives = [
|
|
||||||
*[item_name for item_name, item_data in item_tables.item_table.items() if item_data.type == item_tables.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,
|
item_names.SOA_PROGRESSIVE_PROXY_PYLON,
|
||||||
]
|
]
|
||||||
item_name_groups[ItemGroupNames.SOA_ITEMS] = soa_items = spear_of_adun_actives + spear_of_adun_passives
|
lotv_soa_items = [item_name for item_name in soa_items if item_name != item_names.SOA_PYLON_OVERCHARGE]
|
||||||
lotv_soa_items = [
|
|
||||||
item_name
|
|
||||||
for item_name in soa_items
|
|
||||||
if item_name not in (item_names.SOA_PYLON_OVERCHARGE, item_names.OVERWATCH)
|
|
||||||
]
|
|
||||||
item_name_groups[ItemGroupNames.PROTOSS_GLOBAL_UPGRADES] = [
|
item_name_groups[ItemGroupNames.PROTOSS_GLOBAL_UPGRADES] = [
|
||||||
item_name for item_name, item_data in item_tables.item_table.items() if item_data.type == item_tables.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
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2293,6 +2293,12 @@ spear_of_adun_calldowns = {
|
|||||||
item_names.SOA_SOLAR_BOMBARDMENT
|
item_names.SOA_SOLAR_BOMBARDMENT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spear_of_adun_castable_passives = {
|
||||||
|
item_names.RECONSTRUCTION_BEAM,
|
||||||
|
item_names.OVERWATCH,
|
||||||
|
item_names.GUARDIAN_SHELL,
|
||||||
|
}
|
||||||
|
|
||||||
nova_equipment = {
|
nova_equipment = {
|
||||||
*[item_name for item_name, item_data in get_full_item_list().items()
|
*[item_name for item_name, item_data in get_full_item_list().items()
|
||||||
if item_data.type == TerranItemType.Nova_Gear],
|
if item_data.type == TerranItemType.Nova_Gear],
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ from datetime import timedelta
|
|||||||
|
|
||||||
from Options import (
|
from Options import (
|
||||||
Choice, Toggle, DefaultOnToggle, OptionSet, Range,
|
Choice, Toggle, DefaultOnToggle, OptionSet, Range,
|
||||||
PerGameCommonOptions, VerifyKeys, StartInventory,
|
PerGameCommonOptions, Option, VerifyKeys, StartInventory,
|
||||||
is_iterable_except_str, OptionGroup, Visibility, ItemDict,
|
is_iterable_except_str, OptionGroup, Visibility, ItemDict,
|
||||||
OptionCounter,
|
Accessibility, ProgressionBalancing
|
||||||
)
|
)
|
||||||
from Utils import get_fuzzy_results
|
from Utils import get_fuzzy_results
|
||||||
from BaseClasses import PlandoOptions
|
from BaseClasses import PlandoOptions
|
||||||
from .item import item_names, item_tables, item_groups
|
from .item import item_names, item_tables
|
||||||
|
from .item.item_groups import kerrigan_active_abilities, kerrigan_passives, nova_weapons, nova_gadgets
|
||||||
from .mission_tables import (
|
from .mission_tables import (
|
||||||
SC2Campaign, SC2Mission, lookup_name_to_mission, MissionPools, get_missions_with_any_flags_in_list,
|
SC2Campaign, SC2Mission, lookup_name_to_mission, MissionPools, get_missions_with_any_flags_in_list,
|
||||||
campaign_mission_table, SC2Race, MissionFlag
|
campaign_mission_table, SC2Race, MissionFlag
|
||||||
@@ -699,7 +700,7 @@ class KerriganMaxActiveAbilities(Range):
|
|||||||
"""
|
"""
|
||||||
display_name = "Kerrigan Maximum Active Abilities"
|
display_name = "Kerrigan Maximum Active Abilities"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = len(item_groups.kerrigan_active_abilities)
|
range_end = len(kerrigan_active_abilities)
|
||||||
default = range_end
|
default = range_end
|
||||||
|
|
||||||
|
|
||||||
@@ -710,7 +711,7 @@ class KerriganMaxPassiveAbilities(Range):
|
|||||||
"""
|
"""
|
||||||
display_name = "Kerrigan Maximum Passive Abilities"
|
display_name = "Kerrigan Maximum Passive Abilities"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = len(item_groups.kerrigan_passives)
|
range_end = len(kerrigan_passives)
|
||||||
default = range_end
|
default = range_end
|
||||||
|
|
||||||
|
|
||||||
@@ -828,7 +829,7 @@ class SpearOfAdunMaxAutocastAbilities(Range):
|
|||||||
"""
|
"""
|
||||||
display_name = "Spear of Adun Maximum Passive Abilities"
|
display_name = "Spear of Adun Maximum Passive Abilities"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = sum(item_tables.item_table[item_name].quantity for item_name in item_groups.spear_of_adun_passives)
|
range_end = sum(item.quantity for item_name, item in item_tables.get_full_item_list().items() if item_name in item_tables.spear_of_adun_castable_passives)
|
||||||
default = range_end
|
default = range_end
|
||||||
|
|
||||||
|
|
||||||
@@ -882,7 +883,7 @@ class NovaMaxWeapons(Range):
|
|||||||
"""
|
"""
|
||||||
display_name = "Nova Maximum Weapons"
|
display_name = "Nova Maximum Weapons"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = len(item_groups.nova_weapons)
|
range_end = len(nova_weapons)
|
||||||
default = range_end
|
default = range_end
|
||||||
|
|
||||||
|
|
||||||
@@ -896,7 +897,7 @@ class NovaMaxGadgets(Range):
|
|||||||
"""
|
"""
|
||||||
display_name = "Nova Maximum Gadgets"
|
display_name = "Nova Maximum Gadgets"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = len(item_groups.nova_gadgets)
|
range_end = len(nova_gadgets)
|
||||||
default = range_end
|
default = range_end
|
||||||
|
|
||||||
|
|
||||||
@@ -931,48 +932,33 @@ class TakeOverAIAllies(Toggle):
|
|||||||
display_name = "Take Over AI Allies"
|
display_name = "Take Over AI Allies"
|
||||||
|
|
||||||
|
|
||||||
class Sc2ItemDict(OptionCounter, VerifyKeys, Mapping[str, int]):
|
class Sc2ItemDict(Option[Dict[str, int]], VerifyKeys, Mapping[str, int]):
|
||||||
"""A branch of ItemDict that supports negative item counts"""
|
"""A branch of ItemDict that supports item counts of 0"""
|
||||||
default = {}
|
default = {}
|
||||||
supports_weighting = False
|
supports_weighting = False
|
||||||
verify_item_name = True
|
verify_item_name = True
|
||||||
# convert_name_groups = True
|
# convert_name_groups = True
|
||||||
display_name = 'Unnamed dictionary'
|
display_name = 'Unnamed dictionary'
|
||||||
# Note(phaneros): Limiting minimum to -1 means that if two triggers add -1 to the same item,
|
minimum_value: int = 0
|
||||||
# the validation fails. So give trigger people space to stack a bunch of triggers.
|
|
||||||
min: int = -1000
|
|
||||||
max: int = 1000
|
|
||||||
valid_keys = set(item_tables.item_table) | set(item_groups.item_name_groups)
|
|
||||||
|
|
||||||
def __init__(self, value: dict[str, int]):
|
def __init__(self, value: Dict[str, int]):
|
||||||
self.value = {key: val for key, val in value.items()}
|
self.value = {key: val for key, val in value.items()}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_any(cls, data: list[str] | dict[str, int]) -> 'Sc2ItemDict':
|
def from_any(cls, data: Union[List[str], Dict[str, int]]) -> 'Sc2ItemDict':
|
||||||
if isinstance(data, list):
|
if isinstance(data, list):
|
||||||
raise ValueError(
|
# This is a little default that gets us backwards compatibility with lists.
|
||||||
f"{cls.display_name}: Cannot convert from list. "
|
# It doesn't play nice with trigger merging dicts and lists together, though, so best not to advertise it overmuch.
|
||||||
f"Use dict syntax (no dashes, 'value: number' synax)."
|
data = {item: 0 for item in data}
|
||||||
)
|
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
if not isinstance(value, int):
|
if not isinstance(value, int):
|
||||||
raise ValueError(
|
raise ValueError(f"Invalid type in '{cls.display_name}': element '{key}' maps to '{value}', expected an integer")
|
||||||
f"Invalid type in '{cls.display_name}': "
|
if value < cls.minimum_value:
|
||||||
f"element '{key}' maps to '{value}', expected an integer"
|
raise ValueError(f"Invalid value for '{cls.display_name}': element '{key}' maps to {value}, which is less than the minimum ({cls.minimum_value})")
|
||||||
)
|
|
||||||
if value < cls.min:
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid value for '{cls.display_name}': "
|
|
||||||
f"element '{key}' maps to {value}, which is less than the minimum ({cls.min})"
|
|
||||||
)
|
|
||||||
if value > cls.max:
|
|
||||||
raise ValueError(f"Invalid value for '{cls.display_name}': "
|
|
||||||
f"element '{key}' maps to {value}, which is greater than the maximum ({cls.max})"
|
|
||||||
)
|
|
||||||
return cls(data)
|
return cls(data)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"{cls.display_name}: Cannot convert from non-dictionary, got {type(data)}")
|
raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}")
|
||||||
|
|
||||||
def verify(self, world: Type['World'], player_name: str, plando_options: PlandoOptions) -> None:
|
def verify(self, world: Type['World'], player_name: str, plando_options: PlandoOptions) -> None:
|
||||||
"""Overridden version of function from Options.VerifyKeys for a better error message"""
|
"""Overridden version of function from Options.VerifyKeys for a better error message"""
|
||||||
@@ -988,16 +974,15 @@ class Sc2ItemDict(OptionCounter, VerifyKeys, Mapping[str, int]):
|
|||||||
self.value = new_value
|
self.value = new_value
|
||||||
for item_name in self.value:
|
for item_name in self.value:
|
||||||
if item_name not in world.item_names:
|
if item_name not in world.item_names:
|
||||||
|
from .item import item_groups
|
||||||
picks = get_fuzzy_results(
|
picks = get_fuzzy_results(
|
||||||
item_name,
|
item_name,
|
||||||
list(world.item_names) + list(item_groups.ItemGroupNames.get_all_group_names()),
|
list(world.item_names) + list(item_groups.ItemGroupNames.get_all_group_names()),
|
||||||
limit=1,
|
limit=1,
|
||||||
)
|
)
|
||||||
raise Exception(
|
raise Exception(f"Item {item_name} from option {self} "
|
||||||
f"Item {item_name} from option {self} "
|
f"is not a valid item name from {world.game}. "
|
||||||
f"is not a valid item name from {world.game}. "
|
f"Did you mean '{picks[0][0]}' ({picks[0][1]}% sure)")
|
||||||
f"Did you mean '{picks[0][0]}' ({picks[0][1]}% sure)"
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_option_name(self, value):
|
def get_option_name(self, value):
|
||||||
return ", ".join(f"{key}: {v}" for key, v in value.items())
|
return ", ".join(f"{key}: {v}" for key, v in value.items())
|
||||||
@@ -1013,25 +998,25 @@ class Sc2ItemDict(OptionCounter, VerifyKeys, Mapping[str, int]):
|
|||||||
|
|
||||||
|
|
||||||
class Sc2StartInventory(Sc2ItemDict):
|
class Sc2StartInventory(Sc2ItemDict):
|
||||||
"""Start with these items. Use an amount of -1 to start with all copies of an item."""
|
"""Start with these items."""
|
||||||
display_name = StartInventory.display_name
|
display_name = StartInventory.display_name
|
||||||
|
|
||||||
|
|
||||||
class LockedItems(Sc2ItemDict):
|
class LockedItems(Sc2ItemDict):
|
||||||
"""Guarantees that these items will be unlockable, in the amount specified.
|
"""Guarantees that these items will be unlockable, in the amount specified.
|
||||||
Specify an amount of -1 to lock all copies of an item."""
|
Specify an amount of 0 to lock all copies of an item."""
|
||||||
display_name = "Locked Items"
|
display_name = "Locked Items"
|
||||||
|
|
||||||
|
|
||||||
class ExcludedItems(Sc2ItemDict):
|
class ExcludedItems(Sc2ItemDict):
|
||||||
"""Guarantees that these items will not be unlockable, in the amount specified.
|
"""Guarantees that these items will not be unlockable, in the amount specified.
|
||||||
Specify an amount of -1 to exclude all copies of an item."""
|
Specify an amount of 0 to exclude all copies of an item."""
|
||||||
display_name = "Excluded Items"
|
display_name = "Excluded Items"
|
||||||
|
|
||||||
|
|
||||||
class UnexcludedItems(Sc2ItemDict):
|
class UnexcludedItems(Sc2ItemDict):
|
||||||
"""Undoes an item exclusion; useful for whitelisting or fine-tuning a category.
|
"""Undoes an item exclusion; useful for whitelisting or fine-tuning a category.
|
||||||
Specify an amount of -1 to unexclude all copies of an item."""
|
Specify an amount of 0 to unexclude all copies of an item."""
|
||||||
display_name = "Unexcluded Items"
|
display_name = "Unexcluded Items"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ from typing import Callable, Dict, List, Set, Tuple, TYPE_CHECKING, Iterable
|
|||||||
|
|
||||||
from BaseClasses import Location, ItemClassification
|
from BaseClasses import Location, ItemClassification
|
||||||
from .item import StarcraftItem, ItemFilterFlags, item_names, item_parents, item_groups
|
from .item import StarcraftItem, ItemFilterFlags, item_names, item_parents, item_groups
|
||||||
from .item.item_tables import item_table, TerranItemType, ZergItemType, spear_of_adun_calldowns
|
from .item.item_tables import item_table, TerranItemType, ZergItemType, spear_of_adun_calldowns, \
|
||||||
|
spear_of_adun_castable_passives
|
||||||
from .options import RequiredTactics
|
from .options import RequiredTactics
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -271,7 +272,7 @@ class ValidInventory:
|
|||||||
self.world.random.shuffle(spear_of_adun_actives)
|
self.world.random.shuffle(spear_of_adun_actives)
|
||||||
cull_items_over_maximum(spear_of_adun_actives, self.world.options.spear_of_adun_max_active_abilities.value)
|
cull_items_over_maximum(spear_of_adun_actives, self.world.options.spear_of_adun_max_active_abilities.value)
|
||||||
|
|
||||||
spear_of_adun_autocasts = [item for item in inventory if item.name in item_groups.spear_of_adun_passives]
|
spear_of_adun_autocasts = [item for item in inventory if item.name in spear_of_adun_castable_passives]
|
||||||
self.world.random.shuffle(spear_of_adun_autocasts)
|
self.world.random.shuffle(spear_of_adun_autocasts)
|
||||||
cull_items_over_maximum(spear_of_adun_autocasts, self.world.options.spear_of_adun_max_passive_abilities.value)
|
cull_items_over_maximum(spear_of_adun_autocasts, self.world.options.spear_of_adun_max_passive_abilities.value)
|
||||||
|
|
||||||
|
|||||||
@@ -18,19 +18,19 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
world_options = {
|
world_options = {
|
||||||
**self.ALL_CAMPAIGNS,
|
**self.ALL_CAMPAIGNS,
|
||||||
'locked_items': {
|
'locked_items': {
|
||||||
item_names.MARINE: -1,
|
item_names.MARINE: 0,
|
||||||
item_names.MARAUDER: -1,
|
item_names.MARAUDER: 0,
|
||||||
item_names.MEDIVAC: 1,
|
item_names.MEDIVAC: 1,
|
||||||
item_names.FIREBAT: 1,
|
item_names.FIREBAT: 1,
|
||||||
item_names.ZEALOT: -1,
|
item_names.ZEALOT: 0,
|
||||||
item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2,
|
item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2,
|
||||||
},
|
},
|
||||||
'excluded_items': {
|
'excluded_items': {
|
||||||
item_names.MARINE: -1,
|
item_names.MARINE: 0,
|
||||||
item_names.MARAUDER: -1,
|
item_names.MARAUDER: 0,
|
||||||
item_names.MEDIVAC: -1,
|
item_names.MEDIVAC: 0,
|
||||||
item_names.FIREBAT: 1,
|
item_names.FIREBAT: 1,
|
||||||
item_names.ZERGLING: -1,
|
item_names.ZERGLING: 0,
|
||||||
item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2,
|
item_names.PROGRESSIVE_REGENERATIVE_BIO_STEEL: 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,38 +50,38 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
world_options = {
|
world_options = {
|
||||||
'grant_story_tech': options.GrantStoryTech.option_grant,
|
'grant_story_tech': options.GrantStoryTech.option_grant,
|
||||||
'excluded_items': {
|
'excluded_items': {
|
||||||
item_groups.ItemGroupNames.NOVA_EQUIPMENT: -1,
|
item_groups.ItemGroupNames.NOVA_EQUIPMENT: 15,
|
||||||
item_names.MARINE_PROGRESSIVE_STIMPACK: 1,
|
item_names.MARINE_PROGRESSIVE_STIMPACK: 1,
|
||||||
item_names.MARAUDER_PROGRESSIVE_STIMPACK: 2,
|
item_names.MARAUDER_PROGRESSIVE_STIMPACK: 2,
|
||||||
item_names.MARINE: -1,
|
item_names.MARINE: 0,
|
||||||
item_names.MARAUDER: -1,
|
item_names.MARAUDER: 0,
|
||||||
item_names.REAPER: 1,
|
item_names.REAPER: 1,
|
||||||
item_names.DIAMONDBACK: -1,
|
item_names.DIAMONDBACK: 0,
|
||||||
item_names.HELLION: 1,
|
item_names.HELLION: 1,
|
||||||
# Additional excludes to increase the likelihood that unexcluded items actually appear
|
# Additional excludes to increase the likelihood that unexcluded items actually appear
|
||||||
item_groups.ItemGroupNames.STARPORT_UNITS: -1,
|
item_groups.ItemGroupNames.STARPORT_UNITS: 0,
|
||||||
item_names.WARHOUND: -1,
|
item_names.WARHOUND: 0,
|
||||||
item_names.VULTURE: -1,
|
item_names.VULTURE: 0,
|
||||||
item_names.WIDOW_MINE: -1,
|
item_names.WIDOW_MINE: 0,
|
||||||
item_names.THOR: -1,
|
item_names.THOR: 0,
|
||||||
item_names.GHOST: -1,
|
item_names.GHOST: 0,
|
||||||
item_names.SPECTRE: -1,
|
item_names.SPECTRE: 0,
|
||||||
item_groups.ItemGroupNames.MENGSK_UNITS: -1,
|
item_groups.ItemGroupNames.MENGSK_UNITS: 0,
|
||||||
item_groups.ItemGroupNames.TERRAN_VETERANCY_UNITS: -1,
|
item_groups.ItemGroupNames.TERRAN_VETERANCY_UNITS: 0,
|
||||||
},
|
},
|
||||||
'unexcluded_items': {
|
'unexcluded_items': {
|
||||||
item_names.NOVA_PLASMA_RIFLE: 1, # Necessary to pass logic
|
item_names.NOVA_PLASMA_RIFLE: 1, # Necessary to pass logic
|
||||||
item_names.NOVA_PULSE_GRENADES: -1, # Necessary to pass logic
|
item_names.NOVA_PULSE_GRENADES: 0, # Necessary to pass logic
|
||||||
item_names.NOVA_JUMP_SUIT_MODULE: -1, # Necessary to pass logic
|
item_names.NOVA_JUMP_SUIT_MODULE: 0, # Necessary to pass logic
|
||||||
item_groups.ItemGroupNames.BARRACKS_UNITS: -1,
|
item_groups.ItemGroupNames.BARRACKS_UNITS: 0,
|
||||||
item_names.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE: 1,
|
item_names.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE: 1,
|
||||||
item_names.HELLION: 1,
|
item_names.HELLION: 1,
|
||||||
item_names.MARINE_PROGRESSIVE_STIMPACK: 1,
|
item_names.MARINE_PROGRESSIVE_STIMPACK: 1,
|
||||||
item_names.MARAUDER_PROGRESSIVE_STIMPACK: -1,
|
item_names.MARAUDER_PROGRESSIVE_STIMPACK: 0,
|
||||||
# Additional unexcludes for logic
|
# Additional unexcludes for logic
|
||||||
item_names.MEDIVAC: -1,
|
item_names.MEDIVAC: 0,
|
||||||
item_names.BATTLECRUISER: -1,
|
item_names.BATTLECRUISER: 0,
|
||||||
item_names.SCIENCE_VESSEL: -1,
|
item_names.SCIENCE_VESSEL: 0,
|
||||||
},
|
},
|
||||||
# Terran-only
|
# Terran-only
|
||||||
'enabled_campaigns': {
|
'enabled_campaigns': {
|
||||||
@@ -103,29 +103,11 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
self.assertNotIn(item_names.NOVA_BLAZEFIRE_GUNBLADE, itempool)
|
self.assertNotIn(item_names.NOVA_BLAZEFIRE_GUNBLADE, itempool)
|
||||||
self.assertNotIn(item_names.NOVA_ENERGY_SUIT_MODULE, itempool)
|
self.assertNotIn(item_names.NOVA_ENERGY_SUIT_MODULE, itempool)
|
||||||
|
|
||||||
def test_exclude_2_beats_unexclude_1(self) -> None:
|
|
||||||
world_options = {
|
|
||||||
options.OPTION_NAME[options.ExcludedItems]: {
|
|
||||||
item_names.MARINE: 2,
|
|
||||||
},
|
|
||||||
options.OPTION_NAME[options.UnexcludedItems]: {
|
|
||||||
item_names.MARINE: 1,
|
|
||||||
},
|
|
||||||
# Ensure enough locations that marine doesn't get culled
|
|
||||||
options.OPTION_NAME[options.SelectedRaces]: {
|
|
||||||
SC2Race.TERRAN.get_title(),
|
|
||||||
},
|
|
||||||
options.OPTION_NAME[options.VictoryCache]: 9,
|
|
||||||
}
|
|
||||||
self.generate_world(world_options)
|
|
||||||
itempool = [item.name for item in self.multiworld.itempool]
|
|
||||||
self.assertNotIn(item_names.MARINE, itempool)
|
|
||||||
|
|
||||||
def test_excluding_groups_excludes_all_items_in_group(self):
|
def test_excluding_groups_excludes_all_items_in_group(self):
|
||||||
world_options = {
|
world_options = {
|
||||||
'excluded_items': {
|
'excluded_items': [
|
||||||
item_groups.ItemGroupNames.BARRACKS_UNITS.lower(): -1,
|
item_groups.ItemGroupNames.BARRACKS_UNITS.lower(),
|
||||||
},
|
]
|
||||||
}
|
}
|
||||||
self.generate_world(world_options)
|
self.generate_world(world_options)
|
||||||
itempool = [item.name for item in self.multiworld.itempool]
|
itempool = [item.name for item in self.multiworld.itempool]
|
||||||
@@ -355,9 +337,9 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
# Options under test
|
# Options under test
|
||||||
'vanilla_items_only': True,
|
'vanilla_items_only': True,
|
||||||
'unexcluded_items': {
|
'unexcluded_items': {
|
||||||
item_names.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM: -1,
|
item_names.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM: 0,
|
||||||
item_names.WARHOUND: 1,
|
item_names.WARHOUND: 1,
|
||||||
item_groups.ItemGroupNames.TERRAN_STIMPACKS: -1,
|
item_groups.ItemGroupNames.TERRAN_STIMPACKS: 0,
|
||||||
},
|
},
|
||||||
# Avoid options that lock non-vanilla items for logic
|
# Avoid options that lock non-vanilla items for logic
|
||||||
'required_tactics': options.RequiredTactics.option_any_units,
|
'required_tactics': options.RequiredTactics.option_any_units,
|
||||||
@@ -481,12 +463,12 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
},
|
},
|
||||||
'required_tactics': options.RequiredTactics.option_no_logic,
|
'required_tactics': options.RequiredTactics.option_no_logic,
|
||||||
'enable_morphling': options.EnableMorphling.option_true,
|
'enable_morphling': options.EnableMorphling.option_true,
|
||||||
'excluded_items': {
|
'excluded_items': [
|
||||||
item_groups.ItemGroupNames.ZERG_UNITS.lower(): -1,
|
item_groups.ItemGroupNames.ZERG_UNITS.lower()
|
||||||
},
|
],
|
||||||
'unexcluded_items': {
|
'unexcluded_items': [
|
||||||
item_groups.ItemGroupNames.ZERG_MORPHS.lower(): -1,
|
item_groups.ItemGroupNames.ZERG_MORPHS.lower()
|
||||||
},
|
]
|
||||||
}
|
}
|
||||||
self.generate_world(world_options)
|
self.generate_world(world_options)
|
||||||
itempool = [item.name for item in self.multiworld.itempool]
|
itempool = [item.name for item in self.multiworld.itempool]
|
||||||
@@ -504,12 +486,12 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
},
|
},
|
||||||
'required_tactics': options.RequiredTactics.option_no_logic,
|
'required_tactics': options.RequiredTactics.option_no_logic,
|
||||||
'enable_morphling': options.EnableMorphling.option_false,
|
'enable_morphling': options.EnableMorphling.option_false,
|
||||||
'excluded_items': {
|
'excluded_items': [
|
||||||
item_groups.ItemGroupNames.ZERG_UNITS.lower(): -1,
|
item_groups.ItemGroupNames.ZERG_UNITS.lower()
|
||||||
},
|
],
|
||||||
'unexcluded_items': {
|
'unexcluded_items': [
|
||||||
item_groups.ItemGroupNames.ZERG_MORPHS.lower(): -1,
|
item_groups.ItemGroupNames.ZERG_MORPHS.lower()
|
||||||
},
|
]
|
||||||
}
|
}
|
||||||
self.generate_world(world_options)
|
self.generate_world(world_options)
|
||||||
itempool = [item.name for item in self.multiworld.itempool]
|
itempool = [item.name for item in self.multiworld.itempool]
|
||||||
@@ -538,14 +520,14 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
|
|
||||||
def test_planetary_orbital_module_not_present_without_cc_spells(self) -> None:
|
def test_planetary_orbital_module_not_present_without_cc_spells(self) -> None:
|
||||||
world_options = {
|
world_options = {
|
||||||
"excluded_items": {
|
"excluded_items": [
|
||||||
item_names.COMMAND_CENTER_MULE: -1,
|
item_names.COMMAND_CENTER_MULE,
|
||||||
item_names.COMMAND_CENTER_SCANNER_SWEEP: -1,
|
item_names.COMMAND_CENTER_SCANNER_SWEEP,
|
||||||
item_names.COMMAND_CENTER_EXTRA_SUPPLIES: -1,
|
item_names.COMMAND_CENTER_EXTRA_SUPPLIES
|
||||||
},
|
],
|
||||||
"locked_items": {
|
"locked_items": [
|
||||||
item_names.PLANETARY_FORTRESS: -1,
|
item_names.PLANETARY_FORTRESS
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
self.generate_world(world_options)
|
self.generate_world(world_options)
|
||||||
@@ -949,10 +931,10 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'grant_story_levels': options.GrantStoryLevels.option_additive,
|
'grant_story_levels': options.GrantStoryLevels.option_additive,
|
||||||
'excluded_items': {
|
'excluded_items': [
|
||||||
item_names.KERRIGAN_LEAPING_STRIKE: -1,
|
item_names.KERRIGAN_LEAPING_STRIKE,
|
||||||
item_names.KERRIGAN_MEND: -1,
|
item_names.KERRIGAN_MEND,
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
self.generate_world(world_options)
|
self.generate_world(world_options)
|
||||||
itempool = [item.name for item in self.multiworld.itempool]
|
itempool = [item.name for item in self.multiworld.itempool]
|
||||||
@@ -1226,7 +1208,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
'mission_order': MissionOrder.option_grid,
|
'mission_order': MissionOrder.option_grid,
|
||||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
|
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
|
||||||
'locked_items': {locked_item: -1},
|
'locked_items': [locked_item],
|
||||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||||
'selected_races': [SC2Race.TERRAN.get_title()],
|
'selected_races': [SC2Race.TERRAN.get_title()],
|
||||||
}
|
}
|
||||||
@@ -1267,7 +1249,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_false,
|
'exclude_overpowered_items': ExcludeOverpoweredItems.option_false,
|
||||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||||
'locked_items': {item_name: -1 for item_name in unreleased_items},
|
'locked_items': {item_name: 0 for item_name in unreleased_items},
|
||||||
}
|
}
|
||||||
|
|
||||||
self.generate_world(world_options)
|
self.generate_world(world_options)
|
||||||
@@ -1282,7 +1264,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
**self.ALL_CAMPAIGNS,
|
**self.ALL_CAMPAIGNS,
|
||||||
'mission_order': MissionOrder.option_grid,
|
'mission_order': MissionOrder.option_grid,
|
||||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||||
'excluded_items': {item_name: -1 for item_name in item_groups.terran_mercenaries},
|
'excluded_items': [item_name for item_name in item_groups.terran_mercenaries],
|
||||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||||
'selected_races': [SC2Race.TERRAN.get_title()],
|
'selected_races': [SC2Race.TERRAN.get_title()],
|
||||||
}
|
}
|
||||||
@@ -1298,7 +1280,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
'mission_order': MissionOrder.option_grid,
|
'mission_order': MissionOrder.option_grid,
|
||||||
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
'maximum_campaign_size': MaximumCampaignSize.range_end,
|
||||||
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
|
'exclude_overpowered_items': ExcludeOverpoweredItems.option_true,
|
||||||
'unexcluded_items': {item_names.SOA_TIME_STOP: -1},
|
'unexcluded_items': [item_names.SOA_TIME_STOP],
|
||||||
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
'enable_race_swap': options.EnableRaceSwapVariants.option_shuffle_all,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1340,7 +1322,7 @@ class TestItemFiltering(Sc2SetupTestBase):
|
|||||||
'enabled_campaigns': {
|
'enabled_campaigns': {
|
||||||
SC2Campaign.WOL.campaign_name
|
SC2Campaign.WOL.campaign_name
|
||||||
},
|
},
|
||||||
'excluded_items': {item_names.MARINE: -1, item_names.MEDIC: -1},
|
'excluded_items': [item_names.MARINE, item_names.MEDIC],
|
||||||
'shuffle_no_build': False,
|
'shuffle_no_build': False,
|
||||||
'required_tactics': RequiredTactics.option_standard
|
'required_tactics': RequiredTactics.option_standard
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class ItemFilterTests(Sc2SetupTestBase):
|
|||||||
def test_excluding_all_barracks_units_excludes_infantry_upgrades(self) -> None:
|
def test_excluding_all_barracks_units_excludes_infantry_upgrades(self) -> None:
|
||||||
world_options = {
|
world_options = {
|
||||||
'excluded_items': {
|
'excluded_items': {
|
||||||
item_groups.ItemGroupNames.BARRACKS_UNITS: -1,
|
item_groups.ItemGroupNames.BARRACKS_UNITS: 0
|
||||||
},
|
},
|
||||||
'required_tactics': 'standard',
|
'required_tactics': 'standard',
|
||||||
'min_number_of_upgrades': 1,
|
'min_number_of_upgrades': 1,
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
|||||||
SC2Campaign.NCO.campaign_name
|
SC2Campaign.NCO.campaign_name
|
||||||
},
|
},
|
||||||
'excluded_items': {
|
'excluded_items': {
|
||||||
item_groups.ItemGroupNames.TERRAN_UNITS: -1,
|
item_groups.ItemGroupNames.TERRAN_UNITS: 0,
|
||||||
},
|
},
|
||||||
'unexcluded_items': {
|
'unexcluded_items': {
|
||||||
item_groups.ItemGroupNames.NCO_UNITS: -1,
|
item_groups.ItemGroupNames.NCO_UNITS: 0,
|
||||||
},
|
},
|
||||||
'max_number_of_upgrades': 2,
|
'max_number_of_upgrades': 2,
|
||||||
}
|
}
|
||||||
@@ -81,10 +81,10 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
|||||||
},
|
},
|
||||||
'mission_order': options.MissionOrder.option_vanilla_shuffled,
|
'mission_order': options.MissionOrder.option_vanilla_shuffled,
|
||||||
'excluded_items': {
|
'excluded_items': {
|
||||||
item_groups.ItemGroupNames.TERRAN_ITEMS: -1,
|
item_groups.ItemGroupNames.TERRAN_ITEMS: 0,
|
||||||
},
|
},
|
||||||
'unexcluded_items': {
|
'unexcluded_items': {
|
||||||
item_groups.ItemGroupNames.NCO_MAX_PROGRESSIVE_ITEMS: -1,
|
item_groups.ItemGroupNames.NCO_MAX_PROGRESSIVE_ITEMS: 0,
|
||||||
item_groups.ItemGroupNames.NCO_MIN_PROGRESSIVE_ITEMS: 1,
|
item_groups.ItemGroupNames.NCO_MIN_PROGRESSIVE_ITEMS: 1,
|
||||||
},
|
},
|
||||||
'excluded_missions': [
|
'excluded_missions': [
|
||||||
@@ -398,7 +398,7 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
|||||||
|
|
||||||
self.generate_world(world_options)
|
self.generate_world(world_options)
|
||||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||||
spear_of_adun_actives = [item_name for item_name in world_item_names if item_name in item_groups.spear_of_adun_actives]
|
spear_of_adun_actives = [item_name for item_name in world_item_names if item_name in item_tables.spear_of_adun_calldowns]
|
||||||
|
|
||||||
self.assertLessEqual(len(spear_of_adun_actives), target_number)
|
self.assertLessEqual(len(spear_of_adun_actives), target_number)
|
||||||
|
|
||||||
@@ -418,9 +418,7 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
|||||||
|
|
||||||
self.generate_world(world_options)
|
self.generate_world(world_options)
|
||||||
world_item_names = [item.name for item in self.multiworld.itempool]
|
world_item_names = [item.name for item in self.multiworld.itempool]
|
||||||
spear_of_adun_autocasts = [
|
spear_of_adun_autocasts = [item_name for item_name in world_item_names if item_name in item_tables.spear_of_adun_castable_passives]
|
||||||
item_name for item_name in world_item_names if item_name in item_groups.spear_of_adun_passives
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertLessEqual(len(spear_of_adun_autocasts), target_number)
|
self.assertLessEqual(len(spear_of_adun_autocasts), target_number)
|
||||||
|
|
||||||
@@ -473,12 +471,12 @@ class TestSupportedUseCases(Sc2SetupTestBase):
|
|||||||
],
|
],
|
||||||
'required_tactics': options.RequiredTactics.option_any_units,
|
'required_tactics': options.RequiredTactics.option_any_units,
|
||||||
'excluded_items': {
|
'excluded_items': {
|
||||||
item_groups.ItemGroupNames.TERRAN_UNITS: -1,
|
item_groups.ItemGroupNames.TERRAN_UNITS: 0,
|
||||||
item_groups.ItemGroupNames.ZERG_UNITS: -1,
|
item_groups.ItemGroupNames.ZERG_UNITS: 0,
|
||||||
},
|
},
|
||||||
'unexcluded_items': {
|
'unexcluded_items': {
|
||||||
item_groups.ItemGroupNames.TERRAN_MERCENARIES: -1,
|
item_groups.ItemGroupNames.TERRAN_MERCENARIES: 0,
|
||||||
item_groups.ItemGroupNames.ZERG_MERCENARIES: -1,
|
item_groups.ItemGroupNames.ZERG_MERCENARIES: 0,
|
||||||
},
|
},
|
||||||
'start_inventory': {
|
'start_inventory': {
|
||||||
item_names.PROGRESSIVE_FAST_DELIVERY: 1,
|
item_names.PROGRESSIVE_FAST_DELIVERY: 1,
|
||||||
|
|||||||
Reference in New Issue
Block a user