Skip to content

Commit

Permalink
Merge remote-tracking branch 'archipelago/main' into StardewValley/re…
Browse files Browse the repository at this point in the history
…factor-skill-progression
  • Loading branch information
Jouramie committed Nov 29, 2024
2 parents 3aabfc3 + 1487d32 commit ccadd70
Show file tree
Hide file tree
Showing 371 changed files with 8,929 additions and 4,857 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
worlds/blasphemous/region_data.py linguist-generated=true
worlds/yachtdice/YachtWeights.py linguist-generated=true
2 changes: 1 addition & 1 deletion .github/pyright-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"reportMissingImports": true,
"reportMissingTypeStubs": true,

"pythonVersion": "3.8",
"pythonVersion": "3.10",
"pythonPlatform": "Windows",

"executionEnvironments": [
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/analyze-modified-files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
- uses: actions/setup-python@v5
if: env.diff != ''
with:
python-version: 3.8
python-version: '3.10'

- name: "Install dependencies"
if: env.diff != ''
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ env:
jobs:
# build-release-macos: # LF volunteer

build-win-py38: # RCs will still be built and signed by hand
build-win: # RCs will still be built and signed by hand
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install python
uses: actions/setup-python@v5
with:
python-version: '3.8'
python-version: '3.12'
- name: Download run-time dependencies
run: |
Invoke-WebRequest -Uri https://github.com/Ijwu/Enemizer/releases/download/${Env:ENEMIZER_VERSION}/win-x64.zip -OutFile enemizer.zip
Expand Down Expand Up @@ -111,10 +111,10 @@ jobs:
- name: Get a recent python
uses: actions/setup-python@v5
with:
python-version: '3.11'
python-version: '3.12'
- name: Install build-time dependencies
run: |
echo "PYTHON=python3.11" >> $GITHUB_ENV
echo "PYTHON=python3.12" >> $GITHUB_ENV
wget -nv https://github.com/AppImage/AppImageKit/releases/download/$APPIMAGETOOL_VERSION/appimagetool-x86_64.AppImage
chmod a+rx appimagetool-x86_64.AppImage
./appimagetool-x86_64.AppImage --appimage-extract
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -58,7 +58,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
Expand All @@ -72,4 +72,4 @@ jobs:
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ jobs:
- name: Get a recent python
uses: actions/setup-python@v5
with:
python-version: '3.11'
python-version: '3.12'
- name: Install build-time dependencies
run: |
echo "PYTHON=python3.11" >> $GITHUB_ENV
echo "PYTHON=python3.12" >> $GITHUB_ENV
wget -nv https://github.com/AppImage/AppImageKit/releases/download/$APPIMAGETOOL_VERSION/appimagetool-x86_64.AppImage
chmod a+rx appimagetool-x86_64.AppImage
./appimagetool-x86_64.AppImage --appimage-extract
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,11 @@ jobs:
matrix:
os: [ubuntu-latest]
python:
- {version: '3.8'}
- {version: '3.9'}
- {version: '3.10'}
- {version: '3.11'}
- {version: '3.12'}
include:
- python: {version: '3.8'} # win7 compat
- python: {version: '3.10'} # old compat
os: windows-latest
- python: {version: '3.12'} # current
os: windows-latest
Expand Down Expand Up @@ -89,4 +87,4 @@ jobs:
run: |
source venv/bin/activate
export PYTHONPATH=$(pwd)
python test/hosting/__main__.py
timeout 600 python test/hosting/__main__.py
61 changes: 39 additions & 22 deletions BaseClasses.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
from __future__ import annotations

import collections
import itertools
import functools
import logging
import random
import secrets
import typing # this can go away when Python 3.8 support is dropped
from argparse import Namespace
from collections import Counter, deque
from collections.abc import Collection, MutableSequence
from enum import IntEnum, IntFlag
from typing import (AbstractSet, Any, Callable, ClassVar, Dict, Iterable, Iterator, List, Mapping, NamedTuple,
Optional, Protocol, Set, Tuple, Union, Type)
Optional, Protocol, Set, Tuple, Union, TYPE_CHECKING)

from typing_extensions import NotRequired, TypedDict

import NetUtils
import Options
import Utils

if typing.TYPE_CHECKING:
if TYPE_CHECKING:
from worlds import AutoWorld


Expand Down Expand Up @@ -231,7 +229,7 @@ def set_options(self, args: Namespace) -> None:
for player in self.player_ids:
world_type = AutoWorld.AutoWorldRegister.world_types[self.game[player]]
self.worlds[player] = world_type(self, player)
options_dataclass: typing.Type[Options.PerGameCommonOptions] = world_type.options_dataclass
options_dataclass: type[Options.PerGameCommonOptions] = world_type.options_dataclass
self.worlds[player].options = options_dataclass(**{option_key: getattr(args, option_key)[player]
for option_key in options_dataclass.type_hints})

