Add sphinx project
336
docs/sphinx/AddingGames.md
Normal file
@@ -0,0 +1,336 @@
|
||||
# Adding Games to Archipelago
|
||||
This guide is going to try and be a broad summary of how you can do just that.
|
||||
There are two key steps to incorporating a game into Archipelago:
|
||||
- Game Modification
|
||||
- Archipelago Server Integration
|
||||
|
||||
## Game Modification
|
||||
One half of the work required to integrate a game into Archipelago is the development of the game client. This is
|
||||
typically done through a modding API or other modification process, described further down.
|
||||
|
||||
As an example, modifications to a game typically include (more on this later):
|
||||
- Hooking into when a 'location check' is completed.
|
||||
- Networking with the Archipelago server.
|
||||
- Optionally, UI or HUD updates to show status of the multiworld session or Archipelago server connection.
|
||||
|
||||
In order to determine how to modify a game, refer to the following sections.
|
||||
|
||||
## Engine Identification
|
||||
This is a good way to make the modding process much easier. Being able to identify what engine a game was made in is critical. The first step is to look at a game's files. Let's go over what some game files might look like. It’s important that you be able to see file extensions, so be sure to enable that feature in your file viewer of choice.
|
||||
Examples are provided below.
|
||||
|
||||
### Creepy Castle
|
||||

|
||||
|
||||
This is the delightful title Creepy Castle, which is a fantastic game that I highly recommend. It’s also your worst-case
|
||||
scenario as a modder. All that’s present here is an executable file and some meta-information that Steam uses. You have
|
||||
basically nothing here to work with. If you want to change this game, the only option you have is to do some pretty nasty
|
||||
disassembly and reverse engineering work, which is outside the scope of this tutorial. Let’s look at some other examples
|
||||
of game releases.
|
||||
|
||||
### Heavy Bullets
|
||||

|
||||
|
||||
Here’s the release files for another game, Heavy Bullets. We see a .exe file, like expected, and a few more files.
|
||||
“hello.txt” is a text file, which we can quickly skim in any text editor. Many games have them in some form, usually
|
||||
with a name like README.txt, and they may contain information about a game, such as a EULA, terms of service, licensing
|
||||
information, credits, and general info about the game. You usually won’t find anything too helpful here, but it never
|
||||
hurts to check. In this case, it contains some credits and a changelog for the game, so nothing too important.
|
||||
“steam_api.dll” is a file you can safely ignore, it’s just some code used to interface with Steam.
|
||||
The directory “HEAVY_BULLETS_Data”, however, has some good news.
|
||||
|
||||

|
||||
|
||||
Jackpot! It might not be obvious what you’re looking at here, but I can instantly tell from this folder’s contents that
|
||||
what we have is a game made in the Unity Engine. If you look in the sub-folders, you’ll seem some .dll files which affirm
|
||||
our suspicions. Telltale signs for this are directories titled “Managed” and “Mono”, as well as the numbered, extension-less
|
||||
level files and the sharedassets files. We’ll tell you a bit about why seeing a Unity game is such good news later,
|
||||
but for now, this is what one looks like. Also keep your eyes out for an executable with a name like UnityCrashHandler,
|
||||
that’s another dead giveaway.
|
||||
|
||||
### Stardew Valley
|
||||

|
||||
|
||||
This is the game contents of Stardew Valley. A lot more to look at here, but some key takeaways.
|
||||
Notice the .dll files which include “CSharp” in their name. This tells us that the game was made in C#, which is good news.
|
||||
More on that later.
|
||||
|
||||
### Gato Roboto
|
||||

|
||||
|
||||
Our last example is the game Gato Roboto. This game is made in GameMaker, which is another green flag to look out for.
|
||||
The giveaway is the file titled "data.win". This immediately tips us off that this game was made in GameMaker.
|
||||
|
||||
This isn't all you'll ever see looking at game files, but it's a good place to start.
|
||||
As a general rule, the more files a game has out in plain sight, the more you'll be able to change.
|
||||
This especially applies in the case of code or script files - always keep a lookout for anything you can use to your
|
||||
advantage!
|
||||
|
||||
## Open or Leaked Source Games
|
||||
As a side note, many games have either been made open source, or have had source files leaked at some point.
|
||||
This can be a boon to any would-be modder, for obvious reasons.
|
||||
Always be sure to check - a quick internet search for "(Game) Source Code" might not give results often, but when it
|
||||
does you're going to have a much better time.
|
||||
|
||||
Be sure never to distribute source code for games that you decompile or find if you do not have express permission to do
|
||||
so, or to redistribute any materials obtained through similar methods, as this is illegal and unethical.
|
||||
|
||||
## Modifying Release Versions of Games
|
||||
However, for now we'll assume you haven't been so lucky, and have to work with only what’s sitting in your install directory.
|
||||
Some developers are kind enough to deliberately leave you ways to alter their games, like modding tools,
|
||||
but these are often not geared to the kind of work you'll be doing and may not help much.
|
||||
|
||||
As a general rule, any modding tool that lets you write actual code is something worth using.
|
||||
|
||||
### Research
|
||||
The first step is to research your game. Even if you've been dealt the worst hand in terms of engine modification,
|
||||
it's possible other motivated parties have concocted useful tools for your game already.
|
||||
Always be sure to search the Internet for the efforts of other modders.
|
||||
|
||||
### Analysis Tools
|
||||
Depending on the game’s underlying engine, there may be some tools you can use either in lieu of or in addition to existing game tools.
|
||||
|
||||
#### [dnSpy](https://github.com/dnSpy/dnSpy/releases)
|
||||
The first tool in your toolbox is dnSpy.
|
||||
dnSpy is useful for opening and modifying code files, like .exe and .dll files, that were made in C#.
|
||||
This won't work for executable files made by other means, and obfuscated code (code which was deliberately made
|
||||
difficult to reverse engineer) will thwart it, but 9 times out of 10 this is exactly what you need.
|
||||
You'll want to avoid opening common library files in dnSpy, as these are unlikely to contain the data you're looking to
|
||||
modify.
|
||||
|
||||
For Unity games, the file you’ll want to open will be the file (Data Folder)/Managed/Assembly-CSharp.dll, as pictured below:
|
||||
|
||||

