Skip to content

Commit

Permalink
Merge branch 'ArchipelagoMW:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
gaithern authored Jul 27, 2024
2 parents 813c7c6 + 6994f86 commit 123fcb5
Show file tree
Hide file tree
Showing 102 changed files with 2,738 additions and 7,375 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ venv/
ENV/
env.bak/
venv.bak/
.code-workspace
*.code-workspace
shell.nix

# Spyder project settings
Expand Down
2 changes: 2 additions & 0 deletions CommonClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def _cmd_connect(self, address: str = "") -> bool:
if address:
self.ctx.server_address = None
self.ctx.username = None
self.ctx.password = None
elif not self.ctx.server_address:
self.output("Please specify an address.")
return False
Expand Down Expand Up @@ -514,6 +515,7 @@ def update_permissions(self, permissions: typing.Dict[str, int]):
async def shutdown(self):
self.server_address = ""
self.username = None
self.password = None
self.cancel_autoreconnect()
if self.server and not self.server.socket.closed:
await self.server.socket.close()
Expand Down
13 changes: 9 additions & 4 deletions Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,19 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
for player in multiworld.player_ids:
exclusion_rules(multiworld, player, multiworld.worlds[player].options.exclude_locations.value)
multiworld.worlds[player].options.priority_locations.value -= multiworld.worlds[player].options.exclude_locations.value
world_excluded_locations = set()
for location_name in multiworld.worlds[player].options.priority_locations.value:
try:
location = multiworld.get_location(location_name, player)
except KeyError as e: # failed to find the given location. Check if it's a legitimate location
if location_name not in multiworld.worlds[player].location_name_to_id:
raise Exception(f"Unable to prioritize location {location_name} in player {player}'s world.") from e
else:
except KeyError:
continue

if location.progress_type != LocationProgressType.EXCLUDED:
location.progress_type = LocationProgressType.PRIORITY
else:
logger.warning(f"Unable to prioritize location \"{location_name}\" in player {player}'s world because the world excluded it.")
world_excluded_locations.add(location_name)
multiworld.worlds[player].options.priority_locations.value -= world_excluded_locations

# Set local and non-local item rules.
if multiworld.players > 1:
Expand Down
14 changes: 11 additions & 3 deletions docs/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Archipelago World Code Owners / Maintainers Document
#
# This file is used to notate the current "owners" or "maintainers" of any currently merged world folder. For any pull
# requests that modify these worlds, a code owner must approve the PR in addition to a core maintainer. This is not to
# be used for files/folders outside the /worlds folder, those will always need sign off from a core maintainer.
# This file is used to notate the current "owners" or "maintainers" of any currently merged world folder as well as
# certain documentation. For any pull requests that modify these worlds/docs, a code owner must approve the PR in
# addition to a core maintainer. All other files and folders are owned and maintained by core maintainers directly.
#
# All usernames must be GitHub usernames (and are case sensitive).

Expand Down Expand Up @@ -229,3 +229,11 @@

# Ori and the Blind Forest
# /worlds_disabled/oribf/

###################
## Documentation ##
###################

# Apworld Dev Faq
/docs/apworld_dev_faq.md @qwint @ScipioWright

45 changes: 45 additions & 0 deletions docs/apworld_dev_faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# APWorld Dev FAQ

This document is meant as a reference tool to show solutions to common problems when developing an apworld.
It is not intended to answer every question about Archipelago and it assumes you have read the other docs,
including [Contributing](contributing.md), [Adding Games](<adding games.md>), and [World API](<world api.md>).

---

### My game has a restrictive start that leads to fill errors

Hint to the Generator that an item needs to be in sphere one with local_early_items. Here, `1` represents the number of "Sword" items to attempt to place in sphere one.
```py
early_item_name = "Sword"
self.multiworld.local_early_items[self.player][early_item_name] = 1
```

Some alternative ways to try to fix this problem are:
* Add more locations to sphere one of your world, potentially only when there would be a restrictive start
* Pre-place items yourself, such as during `create_items`
* Put items into the player's starting inventory using `push_precollected`
* Raise an exception, such as an `OptionError` during `generate_early`, to disallow options that would lead to a restrictive start

