Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into sc2-next
Browse files Browse the repository at this point in the history
  • Loading branch information
Ziktofel committed Nov 21, 2023
2 parents 1953dc7 + e916b0d commit ca9d768
Show file tree
Hide file tree
Showing 155 changed files with 35,971 additions and 3,904 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*.apmc
*.apz5
*.aptloz
*.apemerald
*.pyc
*.pyd
*.sfc
Expand Down
18 changes: 13 additions & 5 deletions BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ def extend(self, regions: Iterable[Region]):
for region in regions:
self.region_cache[region.player][region.name] = region

def add_group(self, new_id: int):
self.region_cache[new_id] = {}
self.entrance_cache[new_id] = {}
self.location_cache[new_id] = {}

def __iter__(self) -> Iterator[Region]:
for regions in self.region_cache.values():
yield from regions.values()
Expand Down Expand Up @@ -220,6 +225,7 @@ def add_group(self, name: str, game: str, players: Set[int] = frozenset()) -> Tu
return group_id, group
new_id: int = self.players + len(self.groups) + 1

self.regions.add_group(new_id)
self.game[new_id] = game
self.player_types[new_id] = NetUtils.SlotType.group
world_type = AutoWorld.AutoWorldRegister.world_types[game]
Expand Down Expand Up @@ -617,7 +623,7 @@ class CollectionState():
additional_copy_functions: List[Callable[[CollectionState, CollectionState], CollectionState]] = []