Expand Down Expand Up @@ -341,7 +339,7 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
new_item.classification |= classifications[item_name]
new_itempool.append(new_item)

region = Region("Menu", group_id, self, "ItemLink")
region = Region(group["world"].origin_region_name, group_id, self, "ItemLink")
self.regions.append(region)
locations = region.locations
# ensure that progression items are linked first, then non-progression
Expand Down Expand Up @@ -975,7 +973,7 @@ class Region:
entrances: List[Entrance]
exits: List[Entrance]
locations: List[Location]
entrance_type: ClassVar[Type[Entrance]] = Entrance
entrance_type: ClassVar[type[Entrance]] = Entrance

class Register(MutableSequence):
region_manager: MultiWorld.RegionManager
Expand Down Expand Up @@ -1075,7 +1073,7 @@ def get_connecting_entrance(self, is_main_entrance: Callable[[Entrance], bool])
return entrance.parent_region.get_connecting_entrance(is_main_entrance)

def add_locations(self, locations: Dict[str, Optional[int]],
location_type: Optional[Type[Location]] = None) -> None:
location_type: Optional[type[Location]] = None) -> None:
"""
Adds locations to the Region object, where location_type is your Location class and locations is a dict of
location names to address.
Expand Down Expand Up @@ -1112,7 +1110,7 @@ def create_exit(self, name: str) -> Entrance:
return exit_

def add_exits(self, exits: Union[Iterable[str], Dict[str, Optional[str]]],
rules: Dict[str, Callable[[CollectionState], bool]] = None) -> None:
rules: Dict[str, Callable[[CollectionState], bool]] = None) -> List[Entrance]:
"""
Connects current region to regions in exit dictionary. Passed region names must exist first.
Expand All @@ -1122,10 +1120,14 @@ def add_exits(self, exits: Union[Iterable[str], Dict[str, Optional[str]]],
"""
if not isinstance(exits, Dict):
exits = dict.fromkeys(exits)
for connecting_region, name in exits.items():
self.connect(self.multiworld.get_region(connecting_region, self.player),
name,
rules[connecting_region] if rules and connecting_region in rules else None)
return [
self.connect(
self.multiworld.get_region(connecting_region, self.player),
name,
rules[connecting_region] if rules and connecting_region in rules else None,
)
for connecting_region, name in exits.items()
]

def __repr__(self):
return self.multiworld.get_name_string_for_object(self) if self.multiworld else f'{self.name} (Player {self.player})'
Expand Down Expand Up @@ -1264,6 +1266,14 @@ def useful(self) -> bool:
def trap(self) -> bool:
return ItemClassification.trap in self.classification

@property
def filler(self) -> bool:
return not (self.advancement or self.useful or self.trap)

@property
def excludable(self) -> bool:
return not (self.advancement or self.useful)

@property
def flags(self) -> int:
return self.classification.as_flag()
Expand Down Expand Up @@ -1382,14 +1392,21 @@ def create_playthrough(self, create_paths: bool = True) -> None:

# second phase, sphere 0
removed_precollected: List[Item] = []
for item in (i for i in chain.from_iterable(multiworld.precollected_items.values()) if i.advancement):
logging.debug('Checking if %s (Player %d) is required to beat the game.', item.name, item.player)
multiworld.precollected_items[item.player].remove(item)
multiworld.state.remove(item)
if not multiworld.can_beat_game():
multiworld.push_precollected(item)
else:
removed_precollected.append(item)

for precollected_items in multiworld.precollected_items.values():
# The list of items is mutated by removing one item at a time to determine if each item is required to beat
# the game, and re-adding that item if it was required, so a copy needs to be made before iterating.
for item in precollected_items.copy():
if not item.advancement:
continue
logging.debug('Checking if %s (Player %d) is required to beat the game.', item.name, item.player)
precollected_items.remove(item)
multiworld.state.remove(item)
if not multiworld.can_beat_game():
# Add the item back into `precollected_items` and collect it into `multiworld.state`.
multiworld.push_precollected(item)
else:
removed_precollected.append(item)

# we are now down to just the required progress items in collection_spheres. Unfortunately
# the previous pruning stage could potentially have made certain items dependant on others
Expand Down Expand Up @@ -1528,7 +1545,7 @@ def write_option(option_key: str, option_obj: Options.AssembleOptions) -> None:
[f" {location}: {item}" for (location, item) in sphere.items()] if isinstance(sphere, dict) else
[f" {item}" for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
if self.unreachables:
outfile.write('\n\nUnreachable Items:\n\n')
outfile.write('\n\nUnreachable Progression Items:\n\n')
outfile.write(
'\n'.join(['%s: %s' % (unreachable.item, unreachable) for unreachable in self.unreachables]))

Expand Down
53 changes: 38 additions & 15 deletions CommonClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from MultiServer import CommandProcessor
from NetUtils import (Endpoint, decode, NetworkItem, encode, JSONtoTextParser, ClientStatus, Permission, NetworkSlot,
RawJSONtoTextParser, add_json_text, add_json_location, add_json_item, JSONTypes, SlotType)
RawJSONtoTextParser, add_json_text, add_json_location, add_json_item, JSONTypes, HintStatus, SlotType)
from Utils import Version, stream_input, async_start
from worlds import network_data_package, AutoWorldRegister
import os
Expand Down Expand Up @@ -355,6 +355,8 @@ def __init__(self, server_address: typing.Optional[str] = None, password: typing

self.item_names = self.NameLookupDict(self, "item")
self.location_names = self.NameLookupDict(self, "location")
self.versions = {}
self.checksums = {}

self.jsontotextparser = JSONtoTextParser(self)
self.rawjsontotextparser = RawJSONtoTextParser(self)
Expand Down Expand Up @@ -410,6 +412,7 @@ async def disconnect(self, allow_autoreconnect: bool = False):
await self.server.socket.close()
if self.server_task is not None:
await self.server_task
self.ui.update_hints()

async def send_msgs(self, msgs: typing.List[typing.Any]) -> None:
""" `msgs` JSON serializable """
Expand Down Expand Up @@ -549,7 +552,14 @@ async def shutdown(self):
await self.ui_task
if self.input_task:
self.input_task.cancel()


# Hints
def update_hint(self, location: int, finding_player: int, status: typing.Optional[HintStatus]) -> None:
msg = {"cmd": "UpdateHint", "location": location, "player": finding_player}
if status is not None:
msg["status"] = status
async_start(self.send_msgs([msg]), name="update_hint")

# DataPackage
async def prepare_data_package(self, relevant_games: typing.Set[str],
remote_date_package_versions: typing.Dict[str, int],
Expand All @@ -571,26 +581,34 @@ async def prepare_data_package(self, relevant_games: typing.Set[str],
needed_updates.add(game)
continue

local_version: int = network_data_package["games"].get(game, {}).get("version", 0)
local_checksum: typing.Optional[str] = network_data_package["games"].get(game, {}).get("checksum")
# no action required if local version is new enough
if (not remote_checksum and (remote_version > local_version or remote_version == 0)) \
or remote_checksum != local_checksum:
cached_game = Utils.load_data_package_for_checksum(game, remote_checksum)
cache_version: int = cached_game.get("version", 0)
cache_checksum: typing.Optional[str] = cached_game.get("checksum")
# download remote version if cache is not new enough
if (not remote_checksum and (remote_version > cache_version or remote_version == 0)) \
or remote_checksum != cache_checksum:
needed_updates.add(game)
cached_version: int = self.versions.get(game, 0)
cached_checksum: typing.Optional[str] = self.checksums.get(game)
# no action required if cached version is new enough
if (not remote_checksum and (remote_version > cached_version or remote_version == 0)) \
or remote_checksum != cached_checksum:
local_version: int = network_data_package["games"].get(game, {}).get("version", 0)
local_checksum: typing.Optional[str] = network_data_package["games"].get(game, {}).get("checksum")
if ((remote_checksum or remote_version <= local_version and remote_version != 0)
and remote_checksum == local_checksum):
self.update_game(network_data_package["games"][game], game)
else:
self.update_game(cached_game, game)
cached_game = Utils.load_data_package_for_checksum(game, remote_checksum)
cache_version: int = cached_game.get("version", 0)
cache_checksum: typing.Optional[str] = cached_game.get("checksum")
# download remote version if cache is not new enough
if (not remote_checksum and (remote_version > cache_version or remote_version == 0)) \
or remote_checksum != cache_checksum:
needed_updates.add(game)
else:
self.update_game(cached_game, game)
if needed_updates:
await self.send_msgs([{"cmd": "GetDataPackage", "games": [game_name]} for game_name in needed_updates])

def update_game(self, game_package: dict, game: str):
self.item_names.update_game(game, game_package["item_name_to_id"])
self.location_names.update_game(game, game_package["location_name_to_id"])
self.versions[game] = game_package.get("version", 0)
self.checksums[game] = game_package.get("checksum")

def update_data_package(self, data_package: dict):
for game, game_data in data_package["games"].items():
Expand Down Expand Up @@ -700,6 +718,11 @@ def run_gui(self):

def run_cli(self):
if sys.stdin:
if sys.stdin.fileno() != 0:
from multiprocessing import parent_process
if parent_process():
return # ignore MultiProcessing pipe

# steam overlay breaks when starting console_loop
if 'gameoverlayrenderer' in os.environ.get('LD_PRELOAD', ''):
logger.info("Skipping terminal input, due to conflicting Steam Overlay detected. Please use GUI only.")
Expand Down
Loading

0 comments on commit ccadd70

Please sign in to comment.