Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into subdir
Browse files Browse the repository at this point in the history
  • Loading branch information
hatkirby committed Nov 16, 2023
2 parents 4cdf431 + 185a519 commit d189ed1
Show file tree
Hide file tree
Showing 45 changed files with 4,208 additions and 1,471 deletions.
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
3 changes: 2 additions & 1 deletion 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 Down
3 changes: 3 additions & 0 deletions Main.py
Original file line number Diff line number Diff line change
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
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
47 changes: 40 additions & 7 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 Down
Loading

0 comments on commit d189ed1

Please sign in to comment.