def __init__(self, parent: MultiWorld):
self.prog_items = {player: Counter() for player in parent.player_ids}
self.prog_items = {player: Counter() for player in parent.get_all_ids()}
self.multiworld = parent
self.reachable_regions = {player: set() for player in parent.get_all_ids()}
self.blocked_connections = {player: set() for player in parent.get_all_ids()}
Expand Down Expand Up @@ -711,11 +717,11 @@ def sweep_for_events(self, key_only: bool = False, locations: Optional[Iterable[
def has(self, item: str, player: int, count: int = 1) -> bool:
return self.prog_items[player][item] >= count

def has_all(self, items: Set[str], player: int) -> bool:
def has_all(self, items: Iterable[str], player: int) -> bool:
"""Returns True if each item name of items is in state at least once."""
return all(self.prog_items[player][item] for item in items)

def has_any(self, items: Set[str], player: int) -> bool:
def has_any(self, items: Iterable[str], player: int) -> bool:
"""Returns True if at least one item name of items is in state at least once."""
return any(self.prog_items[player][item] for item in items)

Expand All @@ -724,16 +730,18 @@ def count(self, item: str, player: int) -> int:

def has_group(self, item_name_group: str, player: int, count: int = 1) -> bool:
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
found += self.prog_items[player][item_name]
found += player_prog_items[item_name]
if found >= count:
return True
return False

def count_group(self, item_name_group: str, player: int) -> int:
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
found += self.prog_items[player][item_name]
found += player_prog_items[item_name]
return found

def item_count(self, item: str, player: int) -> int:
Expand Down
10 changes: 8 additions & 2 deletions CommonClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,8 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
elif 'InvalidGame' in errors:
ctx.event_invalid_game()
elif 'IncompatibleVersion' in errors:
raise Exception('Server reported your client version as incompatible')
raise Exception('Server reported your client version as incompatible. '
'This probably means you have to update.')
elif 'InvalidItemsHandling' in errors:
raise Exception('The item handling flags requested by the client are not supported')
# last to check, recoverable problem
Expand All @@ -758,6 +759,7 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
ctx.slot_info = {int(pid): data for pid, data in args["slot_info"].items()}
ctx.hint_points = args.get("hint_points", 0)
ctx.consume_players_package(args["players"])
ctx.stored_data_notification_keys.add(f"_read_hints_{ctx.team}_{ctx.slot}")
msgs = []
if ctx.locations_checked:
msgs.append({"cmd": "LocationChecks",
Expand Down Expand Up @@ -836,10 +838,14 @@ async def process_server_cmd(ctx: CommonContext, args: dict):

elif cmd == "Retrieved":
ctx.stored_data.update(args["keys"])
if ctx.ui and f"_read_hints_{ctx.team}_{ctx.slot}" in args["keys"]:
ctx.ui.update_hints()

elif cmd == "SetReply":
ctx.stored_data[args["key"]] = args["value"]
if args["key"].startswith("EnergyLink"):
if ctx.ui and f"_read_hints_{ctx.team}_{ctx.slot}" == args["key"]:
ctx.ui.update_hints()
elif args["key"].startswith("EnergyLink"):
ctx.current_energy_link_value = args["value"]
if ctx.ui:
ctx.ui.set_new_energy_link_value()
Expand Down
2 changes: 1 addition & 1 deletion Fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:

location.item = None
placed_item.location = None
swap_state = sweep_from_pool(base_state, [placed_item] if unsafe else [])
swap_state = sweep_from_pool(base_state, [placed_item, *item_pool] if unsafe else item_pool)
# unsafe means swap_state assumes we can somehow collect placed_item before item_to_place
# by continuing to swap, which is not guaranteed. This is unsafe because there is no mechanic
# to clean that up later, so there is a chance generation fails.
Expand Down
5 changes: 4 additions & 1 deletion Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
if any(world.item_links.values()):
world._all_state = None

logger.info("Running Item Plando")
logger.info("Running Item Plando.")

distribute_planned(world)

Expand Down Expand Up @@ -354,6 +354,9 @@ def precollect_hint(location):
assert location.item.code is not None, "item code None should be event, " \
"location.address should then also be None. Location: " \
f" {location}"
assert location.address not in locations_data[location.player], (
f"Locations with duplicate address. {location} and "
f"{locations_data[location.player][location.address]}")
locations_data[location.player][location.address] = \
location.item.code, location.item.player, location.item.flags
if location.name in world.worlds[location.player].options.start_location_hints:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Currently, the following games are supported:
* Muse Dash
* DOOM 1993
* Terraria
* Lingo
* Pokémon Emerald

For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
Expand Down
6 changes: 3 additions & 3 deletions UndertaleClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ def _cmd_resync(self):
self.ctx.syncing = True

def _cmd_patch(self):
"""Patch the game."""
"""Patch the game. Only use this command if /auto_patch fails."""
if isinstance(self.ctx, UndertaleContext):
os.makedirs(name=os.path.join(os.getcwd(), "Undertale"), exist_ok=True)
self.ctx.patch_game()
self.output("Patched.")

def _cmd_savepath(self, directory: str):
"""Redirect to proper save data folder. (Use before connecting!)"""
"""Redirect to proper save data folder. This is necessary for Linux users to use before connecting."""
if isinstance(self.ctx, UndertaleContext):
self.ctx.save_game_folder = directory
self.output("Changed to the following directory: " + self.ctx.save_game_folder)
Expand Down Expand Up @@ -67,7 +67,7 @@ def _cmd_auto_patch(self, steaminstall: typing.Optional[str] = None):
self.output("Patching successful!")

def _cmd_online(self):
"""Makes you no longer able to see other Undertale players."""
"""Toggles seeing other Undertale players."""
if isinstance(self.ctx, UndertaleContext):
self.ctx.update_online_mode(not ("Online" in self.ctx.tags))
if "Online" in self.ctx.tags:
Expand Down
8 changes: 8 additions & 0 deletions WargrooveClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ async def server_auth(self, password_requested: bool = False):
async def connection_closed(self):
await super(WargrooveContext, self).connection_closed()
self.remove_communication_files()
self.checked_locations.clear()
self.server_locations.clear()
self.finished_game = False

@property
def endpoints(self):
Expand All @@ -124,6 +127,9 @@ def endpoints(self):
async def shutdown(self):
await super(WargrooveContext, self).shutdown()
self.remove_communication_files()
self.checked_locations.clear()
self.server_locations.clear()
self.finished_game = False

def remove_communication_files(self):
for root, dirs, files in os.walk(self.game_communication_path):
Expand Down Expand Up @@ -402,8 +408,10 @@ async def game_watcher(ctx: WargrooveContext):
if file.find("send") > -1:
st = file.split("send", -1)[1]
sending = sending+[(int(st))]
os.remove(os.path.join(ctx.game_communication_path, file))
if file.find("victory") > -1:
victory = True
os.remove(os.path.join(ctx.game_communication_path, file))
ctx.locations_checked = sending
message = [{"cmd": 'LocationChecks', "locations": sending}]
await ctx.send_msgs(message)
Expand Down
6 changes: 4 additions & 2 deletions WebHostLib/customserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
class CustomClientMessageProcessor(ClientMessageProcessor):
ctx: WebHostContext

def _cmd_video(self, platform, user):
"""Set a link for your name in the WebHostLib tracker pointing to a video stream"""
def _cmd_video(self, platform: str, user: str):
"""Set a link for your name in the WebHostLib tracker pointing to a video stream.
Currently, only YouTube and Twitch platforms are supported.
"""
if platform.lower().startswith("t"): # twitch
self.ctx.video[self.client.team, self.client.slot] = "Twitch", user
self.ctx.save()
Expand Down
71 changes: 54 additions & 17 deletions WebHostLib/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
import os
import typing

import yaml
from jinja2 import Template

import Options
from Utils import __version__, local_path
from Utils import local_path
from worlds.AutoWorld import AutoWorldRegister

handled_in_js = {"start_inventory", "local_items", "non_local_items", "start_hints", "start_location_hints",
Expand All @@ -28,7 +25,7 @@ def get_html_doc(option_type: type(Options.Option)) -> str:
weighted_options = {
"baseOptions": {
"description": "Generated by https://archipelago.gg/",
"name": "Player",
"name": "",
"game": {},
},
"games": {},
Expand All @@ -43,7 +40,7 @@ def get_html_doc(option_type: type(Options.Option)) -> str:
"baseOptions": {
"description": f"Generated by https://archipelago.gg/ for {game_name}",
"game": game_name,
"name": "Player",
"name": "",
},
}

Expand Down Expand Up @@ -117,10 +114,46 @@ def get_html_doc(option_type: type(Options.Option)) -> str:
}

else:
logging.debug(f"{option} not exported to Web options.")
logging.debug(f"{option} not exported to Web Options.")

player_options["gameOptions"] = game_options

player_options["presetOptions"] = {}
for preset_name, preset in world.web.options_presets.items():
player_options["presetOptions"][preset_name] = {}
for option_name, option_value in preset.items():
# Random range type settings are not valid.
assert (not str(option_value).startswith("random-")), \
f"Invalid preset value '{option_value}' for '{option_name}' in '{preset_name}'. Special random " \
f"values are not supported for presets."

# Normal random is supported, but needs to be handled explicitly.
if option_value == "random":
player_options["presetOptions"][preset_name][option_name] = option_value
continue

option = world.options_dataclass.type_hints[option_name].from_any(option_value)
if isinstance(option, Options.SpecialRange) and isinstance(option_value, str):
assert option_value in option.special_range_names, \
f"Invalid preset value '{option_value}' for '{option_name}' in '{preset_name}'. " \
f"Expected {option.special_range_names.keys()} or {option.range_start}-{option.range_end}."

# Still use the true value for the option, not the name.
player_options["presetOptions"][preset_name][option_name] = option.value
elif isinstance(option, Options.Range):
player_options["presetOptions"][preset_name][option_name] = option.value
elif isinstance(option_value, str):
# For Choice and Toggle options, the value should be the name of the option. This is to prevent
# setting a preset for an option with an overridden from_text method that would normally be okay,
# but would not be okay for the webhost's current implementation of player options UI.
assert option.name_lookup[option.value] == option_value, \
f"Invalid option value '{option_value}' for '{option_name}' in preset '{preset_name}'. " \
f"Values must not be resolved to a different option via option.from_text (or an alias)."
player_options["presetOptions"][preset_name][option_name] = option.current_key
else:
# int and bool values are fine, just resolve them to the current key for webhost.
player_options["presetOptions"][preset_name][option_name] = option.current_key

os.makedirs(os.path.join(target_folder, 'player-options'), exist_ok=True)

with open(os.path.join(target_folder, 'player-options', game_name + ".json"), "w") as f:
Expand All @@ -136,16 +169,20 @@ def get_html_doc(option_type: type(Options.Option)) -> str:
option["defaultValue"] = "random"

weighted_options["baseOptions"]["game"][game_name] = 0
weighted_options["games"][game_name] = {}
weighted_options["games"][game_name]["gameSettings"] = game_options
weighted_options["games"][game_name]["gameItems"] = tuple(world.item_names)
weighted_options["games"][game_name]["gameItemGroups"] = [
group for group in world.item_name_groups.keys() if group != "Everything"
]
weighted_options["games"][game_name]["gameLocations"] = tuple(world.location_names)
weighted_options["games"][game_name]["gameLocationGroups"] = [
group for group in world.location_name_groups.keys() if group != "Everywhere"
]
weighted_options["games"][game_name] = {
"gameSettings": game_options,
"gameItems": tuple(world.item_names),
"gameItemGroups": [
group for group in world.item_name_groups.keys() if group != "Everything"
],
"gameItemDescriptions": world.item_descriptions,
"gameLocations": tuple(world.location_names),
"gameLocationGroups": [
group for group in world.location_name_groups.keys() if group != "Everywhere"
],
"gameLocationDescriptions": world.location_descriptions,
}

with open(os.path.join(target_folder, 'weighted-options.json'), "w") as f:
json.dump(weighted_options, f, indent=2, separators=(',', ': '))

Loading

0 comments on commit ca9d768

Please sign in to comment.