---

### I have multiple settings that change the item/location pool counts and need to balance them out

In an ideal situation your system for producing locations and items wouldn't leave any opportunity for them to be unbalanced. But in real, complex situations, that might be unfeasible.

If that's the case, you can create extra filler based on the difference between your unfilled locations and your itempool by comparing [get_unfilled_locations](https://github.com/ArchipelagoMW/Archipelago/blob/main/BaseClasses.py#:~:text=get_unfilled_locations) to your list of items to submit

Note: to use self.create_filler(), self.get_filler_item_name() should be defined to only return valid filler item names
```py
total_locations = len(self.multiworld.get_unfilled_locations(self.player))
item_pool = self.create_non_filler_items()

for _ in range(total_locations - len(item_pool)):
item_pool.append(self.create_filler())

self.multiworld.itempool += item_pool
```

A faster alternative to the `for` loop would be to use a [list comprehension](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions):
```py
item_pool += [self.create_filler() for _ in range(total_locations - len(item_pool))]
```
7 changes: 6 additions & 1 deletion kvui.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,8 +595,9 @@ def command_button_action(self, button):
"!help for server commands.")

def connect_button_action(self, button):
self.ctx.username = None
self.ctx.password = None
if self.ctx.server:
self.ctx.username = None
async_start(self.ctx.disconnect())
else:
async_start(self.ctx.connect(self.server_connect_bar.text.replace("/connect ", "")))
Expand Down Expand Up @@ -836,6 +837,10 @@ def _handle_color(self, node: JSONMessagePart):
return self._handle_text(node)

