Merge tag '0.4.4' into HEAD
@@ -46,12 +46,21 @@
|
||||
# DOOM 1993
|
||||
/worlds/doom_1993/ @Daivuk
|
||||
|
||||
# DOOM II
|
||||
/worlds/doom_ii/ @Daivuk
|
||||
|
||||
# Factorio
|
||||
/worlds/factorio/ @Berserker66
|
||||
|
||||
# Final Fantasy
|
||||
/worlds/ff1/ @jtoyoda
|
||||
|
||||
# Final Fantasy Mystic Quest
|
||||
/worlds/ffmq/ @Alchav @wildham0
|
||||
|
||||
# Heretic
|
||||
/worlds/heretic/ @Daivuk
|
||||
|
||||
# Hollow Knight
|
||||
/worlds/hk/ @BadMagic100 @ThePhar
|
||||
|
||||
@@ -61,6 +70,12 @@
|
||||
# Kingdom Hearts 2
|
||||
/worlds/kh2/ @JaredWeakStrike
|
||||
|
||||
# Landstalker: The Treasures of King Nole
|
||||
/worlds/landstalker/ @Dinopony
|
||||
|
||||
# Lingo
|
||||
/worlds/lingo/ @hatkirby
|
||||
|
||||
# Links Awakening DX
|
||||
/worlds/ladx/ @zig-for
|
||||
|
||||
@@ -92,6 +107,9 @@
|
||||
# Overcooked! 2
|
||||
/worlds/overcooked2/ @toasterparty
|
||||
|
||||
# Pokemon Emerald
|
||||
/worlds/pokemon_emerald/ @Zunawe
|
||||
|
||||
# Pokemon Red and Blue
|
||||
/worlds/pokemon_rb/ @Alchav
|
||||
|
||||
@@ -104,6 +122,9 @@
|
||||
# Risk of Rain 2
|
||||
/worlds/ror2/ @kindasneaki
|
||||
|
||||
# Shivers
|
||||
/worlds/shivers/ @GodlFire
|
||||
|
||||
# Sonic Adventure 2 Battle
|
||||
/worlds/sa2b/ @PoryGone @RaspberrySpace
|
||||
|
||||
|
||||
@@ -1,214 +1,206 @@
|
||||
# How do I add a game to Archipelago?
|
||||
|
||||
|
||||
# How do I add a game 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
|
||||
There are two key steps to incorporating a game into Archipelago:
|
||||
|
||||
- Game Modification
|
||||
- Archipelago Server Integration
|
||||
|
||||
Refer to the following documents as well:
|
||||
- [network protocol.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/network%20protocol.md) for network communication between client and server.
|
||||
- [world api.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md) for documentation on server side code and creating a world package.
|
||||
|
||||
- [network protocol.md](/docs/network%20protocol.md) for network communication between client and server.
|
||||
- [world api.md](/docs/world%20api.md) for documentation on server side code and creating a world package.
|
||||
|
||||
# Game Modification
|
||||
One half of the work required to integrate a game into Archipelago is the development of the game client. This is
|
||||
# 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.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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. If you've identified the game as a Unity game, some useful tools
|
||||
and information to help you on your journey can be found at this
|
||||
[Unity Game Hacking guide.](https://github.com/imadr/Unity-game-hacking)
|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
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. Many games made in C# can be modified using the same tools found in our Unity game hacking toolset; namely BepInEx
|
||||
and MonoMod.
|
||||
|
||||
### 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!
|
||||
|
||||
|
||||

|
||||
|
||||
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. For
|
||||
modifying GameMaker games the [Undertale Mod Tool](https://github.com/krzys-h/UndertaleModTool) is incredibly helpful.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
## 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).
|
||||
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.
|
||||
|
||||
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.
|
||||
### Other helpful tools
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
The tool itself is highly complex and even I have not yet charted its expanses.
|
||||
#### [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, releasing, 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.
|
||||
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:
|
||||
|
||||
- Know when the player has checked a location, and react accordingly
|
||||
- Be able to receive items from the server on the fly
|
||||
- Keep an index for items received in order to resync from disconnections
|
||||
- Add interface for connecting to the Archipelago server with passwords and sessions
|
||||
- Add commands for manually rewarding, re-syncing, releasing, and other actions
|
||||
|
||||
Refer to the [Network Protocol documentation](/docs/network%20protocol.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
|
||||
|
||||
Refer to the [Network Protocol documentation](./network%20protocol.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.
|
||||
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.
|
||||
|
||||
### 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
@@ -217,6 +209,7 @@ Only a bsdiff module is integrated into AP. If the final patch requires or is ba
|
||||
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.
|
||||
@@ -224,121 +217,53 @@ bsdiff between the original and the randomized ROM.
|
||||
To make using APBP easy, they can be generated by inheriting from `worlds.Files.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.
|
||||
generated per seed. If at all possible, it's generally best practice to collect your world information from `slot_data`
|
||||
so that the users don't have to move files around in order to play.
|
||||
|
||||
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 `worlds.Files.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.
|
||||
In order for your game to communicate with the Archipelago server and generate the necessary randomized information,
|
||||
you must create a world package in the main Archipelago repo. This section will cover the requisites and expectations
|
||||
and show the basics of a world. More in depth documentation on the available API can be read in
|
||||
the [world api doc.](/docs/world%20api.md)
|
||||
For setting up your working environment with Archipelago refer
|
||||
to [running from source](/docs/running%20from%20source.md) and the [style guide](/docs/style.md).
|
||||
|
||||
Here’s an example of what your world module can look like:
|
||||
|
||||

|
||||
### Requirements
|
||||
|
||||
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.
|
||||
A world implementation requires a few key things from its implementation
|
||||
|
||||
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.
|
||||
Make sure to check out [world maintainer.md](./world%20maintainer.md) before publishing.
|
||||
- A folder within `worlds` that contains an `__init__.py`
|
||||
- This is what defines it as a Python package and how it's able to be imported
|
||||
into Archipelago's generation system. During generation time only code that is
|
||||
defined within this file will be run. It's suggested to split up your information
|
||||
into more files to improve readability, but all of that information can be
|
||||
imported at its base level within your world.
|
||||
- A `World` subclass where you create your world and define all of its rules
|
||||
and the following requirements:
|
||||
- Your items and locations need a `item_name_to_id` and `location_name_to_id`,
|
||||
respectively, mapping.
|
||||
- An `option_definitions` mapping of your game options with the format
|
||||
`{name: Class}`, where `name` uses Python snake_case.
|
||||
- You must define your world's `create_item` method, because this may be called
|
||||
by the generator in certain circumstances
|
||||
- When creating your world you submit items and regions to the Multiworld.
|
||||
- These are lists of said objects which you can access at
|
||||
`self.multiworld.itempool` and `self.multiworld.regions`. Best practice for
|
||||
adding to these lists is with either `append` or `extend`, where `append` is a
|
||||
single object and `extend` is a list.
|
||||
- Do not use `=` as this will delete other worlds' items and regions.
|
||||
- Regions are containers for holding your world's Locations.
|
||||
- Locations are where players will "check" for items and must exist within
|
||||
a region. It's also important for your world's submitted items to be the same as
|
||||
its submitted locations count.
|
||||
- You must always have a "Menu" Region from which the generation algorithm
|
||||
uses to enter the game and access locations.
|
||||
- Make sure to check out [world maintainer.md](/docs/world%20maintainer.md) before publishing.
|
||||
@@ -29,6 +29,7 @@ The zip can contain arbitrary files in addition what was specified above.
|
||||
|
||||
## Caveats
|
||||
|
||||
Imports from other files inside the apworld have to use relative imports.
|
||||
Imports from other files inside the apworld have to use relative imports. e.g. `from .options import MyGameOptions`
|
||||
|
||||
Imports from AP base have to use absolute imports, e.g. Options.py and worlds/AutoWorld.py.
|
||||
Imports from AP base have to use absolute imports, e.g. `from Options import Toggle` or
|
||||
`from worlds.AutoWorld import World`
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
# Contributing
|
||||
Contributions are welcome. We have a few requests of any new contributors.
|
||||
Contributions are welcome. We have a few requests for new contributors:
|
||||
|
||||
* Ensure that all changes which affect logic are covered by unit tests.
|
||||
* Do not introduce any unit test failures/regressions.
|
||||
* Follow styling as designated in our [styling documentation](/docs/style.md).
|
||||
* **Follow styling guidelines.**
|
||||
Please take a look at the [code style documentation](/docs/style.md)
|
||||
to ensure ease of communication and uniformity.
|
||||
|
||||
Otherwise, we tend to judge code on a case to case basis.
|
||||
* **Ensure that critical changes are covered by tests.**
|
||||
It is strongly recommended that unit tests are used to avoid regression and to ensure everything is still working.
|
||||
If you wish to contribute by adding a new game, please take a look at the [logic unit test documentation](/docs/tests.md).
|
||||
If you wish to contribute to the website, please take a look at [these tests](/test/webhost).
|
||||
|
||||
For adding a new game to Archipelago and other documentation on how Archipelago functions, please see
|
||||
[the docs folder](/docs/) for the relevant information and feel free to ask any questions in the #archipelago-dev
|
||||
channel in our [Discord](https://archipelago.gg/discord).
|
||||
If you want to merge a new game, please make sure to read the responsibilities as
|
||||
[world maintainer](/docs/world%20maintainer.md).
|
||||
* **Do not introduce unit test failures/regressions.**
|
||||
Archipelago supports multiple versions of Python. You may need to download older Python versions to fully test
|
||||
your changes. Currently, the oldest supported version is [Python 3.8](https://www.python.org/downloads/release/python-380/).
|
||||
It is recommended that automated github actions are turned on in your fork to have github run all of the unit tests after pushing.
|
||||
You can turn them on here:
|
||||

|
||||
|
||||
Other than these requests, we tend to judge code on a case by case basis.
|
||||
|
||||
For contribution to the website, please refer to the [WebHost README](/WebHostLib/README.md).
|
||||
|
||||
If you want to contribute to the core, you will be subject to stricter review on your pull requests. It is recommended
|
||||
that you get in touch with other core maintainers via the [Discord](https://archipelago.gg/discord).
|
||||
|
||||
If you want to add Archipelago support for a new game, please take a look at the [adding games documentation](/docs/adding%20games.md), which details what is required
|
||||
to implement support for a game, as well as tips for how to get started.
|
||||
If you want to merge a new game into the main Archipelago repo, please make sure to read the responsibilities as a
|
||||
[world maintainer](/docs/world%20maintainer.md).
|
||||
|
||||
For other questions, feel free to explore the [main documentation folder](/docs/) and ask us questions in the #archipelago-dev channel
|
||||
of the [Discord](https://archipelago.gg/discord).
|
||||
|
||||
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 83 KiB |
BIN
docs/img/github-actions-example.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 82 KiB |
@@ -380,11 +380,13 @@ Additional arguments sent in this package will also be added to the [Retrieved](
|
||||
|
||||
Some special keys exist with specific return data, all of them have the prefix `_read_`, so `hints_{team}_{slot}` is `_read_hints_{team}_{slot}`.
|
||||
|
||||
| Name | Type | Notes |
|
||||
|-------------------------------|--------------------------|---------------------------------------------------|
|
||||
| hints_{team}_{slot} | list\[[Hint](#Hint)\] | All Hints belonging to the requested Player. |
|
||||
| slot_data_{slot} | dict\[str, any\] | slot_data belonging to the requested slot. |
|
||||
| item_name_groups_{game_name} | dict\[str, list\[str\]\] | item_name_groups belonging to the requested game. |
|
||||
| Name | Type | Notes |
|
||||
|----------------------------------|-------------------------------|-------------------------------------------------------|
|
||||
| hints_{team}_{slot} | list\[[Hint](#Hint)\] | All Hints belonging to the requested Player. |
|
||||
| slot_data_{slot} | dict\[str, any\] | slot_data belonging to the requested slot. |
|
||||
| item_name_groups_{game_name} | dict\[str, list\[str\]\] | item_name_groups belonging to the requested game. |
|
||||
| location_name_groups_{game_name} | dict\[str, list\[str\]\] | location_name_groups belonging to the requested game. |
|
||||
| client_status_{team}_{slot} | [ClientStatus](#ClientStatus) | The current game status of the requested player. |
|
||||
|
||||
### 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.
|
||||
@@ -415,6 +417,8 @@ The following operations can be applied to a datastorage key
|
||||
| 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`. |
|
||||
| floor | Floors the current value (`value` is ignored). |
|
||||
| ceil | Ceils the current value (`value` is ignored). |
|
||||
| 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`. |
|
||||
@@ -556,7 +560,7 @@ Color options:
|
||||
`player` marks owning player id for location/item,
|
||||
`flags` contains the [NetworkItem](#NetworkItem) flags that belong to the item
|
||||
|
||||
### Client States
|
||||
### ClientStatus
|
||||
An enumeration containing the possible client states that may be used to inform
|
||||
the server in [StatusUpdate](#StatusUpdate). The MultiServer automatically sets
|
||||
the client state to `ClientStatus.CLIENT_CONNECTED` on the first active connection
|
||||
|
||||
@@ -28,19 +28,23 @@ Choice, and defining `alias_true = option_full`.
|
||||
and is reserved by AP. You can set this as your default value, but you cannot define your own `option_random`.
|
||||
|
||||
As an example, suppose we want an option that lets the user start their game with a sword in their inventory. Let's
|
||||
create our option class (with a docstring), give it a `display_name`, and add it to a dictionary that keeps track of our
|
||||
options:
|
||||
create our option class (with a docstring), give it a `display_name`, and add it to our game's options dataclass:
|
||||
|
||||
```python
|
||||
# Options.py
|
||||
# options.py
|
||||
from dataclasses import dataclass
|
||||
|
||||
from Options import Toggle, PerGameCommonOptions
|
||||
|
||||
|
||||
class StartingSword(Toggle):
|
||||
"""Adds a sword to your starting inventory."""
|
||||
display_name = "Start With Sword"
|
||||
|
||||
|
||||
example_options = {
|
||||
"starting_sword": StartingSword
|
||||
}
|
||||
@dataclass
|
||||
class ExampleGameOptions(PerGameCommonOptions):
|
||||
starting_sword: StartingSword
|
||||
```
|
||||
|
||||
This will create a `Toggle` option, internally called `starting_sword`. To then submit this to the multiworld, we add it
|
||||
@@ -48,29 +52,58 @@ to our world's `__init__.py`:
|
||||
|
||||
```python
|
||||
from worlds.AutoWorld import World
|
||||
from .Options import options
|
||||
from .Options import ExampleGameOptions
|
||||
|
||||
|
||||
class ExampleWorld(World):
|
||||
option_definitions = options
|
||||
# this gives the generator all the definitions for our options
|
||||
options_dataclass = ExampleGameOptions
|
||||
# this gives us typing hints for all the options we defined
|
||||
options: ExampleGameOptions
|
||||
```
|
||||
|
||||
### Option Checking
|
||||
Options are parsed by `Generate.py` before the worlds are created, and then the option classes are created shortly after
|
||||
world instantiation. These are created as attributes on the MultiWorld and can be accessed with
|
||||
`self.multiworld.my_option_name[self.player]`. This is the option class, which supports direct comparison methods to
|
||||
`self.options.my_option_name`. This is an instance of the option class, which supports direct comparison methods to
|
||||
relevant objects (like comparing a Toggle class to a `bool`). If you need to access the option result directly, this is
|
||||
the option class's `value` attribute. For our example above we can do a simple check:
|
||||
```python
|
||||
if self.multiworld.starting_sword[self.player]:
|
||||
if self.options.starting_sword:
|
||||
do_some_things()
|
||||
```
|
||||
|
||||
or if I need a boolean object, such as in my slot_data I can access it as:
|
||||
```python
|
||||
start_with_sword = bool(self.multiworld.starting_sword[self.player].value)
|
||||
start_with_sword = bool(self.options.starting_sword.value)
|
||||
```
|
||||
All numeric options (i.e. Toggle, Choice, Range) can be compared to integers, strings that match their attributes,
|
||||
strings that match the option attributes after "option_" is stripped, and the attributes themselves.
|
||||
```python
|
||||
# options.py
|
||||
class Logic(Choice):
|
||||
option_normal = 0
|
||||
option_hard = 1
|
||||
option_challenging = 2
|
||||
option_extreme = 3
|
||||
option_insane = 4
|
||||
alias_extra_hard = 2
|
||||
crazy = 4 # won't be listed as an option and only exists as an attribute on the class
|
||||
|
||||
# __init__.py
|
||||
from .options import Logic
|
||||
|
||||
if self.options.logic:
|
||||
do_things_for_all_non_normal_logic()
|
||||
if self.options.logic == 1:
|
||||
do_hard_things()
|
||||
elif self.options.logic == "challenging":
|
||||
do_challenging_things()
|
||||
elif self.options.logic == Logic.option_extreme:
|
||||
do_extreme_things()
|
||||
elif self.options.logic == "crazy":
|
||||
do_insane_things()
|
||||
```
|
||||
## Generic Option Classes
|
||||
These options are generically available to every game automatically, but can be overridden for slightly different
|
||||
behavior, if desired. See `worlds/soe/Options.py` for an example.
|
||||
@@ -120,7 +153,7 @@ Like Toggle, but 1 (true) is the default value.
|
||||
A numeric option allowing you to define different sub options. Values are stored as integers, but you can also do
|
||||
comparison methods with the class and strings, so if you have an `option_early_sword`, this can be compared with:
|
||||
```python
|
||||
if self.multiworld.sword_availability[self.player] == "early_sword":
|
||||
if self.options.sword_availability == "early_sword":
|
||||
do_early_sword_things()
|
||||
```
|
||||
|
||||
@@ -128,7 +161,7 @@ or:
|
||||
```python
|
||||
from .Options import SwordAvailability
|
||||
|
||||
if self.multiworld.sword_availability[self.player] == SwordAvailability.option_early_sword:
|
||||
if self.options.sword_availability == SwordAvailability.option_early_sword:
|
||||
do_early_sword_things()
|
||||
```
|
||||
|
||||
@@ -137,13 +170,20 @@ A numeric option allowing a variety of integers including the endpoints. Has a d
|
||||
`range_end` of 1. Allows for negative values as well. This will always be an integer and has no methods for string
|
||||
comparisons.
|
||||
|
||||
### SpecialRange
|
||||
### NamedRange
|
||||
Like range but also allows you to define a dictionary of special names the user can use to equate to a specific value.
|
||||
`special_range_names` can be used to
|
||||
- give descriptive names to certain values from within the range
|
||||
- add option values above or below the regular range, to be associated with a special meaning
|
||||
|
||||
For example:
|
||||
```python
|
||||
range_start = 1
|
||||
range_end = 99
|
||||
special_range_names: {
|
||||
"normal": 20,
|
||||
"extreme": 99,
|
||||
"unlimited": -1,
|
||||
}
|
||||
```
|
||||
|
||||
@@ -160,7 +200,7 @@ within the world.
|
||||
Like choice allows you to predetermine options and has all of the same comparison methods and handling. Also accepts any
|
||||
user defined string as a valid option, so will either need to be validated by adding a validation step to the option
|
||||
class or within world, if necessary. Value for this class is `Union[str, int]` so if you need the value at a specified
|
||||
point, `self.multiworld.my_option[self.player].current_key` will always return a string.
|
||||
point, `self.options.my_option.current_key` will always return a string.
|
||||
|
||||
### PlandoBosses
|
||||
An option specifically built for handling boss rando, if your game can use it. Is a subclass of TextChoice so supports
|
||||
|
||||
@@ -8,7 +8,7 @@ use that version. These steps are for developers or platforms without compiled r
|
||||
|
||||
What you'll need:
|
||||
* [Python 3.8.7 or newer](https://www.python.org/downloads/), not the Windows Store version
|
||||
* **Python 3.11 does not work currently**
|
||||
* **Python 3.12 is currently unsupported**
|
||||
* pip: included in downloads from python.org, separate in many Linux distributions
|
||||
* Matching C compiler
|
||||
* possibly optional, read operating system specific sections
|
||||
@@ -30,7 +30,7 @@ After this, you should be able to run the programs.
|
||||
|
||||
Recommended steps
|
||||
* Download and install a "Windows installer (64-bit)" from the [Python download page](https://www.python.org/downloads)
|
||||
* **Python 3.11 does not work currently**
|
||||
* **Python 3.12 is currently unsupported**
|
||||
|
||||
* **Optional**: Download and install Visual Studio Build Tools from
|
||||
[Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
|
||||
|
||||
90
docs/tests.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Archipelago Unit Testing API
|
||||
|
||||
This document covers some of the generic tests available using Archipelago's unit testing system, as well as some basic
|
||||
steps on how to write your own.
|
||||
|
||||
## Generic Tests
|
||||
|
||||
Some generic tests are run on every World to ensure basic functionality with default options. These basic tests can be
|
||||
found in the [general test directory](/test/general).
|
||||
|
||||
## Defining World Tests
|
||||
|
||||
In order to run tests from your world, you will need to create a `test` package within your world package. This can be
|
||||
done by creating a `test` directory with a file named `__init__.py` inside it inside your world. By convention, a base
|
||||
for your world tests can be created in this file that you can then import into other modules.
|
||||
|
||||
### WorldTestBase
|
||||
|
||||
In order to test basic functionality of varying options, as well as to test specific edge cases or that certain
|
||||
interactions in the world interact as expected, you will want to use the [WorldTestBase](/test/bases.py). This class
|
||||
comes with the basics for test setup as well as a few preloaded tests that most worlds might want to check on varying
|
||||
options combinations.
|
||||
|
||||
Example `/worlds/<my_game>/test/__init__.py`:
|
||||
|
||||
```python
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
|
||||
class MyGameTestBase(WorldTestBase):
|
||||
game = "My Game"
|
||||
```
|
||||
|
||||
The basic tests that WorldTestBase comes with include `test_all_state_can_reach_everything`,
|
||||
`test_empty_state_can_reach_something`, and `test_fill`. These test that with all collected items everything is
|
||||
reachable, with no collected items at least something is reachable, and that a valid multiworld can be completed with
|
||||
all steps being called, respectively.
|
||||
|
||||
### Writing Tests
|
||||
|
||||
#### Using WorldTestBase
|
||||
|
||||
Adding runs for the basic tests for a different option combination is as easy as making a new module in the test
|
||||
package, creating a class that inherits from your game's TestBase, and defining the options in a dict as a field on the
|
||||
class. The new module should be named `test_<something>.py` and have at least one class inheriting from the base, or
|
||||
define its own testing methods. Newly defined test methods should follow standard PEP8 snake_case format and also start
|
||||
with `test_`.
|
||||
|
||||
Example `/worlds/<my_game>/test/test_chest_access.py`:
|
||||
|
||||
```python
|
||||
from . import MyGameTestBase
|
||||
|
||||
|
||||
class TestChestAccess(MyGameTestBase):
|
||||
options = {
|
||||
"difficulty": "easy",
|
||||
"final_boss_hp": 4000,
|
||||
}
|
||||
|
||||
def test_sword_chests(self) -> None:
|
||||
"""Test locations that require a sword"""
|
||||
locations = ["Chest1", "Chest2"]
|
||||
items = [["Sword"]]
|
||||
# This tests that the provided locations aren't accessible without the provided items, but can be accessed once
|
||||
# the items are obtained.
|
||||
# This will also check that any locations not provided don't have the same dependency requirement.
|
||||
# Optionally, passing only_check_listed=True to the method will only check the locations provided.
|
||||
self.assertAccessDependency(locations, items)
|
||||
```
|
||||
|
||||
When tests are run, this class will create a multiworld with a single player having the provided options, and run the
|
||||
generic tests, as well as the new custom test. Each test method definition will create its own separate solo multiworld
|
||||
that will be cleaned up after. If you don't want to run the generic tests on a base, `run_default_tests` can be
|
||||
overridden. For more information on what methods are available to your class, check the
|
||||
[WorldTestBase definition](/test/bases.py#L104).
|
||||
|
||||
#### Alternatives to WorldTestBase
|
||||
|
||||
Unit tests can also be created using [TestBase](/test/bases.py#L14) or
|
||||
[unittest.TestCase](https://docs.python.org/3/library/unittest.html#unittest.TestCase) depending on your use case. These
|
||||
may be useful for generating a multiworld under very specific constraints without using the generic world setup, or for
|
||||
testing portions of your code that can be tested without relying on a multiworld to be created first.
|
||||
|
||||
## Running Tests
|
||||
|
||||
In PyCharm, running all tests can be done by right-clicking the root `test` directory and selecting `run Python tests`.
|
||||
If you do not have pytest installed, you may get import failures. To solve this, edit the run configuration, and set the
|
||||
working directory of the run to the Archipelago directory. If you only want to run your world's defined tests, repeat
|
||||
the steps for the test directory within your world.
|
||||
100
docs/triage role expectations.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Triage Role Expectations
|
||||
|
||||
Users with Triage-level access are selected contributors who can and wish to proactively label/triage issues and pull
|
||||
requests without being granted write access to the Archipelago repository.
|
||||
|
||||
Triage users are not necessarily official members of the Archipelago organization, for the list of core maintainers,
|
||||
please reference [ArchipelagoMW Members](https://github.com/orgs/ArchipelagoMW/people) page.
|
||||
|
||||
## Access Permissions
|
||||
|
||||
Triage users have the following permissions:
|
||||
|
||||
* Apply/dismiss labels on all issues and pull requests.
|
||||
* Close, reopen, and assign all issues and pull requests.
|
||||
* Mark issues and pull requests as duplicate.
|
||||
* Request pull request reviews from repository members.
|
||||
* Hide comments in issues or pull requests from public view.
|
||||
* Hidden comments are not deleted and can be reversed by another triage user or repository member with write access.
|
||||
* And all other standard permissions granted to regular GitHub users.
|
||||
|
||||
For more details on permissions granted by the Triage role, see
|
||||
[GitHub's Role Documentation](https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization).
|
||||
|
||||
## Expectations
|
||||
|
||||
Users with triage-level permissions have no expectation to review code, but, if desired, to review pull requests/issues
|
||||
and apply the relevant labels and ping/request reviews from any relevant [code owners](./CODEOWNERS) for review. Triage
|
||||
users are also expected not to close others' issues or pull requests without strong reason to do so (with exception of
|
||||
`meta: invalid` or `meta: duplicate` scenarios, which are listed below). When in doubt, defer to a core maintainer.
|
||||
|
||||
Triage users are not "moderators" for others' issues or pull requests. However, they may voice their opinions/feedback
|
||||
on issues or pull requests, just the same as any other GitHub user contributing to Archipelago.
|
||||
|
||||
## Labeling
|
||||
|
||||
As of the time of writing this document, there are 15 distinct labels that can be applied to issues and pull requests.
|
||||
|
||||
### Affects
|
||||
|
||||
These labels notate if certain issues or pull requests affect critical aspects of Archipelago that may require specific
|
||||
review. More than one of these labels can be used on a issue or pull request, if relevant.
|
||||
|
||||
* `affects: core` is to be applied to issues/PRs that may affect core Archipelago functionality and should be reviewed
|
||||
with additional scrutiny.
|
||||
* Core is defined as any files not contained in the `WebHostLib` directory or individual world implementations
|
||||
directories inside the `worlds` directory, not including `worlds/generic`.
|
||||
* `affects: webhost` is to be applied to issues/PRs that may affect the core WebHost portion of Archipelago. In
|
||||
general, this is anything being modified inside the `WebHostLib` directory or `WebHost.py` file.
|
||||
* `affects: release/blocker` is to be applied for any issues/PRs that may either negatively impact (issues) or propose
|
||||
to resolve critical issues (pull requests) that affect the current or next official release of Archipelago and should be
|
||||
given top priority for review.
|
||||
|
||||
### Is
|
||||
|
||||
These labels notate what kinds of changes are being made or proposed in issues or pull requests. More than one of these
|
||||
labels can be used on a issue or pull request, if relevant, but at least one of these labels should be applied to every
|
||||
pull request and issue.
|
||||
|
||||
* `is: bug/fix` is to be applied to issues/PRs that report or resolve an issue in core, web, or individual world
|
||||
implementations.
|
||||
* `is: documentation` is to be applied to issues/PRs that relate to adding, updating, or removing documentation in
|
||||
core, web, or individual world implementations without modifying actual code.
|
||||
* `is: enhancement` is to be applied to issues/PRs that relate to adding, modifying, or removing functionality in
|
||||
core, web, or individual world implementations.
|
||||
* `is: refactor/cleanup` is to be applied to issues/PRs that relate to reorganizing existing code to improve
|
||||
readability or performance without adding, modifying, or removing functionality or fixing known regressions.
|
||||
* `is: maintenance` is to be applied to issues/PRs that don't modify logic, refactor existing code, change features.
|
||||
This is typically reserved for pull requests that need to update dependencies or increment version numbers without
|
||||
resolving existing issues.
|
||||
* `is: new game` is to be applied to any pull requests that introduce a new game for the first time to the `worlds`
|
||||
directory.
|
||||
* Issues should not be opened and classified with `is: new game`, and instead should be directed to the
|
||||
#future-game-design channel in Archipelago for opening suggestions. If they are opened, they should be labeled
|
||||
with `meta: invalid` and closed.
|
||||
* Pull requests for new games should only have this label, as enhancement, documentation, bug/fix, refactor, and
|
||||
possibly maintenance is implied.
|
||||
|
||||
### Meta
|
||||
|
||||
These labels allow additional quick meta information for contributors or reviewers for issues and pull requests. They
|
||||
have specific situations where they should be applied.
|
||||
|
||||
* `meta: duplicate` is to be applied to any issues/PRs that are duplicate of another issue/PR that was already opened.
|
||||
* These should be immediately closed after leaving a comment, directing to the original issue or pull request.
|
||||
* `meta: invalid` is to be applied to any issues/PRs that do not relate to Archipelago or are inappropriate for
|
||||
discussion on GitHub.
|
||||
* These should be immediately closed afterwards.
|
||||
* `meta: help wanted` is to be applied to any issues/PRs that require additional attention for whatever reason.
|
||||
* These should include a comment describing what kind of help is requested when the label is added.
|
||||
* Some common reasons include, but are not limited to: Breaking API changes that require developer input/testing or
|
||||
pull requests with large line changes that need additional reviewers to be reviewed effectively.
|
||||
* This label may require some programming experience and familiarity with Archipelago source to determine if
|
||||
requesting additional attention for help is warranted.
|
||||
* `meta: good first issue` is to be applied to any issues that may be a good starting ground for new contributors to try
|
||||
and tackle.
|
||||
* This label may require some programming experience and familiarity with Archipelago source to determine if an
|
||||
issue is a "good first issue".
|
||||
* `meta: wontfix` is to be applied for any issues/PRs that are opened that will not be actioned because it's out of
|
||||
scope or determined to not be an issue.
|
||||
* This should be reserved for use by a world's code owner(s) on their relevant world or by core maintainers.
|
||||
@@ -73,6 +73,53 @@ for your world specifically 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'.
|
||||
|
||||
`options_presets` (optional) A `Dict[str, Dict[str, Any]]` where the keys are the names of the presets and the values
|
||||
are the options to be set for that preset. The options are defined as a `Dict[str, Any]` where the keys are the names of
|
||||
the options and the values are the values to be set for that option. These presets will be available for users to select from on the game's options page.
|
||||
|
||||
Note: The values must be a non-aliased value for the option type and can only include the following option types:
|
||||
|
||||
- If you have a `Range`/`NamedRange` option, the value should be an `int` between the `range_start` and `range_end`
|
||||
values.
|
||||
- If you have a `NamedRange` option, the value can alternatively be a `str` that is one of the
|
||||
`special_range_names` keys.
|
||||
- If you have a `Choice` option, the value should be a `str` that is one of the `option_<name>` values.
|
||||
- If you have a `Toggle`/`DefaultOnToggle` option, the value should be a `bool`.
|
||||
- `random` is also a valid value for any of these option types.
|
||||
|
||||
`OptionDict`, `OptionList`, `OptionSet`, `FreeText`, or custom `Option`-derived classes are not supported for presets on the webhost at this time.
|
||||
|
||||
Here is an example of a defined preset:
|
||||
```python
|
||||
# presets.py
|
||||
options_presets = {
|
||||
"Limited Potential": {
|
||||
"progression_balancing": 0,
|
||||
"fairy_chests_per_zone": 2,
|
||||
"starting_class": "random",
|
||||
"chests_per_zone": 30,
|
||||
"vendors": "normal",
|
||||
"architect": "disabled",
|
||||
"gold_gain_multiplier": "half",
|
||||
"number_of_children": 2,
|
||||
"free_diary_on_generation": False,
|
||||
"health_pool": 10,
|
||||
"mana_pool": 10,
|
||||
"attack_pool": 10,
|
||||
"magic_damage_pool": 10,
|
||||
"armor_pool": 5,
|
||||
"equip_pool": 10,
|
||||
"crit_chance_pool": 5,
|
||||
"crit_damage_pool": 5,
|
||||
}
|
||||
}
|
||||
|
||||
# __init__.py
|
||||
class RLWeb(WebWorld):
|
||||
options_presets = options_presets
|
||||
# ...
|
||||
```
|
||||
|
||||
### MultiWorld Object
|
||||
|
||||
The `MultiWorld` object references the whole multiworld (all items and locations
|
||||
@@ -86,9 +133,11 @@ inside a `World` object.
|
||||
### Player Options
|
||||
|
||||
Players provide customized settings for their World in the form of yamls.
|
||||
Those are accessible through `self.multiworld.<option_name>[self.player]`. A dict
|
||||
of valid options has to be provided in `self.option_definitions`. Options are automatically
|
||||
added to the `World` object for easy access.
|
||||
A `dataclass` of valid options definitions has to be provided in `self.options_dataclass`.
|
||||
(It must be a subclass of `PerGameCommonOptions`.)
|
||||
Option results are automatically added to the `World` object for easy access.
|
||||
Those are accessible through `self.options.<option_name>`, and you can get a dictionary of the option values via
|
||||
`self.options.as_dict(<option_names>)`, passing the desired options as strings.
|
||||
|
||||
### World Settings
|
||||
|
||||
@@ -119,6 +168,38 @@ Classification is one of `LocationProgressType.DEFAULT`, `PRIORITY` or `EXCLUDED
|
||||
The Fill algorithm will force progression items to be placed at priority locations, giving a higher chance of them being
|
||||
required, and will prevent progression and useful items from being placed at excluded locations.
|
||||
|
||||
#### Documenting Locations
|
||||
|
||||
Worlds can optionally provide a `location_descriptions` map which contains
|
||||
human-friendly descriptions of locations or location groups. These descriptions
|
||||
will show up in location-selection options in the Weighted Options page. Extra
|
||||
indentation and single newlines will be collapsed into spaces.
|
||||
|
||||
```python
|
||||
# Locations.py
|
||||
|
||||
location_descriptions = {
|
||||
"Red Potion #6": "In a secret destructible block under the second stairway",
|
||||
"L2 Spaceship": """
|
||||
The group of all items in the spaceship in Level 2.
|
||||
|
||||
This doesn't include the item on the spaceship door, since it can be
|
||||
accessed without the Spaeship Key.
|
||||
"""
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
# __init__.py
|
||||
|
||||
from worlds.AutoWorld import World
|
||||
from .Locations import location_descriptions
|
||||
|
||||
|
||||
class MyGameWorld(World):
|
||||
location_descriptions = location_descriptions
|
||||
```
|
||||
|
||||
### Items
|
||||
|
||||
Items are all things that can "drop" for your game. This may be RPG items like
|
||||
@@ -145,6 +226,37 @@ Other classifications include
|
||||
* `progression_skip_balancing`: the combination of `progression` and `skip_balancing`, i.e., a progression item that
|
||||
will not be moved around by progression balancing; used, e.g., for currency or tokens
|
||||
|
||||
#### Documenting Items
|
||||
|
||||
Worlds can optionally provide an `item_descriptions` map which contains
|
||||
human-friendly descriptions of items or item groups. These descriptions will
|
||||
show up in item-selection options in the Weighted Options page. Extra
|
||||
indentation and single newlines will be collapsed into spaces.
|
||||
|
||||
```python
|
||||
# Items.py
|
||||
|
||||
item_descriptions = {
|
||||
"Red Potion": "A standard health potion",
|
||||
"Spaceship Key": """
|
||||
The key to the spaceship in Level 2.
|
||||
|
||||
This is necessary to get to the Star Realm.
|
||||
"""
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
# __init__.py
|
||||
|
||||
from worlds.AutoWorld import World
|
||||
from .Items import item_descriptions
|
||||
|
||||
|
||||
class MyGameWorld(World):
|
||||
item_descriptions = item_descriptions
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
Events will mark some progress. You define an event location, an
|
||||
@@ -221,11 +333,11 @@ See [pip documentation](https://pip.pypa.io/en/stable/cli/pip_install/#requireme
|
||||
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
|
||||
`worlds/<world_name>/Options.py` and make its `mygame_options` accessible.
|
||||
e.g. `from .options import MyGameOptions` from your `__init__.py` will load
|
||||
`world/[world_name]/options.py` and make its `MyGameOptions` accessible.
|
||||
|
||||
When imported names pile up it may be easier to use `from . import Options`
|
||||
and access the variable as `Options.mygame_options`.
|
||||
When imported names pile up it may be easier to use `from . import options`
|
||||
and access the variable as `options.MyGameOptions`.
|
||||
|
||||
Imports from directories outside your world should use absolute imports.
|
||||
Correct use of relative / absolute imports is required for zipped worlds to
|
||||
@@ -246,7 +358,7 @@ 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`.
|
||||
or your `items.py`. For a more elaborate example see `worlds/oot/Items.py`.
|
||||
|
||||
### Your location type
|
||||
|
||||
@@ -258,30 +370,31 @@ 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):
|
||||
def __init__(self, player: int, name = "", code = None, parent = None) -> None:
|
||||
super(MyGameLocation, self).__init__(player, name, code, parent)
|
||||
self.event = code is None
|
||||
```
|
||||
in your `__init__.py` or your `Locations.py`.
|
||||
in your `__init__.py` or your `locations.py`.
|
||||
|
||||
### Options
|
||||
|
||||
By convention options are defined in `Options.py` and will be used when parsing
|
||||
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, AssembleOptions]`, that is
|
||||
assigned to the world under `self.option_definitions`.
|
||||
The actual name as used in the yaml is defined via the field names of a `dataclass` that is
|
||||
assigned to the world under `self.options_dataclass`. By convention, the strings
|
||||
that define your option names should be in `snake_case`.
|
||||
|
||||
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,
|
||||
These don't need any additional properties defined. After parsing the option,
|
||||
its `value` will either be True or False.
|
||||
|
||||
#### Range
|
||||
@@ -307,10 +420,10 @@ default = 0
|
||||
|
||||
#### Sample
|
||||
```python
|
||||
# Options.py
|
||||
# options.py
|
||||
|
||||
from Options import Toggle, Range, Choice, Option
|
||||
import typing
|
||||
from dataclasses import dataclass
|
||||
from Options import Toggle, Range, Choice, PerGameCommonOptions
|
||||
|
||||
class Difficulty(Choice):
|
||||
"""Sets overall game difficulty."""
|
||||
@@ -333,23 +446,27 @@ 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, AssembleOptions] = {
|
||||
"difficulty": Difficulty,
|
||||
"final_boss_hp": FinalBossHP,
|
||||
"fix_xyz_glitch": FixXYZGlitch,
|
||||
}
|
||||
# By convention, we call the options dataclass `<world>Options`.
|
||||
# It has to be derived from 'PerGameCommonOptions'.
|
||||
@dataclass
|
||||
class MyGameOptions(PerGameCommonOptions):
|
||||
difficulty: Difficulty
|
||||
final_boss_hp: FinalBossHP
|
||||
fix_xyz_glitch: FixXYZGlitch
|
||||
```
|
||||
|
||||
```python
|
||||
# __init__.py
|
||||
|
||||
from worlds.AutoWorld import World
|
||||
from .Options import mygame_options # import the options dict
|
||||
from .options import MyGameOptions # import the options dataclass
|
||||
|
||||
|
||||
class MyGameWorld(World):
|
||||
#...
|
||||
option_definitions = mygame_options # assign the options dict to the world
|
||||
#...
|
||||
# ...
|
||||
options_dataclass = MyGameOptions # assign the options dataclass to the world
|
||||
options: MyGameOptions # typing for option results
|
||||
# ...
|
||||
```
|
||||
|
||||
### A World Class Skeleton
|
||||
@@ -359,13 +476,14 @@ class MyGameWorld(World):
|
||||
|
||||
import settings
|
||||
import typing
|
||||
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 .options import MyGameOptions # 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 worlds.AutoWorld import World
|
||||
from BaseClasses import Region, Location, Entrance, Item, RegionType, ItemClassification
|
||||
|
||||
|
||||
|
||||
class MyGameItem(Item): # or from Items import MyGameItem
|
||||
game = "My Game" # name of the game/world this item is from
|
||||
|
||||
@@ -374,6 +492,7 @@ class MyGameLocation(Location): # or from Locations import MyGameLocation
|
||||
game = "My Game" # name of the game/world this location is in
|
||||
|
||||
|
||||
|
||||
class MyGameSettings(settings.Group):
|
||||
class RomFile(settings.SNESRomPath):
|
||||
"""Insert help text for host.yaml here."""
|
||||
@@ -384,7 +503,8 @@ class MyGameSettings(settings.Group):
|
||||
class MyGameWorld(World):
|
||||
"""Insert description of the world/game here."""
|
||||
game = "My Game" # name of the game/world
|
||||
option_definitions = mygame_options # options the player can set
|
||||
options_dataclass = MyGameOptions # options the player can set
|
||||
options: MyGameOptions # typing hints for option results
|
||||
settings: typing.ClassVar[MyGameSettings] # will be automatically assigned from type hint
|
||||
topology_present = True # show path to required location checks in spoiler
|
||||
|
||||
@@ -417,7 +537,7 @@ The world has to provide the following things for generation
|
||||
* additions to the regions list: at least one called "Menu"
|
||||
* locations placed inside those regions
|
||||
* a `def create_item(self, item: str) -> MyGameItem` to create any item on demand
|
||||
* applying `self.multiworld.push_precollected` for start inventory
|
||||
* applying `self.multiworld.push_precollected` for world defined start inventory
|
||||
* `required_client_version: Tuple[int, int, int]`
|
||||
Optional 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.
|
||||
@@ -427,31 +547,32 @@ In addition, the following methods can be implemented and are called in this ord
|
||||
* `stage_assert_generate(cls, multiworld)` 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.
|
||||
* `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)`
|
||||
* `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. This is the earliest step where the world should start setting up for the current
|
||||
multiworld as any steps before this, the multiworld itself is still getting set up
|
||||
* `create_regions(self)`
|
||||
called to place player's regions and their locations into the MultiWorld's regions list. If it's
|
||||
hard to separate, this can be done during `generate_early` or `create_items` as well.
|
||||
* `def create_items(self)`
|
||||
* `create_items(self)`
|
||||
called to place player's items into the MultiWorld's itempool. After this step all regions and items have to be in
|
||||
the MultiWorld's regions and itempool, and these lists should not be modified afterwards.
|
||||
* `def set_rules(self)`
|
||||
* `set_rules(self)`
|
||||
called to set access and item rules on locations and entrances.
|
||||
Locations have to be defined before this, or rule application can miss them.
|
||||
* `def generate_basic(self)`
|
||||
* `generate_basic(self)`
|
||||
called after the previous steps. Some placement and player specific
|
||||
randomizations can be done here.
|
||||
* `pre_fill`, `fill_hook` and `post_fill` are called to modify item placement
|
||||
* `pre_fill(self)`, `fill_hook(self)` and `post_fill(self)` are called to modify item placement
|
||||
before, during and after the regular fill process, before `generate_output`.
|
||||
If items need to be placed during pre_fill, these items can be determined
|
||||
and created using `get_prefill_items`
|
||||
* `def generate_output(self, output_directory: str)` that creates the output
|
||||
* `generate_output(self, output_directory: str)` that creates the output
|
||||
files if there is output to be generated. When this is
|
||||
called, `self.multiworld.get_locations(self.player)` has all locations for the player, with
|
||||
attribute `item` pointing to the item.
|
||||
`location.item.player` can be used to see if it's a local item.
|
||||
* `fill_slot_data` and `modify_multidata` can be used to modify the data that
|
||||
* `fill_slot_data(self)` and `modify_multidata(self, multidata: Dict[str, Any])` can be used to modify the data that
|
||||
will be used by the server to host the MultiWorld.
|
||||
|
||||
|
||||
@@ -460,7 +581,7 @@ In addition, the following methods can be implemented and are called in this ord
|
||||
```python
|
||||
def generate_early(self) -> None:
|
||||
# read player settings to world instance
|
||||
self.final_boss_hp = self.multiworld.final_boss_hp[self.player].value
|
||||
self.final_boss_hp = self.options.final_boss_hp.value
|
||||
```
|
||||
|
||||
#### create_item
|
||||
@@ -468,9 +589,9 @@ def generate_early(self) -> None:
|
||||
```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
|
||||
from .items import is_progression # this is just a dummy
|
||||
|
||||
def create_item(self, item: str):
|
||||
def create_item(self, item: str) -> MyGameItem:
|
||||
# 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 \
|
||||
@@ -478,7 +599,7 @@ def create_item(self, item: str):
|
||||
return MyGameItem(item, classification, self.item_name_to_id[item],
|
||||
self.player)
|
||||
|
||||
def create_event(self, event: str):
|
||||
def create_event(self, event: str) -> MyGameItem:
|
||||
# while we are at it, we can also add a helper to create events
|
||||
return MyGameItem(event, True, None, self.player)
|
||||
```
|
||||
@@ -559,13 +680,19 @@ def generate_basic(self) -> None:
|
||||
# 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
|
||||
|
||||
# for debugging purposes, you may want to visualize the layout of your world. Uncomment the following code to
|
||||
# write a PlantUML diagram to the file "my_world.puml" that can help you see whether your regions and locations
|
||||
# are connected and placed as desired
|
||||
# from Utils import visualize_regions
|
||||
# visualize_regions(self.multiworld.get_region("Menu", self.player), "my_world.puml")
|
||||
```
|
||||
|
||||
### Setting Rules
|
||||
|
||||
```python
|
||||
from worlds.generic.Rules import add_rule, set_rule, forbid_item
|
||||
from Items import get_item_type
|
||||
from worlds.generic.Rules import add_rule, set_rule, forbid_item, add_item_rule
|
||||
from .items import get_item_type
|
||||
|
||||
|
||||
def set_rules(self) -> None:
|
||||
@@ -591,7 +718,7 @@ def set_rules(self) -> None:
|
||||
# require one item from an item group
|
||||
add_rule(self.multiworld.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
|
||||
# state also has .count() for items, .has_any() and .has_all() for multiple
|
||||
# and .count_group() for groups
|
||||
# set_rule is likely to be a bit faster than add_rule
|
||||
|
||||
@@ -634,12 +761,12 @@ Please do this with caution and only when necessary.
|
||||
#### Sample
|
||||
|
||||
```python
|
||||
# Logic.py
|
||||
# logic.py
|
||||
|
||||
from worlds.AutoWorld import LogicMixin
|
||||
|
||||
class MyGameLogic(LogicMixin):
|
||||
def mygame_has_key(self, player: int):
|
||||
def mygame_has_key(self, player: int) -> bool:
|
||||
# Arguments above are free to choose
|
||||
# MultiWorld can be accessed through self.multiworld, explicitly passing in
|
||||
# MyGameWorld instance for easy options access is also a valid approach
|
||||
@@ -649,11 +776,11 @@ class MyGameLogic(LogicMixin):
|
||||
# __init__.py
|
||||
|
||||
from worlds.generic.Rules import set_rule
|
||||
import .Logic # apply the mixin by importing its file
|
||||
import .logic # apply the mixin by importing its file
|
||||
|
||||
class MyGameWorld(World):
|
||||
# ...
|
||||
def set_rules(self):
|
||||
def set_rules(self) -> None:
|
||||
set_rule(self.multiworld.get_location("A Door", self.player),
|
||||
lambda state: state.mygame_has_key(self.player))
|
||||
```
|
||||
@@ -661,10 +788,10 @@ class MyGameWorld(World):
|
||||
### Generate Output
|
||||
|
||||
```python
|
||||
from .Mod import generate_mod
|
||||
from .mod import generate_mod
|
||||
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
# 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
|
||||
@@ -679,12 +806,10 @@ def generate_output(self, output_directory: str):
|
||||
# make sure to mark as not remote_start_inventory when connecting if stored in rom/mod
|
||||
"starter_items": [item.name for item
|
||||
in self.multiworld.precollected_items[self.player]],
|
||||
"final_boss_hp": self.final_boss_hp,
|
||||
# store option name "easy", "normal" or "hard" for difficuly
|
||||
"difficulty": self.multiworld.difficulty[self.player].current_key,
|
||||
# store option value True or False for fixing a glitch
|
||||
"fix_xyz_glitch": self.multiworld.fix_xyz_glitch[self.player].value,
|
||||
}
|
||||
|
||||
# add needed option results to the dictionary
|
||||
data.update(self.options.as_dict("final_boss_hp", "difficulty", "fix_xyz_glitch"))
|
||||
# point to a ROM specified by the installation
|
||||
src = self.settings.rom_file
|
||||
# or point to worlds/mygame/data/mod_template
|
||||
@@ -696,6 +821,26 @@ def generate_output(self, output_directory: str):
|
||||
generate_mod(src, out_file, data)
|
||||
```
|
||||
|
||||
### Slot Data
|
||||
|
||||
If the game client needs to know information about the generated seed, a preferred method of transferring the data
|
||||
is through the slot data. This can be filled from the `fill_slot_data` method of your world by returning a `Dict[str, Any]`,
|
||||
but should be limited to data that is absolutely necessary to not waste resources. Slot data is sent to your client once
|
||||
it has successfully [connected](network%20protocol.md#connected).
|
||||
If you need to know information about locations in your world, instead
|
||||
of propagating the slot data, it is preferable to use [LocationScouts](network%20protocol.md#locationscouts) since that
|
||||
data already exists on the server. The most common usage of slot data is to send option results that the client needs
|
||||
to be aware of.
|
||||
|
||||
```python
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
# in order for our game client to handle the generated seed correctly we need to know what the user selected
|
||||
# for their difficulty and final boss HP
|
||||
# a dictionary returned from this method gets set as the slot_data and will be sent to the client after connecting
|
||||
# the options dataclass has a method to return a `Dict[str, Any]` of each option name provided and the option's value
|
||||
return self.options.as_dict("difficulty", "final_boss_hp")
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
Each world implementation should have a tutorial and a game info page. These are both rendered on the website by reading
|
||||
@@ -723,8 +868,9 @@ multiworld for each test written using it. Within subsequent modules, classes sh
|
||||
TestBase, and can then define options to test in the class body, and run tests in each test method.
|
||||
|
||||
Example `__init__.py`
|
||||
|
||||
```python
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
|
||||
class MyGameTestBase(WorldTestBase):
|
||||
@@ -733,23 +879,25 @@ class MyGameTestBase(WorldTestBase):
|
||||
|
||||
Next using the rules defined in the above `set_rules` we can test that the chests have the correct access rules.
|
||||
|
||||
Example `testChestAccess.py`
|
||||
Example `test_chest_access.py`
|
||||
```python
|
||||
from . import MyGameTestBase
|
||||
|
||||
|
||||
class TestChestAccess(MyGameTestBase):
|
||||
def test_sword_chests(self):
|
||||
def test_sword_chests(self) -> None:
|
||||
"""Test locations that require a sword"""
|
||||
locations = ["Chest1", "Chest2"]
|
||||
items = [["Sword"]]
|
||||
# this will test that each location can't be accessed without the "Sword", but can be accessed once obtained.
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
def test_any_weapon_chests(self):
|
||||
def test_any_weapon_chests(self) -> None:
|
||||
"""Test locations that require any weapon"""
|
||||
locations = [f"Chest{i}" for i in range(3, 6)]
|
||||
items = [["Sword"], ["Axe"], ["Spear"]]
|
||||
# this will test that chests 3-5 can't be accessed without any weapon, but can be with just one of them.
|
||||
self.assertAccessDependency(locations, items)
|
||||
```
|
||||
|
||||
For more information on tests check the [tests doc](tests.md).
|
||||
|
||||