Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
42d24b4
HK: add manifest (#6057)
qwint Apr 17, 2026
bdde214
Core: Remove worlds.generic.PlandoItem (#6035)
duckboycool Apr 17, 2026
5f9e38b
Test: add test for unpickling NetUtils enums (#5326)
black-sliver Apr 18, 2026
66712bb
APQuest: Switch to Rule Builder (#5906)
NewSoupVi Apr 18, 2026
0a742b6
Options: Add more Option unit tests, add generic Option.__eq__, make …
NewSoupVi Apr 18, 2026
a6c1347
The Witness: Make "Early Good Item" an OptionSet allowing Symbols, Do…
NewSoupVi Apr 18, 2026
0b38065
APQuest: Fix Easy Mode boss having too much health to beat (#6146)
NewSoupVi Apr 18, 2026
dc91139
The Witness: More control over progressive symbols (#3961)
NewSoupVi Apr 18, 2026
d41cec6
CI: update softprops/action-gh-release to v3.0.0 (#6162)
black-sliver Apr 25, 2026
d2395de
Factorio: move communication savegame to user dir (#5646)
lepideble Apr 27, 2026
9c78edc
Rule Builder: Fix rule hash collisions (#6169)
drtchops Apr 29, 2026
15561e1
Generate: Added "quantity" option for player yamls to use multiple ti…
mrkssr Apr 29, 2026
0103394
MMBN3: Add spaces in concatenated string#5689
duckboycool Apr 29, 2026
c851eff
lufia2ac/docs: fix some errors (#5618)
el-u Apr 29, 2026
b716096
SML2: Send logic affecting randomiser options as slot_data (#5751)
Oshroth Apr 29, 2026
5967492
Docs: added an italian translation of the setup guide for Muse Dash (…
UCSA-MM Apr 29, 2026
7893705
sm64ex: Recommend Flatpak for Linux users (#5897)
N00byKing Apr 29, 2026
0601494
Pokemon RB: Fix mutating global item_groups in get_filler_item_name()…
Mysteryem Apr 29, 2026
4346191
Docs: Add a warning to world api.md#events that you want StatusUpdate…
Ixrec Apr 29, 2026
d5cc201
AHIT: Update FAQ in setup guide (#5975)
CookieCat45 Apr 29, 2026
73856f6
Docs: Mention APQuest as a reference (#6000)
Sophira Apr 29, 2026
a48bf01
Docs: Make RB OptionFilter docs clearer (#6028)
drtchops Apr 29, 2026
af39d79
LADX: doc update (#6050)
threeandthreee Apr 29, 2026
a0236b2
Jak1: yamlless UT support (#6066)
massimilianodelliubaldini Apr 29, 2026
be9c7b1
KH2: Update setup_en.md (#6129)
rocket0634 Apr 29, 2026
31f5a6c
Docs: Rule Builder Filtered Example Fix (#6152)
IQubic Apr 29, 2026
74a0cd6
MultiServer: update help doc on auto_shutdown#5917
qwint Apr 29, 2026
8ac335f
Docs: Rewrite get_filler_item_name's docstring to avoid implying it's…
Ixrec Apr 29, 2026
d36ba62
The Messenger: Add missing rules for Key of Strength#6124
Jouramie Apr 29, 2026
fd8f3fc
The Messenger: Add missing indirect conditions (#6101)
Mysteryem Apr 29, 2026
90b72c0
Docs: Add missing space to plando doc #6060
palex00 Apr 29, 2026
9da3c29
Terraria: fix failing to generate with Calamity enabled and full acce…
Seldom-SE Apr 29, 2026
1875062
Undertale: Handle zero arg /auto_patch (#5130)
qwint Apr 29, 2026
798eeaa
Muse Dash: Update to DASH AND SHOOT (#6163)
DeamonHunter May 1, 2026
234b058
ALttP: Fix starting mail and shield equipment from start inventory (#…
Alchav May 1, 2026
a68109f
WebHost: Add deletion of old content to cleanup function (#6119)
Berserker66 May 1, 2026
4486510
Options: make template generation log the failing world if one fails …
qwint May 2, 2026
6d9d340
Launcher: display window for failed world loads if any (#6058)
Silvris May 5, 2026
b59e52a
Options: Remove the "" from the start inventory examples (#6165)
ScipioWright May 5, 2026
2bd572c
Super Mario 64: Basic testing (#4335)
josephwhite May 9, 2026
29a6f40
Blasphemous: Increase logic performance (#4585)
Mysteryem May 9, 2026
e081002
WebHost: Fix adding weighted text options (#5116)
josephwhite May 9, 2026
d0407a7
Docs: Added new documentation for DS3's enemy randomization (random_e…
Paperkoopa May 9, 2026
4a28888
Rule Builder: Implement AtLeast (#6085)
Ishigh1 May 9, 2026
06dc1b8
Pokemon Emerald: Switch to rule builder (#5923)
Zunawe May 9, 2026
7a5acfe
Core: Make manifest accessible as a world attribute (#6122)
duckboycool May 9, 2026
3fcd337
WebHost: Add authors to supported games page (#6123)
duckboycool May 9, 2026
55a1b12
CommonClient: Make hint cost (shown in server label) update (#6149)
duckboycool May 9, 2026
0cd81ff
A Link to the Past: We Have Enemizer at Home (#6153)
Alchav May 9, 2026
4ef1fb7
Muse Dash: Convert to Rule Builder (#6166)
DeamonHunter May 9, 2026
799e0b7
KH2: fix ice cream double counting for logic (#6068)
JaredWeakStrike May 11, 2026
38e77e1
Factorio: Remove the need to have two separate installs (#4221)
silasary May 16, 2026
24f75ba
Settings: validate FilePath hash on use (#5854)
Silvris May 17, 2026
d8d148a
Stardew Valley: Bypass CollectionState overhead by just calling prog_…
Jouramie May 17, 2026
b575983
SM64: Change power star item classification to be deprioritized (#6191)
duh102 May 17, 2026
d0abfeb
The Messenger: Fix portal shuffle logic not using unlocked portals (#…
Jouramie May 18, 2026
545171f
Core: reset log level when using pyximport (#6212)
qwint May 20, 2026
787ddc4
Core: Bump version from 0.6.7 to 0.6.8 (#6114)
Berserker66 May 27, 2026
b24b3d1
Factorio: Locale changes (#5908)
Enderdraak May 30, 2026
d241829
Bump mistune from 3.2.0 to 3.2.1 in /WebHostLib (#6185)
dependabot[bot] May 31, 2026
65277a5
Muse Dash: Update to Touhou Mugakudan -V- (#6234)
DeamonHunter Jun 1, 2026
ad0af18
Rule Builder: Resolve empty and() to True_() instead of False_()(#6239)
Ishigh1 Jun 2, 2026
2a8ebe2
Core+RB: Make has_from_list, has_from_list_unique, has_group and has_…
Ishigh1 Jun 3, 2026
09aba95
Factorio: Fixed a needlessly small bug that does not affect gameplay …
Enderdraak Jun 3, 2026
5ccef98
WebHost: fix Authors on supported game page (#6218)
EmilyV99 Jun 7, 2026
e6e0bc3
WebHost: Config option for custom port ranges v2 (#6009)
ImUrX Jun 20, 2026
81db4e4
Staging to Main 0.6.0
Taoshix Jul 10, 2026
9265a69
Merge branch 'ArchipelagoMW:main' into main
Taoshix Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ dist
/prof/
README.html
.vs/
EnemizerCLI/
/Players/
/SNI/
/sni-*/
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV # tag x.y.z will become "Archipelago x.y.z"
- name: Create Release
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
draft: true # don't publish right away, especially since windows build is added by hand
prerelease: false
Expand Down Expand Up @@ -97,13 +97,15 @@ jobs:
build/exe.*/ArchipelagoServer.exe
setups/*
- name: Add to Release
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
draft: true # see above
prerelease: false
name: Archipelago ${{ env.RELEASE_VERSION }}
files: |
setups/*
fail_on_unmatched_files: true
overwrite_files: false # Windows release is usually built by hand
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand Down Expand Up @@ -165,12 +167,14 @@ jobs:
build/exe.*/ArchipelagoServer
dist/*
- name: Add to Release
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
draft: true # see above
prerelease: false
name: Archipelago ${{ env.RELEASE_VERSION }}
files: |
dist/*
fail_on_unmatched_files: true
overwrite_files: false # should never happen; avoids accidentally changing a release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
8 changes: 8 additions & 0 deletions BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,8 @@ def count(self, item: str, player: int) -> int:

def has_from_list(self, items: Iterable[str], player: int, count: int) -> bool:
"""Returns True if the state contains at least `count` items matching any of the item names from a list."""
if count <= 0:
return True
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in items:
Expand All @@ -1049,6 +1051,8 @@ def has_from_list(self, items: Iterable[str], player: int, count: int) -> bool:
def has_from_list_unique(self, items: Iterable[str], player: int, count: int) -> bool:
"""Returns True if the state contains at least `count` items matching any of the item names from a list.
Ignores duplicates of the same item."""
if count <= 0:
return True
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in items:
Expand Down Expand Up @@ -1077,6 +1081,8 @@ def count_from_list_unique(self, items: Iterable[str], player: int) -> int:
# item name group related
def has_group(self, item_name_group: str, player: int, count: int = 1) -> bool:
"""Returns True if the state contains at least `count` items present in a specified item group."""
if count <= 0:
return True
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
Expand All @@ -1089,6 +1095,8 @@ def has_group_unique(self, item_name_group: str, player: int, count: int = 1) ->
"""Returns True if the state contains at least `count` items present in a specified item group.
Ignores duplicates of the same item.
"""
if count <= 0:
return True
found: int = 0
player_prog_items = self.prog_items[player]
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]:
Expand Down
6 changes: 5 additions & 1 deletion CommonClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -1069,14 +1069,18 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
if "players" in args:
ctx.consume_players_package(args["players"])
if "hint_points" in args:
ctx.hint_points = args['hint_points']
ctx.hint_points = args["hint_points"]
if "checked_locations" in args:
checked = set(args["checked_locations"])
ctx.checked_locations |= checked
ctx.missing_locations -= checked
if "permissions" in args:
ctx.update_permissions(args["permissions"])

# Update hint info for local display
if "hint_cost" in args:
ctx.hint_cost = int(args["hint_cost"])

elif cmd == 'Print':
ctx.on_print(args)

Expand Down
27 changes: 0 additions & 27 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
# hadolint global ignore=SC1090,SC1091

# Source
FROM scratch AS release
WORKDIR /release
ADD https://github.com/Ijwu/Enemizer/releases/latest/download/ubuntu.16.04-x64.zip Enemizer.zip

# Enemizer
FROM alpine:3.21 AS enemizer
ARG TARGETARCH
WORKDIR /release
COPY --from=release /release/Enemizer.zip .

# No release for arm architecture. Skip.
RUN if [ "$TARGETARCH" = "amd64" ]; then \
apk add unzip=6.0-r15 --no-cache && \
unzip -u Enemizer.zip -d EnemizerCLI && \
chmod -R 777 EnemizerCLI; \
else touch EnemizerCLI; fi

# Cython builder stage
FROM python:3.12 AS cython-builder

Expand Down Expand Up @@ -81,15 +63,6 @@ RUN apt-get purge -y \
g++ && \
apt-get autoremove -y

# Copy necessary components
COPY --from=enemizer /release/EnemizerCLI /tmp/EnemizerCLI

# No release for arm architecture. Skip.
RUN if [ "$TARGETARCH" = "amd64" ]; then \
cp -r /tmp/EnemizerCLI EnemizerCLI; \
fi; \
rm -rf /tmp/EnemizerCLI

# Define health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:${PORT:-80} || exit 1
Expand Down
15 changes: 13 additions & 2 deletions Generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def mystery_argparse(argv: list[str] | None = None) -> argparse.Namespace:
parser.add_argument('--spoiler', type=int, default=defaults.spoiler)
parser.add_argument('--outputpath', default=settings.general_options.output_path,
help="Path to output folder. Absolute or relative to cwd.") # absolute or relative to cwd
parser.add_argument('--allow_quantity', action="store_true", default=defaults.allow_quantity,
help='Allows the use of the quantity option in yamls. Default is the set value in the host.yaml.')
parser.add_argument('--race', action='store_true', default=defaults.race)
parser.add_argument('--meta_file_path', default=defaults.meta_file_path)
parser.add_argument('--log_level', default=defaults.loglevel, help='Sets log level')
Expand Down Expand Up @@ -123,6 +125,7 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
player_id: int = 1
player_files: dict[int, str] = {}
player_errors: list[str] = []
allow_quantity = args.allow_quantity
for file in os.scandir(args.player_files_path):
fname = file.name
if file.is_file() and not fname.startswith(".") and not fname.lower().endswith(".ini") and \
Expand All @@ -134,7 +137,14 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
if yaml is None:
logging.warning(f"Ignoring empty yaml document #{doc_idx + 1} in {fname}")
else:
weights_for_file.append(yaml)
quantity = yaml.get("quantity", 1)
if quantity <= 0:
raise ValueError("A quantity of 0 or less is invalid. Please change it to at least 1.")
if not allow_quantity and quantity > 1:
raise ValueError("Quantity greater than 1 is deactivated by host settings.")

for _ in range(quantity):
weights_for_file.append(yaml)
weights_cache[fname] = tuple(weights_for_file)

except Exception as e:
Expand Down Expand Up @@ -575,7 +585,8 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
raise Exception(f"Invalid game: {ret.game}")
if ret.game not in AutoWorldRegister.world_types:
from worlds import failed_world_loads
picks = Utils.get_fuzzy_results(ret.game, list(AutoWorldRegister.world_types) + failed_world_loads, limit=1)[0]
picks = Utils.get_fuzzy_results(ret.game, list(AutoWorldRegister.world_types) + list(failed_world_loads.keys()),
limit=1)[0]
if picks[0] in failed_world_loads:
raise Exception(f"No functional world found to handle game {ret.game}. "
f"Did you mean '{picks[0]}' ({picks[1]}% sure)? "
Expand Down
35 changes: 35 additions & 0 deletions Launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
init_logging('Launcher')

from worlds.LauncherComponents import Component, components, icon_paths, SuffixIdentifier, Type
from worlds import failed_world_loads


def open_host_yaml():
Expand Down Expand Up @@ -275,6 +276,7 @@ class Launcher(ThemedApp):
search_box: MDTextField = ObjectProperty(None)
cards: list[LauncherCard]
current_filter: Sequence[str | Type] | None
failed_worlds: bool = bool(failed_world_loads)

def __init__(self, ctx=None, components=None, args=None):
self.title = self.base_title + " " + Utils.__version__
Expand Down Expand Up @@ -422,6 +424,39 @@ def component_action(button):
MDSnackbar(MDSnackbarText(text=open_text), y=dp(24), pos_hint={"center_x": 0.5},
size_hint_x=0.5).open()

@staticmethod
def copy_to_clipboard(text):
from kivy.core.clipboard import Clipboard
Clipboard.copy(text)
MDSnackbar(MDSnackbarText(text="Copied to clipboard."), y=dp(24), pos_hint={"center_x": 0.5},
size_hint_x=0.5).open()

def display_failed(self):
"""Display a dialog showing the exceptions produced by any world that failed to load during
initialization."""
if not self.failed_worlds:
return
from kivymd.uix.dialog import MDDialog, MDDialogIcon, MDDialogHeadlineText, MDDialogContentContainer
from kivymd.uix.divider import MDDivider
from kivymd.uix.list import MDListItem, MDListItemHeadlineText, MDListItemSupportingText
entries = []
for world, reason in failed_world_loads.items():
entries.append(MDListItem(
MDListItemHeadlineText(text=world),
MDListItemSupportingText(text=reason),
on_release=lambda x, r=reason: self.copy_to_clipboard(r)
))
dialog = MDDialog(
MDDialogIcon(icon="alert"),
MDDialogHeadlineText(text="Failed World Loads"),
MDDialogContentContainer(
MDDivider(),
*entries,
orientation="vertical",
)
)
dialog.open()

def _on_drop_file(self, window: Window, filename: bytes, x: int, y: int) -> None:
""" When a patch file is dropped into the window, run the associated component. """
file, component = identify(filename.decode())
Expand Down
4 changes: 2 additions & 2 deletions MMBN3Client.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ async def gba_sync_task(ctx: MMBN3Context):
await ctx.server_auth(False)
else:
if not ctx.version_warning:
logger.warning(f"Your Lua script is version {reported_version}, expected {script_version}."
"Please update to the latest version."
logger.warning(f"Your Lua script is version {reported_version}, expected {script_version}. "
"Please update to the latest version. "
"Your connection to the Archipelago server will not be accepted.")
ctx.version_warning = True
except asyncio.TimeoutError:
Expand Down
4 changes: 2 additions & 2 deletions MultiServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2633,8 +2633,8 @@ def parse_args() -> argparse.Namespace:
goal: !remaining can be used after goal completion
''')
parser.add_argument('--auto_shutdown', default=defaults["auto_shutdown"], type=int,
help="automatically shut down the server after this many minutes without new location checks. "
"0 to keep running. Not yet implemented.")
help="automatically shut down the server after this many seconds without new location checks. "
"0 to keep running.")
parser.add_argument('--use_embedded_options', action="store_true",
help='retrieve release, remaining and hint options from the multidata file,'
' instead of host.yaml')
Expand Down
4 changes: 4 additions & 0 deletions NetUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,11 @@ class MultiData(typing.TypedDict):
except ImportError:
pyximport = None
try:
import logging
logger = logging.getLogger()
old_level = logger.level
from _speedups import LocationStore
logger.setLevel(old_level)
except ImportError:
warnings.warn("_speedups not available. Falling back to pure python LocationStore. "
"Install a matching C++ compiler for your platform to compile _speedups.")
Expand Down
Loading
Loading