def _handle_text(self, node: JSONMessagePart):
# All other text goes through _handle_color, and we don't want to escape markup twice,
# or mess up text that already has intentional markup applied to it
if node.get("type", "text") == "text":
node["text"] = escape_markup(node["text"])
for ref in node.get("refs", []):
node["text"] = f"[ref={self.ref_count}|{ref}]{node['text']}[/ref]"
self.ref_count += 1
Expand Down
18 changes: 13 additions & 5 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
This is different from player options.
"""

import os
import os.path
import shutil
import sys
Expand All @@ -11,7 +12,6 @@
from enum import IntEnum
from threading import Lock
from typing import cast, Any, BinaryIO, ClassVar, Dict, Iterator, List, Optional, TextIO, Tuple, Union, TypeVar
import os

__all__ = [
"get_settings", "fmt_doc", "no_gui",
Expand Down Expand Up @@ -798,6 +798,7 @@ def autosave() -> None:
atexit.register(autosave)

def save(self, location: Optional[str] = None) -> None: # as above
from Utils import parse_yaml
location = location or self._filename
assert location, "No file specified"
temp_location = location + ".tmp" # not using tempfile to test expected file access
Expand All @@ -807,10 +808,18 @@ def save(self, location: Optional[str] = None) -> None: # as above
# can't use utf-8-sig because it breaks backward compat: pyyaml on Windows with bytes does not strip the BOM
with open(temp_location, "w", encoding="utf-8") as f:
self.dump(f)
# replace old with new
if os.path.exists(location):
f.flush()
if hasattr(os, "fsync"):
os.fsync(f.fileno())
# validate new file is valid yaml
with open(temp_location, encoding="utf-8") as f:
parse_yaml(f.read())
# replace old with new, try atomic operation first
try:
os.rename(temp_location, location)
except (OSError, FileExistsError):
os.unlink(location)
os.rename(temp_location, location)
os.rename(temp_location, location)
self._filename = location

def dump(self, f: TextIO, level: int = 0) -> None:
Expand All @@ -832,7 +841,6 @@ def get_settings() -> Settings:
with _lock: # make sure we only have one instance
res = getattr(get_settings, "_cache", None)
if not res:
import os
from Utils import user_path, local_path
filenames = ("options.yaml", "host.yaml")
locations: List[str] = []
Expand Down
29 changes: 27 additions & 2 deletions test/general/test_host_yaml.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import os
import os.path
import unittest
from io import StringIO
from tempfile import TemporaryFile
from tempfile import TemporaryDirectory, TemporaryFile
from typing import Any, Dict, List, cast

import Utils
from settings import Settings, Group
from settings import Group, Settings, ServerOptions


class TestIDs(unittest.TestCase):
Expand Down Expand Up @@ -80,3 +81,27 @@ class AGroup(Group):
self.assertEqual(value_spaces[2], value_spaces[0]) # start of sub-list
self.assertGreater(value_spaces[3], value_spaces[0],
f"{value_lines[3]} should have more indentation than {value_lines[0]} in {lines}")


class TestSettingsSave(unittest.TestCase):
def test_save(self) -> None:
"""Test that saving and updating works"""
with TemporaryDirectory() as d:
filename = os.path.join(d, "host.yaml")
new_release_mode = ServerOptions.ReleaseMode("enabled")
# create default host.yaml
settings = Settings(None)
settings.save(filename)
self.assertTrue(os.path.exists(filename),
"Default settings could not be saved")
self.assertNotEqual(settings.server_options.release_mode, new_release_mode,
"Unexpected default release mode")
# update host.yaml
settings.server_options.release_mode = new_release_mode
settings.save(filename)
self.assertFalse(os.path.exists(filename + ".tmp"),
"Temp file was not removed during save")
# read back host.yaml
settings = Settings(filename)
self.assertEqual(settings.server_options.release_mode, new_release_mode,
"Settings were not overwritten")
63 changes: 13 additions & 50 deletions worlds/ahit/docs/setup_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,29 @@

## Instructions

1. Have Steam running. Open the Steam console with this link: [steam://open/console](steam://open/console)
This may not work for some browsers. If that's the case, and you're on Windows, open the Run dialog using Win+R,
paste the link into the box, and hit Enter.
1. **BACK UP YOUR SAVE FILES IN YOUR MAIN INSTALL IF YOU CARE ABOUT THEM!!!**
Go to `steamapps/common/HatinTime/HatinTimeGame/SaveData/` and copy everything inside that folder over to a safe place.
**This is important! Changing the game version CAN and WILL break your existing save files!!!**


2. In the Steam console, enter the following command:
`download_depot 253230 253232 7770543545116491859`. ***Wait for the console to say the download is finished!***
This can take a while to finish (30+ minutes) depending on your connection speed, so please be patient. Additionally,
**try to prevent your connection from being interrupted or slowed while Steam is downloading the depot,**
or else the download may potentially become corrupted (see first FAQ issue below).
2. In your Steam library, right-click on **A Hat in Time** in the list of games and click on **Properties**.


3. Once the download finishes, go to `steamapps/content/app_253230` in Steam's program folder.
3. Click the **Betas** tab. In the **Beta Participation** dropdown, select `tcplink`.
While it downloads, you can subscribe to the [Archipelago workshop mod.]((https://steamcommunity.com/sharedfiles/filedetails/?id=3026842601))


4. There should be a folder named `depot_253232`. Rename it to HatinTime_AP and move it to your `steamapps/common` folder.
4. Once the game finishes downloading, start it up.
In Game Settings, make sure **Enable Developer Console** is checked.


5. In the HatinTime_AP folder, navigate to `Binaries/Win64` and create a new file: `steam_appid.txt`.
In this new text file, input the number **253230** on the first line.


6. Create a shortcut of `HatinTimeGame.exe` from that folder and move it to wherever you'd like.
You will use this shortcut to open the Archipelago-compatible version of A Hat in Time.


7. Start up the game using your new shortcut. To confirm if you are on the correct version,
go to Settings -> Game Settings. If you don't see an option labelled ***Live Game Events*** you should be running
the correct version of the game. In Game Settings, make sure ***Enable Developer Console*** is checked.
5. You should now be good to go. See below for more details on how to use the mod and connect to an Archipelago game.


## Connecting to the Archipelago server

To connect to the multiworld server, simply run the **ArchipelagoAHITClient**
(or run it from the Launcher if you have the apworld installed) and connect it to the Archipelago server.
To connect to the multiworld server, simply run the **Archipelago AHIT Client** from the Launcher
and connect it to the Archipelago server.
The game will connect to the client automatically when you create a new save file.


Expand All @@ -61,33 +49,8 @@ make sure ***Enable Developer Console*** is checked in Game Settings and press t


## FAQ/Common Issues
### I followed the setup, but I receive an odd error message upon starting the game or creating a save file!
If you receive an error message such as
**"Failed to find default engine .ini to retrieve My Documents subdirectory to use. Force quitting."** or
**"Failed to load map "hub_spaceship"** after booting up the game or creating a save file respectively, then the depot
download was likely corrupted. The only way to fix this is to start the entire download all over again.
Unfortunately, this appears to be an underlying issue with Steam's depot downloader. The only way to really prevent this
from happening is to ensure that your connection is not interrupted or slowed while downloading.

### The game keeps crashing on startup after the splash screen!
This issue is unfortunately very hard to fix, and the underlying cause is not known. If it does happen however,
try the following:

- Close Steam **entirely**.
- Open the downpatched version of the game (with Steam closed) and allow it to load to the titlescreen.
- Close the game, and then open Steam again.
- After launching the game, the issue should hopefully disappear. If not, repeat the above steps until it does.

### I followed the setup, but "Live Game Events" still shows up in the options menu!
The most common cause of this is the `steam_appid.txt` file. If you're on Windows 10, file extensions are hidden by
default (thanks Microsoft). You likely made the mistake of still naming the file `steam_appid.txt`, which, since file
extensions are hidden, would result in the file being named `steam_appid.txt.txt`, which is incorrect.
To show file extensions in Windows 10, open any folder, click the View tab at the top, and check
"File name extensions". Then you can correct the name of the file. If the name of the file is correct,
and you're still running into the issue, re-read the setup guide again in case you missed a step.
If you still can't get it to work, ask for help in the Discord thread.

### The game is running on the older version, but it's not connecting when starting a new save!

### The game is not connecting when starting a new save!
For unknown reasons, the mod will randomly disable itself in the mod menu. To fix this, go to the Mods menu
(rocket icon) in-game, and re-enable the mod.

Expand Down
2 changes: 1 addition & 1 deletion worlds/bomb_rush_cyberfunk/Locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ class EventDict(TypedDict):
'game_id': "graf385"},
{'name': "Tagged 389 Graffiti Spots",
'stage': Stages.Misc,
'game_id': "graf379"},
'game_id': "graf389"},
]


Expand Down
4 changes: 4 additions & 0 deletions worlds/dlcquest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
from .Options import DLCQuestOptions
from .Regions import create_regions
from .Rules import set_rules
from .presets import dlcq_options_presets
from .option_groups import dlcq_option_groups

client_version = 0


class DLCqwebworld(WebWorld):
options_presets = dlcq_options_presets
option_groups = dlcq_option_groups
setup_en = Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Archipelago DLCQuest game on your computer.",
Expand Down
27 changes: 27 additions & 0 deletions worlds/dlcquest/option_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import List

from Options import ProgressionBalancing, Accessibility, OptionGroup
from .Options import (Campaign, ItemShuffle, TimeIsMoney, EndingChoice, PermanentCoins, DoubleJumpGlitch, CoinSanity,
CoinSanityRange, DeathLink)

dlcq_option_groups: List[OptionGroup] = [
OptionGroup("General", [
Campaign,
ItemShuffle,
CoinSanity,
]),
OptionGroup("Customization", [
EndingChoice,
PermanentCoins,
CoinSanityRange,
]),
OptionGroup("Tedious and Grind", [
TimeIsMoney,
DoubleJumpGlitch,
]),
OptionGroup("Advanced Options", [
DeathLink,
ProgressionBalancing,
Accessibility,
]),
]
Loading

0 comments on commit 123fcb5

Please sign in to comment.