|
||||
|
||||
This file will contain the data of the actual game.
|
||||
For other C# games, the file you want is usually just the executable itself.
|
||||
|
||||
With dnSpy, you can view the game’s C# code, but the tool isn’t perfect.
|
||||
Although the names of classes, methods, variables, and more will be preserved, code structures may not remain entirely intact. This is because compilers will often subtly rewrite code to be more optimal, so that it works the same as the original code but uses fewer resources. Compiled C# files also lose comments and other documentation.
|
||||
|
||||
#### [UndertaleModTool](https://github.com/krzys-h/UndertaleModTool/releases)
|
||||
This is currently the best tool for modifying games made in GameMaker, and supports games made in both GMS 1 and 2.
|
||||
It allows you to modify code in GML, if the game wasn't made with the wrong compiler (usually something you don't have
|
||||
to worry about).
|
||||
|
||||
You'll want to open the data.win file, as this is where all the goods are kept.
|
||||
Like dnSpy, you won’t be able to see comments.
|
||||
In addition, you will be able to see and modify many hidden fields on items that GameMaker itself will often hide from
|
||||
creators.
|
||||
|
||||
Fonts in particular are notoriously complex, and to add new sprites you may need to modify existing sprite sheets.
|
||||
|
||||
#### [CheatEngine](https://cheatengine.org/)
|
||||
CheatEngine is a tool with a very long and storied history.
|
||||
Be warned that because it performs live modifications to the memory of other processes, it will likely be flagged as
|
||||
malware (because this behavior is most commonly found in malware and rarely used by other programs).
|
||||
If you use CheatEngine, you need to have a deep understanding of how computers work at the nuts and bolts level,
|
||||
including binary data formats, addressing, and assembly language programming.
|
||||
|
||||
The tool itself is highly complex and even I have not yet charted its expanses.
|
||||
However, it can also be a very powerful tool in the right hands, allowing you to query and modify gamestate without ever
|
||||
modifying the actual game itself.
|
||||
In theory it is compatible with any piece of software you can run on your computer, but there is no "easy way" to do
|
||||
anything with it.
|
||||
|
||||
### What Modifications You Should Make to the Game
|
||||
We talked about this briefly in [Game Modification](#game-modification) section.
|
||||
The next step is to know what you need to make the game do now that you can modify it. Here are your key goals:
|
||||
- Modify the game so that checks are shuffled
|
||||
- Know when the player has completed a check, and react accordingly
|
||||
- Listen for messages from the Archipelago server
|
||||
- Modify the game to display messages from the Archipelago server
|
||||
- Add interface for connecting to the Archipelago server with passwords and sessions
|
||||
- Add commands for manually rewarding, re-syncing, forfeiting, and other actions
|
||||
|
||||
To elaborate, you need to be able to inform the server whenever you check locations, print out messages that you receive
|
||||
from the server in-game so players can read them, award items when the server tells you to, sync and re-sync when necessary,
|
||||
avoid double-awarding items while still maintaining game file integrity, and allow players to manually enter commands in
|
||||
case the client or server make mistakes.
|
||||
|
||||
Refer to the [Network Protocol documentation](../NetworkProtocol.md) for how to communicate with Archipelago's servers.
|
||||
|
||||
## But my Game is a console game. Can I still add it?
|
||||
That depends – what console?
|
||||
|
||||
### My Game is a recent game for the PS4/Xbox-One/Nintendo Switch/etc
|
||||
Most games for recent generations of console platforms are inaccessible to the typical modder. It is generally advised
|
||||
that you do not attempt to work with these games as they are difficult to modify and are protected by their copyright
|
||||
holders. Most modern AAA game studios will provide a modding interface or otherwise deny modifications for their console games.
|
||||
|
||||
### My Game isn’t that old, it’s for the Wii/PS2/360/etc
|
||||
This is very complex, but doable.
|
||||
If you don't have good knowledge of stuff like Assembly programming, this is not where you want to learn it.
|
||||
There exist many disassembly and debugging tools, but more recent content may have lackluster support.
|
||||
|
||||
### My Game is a classic for the SNES/Sega Genesis/etc
|
||||
That’s a lot more feasible.
|
||||
There are many good tools available for understanding and modifying games on these older consoles, and the emulation
|
||||
community will have figured out the bulk of the console’s secrets.
|
||||
Look for debugging tools, but be ready to learn assembly.
|
||||
Old consoles usually have their own unique dialects of ASM you’ll need to get used to.
|
||||
|
||||
Also make sure there’s a good way to interface with a running emulator, since that’s the only way you can connect these
|
||||
older consoles to the Internet.
|
||||
There are also hardware mods and flash carts, which can do the same things an emulator would when connected to a computer,
|
||||
but these will require the same sort of interface software to be written in order to work properly - from your perspective
|
||||
the two won't really look any different.
|
||||
|
||||
### My Game is an exclusive for the Super Baby Magic Dream Boy. It’s this console from the Soviet Union that-
|
||||
Unless you have a circuit schematic for the Super Baby Magic Dream Boy sitting on your desk, no.
|
||||
Obscurity is your enemy – there will likely be little to no emulator or modding information, and you’d essentially be
|
||||
working from scratch.
|
||||
|
||||
## How to Distribute Game Modifications
|
||||
**NEVER EVER distribute anyone else's copyrighted work UNLESS THEY EXPLICITLY GIVE YOU PERMISSION TO DO SO!!!**
|
||||
|
||||
This is a good way to get any project you're working on sued out from under you.
|
||||
The right way to distribute modified versions of a game's binaries, assuming that the licensing terms do not allow you
|
||||
to copy them wholesale, is as patches.
|
||||
|
||||
There are many patch formats, which I'll cover in brief. The common theme is that you can’t distribute anything that
|
||||
wasn't made by you. Patches are files that describe how your modified file differs from the original one, thus avoiding
|
||||
the issue of distributing someone else’s original work.
|
||||
|
||||
Users who have a copy of the game just need to apply the patch, and those who don’t are unable to play.
|
||||
|
||||
### Patches
|
||||
|
||||
#### IPS
|
||||
IPS patches are a simple list of chunks to replace in the original to generate the output. It is not possible to encode
|
||||
moving of a chunk, so they may inadvertently contain copyrighted material and should be avoided unless you know it's
|
||||
fine.
|
||||
|
||||
#### UPS, BPS, VCDIFF (xdelta), bsdiff
|
||||
Other patch formats generate the difference between two streams (delta patches) with varying complexity. This way it is
|
||||
possible to insert bytes or move chunks without including any original data. Bsdiff is highly optimized and includes
|
||||
compression, so this format is used by APBP.
|
||||
|
||||
Only a bsdiff module is integrated into AP. If the final patch requires or is based on any other patch, convert them to
|
||||
bsdiff or APBP before adding it to the AP source code as "basepatch.bsdiff4" or "basepatch.apbp".
|
||||
|
||||
#### APBP Archipelago Binary Patch
|
||||
Starting with version 4 of the APBP format, this is a ZIP file containing metadata in `archipelago.json` and additional
|
||||
files required by the game / patching process. For ROM-based games the ZIP will include a `delta.bsdiff4` which is the
|
||||
bsdiff between the original and the randomized ROM.
|
||||
|
||||
To make using APBP easy, they can be generated by inheriting from `Patch.APDeltaPatch`.
|
||||
|
||||
### Mod files
|
||||
Games which support modding will usually just let you drag and drop the mod’s files into a folder somewhere.
|
||||
Mod files come in many forms, but the rules about not distributing other people's content remain the same.
|
||||
They can either be generic and modify the game using a seed or `slot_data` from the AP websocket, or they can be
|
||||
generated per seed.
|
||||
|
||||
If the mod is generated by AP and is installed from a ZIP file, it may be possible to include APBP metadata for easy
|
||||
integration into the Webhost by inheriting from `Patch.APContainer`.
|
||||
|
||||
|
||||
## Archipelago Integration
|
||||
Integrating a randomizer into Archipelago involves a few steps.
|
||||
There are several things that may need to be done, but the most important is to create an implementation of the
|
||||
`World` class specific to your game. This implementation should exist as a Python module within the `worlds` folder
|
||||
in the Archipelago file structure.
|
||||
|
||||
This encompasses most of the data for your game – the items available, what checks you have, the logic for reaching those
|
||||
checks, what options to offer for the player’s yaml file, and the code to initialize all this data.
|
||||
|
||||
Here’s an example of what your world module can look like:
|
||||
|
||||

|
||||
|
||||
The minimum requirements for a new archipelago world are the package itself (the world folder containing a file named `__init__.py`),
|
||||
which must define a `World` class object for the game with a game name, create an equal number of items and locations with rules,
|
||||
a win condition, and at least one `Region` object.
|
||||
|
||||
Let's give a quick breakdown of what the contents for these files look like.
|
||||
This is just one example of an Archipelago world - the way things are done below is not an immutable property of Archipelago.
|
||||
|
||||
### Items.py
|
||||
This file is used to define the items which exist in a given game.
|
||||
|
||||

|
||||
|
||||
Some important things to note here. The center of our Items.py file is the item_table, which individually lists every
|
||||
item in the game and associates them with an ItemData.
|
||||
|
||||
This file is rather skeletal - most of the actual data has been stripped out for simplicity.
|
||||
Each ItemData gives a numeric ID to associate with the item and a boolean telling us whether the item might allow the
|
||||
player to do more than they would have been able to before.
|
||||
|
||||
Next there's the item_frequencies. This simply tells Archipelago how many times each item appears in the pool.
|
||||
Items that appear exactly once need not be listed - Archipelago will interpret absence from this dictionary as meaning
|
||||
that the item appears once.
|
||||
|
||||
Lastly, note the `lookup_id_to_name` dictionary, which is typically imported and used in your Archipelago `World`
|
||||
implementation. This is how Archipelago is told about the items in your world.
|
||||
|
||||
### Locations.py
|
||||
This file lists all locations in the game.
|
||||
|
||||

|
||||
|
||||
First is the achievement_table. It lists each location, the region that it can be found in (more on regions later),
|
||||
and a numeric ID to associate with each location.
|
||||
|
||||
The exclusion table is a series of dictionaries which are used to exclude certain checks from the pool of progression
|
||||
locations based on user settings, and the events table associates certain specific checks with specific items.
|
||||
|
||||
`lookup_id_to_name` is also present for locations, though this is a separate dictionary, to be clear.
|
||||
|
||||
### Options.py
|
||||
This file details options to be searched for in a player's YAML settings file.
|
||||
|
||||

|
||||
|
||||
There are several types of option Archipelago has support for.
|
||||
In our case, we have three separate choices a player can toggle, either On or Off.
|
||||
You can also have players choose between a number of predefined values, or have them provide a numeric value within a
|
||||
specified range.
|
||||
|
||||
### Regions.py
|
||||
This file contains data which defines the world's topology.
|
||||
In other words, it details how different regions of the game connect to each other.
|
||||
|
||||

|
||||
|
||||
`terraria_regions` contains a list of tuples.
|
||||
The first element of the tuple is the name of the region, and the second is a list of connections that lead out of the region.
|
||||
|
||||
`mandatory_connections` describe where the connection leads.
|
||||
|
||||
Above this data is a function called `link_terraria_structures` which uses our defined regions and connections to create
|
||||
something more usable for Archipelago, but this has been left out for clarity.
|
||||
|
||||
### Rules.py
|
||||
This is the file that details rules for what players can and cannot logically be required to do, based on items and settings.
|
||||
|
||||

|
||||
|
||||
This is the most complicated part of the job, and is one part of Archipelago that is likely to see some changes in the future.
|
||||
The first class, called `TerrariaLogic`, is an extension of the `LogicMixin` class.
|
||||
This is where you would want to define methods for evaluating certain conditions, which would then return a boolean to
|
||||
indicate whether conditions have been met. Your rule definitions should start with some sort of identifier to delineate it
|
||||
from other games, as all rules are mixed together due to `LogicMixin`. In our case, `_terraria_rule` would be a better name.
|
||||
|
||||
The method below, `set_rules()`, is where you would assign these functions as "rules", using lambdas to associate these
|
||||
functions or combinations of them (or any other code that evaluates to a boolean, in my case just the placeholder `True`)
|
||||
to certain tasks, like checking locations or using entrances.
|
||||
|
||||
### \_\_init\_\_.py
|
||||
This is the file that actually extends the `World` class, and is where you expose functionality and data to Archipelago.
|
||||
|
||||

|
||||
|
||||
This is the most important file for the implementation, and technically the only one you need, but it's best to keep this
|
||||
file as short as possible and use other script files to do most of the heavy lifting.
|
||||
If you've done things well, this will just be where you assign everything you set up in the other files to their associated
|
||||
fields in the class being extended.
|
||||
|
||||
This is also a good place to put game-specific quirky behavior that needs to be managed, as it tends to make things a bit
|
||||
cluttered if you put these things elsewhere.
|
||||
|
||||
The various methods and attributes are documented in `/worlds/AutoWorld.py[World]` and
|
||||
[world api.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md),
|
||||
though it is also recommended to look at existing implementations to see how all this works first-hand.
|
||||
Once you get all that, all that remains to do is test the game and publish your work.
|
||||
19
docs/sphinx/AutoWorld.md
Normal file
@@ -0,0 +1,19 @@
|
||||
World Class
|
||||
===========
|
||||
|
||||
```{eval-rst}
|
||||
.. currentmodule:: worlds.AutoWorld
|
||||
.. autoclass:: World
|
||||
:members: options, game, topology_present, all_item_and_group_names,
|
||||
item_name_to_id, location_name_to_id, item_name_groups, data_version,
|
||||
required_client_version, required_server_version, hint_blacklist,
|
||||
remote_items, remote_start_inventory, forced_auto_forfeit, hidden,
|
||||
world, player, item_id_to_name, location_id_to_name, item_names,
|
||||
location_names, web, assert_generate, generate_early, create_regions,
|
||||
create_items, set_rules, generate_basic, pre_fill, fill_hook, post_fill,
|
||||
generate_output, fill_slot_data, modify_multidata, write_spoiler_header,
|
||||
write_spoiler, write_spoiler_end, create_item, get_filler_item_name,
|
||||
collect_item, get_pre_fill_items
|
||||
:undoc-members:
|
||||
:special-members: __init__
|
||||
```
|
||||
20
docs/sphinx/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
168
docs/sphinx/NetworkDiagram.md
Normal file
@@ -0,0 +1,168 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
%% Diagram arranged specifically so output generates no terrible crossing lines.
|
||||
%% AP Server
|
||||
AS{Archipelago Server}
|
||||
|
||||
%% CommonClient.py
|
||||
CC[CommonClient.py]
|
||||
AS <-- WebSockets --> CC
|
||||
|
||||
subgraph "Starcraft 2"
|
||||
SC2[Starcraft 2 Game Client]
|
||||
SC2C[Starcraft2Client.py]
|
||||
SC2AI[apsc2 Python Package]
|
||||
|
||||
SC2C <--> SC2AI <-- WebSockets --> SC2
|
||||
end
|
||||
CC <-- Integrated --> SC2C
|
||||
|
||||
%% ChecksFinder
|
||||
subgraph ChecksFinder
|
||||
CFC[ChecksFinderClient]
|
||||
CF[ChecksFinder]
|
||||
CFC <--> CF
|
||||
end
|
||||
CC <-- Integrated --> CFC
|
||||
|
||||
%% A Link to the Past
|
||||
subgraph A Link to the Past
|
||||
LTTP[SNES]
|
||||
end
|
||||
SNI <-- Various, depending on SNES device --> LTTP
|
||||
|
||||
%% Final Fantasy
|
||||
subgraph Final Fantasy 1
|
||||
FF1[FF1Client]
|
||||
FFLUA[Lua Connector]
|
||||
BZFF[BizHawk with Final Fantasy Loaded]
|
||||
FF1 <-- LuaSockets --> FFLUA
|
||||
FFLUA <--> BZFF
|
||||
end
|
||||
CC <-- Integrated --> FF1
|
||||
|
||||
%% Ocarina of Time
|
||||
subgraph Ocarina of Time
|
||||
OC[OoTClient]
|
||||
LC[Lua Connector]
|
||||
OCB[BizHawk with Ocarina of Time Loaded]
|
||||
OC <-- LuaSockets --> LC
|
||||
LC <--> OCB
|
||||
end
|
||||
CC <-- Integrated --> OC
|
||||
|
||||
%% SNI Connectors
|
||||
SC[SNIClient]
|
||||
SNI["Super Nintendo Interface (SNI)"]
|
||||
CC <-- Integrated --> SC
|
||||
SC <-- WebSockets --> SNI
|
||||
|
||||
%% Super Metroid
|
||||
subgraph Super Metroid
|
||||
SM[SNES]
|
||||
end
|
||||
SNI <-- Various, depending on SNES device --> SM
|
||||
|
||||
%% Super Metroid/A Link to the Past Combo Randomizer
|
||||
subgraph "SMZ3"
|
||||
SMZ[SNES]
|
||||
end
|
||||
SNI <-- Various, depending on SNES device --> SMZ
|
||||
|
||||
%% Native Clients or Games
|
||||
%% Games or clients which compile to native or which the client is integrated in the game.
|
||||
subgraph "Native"
|
||||
APCLIENTPP[Game using apclientpp Client Library]
|
||||
APCPP[Game using Apcpp Client Library]
|
||||
subgraph Secret of Evermore
|
||||
SOE[ap-soeclient]
|
||||
end
|
||||
SM64[Super Mario 64 Ex]
|
||||
V6[VVVVVV]
|
||||
MT[Meritous]
|
||||
TW[The Witness]
|
||||
SA2B[Sonic Adventure 2: Battle]
|
||||
|
||||
APCLIENTPP <--> SOE
|
||||
APCLIENTPP <--> MT
|
||||
APCLIENTPP <-- The Witness Randomizer --> TW
|
||||
APCPP <--> SM64
|
||||
APCPP <--> V6
|
||||
APCPP <--> SA2B
|
||||
end
|
||||
SOE <--> SNI <-- Various, depending on SNES device --> SOESNES
|
||||
AS <-- WebSockets --> APCLIENTPP
|
||||
AS <-- WebSockets --> APCPP
|
||||
|
||||
%% Java Based Games
|
||||
subgraph Java
|
||||
JM[Mod with Archipelago.MultiClient.Java]
|
||||
STS[Slay the Spire]
|
||||
JM <-- Mod the Spire --> STS
|
||||
subgraph Minecraft
|
||||
MCS[Minecraft Forge Server]
|
||||
JMC[Any Java Minecraft Clients]
|
||||
MCS <-- TCP --> JMC
|
||||
end
|
||||
JM <-- Forge Mod Loader --> MCS
|
||||
end
|
||||
AS <-- WebSockets --> JM
|
||||
|
||||
%% .NET Based Games
|
||||
subgraph .NET
|
||||
NM[Mod with Archipelago.MultiClient.Net]
|
||||
subgraph FNA/XNA
|
||||
TS[Timespinner]
|
||||
RL[Rogue Legacy]
|
||||
end
|
||||
NM <-- TsRandomizer --> TS
|
||||
NM <-- RogueLegacyRandomizer --> RL
|
||||
subgraph Unity
|
||||
ROR[Risk of Rain 2]
|
||||
SN[Subnautica]
|
||||
HK[Hollow Knight]
|
||||
R[Raft]
|
||||
end
|
||||
NM <-- BepInEx --> ROR
|
||||
NM <-- "QModLoader (BepInEx)" --> SN
|
||||
NM <-- HK Modding API --> HK
|
||||
NM <--> R
|
||||
end
|
||||
AS <-- WebSockets --> NM
|
||||
|
||||
%% Archipelago WebHost
|
||||
subgraph "WebHost (archipelago.gg)"
|
||||
WHNOTE(["Configurable (waitress, gunicorn, flask)"])
|
||||
AH[AutoHoster]
|
||||
PDB[(PonyORM DB)]
|
||||
WH[WebHost]
|
||||
FWC[Flask WebContent]
|
||||
AG[AutoGenerator]
|
||||
|
||||
AH <-- SQL --> PDB
|
||||
WH -- Subprocesses --> AH
|
||||
FWC <-- SQL --> PDB
|
||||
WH --> FWC
|
||||
AG -- Deposit Generated Worlds --> PDB
|
||||
PDB -- Provide Generation Instructions --> AG
|
||||
WH -- Subprocesses --> AG
|
||||
end
|
||||
AH -- Subprocesses --> AS
|
||||
|
||||
%% Special subgraph for SoE for its SNES connection
|
||||
subgraph Secret of Evermore
|
||||
SOESNES[SNES]
|
||||
end
|
||||
|
||||
%% Factorio
|
||||
subgraph Factorio
|
||||
FC[FactorioClient] <-- RCON --> FS[Factorio Server]
|
||||
FS <-- UDP --> FG[Factorio Games]
|
||||
FMOD[Factorio Mod Generated by AP]
|
||||
FMAPI[Factorio Modding API]
|
||||
FMAPI <--> FS
|
||||
FMAPI <--> FG
|
||||
FMOD <--> FMAPI
|
||||
end
|
||||
CC <-- Integrated --> FC
|
||||
```
|
||||
658
docs/sphinx/NetworkProtocol.md
Normal file
@@ -0,0 +1,658 @@
|
||||
# Archipelago Network Protocol
|
||||
## Archipelago Connection Handshake
|
||||
These steps should be followed in order to establish a gameplay connection with an Archipelago session.
|
||||
|
||||
1. Client establishes WebSocket connection to Archipelago server.
|
||||
2. Server accepts connection and responds with a [RoomInfo](#roominfo) packet.
|
||||
3. Client may send a [GetDataPackage](#getdatapackage) packet.
|
||||
4. Server sends a [DataPackage](#datapackage) packet in return. (If the client sent GetDataPackage.)
|
||||
5. Client sends [Connect](#connect) packet in order to authenticate with the server.
|
||||
6. Server validates the client's packet and responds with [Connected](#connected) or
|
||||
[ConnectionRefused](#connectionrefused).
|
||||
7. Server may send [ReceivedItems](#receiveditems) to the client, in the case that the client is missing items that
|
||||
are queued up for it.
|
||||
8. Server sends [Print](#print) to all players to notify them of the new client connection.
|
||||
|
||||
In the case that the client does not authenticate properly and receives a [ConnectionRefused](#connectionrefused) then
|
||||
the server will maintain the connection and allow for follow-up [Connect](#connect) packet.
|
||||
|
||||
There are libraries available that implement this network protocol in
|
||||
[Python](https://github.com/ArchipelagoMW/Archipelago/blob/main/CommonClient.py),
|
||||
[Java](https://github.com/ArchipelagoMW/Archipelago.MultiClient.Java),
|
||||
[.NET](https://github.com/ArchipelagoMW/Archipelago.MultiClient.Net) and
|
||||
[C++](https://github.com/black-sliver/apclientpp)
|
||||
|
||||
For Super Nintendo games there are clients available in
|
||||
[Python](https://github.com/ArchipelagoMW/Archipelago/blob/main/SNIClient.py). There is also a game specific client for
|
||||
[Final Fantasy 1](https://github.com/ArchipelagoMW/Archipelago/blob/main/FF1Client.py)
|
||||
|
||||
## Synchronizing Items
|
||||
When the client receives a [ReceivedItems](#receiveditems) packet, if the `index` argument does not match the next index
|
||||
that the client expects then it is expected that the client will re-sync items with the server. This can be accomplished
|
||||
by sending the server a [Sync](#sync) packet and then a [LocationChecks](#locationchecks) packet.
|
||||
|
||||
Even if the client detects a desync, it can still accept the items provided in this packet to prevent gameplay
|
||||
interruption.
|
||||
|
||||
When the client receives a [ReceivedItems](#receiveditems) packet and the `index` arg is `0` (zero) then the client
|
||||
should accept the provided `items` list as its full inventory. (Abandon previous inventory.)
|
||||
|
||||
## Archipelago Protocol Packets
|
||||
Packets are sent between the multiworld server and client in order to sync information between them.
|
||||
Below is a directory of each packet.
|
||||
|
||||
Packets are simple JSON lists in which any number of ordered network commands can be sent, which are objects.
|
||||
Each command has a "cmd" key, indicating its purpose. All packet argument types documented here refer to JSON types,
|
||||
unless otherwise specified.
|
||||
|
||||
An object can contain the "class" key, which will tell the content data type, such as "Version" in the following
|
||||
example.
|
||||
|
||||
Example:
|
||||
```javascript
|
||||
[{"cmd": "RoomInfo", "version": {"major": 0, "minor": 1, "build": 3, "class": "Version"}, "tags": ["WebHost"], ... }]
|
||||
```
|
||||
|
||||
## (Server -> Client)
|
||||
These packets are are sent from the multiworld server to the client. They are not messages which the server accepts.
|
||||
* [RoomInfo](#roominfo)
|
||||
* [ConnectionRefused](#connectionrefused)
|
||||
* [Connected](#connected)
|
||||
* [ReceivedItems](#receiveditems)
|
||||
* [LocationInfo](#locationinfo)
|
||||
* [RoomUpdate](#roomupdate)
|
||||
* [Print](#print)
|
||||
* [PrintJSON](#printjson)
|
||||
* [DataPackage](#datapackage)
|
||||
* [Bounced](#bounced)
|
||||
* [InvalidPacket](#invalidpacket)
|
||||
* [Retrieved](#retrieved)
|
||||
* [SetReply](#setreply)
|
||||
|
||||
### RoomInfo
|
||||
Sent to clients when they connect to an Archipelago server.
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| version | [NetworkVersion](#networkversion) | Object denoting the version of Archipelago which the server is running. |
|
||||
| tags | list\[str\] | Denotes special features or capabilities that the sender is capable of. Example: `WebHost` |
|
||||
| password | bool | Denoted whether a password is required to join this room.|
|
||||
| permissions | dict\[str, [Permission](#permission)\[int\]\] | Mapping of permission name to [Permission](#permission), keys are: "forfeit", "collect" and "remaining". |
|
||||
| hint_cost | int | The amount of points it costs to receive a hint from the server. |
|
||||
| location_check_points | int | The amount of hint points you receive per item/location check completed. ||
|
||||
| games | list\[str\] | List of games present in this multiworld. |
|
||||
| datapackage_version | int | Sum of individual games' datapackage version. Deprecated. Use `datapackage_versions` instead. |
|
||||
| datapackage_versions | dict\[str, int\] | Data versions of the individual games' data packages the server will send. Used to decide which games' caches are outdated. See [Data Package Contents](#data-package-contents). |
|
||||
| seed_name | str | uniquely identifying name of this generation |
|
||||
| time | float | Unix time stamp of "now". Send for time synchronization if wanted for things like the DeathLink Bounce. |
|
||||
|
||||
#### forfeit
|
||||
Dictates what is allowed when it comes to a player forfeiting their run. A forfeit is an action which distributes the
|
||||
rest of the items in a player's run to those other players awaiting them.
|
||||
|
||||
* `auto`: Distributes a player's items to other players when they complete their goal.
|
||||
* `enabled`: Denotes that players may forfeit at any time in the game.
|
||||
* `auto-enabled`: Both of the above options together.
|
||||
* `disabled`: All forfeit modes disabled.
|
||||
* `goal`: Allows for manual use of forfeit command once a player completes their goal. (Disabled until goal completion)
|
||||
|
||||
#### collect
|
||||
Dictates what is allowed when it comes to a player collecting their run. A collect is an action which sends the rest of
|
||||
the items in a player's run.
|
||||
|
||||
* `auto`: Automatically when they complete their goal.
|
||||
* `enabled`: Denotes that players may !collect at any time in the game.
|
||||
* `auto-enabled`: Both of the above options together.
|
||||
* `disabled`: All collect modes disabled.
|
||||
* `goal`: Allows for manual use of collect command once a player completes their goal. (Disabled until goal completion)
|
||||
|
||||
|
||||
#### remaining
|
||||
Dictates what is allowed when it comes to a player querying the items remaining in their run.
|
||||
|
||||
* `goal`: Allows a player to query for items remaining in their run but only after they completed their own goal.
|
||||
* `enabled`: Denotes that players may query for any items remaining in their run (even those belonging to other players).
|
||||
* `disabled`: All remaining item query modes disabled.
|
||||
|
||||
### ConnectionRefused
|
||||
Sent to clients when the server refuses connection. This is sent during the initial connection handshake.
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| errors | list\[str\] | Optional. When provided, should contain any one of: `InvalidSlot`, `InvalidGame`, `IncompatibleVersion`, `InvalidPassword`, or `InvalidItemsHandling`. |
|
||||
|
||||
InvalidSlot indicates that the sent 'name' field did not match any auth entry on the server.
|
||||
InvalidGame indicates that a correctly named slot was found, but the game for it mismatched.
|
||||
IncompatibleVersion indicates a version mismatch.
|
||||
InvalidPassword indicates the wrong, or no password when it was required, was sent.
|
||||
InvalidItemsHandling indicates a wrong value type or flag combination was sent.
|
||||
|
||||
### Connected
|
||||
Sent to clients when the connection handshake is successfully completed.
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| team | int | Your team number. See [NetworkPlayer](#networkplayer) for more info on team number. |
|
||||
| slot | int | Your slot number on your team. See [NetworkPlayer](#networkplayer) for more info on the slot number. |
|
||||
| players | list\[[NetworkPlayer](#networkplayer)\] | List denoting other players in the multiworld, whether connected or not. |
|
||||
| missing_locations | list\[int\] | Contains ids of remaining locations that need to be checked. Useful for trackers, among other things. |
|
||||
| checked_locations | list\[int\] | Contains ids of all locations that have been checked. Useful for trackers, among other things. Location ids are in the range of ± 2<sup>53</sup>-1. |
|
||||
| slot_data | dict | Contains a json object for slot related data, differs per game. Empty if not required. |
|
||||
| slot_info | dict\[int, [NetworkSlot](#networkslot)\] | maps each slot to a [NetworkSlot](#networkslot) information |
|
||||
|
||||
### ReceivedItems
|
||||
Sent to clients when they receive an item.
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| index | int | The next empty slot in the list of items for the receiving client. |
|
||||
| items | list\[[NetworkItem](#networkitem)\] | The items which the client is receiving. |
|
||||
|
||||
### LocationInfo
|
||||
Sent to clients to acknowledge a received [LocationScouts](#locationscouts) packet and responds with the item in the location(s) being scouted.
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| locations | list\[[NetworkItem](#networkitem)\] | Contains list of item(s) in the location(s) scouted. |
|
||||
|
||||
### RoomUpdate
|
||||
Sent when there is a need to update information about the present game session. Generally useful for async games.
|
||||
Once authenticated (received Connected), this may also contain data from Connected.
|
||||
#### Arguments
|
||||
The arguments for RoomUpdate are identical to [RoomInfo](#roominfo) barring:
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| hint_points | int | New argument. The client's current hint points. |
|
||||
| players | list\[[NetworkPlayer](#networkplayer)\] | Send in the event of an alias rename. Always sends all players, whether connected or not. |
|
||||
| checked_locations | list\[int\] | May be a partial update, containing new locations that were checked, especially from a coop partner in the same slot. |
|
||||
| missing_locations | list\[int\] | Should never be sent as an update, if needed is the inverse of checked_locations. |
|
||||
|
||||
All arguments for this packet are optional, only changes are sent.
|
||||
|
||||
### Print
|
||||
Sent to clients purely to display a message to the player.
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| text | str | Message to display to player. |
|
||||
|
||||
### PrintJSON
|
||||
Sent to clients purely to display a message to the player. This packet differs from [Print](#print) in that the data
|
||||
being sent with this packet allows for more configurable or specific messaging.
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| data | list\[[JSONMessagePart](#jsonmessagepart)\] | Type of this part of the message. |
|
||||
| type | str | May be present to indicate the nature of this message. Known types are Hint and ItemSend. |
|
||||
| receiving | int | Is present if type is Hint or ItemSend and marks the destination player's ID. |
|
||||
| item | [NetworkItem](#networkitem) | Is present if type is Hint or ItemSend and marks the source player id, location id, item id and item flags. |
|
||||
| found | bool | Is present if type is Hint, denotes whether the location hinted for was checked. |
|
||||
|
||||
### DataPackage
|
||||
Sent to clients to provide what is known as a 'data package' which contains information to enable a client to most
|
||||
easily communicate with the Archipelago server. Contents include things like location id to name mappings,
|
||||
among others; see [Data Package Contents](#data-package-contents) for more info.
|
||||
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| data | [DataPackageObject](#data-package-contents) | The data package as a JSON object. |
|
||||
|
||||
### Bounced
|
||||
Sent to clients after a client requested this message be sent to them, more info in the [Bounce](#bounce) package.
|
||||
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| games | list\[str\] | Optional. Game names this message is targeting |
|
||||
| slots | list\[int\] | Optional. Player slot IDs that this message is targeting |
|
||||
| tags | list\[str\] | Optional. Client [Tags](#tags) this message is targeting |
|
||||
| data | dict | The data in the [Bounce](#bounce) package copied |
|
||||
|
||||
### InvalidPacket
|
||||
Sent to clients if the server caught a problem with a packet. This only occurs for errors that are explicitly checked
|
||||
for.
|
||||
|
||||
### Retrieved
|
||||
Sent to clients as a response the a [Get](#get) package
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| keys | dict\[str\, any] | A key-value collection containing all the values for the keys requested in the [Get](#get) package. |
|
||||
|
||||
Additional arguments added to the [Get](#get) package that triggered this [Retrieved](#retrieved) will also be passed
|
||||
along.
|
||||
|
||||
### SetReply
|
||||
Sent to clients in response to a [Set](#set) package if want_reply was set to true, or if the client has registered to
|
||||
receive updates for a certain key using the [SetNotify](#setnotify) package. SetReply packages are sent even if a
|
||||
[Set](#set) package did not alter the value for the key.
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| key | str | The key that was updated. |
|
||||
| value | any | The new value for the key. |
|
||||
| original_value | any | The value the key had before it was updated. |
|
||||
|
||||
Additional arguments added to the [Set](#set) package that triggered this [SetReply](#setreply) will also be passed
|
||||
along.
|
||||
|
||||
## (Client -> Server)
|
||||
These packets are sent purely from client to server. They are not accepted by clients.
|
||||
|
||||
* [Connect](#connect)
|
||||
* [Sync](#sync)
|
||||
* [LocationChecks](#locationchecks)
|
||||
* [LocationScouts](#locationscouts)
|
||||
* [StatusUpdate](#statusupdate)
|
||||
* [Say](#say)
|
||||
* [GetDataPackage](#getdatapackage)
|
||||
* [Bounce](#bounce)
|
||||
* [Get](#get)
|
||||
* [Set](#set)
|
||||
* [SetNotify](#setnotify)
|
||||
|
||||
### Connect
|
||||
Sent by the client to initiate a connection to an Archipelago game session.
|
||||
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| password | str | If the game session requires a password, it should be passed here. |
|
||||
| game | str | The name of the game the client is playing. Example: `A Link to the Past` |
|
||||
| name | str | The player name for this client. |
|
||||
| uuid | str | Unique identifier for player client. |
|
||||
| version | [NetworkVersion](#networkversion) | An object representing the Archipelago version this client supports. |
|
||||
| items_handling | int | Flags configuring which items should be sent by the server. Read below for individual flags. |
|
||||
| tags | list\[str\] | Denotes special features or capabilities that the sender is capable of. [Tags](#tags) |
|
||||
|
||||
#### items_handling flags
|
||||
| Value | Meaning |
|
||||
| ----- | ------- |
|
||||
| 0b000 | No ReceivedItems is sent to you, ever. |
|
||||
| 0b001 | Indicates you get items sent from other worlds. |
|
||||
| 0b010 | Indicates you get items sent from your own world. Requires 0b001 to be set. |
|
||||
| 0b100 | Indicates you get your starting inventory sent. Requires 0b001 to be set. |
|
||||
| null | Null or undefined loads settings from world definition for backwards compatibility. This is deprecated. |
|
||||
|
||||
#### Authentication
|
||||
Many, if not all, other packets require a successfully authenticated client. This is described in more detail in
|
||||
[Archipelago Connection Handshake](#archipelago-connection-handshake).
|
||||
|
||||
### ConnectUpdate
|
||||
Update arguments from the Connect package, currently only updating tags and items_handling is supported.
|
||||
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| items_handling | int | Flags configuring which items should be sent by the server. |
|
||||
| tags | list\[str\] | Denotes special features or capabilities that the sender is capable of. [Tags](#tags) |
|
||||
|
||||
### Sync
|
||||
Sent to server to request a [ReceivedItems](#receiveditems) packet to synchronize items.
|
||||
#### Arguments
|
||||
No arguments necessary.
|
||||
|
||||
### LocationChecks
|
||||
Sent to server to inform it of locations that the client has checked. Used to inform the server of new checks that are
|
||||
made, as well as to sync state.
|
||||
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| locations | list\[int\] | The ids of the locations checked by the client. May contain any number of checks, even ones sent before; duplicates do not cause issues with the Archipelago server. |
|
||||
|
||||
### LocationScouts
|
||||
Sent to the server to inform it of locations the client has seen, but not checked. Useful in cases in which the item may
|
||||
appear in the game world, such as 'ledge items' in A Link to the Past. The server will always respond with a
|
||||
[LocationInfo](#locationinfo) packet with the items located in the scouted location.
|
||||
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| locations | list\[int\] | The ids of the locations seen by the client. May contain any number of locations, even ones sent before; duplicates do not cause issues with the Archipelago server. |
|
||||
| create_as_hint | int | If non-zero, the scouted locations get created and broadcast as a player-visible hint. <br/>If 2 only new hints are broadcast, however this does not remove them from the LocationInfo reply. |
|
||||
|
||||
### StatusUpdate
|
||||
Sent to the server to update on the sender's status. Examples include readiness or goal completion. (Example: defeated Ganon in A Link to the Past)
|
||||
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| status | ClientStatus\[int\] | One of [Client States](#client-states). Send as int. Follow the link for more information. |
|
||||
|
||||
### Say
|
||||
Basic chat command which sends text to the server to be distributed to other clients.
|
||||
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ------ | ----- | ------ |
|
||||
| text | str | Text to send to others. |
|
||||
|
||||
### GetDataPackage
|
||||
Requests the data package from the server. Does not require client authentication.
|
||||
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
|-------| ----- | ---- |
|
||||
| games | list\[str\] | Optional. If specified, will only send back the specified data. Such as, \["Factorio"\] -> Datapackage with only Factorio data. |
|
||||
|
||||
### Bounce
|
||||
Send this message to the server, tell it which clients should receive the message and
|
||||
the server will forward the message to all those targets to which any one requirement applies.
|
||||
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ------ | ----- | ------ |
|
||||
| games | list\[str\] | Optional. Game names that should receive this message |
|
||||
| slots | list\[int\] | Optional. Player IDs that should receive this message |
|
||||
| tags | list\[str\] | Optional. Client tags that should receive this message |
|
||||
| data | dict | Any data you want to send |
|
||||
|
||||
### Get
|
||||
Used to request a single or multiple values from the server's data storage, see the [Set](#set) package for how to write
|
||||
values to the data storage. A Get package will be answered with a [Retrieved](#retrieved) package.
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ------ | ----- | ------ |
|
||||
| keys | list\[str\] | Keys to retrieve the values for. |
|
||||
|
||||
Additional arguments sent in this package will also be added to the [Retrieved](#retrieved) package it triggers.
|
||||
|
||||
### Set
|
||||
Used to write data to the server's data storage, that data can then be shared across worlds or just saved for later.
|
||||
Values for keys in the data storage can be retrieved with a [Get](#get) package, or monitored with a
|
||||
[SetNotify](#setnotify) package.
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ------ | ----- | ------ |
|
||||
| key | str | The key to manipulate. |
|
||||
| default | any | The default value to use in case the key has no value on the server. |
|
||||
| want_reply | bool | If set, the server will send a [SetReply](#setreply) response back to the client. |
|
||||
| operations | list\[[DataStorageOperation](#datastorageoperation)\] | Operations to apply to the value, multiple operations can be present and they will be executed in order of appearance. |
|
||||
|
||||
Additional arguments sent in this package will also be added to the [SetReply](#setreply) package it triggers.
|
||||
|
||||
#### DataStorageOperation
|
||||
A DataStorageOperation manipulates or alters the value of a key in the data storage. If the operation transforms the
|
||||
value from one state to another then the current value of the key is used as the starting point otherwise the
|
||||
[Set](#set)'s package `default` is used if the key does not exist on the server already.
|
||||
|
||||
DataStorageOperations consist of an object containing both the operation to be applied, provided in the form of a
|
||||
string, as well as the value to be used for that operation, Example:
|
||||
```json
|
||||
{"operation": "add", "value": 12}
|
||||
```
|
||||
|
||||
The following operations can be applied to a datastorage key
|
||||
| Operation | Effect |
|
||||
| ------ | ----- |
|
||||
| replace | Sets the current value of the key to `value`. |
|
||||
| default | If the key has no value yet, sets the current value of the key to `default` of the [Set](#set)'s package (`value` is ignored). |
|
||||
| add | Adds `value` to the current value of the key, if both the current value and `value` are arrays then `value` will be appended to the current value. |
|
||||
| mul | Multiplies the current value of the key by `value`. |
|
||||
| pow | Multiplies the current value of the key to the power of `value`. |
|
||||
| mod | Sets the current value of the key to the remainder after division by `value`. |
|
||||
| max | Sets the current value of the key to `value` if `value` is bigger. |
|
||||
| min | Sets the current value of the key to `value` if `value` is lower. |
|
||||
| and | Applies a bitwise AND to the current value of the key with `value`. |
|
||||
| or | Applies a bitwise OR to the current value of the key with `value`. |
|
||||
| xor | Applies a bitwise Exclusive OR to the current value of the key with `value`. |
|
||||
| left_shift | Applies a bitwise left-shift to the current value of the key by `value`. |
|
||||
| right_shift | Applies a bitwise right-shift to the current value of the key by `value`. |
|
||||
|
||||
### SetNotify
|
||||
Used to register your current session for receiving all [SetReply](#setreply) packages of certain keys to allow your client to keep track of changes.
|
||||
#### Arguments
|
||||
| Name | Type | Notes |
|
||||
| ------ | ----- | ------ |
|
||||
| keys | list\[str\] | Keys to receive all [SetReply](#setreply) packages for. |
|
||||
|
||||
## Appendix
|
||||
|
||||
### Coop
|
||||
Coop in Archipelago is automatically facilitated by the server, however some default behaviour may not be what you
|
||||
desire.
|
||||
|
||||
If the game in question is a remote-items game (attribute on AutoWorld), then all items will always be sent and received.
|
||||
If the game in question is not a remote-items game, then any items that are placed within the same world will not be
|
||||
sent by the server.
|
||||
|
||||
To manually react to others in the same player slot doing checks, listen to [RoomUpdate](#roomupdate) ->
|
||||
checked_locations.
|
||||
|
||||
### NetworkPlayer
|
||||
A list of objects. Each object denotes one player. Each object has four fields about the player, in this order: `team`,
|
||||
`slot`, `alias`, and `name`. `team` and `slot` are ints, `alias` and `name` are strings.
|
||||
|
||||
Each player belongs to a `team` and has a `slot`. Team numbers start at `0`. Slot numbers are unique per team and start
|
||||
at `1`. Slot number `0` refers to the Archipelago server; this may appear in instances where the server grants the
|
||||
player an item.
|
||||
|
||||
`alias` represents the player's name in current time. `name` is the original name used when the session was generated.
|
||||
This is typically distinct in games which require baking names into ROMs or for async games.
|
||||
|
||||
```python
|
||||
from typing import NamedTuple
|
||||
class NetworkPlayer(NamedTuple):
|
||||
team: int
|
||||
slot: int
|
||||
alias: str
|
||||
name: str
|
||||
```
|
||||
|
||||
Example:
|
||||
```json
|
||||
[
|
||||
{"team": 0, "slot": 1, "alias": "Lord MeowsiePuss", "name": "Meow"},
|
||||
{"team": 0, "slot": 2, "alias": "Doggo", "name": "Bork"},
|
||||
{"team": 1, "slot": 1, "alias": "Angry Duck", "name": "Angry Quack"},
|
||||
{"team": 1, "slot": 2, "alias": "Mountain Duck", "name": "Honk"}
|
||||
]
|
||||
```
|
||||
|
||||
### NetworkItem
|
||||
Items that are sent over the net (in packets) use the following data structure and are sent as objects:
|
||||
```python
|
||||
from typing import NamedTuple
|
||||
class NetworkItem(NamedTuple):
|
||||
item: int
|
||||
location: int
|
||||
player: int
|
||||
flags: int
|
||||
```
|
||||
In JSON this may look like:
|
||||
```json
|
||||
[
|
||||
{"item": 1, "location": 1, "player": 1, "flags": 1},
|
||||
{"item": 2, "location": 2, "player": 2, "flags": 2},
|
||||
{"item": 3, "location": 3, "player": 3, "flags": 0}
|
||||
]
|
||||
```
|
||||
`item` is the item id of the item. Item ids are in the range of ± 2<sup>53</sup>-1.
|
||||
|
||||
`location` is the location id of the item inside the world. Location ids are in the range of ± 2<sup>53</sup>-1.
|
||||
|
||||
`player` is the player slot of the world the item is located in, except when inside an [LocationInfo](#locationinfo)
|
||||
Packet then it will be the slot of the player to receive the item.
|
||||
|
||||
`flags` are bit flags:
|
||||
| Flag | Meaning |
|
||||
| ----- | ----- |
|
||||
| 0 | Nothing special about this item |
|
||||
| 0b001 | If set, indicates the item can unlock logical advancement |
|
||||
| 0b010 | If set, indicates the item is important but not in a way that unlocks advancement |
|
||||
| 0b100 | If set, indicates the item is a trap |
|
||||
|
||||
### JSONMessagePart
|
||||
Message nodes sent along with [PrintJSON](#printjson) packet to be reconstructed into a legible message.
|
||||
The nodes are intended to be read in the order they are listed in the packet.
|
||||
|
||||
```python
|
||||
from typing import TypedDict, Optional
|
||||
class JSONMessagePart(TypedDict):
|
||||
type: Optional[str]
|
||||
text: Optional[str]
|
||||
color: Optional[str] # only available if type is a color
|
||||
flags: Optional[int] # only available if type is an item_id or item_name
|
||||
player: Optional[int] # only available if type is either item or location
|
||||
```
|
||||
|
||||
`type` is used to denote the intent of the message part. This can be used to indicate special information which may be
|
||||
rendered differently depending on client. How these types are displayed in Archipelago's ALttP client is not the end-all
|
||||
be-all. Other clients may choose to interpret and display these messages differently.
|
||||
|
||||
Possible values for `type` include:
|
||||
|
||||
| Name | Notes |
|
||||
| ---- | ----- |
|
||||
| text | Regular text content. Is the default type and as such may be omitted. |
|
||||
| player_id | player ID of someone on your team, should be resolved to Player Name |
|
||||
| player_name | Player Name, could be a player within a multiplayer game or from another team, not ID resolvable |
|
||||
| item_id | Item ID, should be resolved to Item Name |
|
||||
| item_name | Item Name, not currently used over network, but supported by reference Clients. |
|
||||
| location_id | Location ID, should be resolved to Location Name |
|
||||
| location_name | Location Name, not currently used over network, but supported by reference Clients. |
|
||||
| entrance_name | Entrance Name. No ID mapping exists. |
|
||||
| color | Regular text that should be colored. Only `type` that will contain `color` data. |
|
||||
|
||||
|
||||
`color` is used to denote a console color to display the message part with and is only send if the `type` is `color`.
|
||||
This is limited to console colors due to backwards compatibility needs with games such as ALttP.
|
||||
Although background colors as well as foreground colors are listed, only one may be applied to a
|
||||
[JSONMessagePart](#jsonmessagepart) at a time.
|
||||
|
||||
Color options:
|
||||
* bold
|
||||
* underline
|
||||
* black
|
||||
* red
|
||||
* green
|
||||
* yellow
|
||||
* blue
|
||||
* magenta
|
||||
* cyan
|
||||
* white
|
||||
* black_bg
|
||||
* red_bg
|
||||
* green_bg
|
||||
* yellow_bg
|
||||
* blue_bg
|
||||
* purple_bg
|
||||
* cyan_bg
|
||||
* white_bg
|
||||
|
||||
`text` is the content of the message part to be displayed.
|
||||
`player` marks owning player id for location/item,
|
||||
`flags` contains the [NetworkItem](#networkitem) flags that belong to the item
|
||||
|
||||
### Client States
|
||||
An enumeration containing the possible client states that may be used to inform the server in
|
||||
[StatusUpdate](#statusupdate).
|
||||
|
||||
```python
|
||||
import enum
|
||||
class ClientStatus(enum.IntEnum):
|
||||
CLIENT_UNKNOWN = 0
|
||||
CLIENT_READY = 10
|
||||
CLIENT_PLAYING = 20
|
||||
CLIENT_GOAL = 30
|
||||
```
|
||||
|
||||
### NetworkVersion
|
||||
An object representing software versioning. Used in the [Connect](#connect) packet to allow the client to inform the
|
||||
server of the Archipelago version it supports.
|
||||
```python
|
||||
from typing import NamedTuple
|
||||
class Version(NamedTuple):
|
||||
major: int
|
||||
minor: int
|
||||
build: int
|
||||
```
|
||||
|
||||
### SlotType
|
||||
An enum representing the nature of a slot.
|
||||
|
||||
```python
|
||||
import enum
|
||||
class SlotType(enum.IntFlag):
|
||||
spectator = 0b00
|
||||
player = 0b01
|
||||
group = 0b10
|
||||
```
|
||||
|
||||
### NetworkSlot
|
||||
An object representing static information about a slot.
|
||||
|
||||
```python
|
||||
import typing
|
||||
from NetUtils import SlotType
|
||||
class NetworkSlot(typing.NamedTuple):
|
||||
name: str
|
||||
game: str
|
||||
type: SlotType
|
||||
group_members: typing.List[int] = [] # only populated if type == group
|
||||
```
|
||||
|
||||
### Permission
|
||||
An enumeration containing the possible command permission, for commands that may be restricted.
|
||||
```python
|
||||
import enum
|
||||
class Permission(enum.IntEnum):
|
||||
disabled = 0b000 # 0, completely disables access
|
||||
enabled = 0b001 # 1, allows manual use
|
||||
goal = 0b010 # 2, allows manual use after goal completion
|
||||
auto = 0b110 # 6, forces use after goal completion, only works for forfeit and collect
|
||||
auto_enabled = 0b111 # 7, forces use after goal completion, allows manual use any time
|
||||
```
|
||||
|
||||
### Data Package Contents
|
||||
A data package is a JSON object which may contain arbitrary metadata to enable a client to interact with the Archipelago
|
||||
server most easily. Currently, this package is used to send ID to name mappings so that clients need not maintain their
|
||||
own mappings.
|
||||
|
||||
We encourage clients to cache the data package they receive on disk, or otherwise not tied to a session.
|
||||
You will know when your cache is outdated if the [RoomInfo](#roominfo) packet or the datapackage itself denote a
|
||||
different version. A special case is datapackage version 0, where it is expected the package is custom and should not be
|
||||
cached.
|
||||
|
||||
Note:
|
||||
* Any ID is unique to its type across AP: Item 56 only exists once and Location 56 only exists once.
|
||||
* Any Name is unique to its type across its own Game only: Single Arrow can exist in two games.
|
||||
* The IDs from the game "Archipelago" may be used in any other game.
|
||||
Especially Location ID -1: Cheat Console and -2: Server (typically Remote Start Inventory)
|
||||
|
||||
#### Contents
|
||||
| Name | Type | Notes |
|
||||
| ------ | ----- | ------ |
|
||||
| games | dict[str, GameData] | Mapping of all Games and their respective data |
|
||||
|
||||
#### GameData
|
||||
GameData is a **dict** but contains these keys and values. It's broken out into another "type" for ease of documentation.
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ----- |
|
||||
| item_name_to_id | dict[str, int] | Mapping of all item names to their respective ID. |
|
||||
| location_name_to_id | dict[str, int] | Mapping of all location names to their respective ID. |
|
||||
| version | int | Version number of this game's data |
|
||||
|
||||
### Tags
|
||||
Tags are represented as a list of strings, the common Client tags follow:
|
||||
|
||||
| Name | Notes |
|
||||
|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| AP | Signifies that this client is a reference client, its usefulness is mostly in debugging to compare client behaviours more easily. |
|
||||
| IgnoreGame | Deprecated. See Tracker and TextOnly. Tells the server to ignore the "game" attribute in the [Connect](#connect) packet. |
|
||||
| DeathLink | Client participates in the DeathLink mechanic, therefore will send and receive DeathLink bounce packets |
|
||||
| Tracker | Tells the server that this client will not send locations and is actually a Tracker. When specified and used with empty or null `game` in [Connect](#connect), game and game's version validation will be skipped. |
|
||||
| TextOnly | Tells the server that this client will not send locations and is intended for chat. When specified and used with empty or null `game` in [Connect](#connect), game and game's version validation will be skipped. |
|
||||
|
||||
### DeathLink
|
||||
A special kind of Bounce packet that can be supported by any AP game. It targets the tag "DeathLink" and carries the following data:
|
||||
|
||||
| Name | Type | Notes |
|
||||
| ---- | ---- | ---- |
|
||||
| time | float | Unix Time Stamp of time of death. |
|
||||
| cause | str | Optional. Text to explain the cause of death, ex. "Berserker was run over by a train." |
|
||||
| source | str | Name of the player who first died. Can be a slot name, but can also be a name from within a multiplayer game. |
|
||||
674
docs/sphinx/WorldAPI.md
Normal file
@@ -0,0 +1,674 @@
|
||||
# Archipelago API
|
||||
|
||||
This document tries to explain some internals required to implement a game for
|
||||
Archipelago's generation and server. Once a seed is generated, a client or mod is
|
||||
required to send and receive items between the game and server.
|
||||
|
||||
Client implementation is out of scope of this document. Please refer to an
|
||||
existing game that provides a similar API to yours.
|
||||
|
||||
Archipelago will be abbreviated as "AP" from now on.
|
||||
|
||||
## Language
|
||||
|
||||
AP worlds are written in python3.
|
||||
Clients that connect to the server to sync items can be in any language that
|
||||
allows using WebSockets.
|
||||
|
||||
## Coding style
|
||||
|
||||
AP follows all the PEPs. When in doubt use an IDE with coding style
|
||||
linter, for example PyCharm Community Edition.
|
||||
|
||||
## Docstrings
|
||||
|
||||
Docstrings are strings attached to an object in Python that describe what the
|
||||
object is supposed to be. Certain docstrings will be picked up and used by AP.
|
||||
They are assigned by writing a string without any assignment right below a
|
||||
definition. The string must be a triple-quoted string.
|
||||
Example:
|
||||
```python
|
||||
from worlds.AutoWorld import World
|
||||
class MyGameWorld(World):
|
||||
"""This is the description of My Game that will be displayed on the AP
|
||||
website."""
|
||||
```
|
||||
|
||||
## Definitions
|
||||
|
||||
This section will cover various classes and objects you can use for your world.
|
||||
While some of the attributes and methods are mentioned here not all of them are,
|
||||
but you can find them in `BaseClasses.py`.
|
||||
|
||||
### World Class
|
||||
|
||||
A `World` class is the class with all the specifics of a certain game to be
|
||||
included. It will be instantiated for each player that rolls a seed for that
|
||||
game.
|
||||
|
||||
### WebWorld Class
|
||||
|
||||
A `WebWorld` class contains specific attributes and methods that can be modified
|
||||
for your world specifically on the webhost.
|
||||
|
||||
`settings_page` which can be changed to a link instead of an AP generated settings page.
|
||||
|
||||
`theme` to be used for your game specific AP pages. Available themes:
|
||||
| dirt | grass (default) | grassFlowers | ice | jungle | ocean | partyTime | stone |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| <img src="_static/theme_dirt.JPG" width="200"> | <img src="_static/theme_grass.JPG" width="200"> | <img src="_static/theme_grassFlowers.JPG" width="200"> | <img src="_static/theme_ice.JPG" width="200"> | <img src="_static/theme_jungle.JPG" width="200"> | <img src="_static/theme_ocean.JPG" width="200"> | <img src="_static/theme_partyTime.JPG" width="200"> | <img src="_static/theme_stone.JPG" width="200"> |
|
||||
|
||||
`bug_report_page` (optional) can be a link to a bug reporting page, most likely a GitHub issue page, that will be placed by the site to help direct users to report bugs.
|
||||
|
||||
`tutorials` list of `Tutorial` classes where each class represents a guide to be generated on the webhost.
|
||||
|
||||
`game_info_languages` (optional) List of strings for defining the existing gameinfo pages your game supports. The documents must be
|
||||
prefixed with the same string as defined here. Default already has 'en'.
|
||||
|
||||
### MultiWorld Object
|
||||
|
||||
The `MultiWorld` object references the whole multiworld (all items and locations
|
||||
for all players) and is accessible through `self.world` inside a `World` object.
|
||||
|
||||
### Player
|
||||
|
||||
The player is just an integer in AP and is accessible through `self.player`
|
||||
inside a World object.
|
||||
|
||||
### Player Options
|
||||
|
||||
Players provide customized settings for their World in the form of yamls.
|
||||
Those are accessible through `self.world.<option_name>[self.player]`. A dict
|
||||
of valid options has to be provided in `self.options`. Options are automatically
|
||||
added to the `World` object for easy access.
|
||||
|
||||
### World Options
|
||||
|
||||
Any AP installation can provide settings for a world, for example a ROM file,
|
||||
accessible through `Utils.get_options()['<world>_options']['<option>']`.
|
||||
|
||||
Users can set those in their `host.yaml` file.
|
||||
|
||||
### Locations
|
||||
|
||||
Locations are places where items can be located in your game. This may be chests
|
||||
or boss drops for RPG-like games but could also be progress in a research tree.
|
||||
|
||||
Each location has a `name` and an `id` (a.k.a. "code" or "address"), is placed
|
||||
in a Region and has access rules.
|
||||
The name needs to be unique in each game, the ID needs to be unique across all
|
||||
games and is best in the same range as the item IDs.
|
||||
World-specific IDs are 1 to 2<sup>53</sup>-1, IDs ≤ 0 are global and reserved.
|
||||
|
||||
Special locations with ID `None` can hold events.
|
||||
|
||||
### Items
|
||||
|
||||
Items are all things that can "drop" for your game. This may be RPG items like
|
||||
weapons, could as well be technologies you normally research in a research tree.
|
||||
|
||||
Each item has a `name`, an `id` (can be known as "code"), and a classification.
|
||||
The most important classification is `progression` (formerly advancement).
|
||||
Progression items are items which a player may require to progress in
|
||||
their world. Progression items will be assigned to locations with higher
|
||||
priority and moved around to meet defined rules and accomplish progression
|
||||
balancing.
|
||||
|
||||
Special items with ID `None` can mark events (read below).
|
||||
|
||||
Other classifications include
|
||||
* filler: a regular item or trash item
|
||||
* useful: generally quite useful, but not required for anything logical
|
||||
* trap: negative impact on the player
|
||||
* skip_balancing: add to progression to skip balancing; e.g. currency or tokens
|
||||
|
||||
### Events
|
||||
|
||||
Events will mark some progress. You define an event location, an
|
||||
event item, strap some rules to the location (i.e. hold certain
|
||||
items) and manually place the event item at the event location.
|
||||
|
||||
Events can be used to either simplify the logic or to get better spoiler logs.
|
||||
Events will show up in the spoiler playthrough but they do not represent actual
|
||||
items or locations within the game.
|
||||
|
||||
There is one special case for events: Victory. To get the win condition to show
|
||||
up in the spoiler log, you create an event item and place it at an event
|
||||
location with the `access_rules` for game completion. Once that's done, the
|
||||
world's win condition can be as simple as checking for that item.
|
||||
|
||||
By convention the victory event is called `"Victory"`. It can be placed at one
|
||||
or more event locations based on player options.
|
||||
|
||||
### Regions
|
||||
|
||||
Regions are logical groups of locations that share some common access rules. If
|
||||
location logic is written from scratch, using regions greatly simplifies the
|
||||
definition and allow to somewhat easily implement things like entrance
|
||||
randomizer in logic.
|
||||
|
||||
Regions have a list called `exits` which are `Entrance` objects representing
|
||||
transitions to other regions.
|
||||
|
||||
There has to be one special region "Menu" from which the logic unfolds. AP
|
||||
assumes that a player will always be able to return to the "Menu" region by
|
||||
resetting the game ("Save and quit").
|
||||
|
||||
### Entrances
|
||||
|
||||
An `Entrance` connects to a region, is assigned to region's exits and has rules
|
||||
to define if it and thus the connected region is accessible.
|
||||
They can be static (regular logic) or be defined/connected during generation
|
||||
(entrance randomizer).
|
||||
|
||||
### Access Rules
|
||||
|
||||
An access rule is a function that returns `True` or `False` for a `Location` or
|
||||
`Entrance` based on the the current `state` (items that can be collected).
|
||||
|
||||
### Item Rules
|
||||
|
||||
An item rule is a function that returns `True` or `False` for a `Location` based
|
||||
on a single item. It can be used to reject placement of an item there.
|
||||
|
||||
|
||||
## Implementation
|
||||
|
||||
### Your World
|
||||
|
||||
All code for your world implementation should be placed in a python package in
|
||||
the `/worlds` directory. The starting point for the package is `__init.py__`.
|
||||
Conventionally, your world class is placed in that file.
|
||||
|
||||
World classes must inherit from the `World` class in `/worlds/AutoWorld.py`,
|
||||
which can be imported as `..AutoWorld.World` from your package.
|
||||
|
||||
AP will pick up your world automatically due to the `AutoWorld` implementation.
|
||||
|
||||
### Requirements
|
||||
|
||||
If your world needs specific python packages, they can be listed in
|
||||
`world/[world_name]/requirements.txt`.
|
||||
See [pip documentation](https://pip.pypa.io/en/stable/cli/pip_install/#requirements-file-format)
|
||||
|
||||
### Relative Imports
|
||||
|
||||
AP will only import the `__init__.py`. Depending on code size it makes sense to
|
||||
use multiple files and use relative imports to access them.
|
||||
|
||||
e.g. `from .Options import mygame_options` from your `__init__.py` will load
|
||||
`world/[world_name]/Options.py` and make its `mygame_options` accesible.
|
||||
|
||||
When imported names pile up it may be easier to use `from . import Options`
|
||||
and access the variable as `Options.mygame_options`.
|
||||
|
||||
### Your Item Type
|
||||
|
||||
Each world uses its own subclass of `BaseClasses.Item`. The constuctor can be
|
||||
overridden to attach additional data to it, e.g. "price in shop".
|
||||
Since the constructor is only ever called from your code, you can add whatever
|
||||
arguments you like to the constructor.
|
||||
|
||||
In its simplest form we only set the game name and use the default constuctor
|
||||
```python
|
||||
from BaseClasses import Item
|
||||
|
||||
class MyGameItem(Item):
|
||||
game: str = "My Game"
|
||||
```
|
||||
By convention this class definition will either be placed in your `__init__.py`
|
||||
or your `Items.py`. For a more elaborate example see `worlds/oot/Items.py`.
|
||||
|
||||
### Your location type
|
||||
|
||||
The same we have done for items above, we will do for locations
|
||||
```python
|
||||
from BaseClasses import Location
|
||||
|
||||
class MyGameLocation(Location):
|
||||
game: str = "My Game"
|
||||
|
||||
# override constructor to automatically mark event locations as such
|
||||
def __init__(self, player: int, name = '', code = None, parent = None):
|
||||
super(MyGameLocation, self).__init__(player, name, code, parent)
|
||||
self.event = code is None
|
||||
```
|
||||
in your `__init__.py` or your `Locations.py`.
|
||||
|
||||
### Options
|
||||
|
||||
By convention options are defined in `Options.py` and will be used when parsing
|
||||
the players' yaml files.
|
||||
|
||||
Each option has its own class, inherits from a base option type, has a docstring
|
||||
to describe it and a `display_name` property for display on the website and in
|
||||
spoiler logs.
|
||||
|
||||
The actual name as used in the yaml is defined in a `dict[str, Option]`, that is
|
||||
assigned to the world under `self.options`.
|
||||
|
||||
Common option types are `Toggle`, `DefaultOnToggle`, `Choice`, `Range`.
|
||||
For more see `Options.py` in AP's base directory.
|
||||
|
||||
#### Toggle, DefaultOnToggle
|
||||
|
||||
Those don't need any additional properties defined. After parsing the option,
|
||||
its `value` will either be True or False.
|
||||
|
||||
#### Range
|
||||
|
||||
Define properties `range_start`, `range_end` and `default`. Ranges will be
|
||||
displayed as sliders on the website and can be set to random in the yaml.
|
||||
|
||||
#### Choice
|
||||
|
||||
Choices are like toggles, but have more options than just True and False.
|
||||
Define a property `option_<name> = <number>` per selectable value and
|
||||
`default = <number>` to set the default selection. Aliases can be set by
|
||||
defining a property `alias_<name> = <same number>`.
|
||||
|
||||
One special case where aliases are required is when option name is `yes`, `no`,
|
||||
`on` or `off` because they parse to `True` or `False`:
|
||||
```python
|
||||
option_off = 0
|
||||
option_on = 1
|
||||
option_some = 2
|
||||
alias_false = 0
|
||||
alias_true = 1
|
||||
default = 0
|
||||
```
|
||||
|
||||
#### Sample
|
||||
```python
|
||||
# Options.py
|
||||
|
||||
from Options import Toggle, Range, Choice, Option
|
||||
import typing
|
||||
|
||||
class Difficulty(Choice):
|
||||
"""Sets overall game difficulty."""
|
||||
display_name = "Difficulty"
|
||||
option_easy = 0
|
||||
option_normal = 1
|
||||
option_hard = 2
|
||||
alias_beginner = 0 # same as easy
|
||||
alias_expert = 2 # same as hard
|
||||
default = 1 # default to normal
|
||||
|
||||
class FinalBossHP(Range):
|
||||
"""Sets the HP of the final boss"""
|
||||
display_name = "Final Boss HP"
|
||||
range_start = 100
|
||||
range_end = 10000
|
||||
default = 2000
|
||||
|
||||
class FixXYZGlitch(Toggle):
|
||||
"""Fixes ABC when you do XYZ"""
|
||||
display_name = "Fix XYZ Glitch"
|
||||
|
||||
# By convention we call the options dict variable `<world>_options`.
|
||||
mygame_options: typing.Dict[str, type(Option)] = {
|
||||
"difficulty": Difficulty,
|
||||
"final_boss_hp": FinalBossHP,
|
||||
"fix_xyz_glitch": FixXYZGlitch
|
||||
}
|
||||
```
|
||||
```python
|
||||
# __init__.py
|
||||
|
||||
from ..AutoWorld import World
|
||||
from .Options import mygame_options # import the options dict
|
||||
|
||||
class MyGameWorld(World):
|
||||
#...
|
||||
options = mygame_options # assign the options dict to the world
|
||||
#...
|
||||
```
|
||||
|
||||
### Local or Remote
|
||||
|
||||
A world with `remote_items` set to `True` gets all items items from the server
|
||||
and no item from the local game. So for an RPG opening a chest would not add
|
||||
any item to your inventory, instead the server will send you what was in that
|
||||
chest. The advantage is that a generic mod can be used that does not need to
|
||||
know anything about the seed.
|
||||
|
||||
A world with `remote_items` set to `False` will locally reward its local items.
|
||||
For console games this can remove delay and make script/animation/dialog flow
|
||||
more natural. These games typically have been edited to 'bake in' the items.
|
||||
|
||||
### A World Class Skeleton
|
||||
|
||||
```python
|
||||
# world/mygame/__init__.py
|
||||
|
||||
from .Options import mygame_options # the options we defined earlier
|
||||
from .Items import mygame_items # data used below to add items to the World
|
||||
from .Locations import mygame_locations # same as above
|
||||
from ..AutoWorld import World
|
||||
from BaseClasses import Region, Location, Entrance, Item, RegionType, ItemClassification
|
||||
from Utils import get_options, output_path
|
||||
|
||||
class MyGameItem(Item): # or from Items import MyGameItem
|
||||
game = "My Game" # name of the game/world this item is from
|
||||
|
||||
class MyGameLocation(Location): # or from Locations import MyGameLocation
|
||||
game = "My Game" # name of the game/world this location is in
|
||||
|
||||
class MyGameWorld(World):
|
||||
"""Insert description of the world/game here."""
|
||||
game: str = "My Game" # name of the game/world
|
||||
options = mygame_options # options the player can set
|
||||
topology_present: bool = True # show path to required location checks in spoiler
|
||||
remote_items: bool = False # True if all items come from the server
|
||||
remote_start_inventory: bool = False # True if start inventory comes from the server
|
||||
|
||||
# data_version is used to signal that items, locations or their names
|
||||
# changed. Set this to 0 during development so other games' clients do not
|
||||
# cache any texts, then increase by 1 for each release that makes changes.
|
||||
data_version = 0
|
||||
|
||||
# ID of first item and location, could be hard-coded but code may be easier
|
||||
# to read with this as a propery.
|
||||
base_id = 1234
|
||||
# Instead of dynamic numbering, IDs could be part of data.
|
||||
|
||||
# The following two dicts are required for the generation to know which
|
||||
# items exist. They could be generated from json or something else. They can
|
||||
# include events, but don't have to since events will be placed manually.
|
||||
item_name_to_id = {name: id for
|
||||
id, name in enumerate(mygame_items, base_id)}
|
||||
location_name_to_id = {name: id for
|
||||
id, name in enumerate(mygame_locations, base_id)}
|
||||
|
||||
# Items can be grouped using their names to allow easy checking if any item
|
||||
# from that group has been collected. Group names can also be used for !hint
|
||||
item_name_groups = {
|
||||
"weapons": {"sword", "lance"}
|
||||
}
|
||||
```
|
||||
|
||||
### Generation
|
||||
|
||||
The world has to provide the following things for generation
|
||||
|
||||
* the properties mentioned above
|
||||
* additions to the item pool
|
||||
* additions to the regions list: at least one called "Menu"
|
||||
* locations placed inside those regions
|
||||
* a `def create_item(self, item: str) -> MyGameItem` for plando/manual placing
|
||||
* applying `self.world.precollected_items` for plando/start inventory
|
||||
if not using a `remote_start_inventory`
|
||||
* a `def generate_output(self, output_directory: str)` that creates the output
|
||||
if there is output to be generated. If only items are randomized and
|
||||
`remote_items = True` it is possible to have a generic mod and output
|
||||
generation can be skipped. In all other cases this is required. When this is
|
||||
called, `self.world.get_locations()` has all locations for all players, with
|
||||
properties `item` pointing to the item and `player` identifying the player.
|
||||
`self.world.get_filled_locations(self.player)` will filter for this world.
|
||||
`item.player` can be used to see if it's a local item.
|
||||
|
||||
In addition, the following methods can be implemented and attributes can be set
|
||||
|
||||
* `def generate_early(self)`
|
||||
called per player before any items or locations are created. You can set
|
||||
properties on your world here. Already has access to player options and RNG.
|
||||
* `def create_regions(self)`
|
||||
called to place player's regions into the MultiWorld's regions list. If it's
|
||||
hard to separate, this can be done during `generate_early` or `basic` as well.
|
||||
* `def create_items(self)`
|
||||
called to place player's items into the MultiWorld's itempool.
|
||||
* `def set_rules(self)`
|
||||
called to set access and item rules on locations and entrances.
|
||||
* `def generate_basic(self)`
|
||||
called after the previous steps. Some placement and player specific
|
||||
randomizations can be done here. After this step all regions and items have
|
||||
to be in the MultiWorld's regions and itempool.
|
||||
* `pre_fill`, `fill_hook` and `post_fill` are called to modify item placement
|
||||
before, during and after the regular fill process, before `generate_output`.
|
||||
* `fill_slot_data` and `modify_multidata` can be used to modify the data that
|
||||
will be used by the server to host the MultiWorld.
|
||||
* `required_client_version: Tuple(int, int, int)`
|
||||
Client version as tuple of 3 ints to make sure the client is compatible to
|
||||
this world (e.g. implements all required features) when connecting.
|
||||
* `assert_generate(cls, world)` is a class method called at the start of
|
||||
generation to check the existence of prerequisite files, usually a ROM for
|
||||
games which require one.
|
||||
|
||||
#### generate_early
|
||||
|
||||
```python
|
||||
def generate_early(self) -> None:
|
||||
# read player settings to world instance
|
||||
self.final_boss_hp = self.world.final_boss_hp[self.player].value
|
||||
```
|
||||
|
||||
#### create_item
|
||||
|
||||
```python
|
||||
# we need a way to know if an item provides progress in the game ("key item")
|
||||
# this can be part of the items definition, or depend on recipe randomization
|
||||
from .Items import is_progression # this is just a dummy
|
||||
|
||||
def create_item(self, item: str):
|
||||
# This is called when AP wants to create an item by name (for plando) or
|
||||
# when you call it from your own code.
|
||||
classification = ItemClassification.progression if is_progression(item) else \
|
||||
ItemClassification.filler
|
||||
return MyGameItem(item, classification, self.item_name_to_id[item],
|
||||
self.player)
|
||||
|
||||
def create_event(self, event: str):
|
||||
# while we are at it, we can also add a helper to create events
|
||||
return MyGameItem(event, True, None, self.player)
|
||||
```
|
||||
|
||||
#### create_items
|
||||
|
||||
```python
|
||||
def create_items(self) -> None:
|
||||
# Add items to the Multiworld.
|
||||
# If there are two of the same item, the item has to be twice in the pool.
|
||||
# Which items are added to the pool may depend on player settings,
|
||||
# e.g. custom win condition like triforce hunt.
|
||||
# Having an item in the start inventory won't remove it from the pool.
|
||||
# If an item can't have duplicates it has to be excluded manually.
|
||||
|
||||
# List of items to exclude, as a copy since it will be destroyed below
|
||||
exclude = [item for item in self.world.precollected_items[self.player]]
|
||||
|
||||
for item in map(self.create_item, mygame_items):
|
||||
if item in exclude:
|
||||
exclude.remove(item) # this is destructive. create unique list above
|
||||
self.world.itempool.append(self.create_item('nothing'))
|
||||
else:
|
||||
self.world.itempool.append(item)
|
||||
|
||||
# itempool and number of locations should match up.
|
||||
# If this is not the case we want to fill the itempool with junk.
|
||||
junk = 0 # calculate this based on player settings
|
||||
self.world.itempool += [self.create_item('nothing') for _ in range(junk)]
|
||||
```
|
||||
|
||||
#### create_regions
|
||||
|
||||
```python
|
||||
def create_regions(self) -> None:
|
||||
# Add regions to the multiworld. "Menu" is the required starting point.
|
||||
# Arguments to Region() are name, type, human_readable_name, player, world
|
||||
r = Region("Menu", RegionType.Generic, "Menu", self.player, self.world)
|
||||
# Set Region.exits to a list of entrances that are reachable from region
|
||||
r.exits = [Entrance(self.player, "New game", r)] # or use r.exits.append
|
||||
# Append region to MultiWorld's regions
|
||||
self.world.regions.append(r) # or use += [r...]
|
||||
|
||||
r = Region("Main Area", RegionType.Generic, "Main Area", self.player, self.world)
|
||||
# Add main area's locations to main area (all but final boss)
|
||||
r.locations = [MyGameLocation(self.player, location.name,
|
||||
self.location_name_to_id[location.name], r)]
|
||||
r.exits = [Entrance(self.player, "Boss Door", r)]
|
||||
self.world.regions.append(r)
|
||||
|
||||
r = Region("Boss Room", RegionType.Generic, "Boss Room", self.player, self.world)
|
||||
# add event to Boss Room
|
||||
r.locations = [MyGameLocation(self.player, "Final Boss", None, r)]
|
||||
self.world.regions.append(r)
|
||||
|
||||
# If entrances are not randomized, they should be connected here, otherwise
|
||||
# they can also be connected at a later stage.
|
||||
self.world.get_entrance("New Game", self.player)\
|
||||
.connect(self.world.get_region("Main Area", self.player))
|
||||
self.world.get_entrance("Boss Door", self.player)\
|
||||
.connect(self.world.get_region("Boss Room", self.player))
|
||||
|
||||
# If setting location access rules from data is easier here, set_rules can
|
||||
# possibly omitted.
|
||||
```
|
||||
|
||||
#### generate_basic
|
||||
|
||||
```python
|
||||
def generate_basic(self) -> None:
|
||||
# place "Victory" at "Final Boss" and set collection as win condition
|
||||
self.world.get_location("Final Boss", self.player)\
|
||||
.place_locked_item(self.create_event("Victory"))
|
||||
self.world.completion_condition[self.player] = \
|
||||
lambda state: state.has("Victory", self.player)
|
||||
|
||||
# place item Herb into location Chest1 for some reason
|
||||
item = self.create_item("Herb")
|
||||
self.world.get_location("Chest1", self.player).place_locked_item(item)
|
||||
# in most cases it's better to do this at the same time the itempool is
|
||||
# filled to avoid accidental duplicates:
|
||||
# manually placed and still in the itempool
|
||||
```
|
||||
|
||||
### Setting Rules
|
||||
|
||||
```python
|
||||
from ..generic.Rules import add_rule, set_rule, forbid_item
|
||||
from Items import get_item_type
|
||||
|
||||
def set_rules(self) -> None:
|
||||
# For some worlds this step can be omitted if either a Logic mixin
|
||||
# (see below) is used, it's easier to apply the rules from data during
|
||||
# location generation or everything is in generate_basic
|
||||
|
||||
# set a simple rule for an region
|
||||
set_rule(self.world.get_entrance("Boss Door", self.player),
|
||||
lambda state: state.has("Boss Key", self.player))
|
||||
# combine rules to require two items
|
||||
add_rule(self.world.get_location("Chest2", self.player),
|
||||
lambda state: state.has("Sword", self.player))
|
||||
add_rule(self.world.get_location("Chest2", self.player),
|
||||
lambda state: state.has("Shield", self.player))
|
||||
# or simply combine yourself
|
||||
set_rule(self.world.get_location("Chest2", self.player),
|
||||
lambda state: state.has("Sword", self.player) and
|
||||
state.has("Shield", self.player))
|
||||
# require two of an item
|
||||
set_rule(self.world.get_location("Chest3", self.player),
|
||||
lambda state: state.has("Key", self.player, 2))
|
||||
# require one item from an item group
|
||||
add_rule(self.world.get_location("Chest3", self.player),
|
||||
lambda state: state.has_group("weapons", self.player))
|
||||
# state also has .item_count() for items, .has_any() and.has_all() for sets
|
||||
# and .count_group() for groups
|
||||
# set_rule is likely to be a bit faster than add_rule
|
||||
|
||||
# disallow placing a specific local item at a specific location
|
||||
forbid_item(self.world.get_location("Chest4", self.player), "Sword")
|
||||
# disallow placing items with a specific property
|
||||
add_item_rule(self.world.get_location("Chest5", self.player),
|
||||
lambda item: get_item_type(item) == "weapon")
|
||||
# get_item_type needs to take player/world into account
|
||||
# if MyGameItem has a type property, a more direct implementation would be
|
||||
add_item_rule(self.world.get_location("Chest5", self.player),
|
||||
lambda item: item.player != self.player or\
|
||||
item.my_type == "weapon")
|
||||
# location.item_rule = ... is likely to be a bit faster
|
||||
```
|
||||
|
||||
### Logic Mixin
|
||||
|
||||
While lambdas and events could do pretty much anything, by convention we
|
||||
implement more complex logic in logic mixins, even if there is no need to add
|
||||
properties to the `BaseClasses.CollectionState` state object.
|
||||
|
||||
When importing a file that defines a class that inherits from
|
||||
`..AutoWorld.LogicMixin` the state object's class is automatically extended by
|
||||
the mixin's members. These members should be prefixed with underscore following
|
||||
the name of the implementing world. This is due to sharing a namespace with all
|
||||
other logic mixins.
|
||||
|
||||
Typical uses are defining methods that are used instead of `state.has`
|
||||
in lambdas, e.g.`state._mygame_has(custom, world, player)` or recurring checks
|
||||
like `state._mygame_can_do_something(world, player)` to simplify lambdas.
|
||||
|
||||
More advanced uses could be to add additional variables to the state object,
|
||||
override `World.collect(self, state, item)` and `remove(self, state, item)`
|
||||
to update the state object, and check those added variables in added methods.
|
||||
Please do this with caution and only when neccessary.
|
||||
|
||||
#### Sample
|
||||
|
||||
```python
|
||||
# Logic.py
|
||||
|
||||
from ..AutoWorld import LogicMixin
|
||||
|
||||
class MyGameLogic(LogicMixin):
|
||||
def _mygame_has_key(self, world: MultiWorld, player: int):
|
||||
# Arguments above are free to choose
|
||||
# it may make sense to use World as argument instead of MultiWorld
|
||||
return self.has('key', player) # or whatever
|
||||
```
|
||||
```python
|
||||
# __init__.py
|
||||
|
||||
from ..generic.Rules import set_rule
|
||||
import .Logic # apply the mixin by importing its file
|
||||
|
||||
class MyGameWorld(World):
|
||||
# ...
|
||||
def set_rules(self):
|
||||
set_rule(self.world.get_location("A Door", self.player),
|
||||
lamda state: state._mygame_has_key(self.world, self.player))
|
||||
```
|
||||
|
||||
### Generate Output
|
||||
|
||||
```python
|
||||
from .Mod import generate_mod
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
# How to generate the mod or ROM highly depends on the game
|
||||
# if the mod is written in Lua, Jinja can be used to fill a template
|
||||
# if the mod reads a json file, `json.dump()` can be used to generate that
|
||||
# code below is a dummy
|
||||
data = {
|
||||
"seed": self.world.seed_name, # to verify the server's multiworld
|
||||
"slot": self.world.player_name[self.player], # to connect to server
|
||||
"items": {location.name: location.item.name
|
||||
if location.item.player == self.player else "Remote"
|
||||
for location in self.world.get_filled_locations(self.player)},
|
||||
# store start_inventory from player's .yaml
|
||||
"starter_items": [item.name for item
|
||||
in self.world.precollected_items[self.player]],
|
||||
"final_boss_hp": self.final_boss_hp,
|
||||
# store option name "easy", "normal" or "hard" for difficuly
|
||||
"difficulty": self.world.difficulty[self.player].current_key,
|
||||
# store option value True or False for fixing a glitch
|
||||
"fix_xyz_glitch": self.world.fix_xyz_glitch[self.player].value
|
||||
}
|
||||
# point to a ROM specified by the installation
|
||||
src = Utils.get_options()["mygame_options"]["rom_file"]
|
||||
# or point to worlds/mygame/data/mod_template
|
||||
src = os.path.join(os.path.dirname(__file__), "data", "mod_template")
|
||||
# generate output path
|
||||
mod_name = f"AP-{self.world.seed_name}-P{self.player}-{self.world.player_name[self.player]}"
|
||||
out_file = os.path.join(output_directory, mod_name + ".zip")
|
||||
# generate the file
|
||||
generate_mod(src, out_file, data)
|
||||
```
|
||||
|
||||
BIN
docs/sphinx/_static/archipelago-world-directory-example.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
docs/sphinx/_static/creepy-castle-directory.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/sphinx/_static/example-init-py-file.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
docs/sphinx/_static/example-items-py-file.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
docs/sphinx/_static/example-locations-py-file.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
docs/sphinx/_static/example-options-py-file.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
docs/sphinx/_static/example-regions-py-file.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
docs/sphinx/_static/example-rules-py-file.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
docs/sphinx/_static/gato-roboto-directory.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
docs/sphinx/_static/heavy-bullets-data-directory.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/sphinx/_static/heavy-bullets-directory.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/sphinx/_static/heavy-bullets-managed-directory.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
docs/sphinx/_static/stardew-valley-directory.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
docs/sphinx/_static/theme_dirt.JPG
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
docs/sphinx/_static/theme_grass.JPG
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
docs/sphinx/_static/theme_grassFlowers.JPG
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
docs/sphinx/_static/theme_ice.JPG
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
docs/sphinx/_static/theme_jungle.JPG
Normal file
|
After Width: | Height: | Size: 257 KiB |
BIN
docs/sphinx/_static/theme_ocean.JPG
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
docs/sphinx/_static/theme_partyTime.JPG
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
docs/sphinx/_static/theme_stone.JPG
Normal file
|
After Width: | Height: | Size: 193 KiB |
55
docs/sphinx/conf.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'Archipelago'
|
||||
copyright = '2022, Archipelago Team and Contributors'
|
||||
author = 'Archipelago Team and Contributors'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"myst_parser",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.autosummary",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
myst_heading_anchors = 3
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
26
docs/sphinx/index.md
Normal file
@@ -0,0 +1,26 @@
|
||||
% Archipelago documentation master file, created by
|
||||
% sphinx-quickstart on Wed Jul 6 20:09:51 2022.
|
||||
% You can adapt this file completely to your liking, but it should at least
|
||||
% contain the root `toctree` directive.
|
||||
|
||||
Welcome to Archipelago's documentation!
|
||||
=======================================
|
||||
|
||||
```{toctree}
|
||||
---
|
||||
maxdepth: 2
|
||||
caption: "Contents:"
|
||||
---
|
||||
AddingGames
|
||||
WorldAPI
|
||||
NetworkProtocol
|
||||
```
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* {ref}`genindex`
|
||||
* {ref}`modindex`
|
||||
* {ref}`search`
|
||||
35
docs/sphinx/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
4
docs/sphinx/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
sphinx==5.0.2
|
||||
sphinx_rtd_theme==1.0.0
|
||||
readthedocs-sphinx-search==0.1.2
|
||||
myst-parser==0.18
|
||||