mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-14 19:43:48 -07:00
Compare commits
1 Commits
plando-con
...
empty-deat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c5418d708 |
@@ -511,7 +511,7 @@ if __name__ == '__main__':
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -1128,7 +1128,7 @@ def run_as_textclient(*args):
|
||||
args = handle_url_arg(args, parser=parser)
|
||||
|
||||
# use colorama to display colored text highlighting on windows
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -261,7 +261,7 @@ if __name__ == '__main__':
|
||||
|
||||
parser = get_base_parser()
|
||||
args = parser.parse_args()
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -803,6 +803,6 @@ async def main():
|
||||
await ctx.shutdown()
|
||||
|
||||
if __name__ == '__main__':
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -370,7 +370,7 @@ if __name__ == "__main__":
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -47,7 +47,7 @@ from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, Networ
|
||||
from BaseClasses import ItemClassification
|
||||
|
||||
min_client_version = Version(0, 1, 6)
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
|
||||
|
||||
def remove_from_list(container, value):
|
||||
|
||||
@@ -346,7 +346,7 @@ if __name__ == '__main__':
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -735,6 +735,6 @@ async def main() -> None:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -500,7 +500,7 @@ def main():
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
|
||||
asyncio.run(_main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -446,6 +446,6 @@ if __name__ == '__main__':
|
||||
parser = get_base_parser(description="Wargroove Client, for text interfacing.")
|
||||
|
||||
args, rest = parser.parse_known_args()
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro RandomizeButton(option_name, option) %}
|
||||
<div class="randomize-button" data-tooltip="Pick a random value for this option.">
|
||||
<div class="randomize-button" data-tooltip="Toggle randomization for this option!">
|
||||
<label for="random-{{ option_name }}">
|
||||
<input type="checkbox" id="random-{{ option_name }}" name="random-{{ option_name }}" class="randomize-checkbox" data-option-name="{{ option_name }}" {{ "checked" if option.default == "random" }} />
|
||||
🎲
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
|
||||
{% else %}
|
||||
<div class="unsupported-option">
|
||||
This option cannot be modified here. Please edit your .yaml file manually.
|
||||
This option is not supported. Please edit your .yaml file manually.
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
@@ -386,7 +386,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument('diff_file', default="", type=str, nargs="?",
|
||||
help='Path to a Archipelago Binary Patch file')
|
||||
args = parser.parse_args()
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -756,8 +756,8 @@ Tags are represented as a list of strings, the common client tags follow:
|
||||
### 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. When provided, or checked, this should contain the player name, 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. |
|
||||
| Name | Type | Notes |
|
||||
|--------|-------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| time | float | Unix Time Stamp of time of death. |
|
||||
| cause | str | Optional. Text to explain the cause of death. When provided, or checked, if the string is non-empty, it should contain the player name, 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. |
|
||||
|
||||
@@ -276,6 +276,6 @@ def launch(*launch_args: str) -> None:
|
||||
|
||||
Utils.init_logging("BizHawkClient", exception_logger="Client")
|
||||
import colorama
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -261,6 +261,6 @@ def launch():
|
||||
# options = Utils.get_options()
|
||||
|
||||
import colorama
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -19,8 +19,8 @@ from worlds.AutoWorld import WebWorld, World
|
||||
|
||||
from .aesthetics import shuffle_sub_weapons, get_location_data, get_countdown_flags, populate_enemy_drops, \
|
||||
get_start_inventory_data
|
||||
from .rom import RomData, patch_rom, get_base_rom_path, CVCotMProcedurePatch, CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH
|
||||
# CVCOTM_VC_US_HASH
|
||||
from .rom import RomData, patch_rom, get_base_rom_path, CVCotMProcedurePatch, CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, \
|
||||
CVCOTM_VC_US_HASH
|
||||
from .client import CastlevaniaCotMClient
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ class CVCotMSettings(settings.Group):
|
||||
"""File name of the Castlevania CotM US rom"""
|
||||
copy_to = "Castlevania - Circle of the Moon (USA).gba"
|
||||
description = "Castlevania CotM (US) ROM File"
|
||||
# md5s = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]
|
||||
md5s = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH]
|
||||
md5s = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]
|
||||
|
||||
rom_file: RomFile = RomFile(RomFile.copy_to)
|
||||
|
||||
|
||||
@@ -153,10 +153,11 @@ Advance Collection ROM; most notably the fact that the audio does not function w
|
||||
which is currently a requirement to connect to a multiworld. This happens because all audio code was stripped
|
||||
from the ROM, and all sound is instead played by the collection through external means.
|
||||
|
||||
The Wii U Virtual Console version does not work due to changes in the code in that version.
|
||||
For this reason, it is most recommended to obtain the ROM by dumping it from an original cartridge of the game that you legally own.
|
||||
Though, the Advance Collection *can* still technically be an option if you cannot do that and don't mind the lack of sound.
|
||||
|
||||
Due to the reasons mentioned above, it is most recommended to obtain the ROM by dumping it from an original cartridge of the
|
||||
game that you legally own. However, the Advance Collection *is* an option if you cannot do that and don't mind the lack of sound.
|
||||
The Wii U Virtual Console version is currently untested. If you happen to have purchased it before the Wii U eShop shut down, you can try
|
||||
dumping and playing with it. However, at the moment, we cannot guarantee that it will work well due to it being untested.
|
||||
|
||||
Regardless of which released ROM you intend to try playing with, the US version of the game is required.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest).
|
||||
- A Castlevania: Circle of the Moon ROM of the US version specifically. The Archipelago community cannot provide this.
|
||||
The Castlevania Advance Collection ROM can be used, but it has no audio. The Wii U Virtual Console ROM does not work.
|
||||
The Castlevania Advance Collection ROM can technically be used, but it has no audio. The Wii U Virtual Console ROM is untested.
|
||||
- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 or later.
|
||||
|
||||
### Configuring BizHawk
|
||||
|
||||
@@ -22,9 +22,11 @@ if TYPE_CHECKING:
|
||||
|
||||
CVCOTM_CT_US_HASH = "50a1089600603a94e15ecf287f8d5a1f" # Original GBA cartridge ROM
|
||||
CVCOTM_AC_US_HASH = "87a1bd6577b6702f97a60fc55772ad74" # Castlevania Advance Collection ROM
|
||||
# CVCOTM_VC_US_HASH = "2cc38305f62b337281663bad8c901cf9" # Wii U Virtual Console ROM
|
||||
CVCOTM_VC_US_HASH = "2cc38305f62b337281663bad8c901cf9" # Wii U Virtual Console ROM
|
||||
|
||||
# The Wii U VC version is not currently supported. See the Game Page for more info.
|
||||
# NOTE: The Wii U VC version is untested as of when this comment was written. I am only including its hash in case it
|
||||
# does work. If someone who has it can confirm it does indeed work, this comment should be removed. If it doesn't, the
|
||||
# hash should be removed in addition. See the Game Page for more information about supported versions.
|
||||
|
||||
ARCHIPELAGO_IDENTIFIER_START = 0x7FFF00
|
||||
ARCHIPELAGO_IDENTIFIER = "ARCHIPELAG03"
|
||||
@@ -516,8 +518,7 @@ class CVCotMPatchExtensions(APPatchExtension):
|
||||
|
||||
|
||||
class CVCotMProcedurePatch(APProcedurePatch, APTokenMixin):
|
||||
# hash = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]
|
||||
hash = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH]
|
||||
hash = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]
|
||||
patch_file_ending: str = ".apcvcotm"
|
||||
result_file_ending: str = ".gba"
|
||||
|
||||
@@ -584,8 +585,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
|
||||
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(base_rom_bytes)
|
||||
# if basemd5.hexdigest() not in [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]:
|
||||
if basemd5.hexdigest() not in [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH]:
|
||||
if basemd5.hexdigest() not in [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]:
|
||||
raise Exception("Supplied Base ROM does not match known MD5s for Castlevania: Circle of the Moon USA."
|
||||
"Get the correct game and version, then dump it.")
|
||||
setattr(get_base_rom_bytes, "base_rom_bytes", base_rom_bytes)
|
||||
|
||||
@@ -530,7 +530,7 @@ server_args = ("--rcon-port", rcon_port, "--rcon-password", rcon_password)
|
||||
def launch():
|
||||
import colorama
|
||||
global executable, server_settings, server_args
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
|
||||
if server_settings:
|
||||
server_settings = os.path.abspath(server_settings)
|
||||
|
||||
@@ -212,7 +212,7 @@ its [plando guide](/tutorial/A%20Link%20to%20the%20Past/plando/en#connections).
|
||||
* `percentage` is the percentage chance for this connection from 0 to 100 and defaults to 100.
|
||||
* Every connection has an `entrance` and an `exit`. These can be unlinked like in A Link to the Past insanity entrance
|
||||
shuffle.
|
||||
* `direction` can be `both`, `entrance`, or `exit`. How this is handled varies between games. `direction` defaults to `both`.
|
||||
* `direction` can be `both`, `entrance`, or `exit` and determines in which direction this connection will operate. `direction` defaults to `both`.
|
||||
|
||||
[A Link to the Past connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852)
|
||||
|
||||
|
||||
@@ -295,6 +295,6 @@ def launch():
|
||||
parser = get_base_parser(description="KH1 Client, for text interfacing.")
|
||||
|
||||
args, rest = parser.parse_known_args()
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -981,6 +981,6 @@ def launch():
|
||||
parser = get_base_parser(description="KH2 Client, for text interfacing.")
|
||||
|
||||
args, rest = parser.parse_known_args()
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
asyncio.run(main(args))
|
||||
colorama.deinit()
|
||||
|
||||
@@ -100,15 +100,14 @@ class OOTWeb(WebWorld):
|
||||
["Edos"]
|
||||
)
|
||||
|
||||
# Very out of date, requires updating to match current
|
||||
# setup_es = Tutorial(
|
||||
# setup.tutorial_name,
|
||||
# setup.description,
|
||||
# "Español",
|
||||
# "setup_es.md",
|
||||
# "setup/es",
|
||||
# setup.authors
|
||||
# )
|
||||
setup_es = Tutorial(
|
||||
setup.tutorial_name,
|
||||
setup.description,
|
||||
"Español",
|
||||
"setup_es.md",
|
||||
"setup/es",
|
||||
setup.authors
|
||||
)
|
||||
|
||||
setup_fr = Tutorial(
|
||||
setup.tutorial_name,
|
||||
@@ -128,7 +127,7 @@ class OOTWeb(WebWorld):
|
||||
["Held_der_Zeit"]
|
||||
)
|
||||
|
||||
tutorials = [setup, setup_fr, setup_de]
|
||||
tutorials = [setup, setup_es, setup_fr, setup_de]
|
||||
option_groups = oot_option_groups
|
||||
|
||||
|
||||
|
||||
@@ -1625,6 +1625,6 @@ def get_location_offset(mission_id):
|
||||
|
||||
|
||||
def launch():
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
# StarCraft 2
|
||||
|
||||
## Game page in other languages:
|
||||
|
||||
* [Français](/games/Starcraft%202/info/fr)
|
||||
|
||||
## What does randomization do to this game?
|
||||
|
||||
### Items and locations
|
||||
|
||||
The following unlocks are randomized as items:
|
||||
1. Your ability to build any non-worker unit.
|
||||
2. Unit specific upgrades including some combinations not available in the vanilla campaigns, such as both strain
|
||||
@@ -37,28 +34,18 @@ When you receive items, they will immediately become available, even during a mi
|
||||
notified via a text box in the top-right corner of the game screen.
|
||||
Item unlocks are also logged in the Archipelago client.
|
||||
|
||||
### Mission order
|
||||
|
||||
The missions and the order in which they need to be completed, referred to as the mission order, can also be randomized.
|
||||
The four StarCraft 2 campaigns can be used to populate the mission order.
|
||||
Note that the evolution missions from Heart of the Swarm are not included in the randomizer.
|
||||
The default mission order follows the structure of the selected campaigns but several other options are available,
|
||||
e.g., blitz, grid, etc.
|
||||
|
||||
Missions are launched through the StarCraft 2 Archipelago client, through the StarCraft 2 Launcher tab.
|
||||
The between mission segments on the Hyperion, the Leviathan, and the Spear of Adun are not included.
|
||||
Additionally, metaprogression currencies such as credits and Solarite are not used.
|
||||
Available missions are in blue; missions where all locations were collected are in white.
|
||||
If you move your mouse over a mission, the uncollected locations will be displayed, categorized by type.
|
||||
Unavailable missions are in grey; their requirements will also be shown there.
|
||||
|
||||
## What is the goal of this game when randomized?
|
||||
|
||||
The goal is to beat the final mission in the mission order.
|
||||
The yaml configuration file controls the mission order, which combination of the four StarCraft 2 campaigns can be
|
||||
used, and how missions are shuffled.
|
||||
The yaml configuration file controls the mission order (e.g. blitz, grid, etc.), which combination of the four
|
||||
StarCraft 2 campaigns can be used to populate the mission order and how missions are shuffled.
|
||||
Since the first two options determine the number of missions in a StarCraft 2 world, they can be used to customize the
|
||||
expected time to complete the world.
|
||||
Note that the evolution missions from Heart of the Swarm are not included in the randomizer.
|
||||
|
||||
## What non-randomized changes are there from vanilla StarCraft 2?
|
||||
|
||||
@@ -91,7 +78,9 @@ Will overwrite existing files
|
||||
* `/game_speed [game_speed]` Overrides the game speed for the world
|
||||
* Options: default, slower, slow, normal, fast, faster
|
||||
* `/color [faction] [color]` Changes your color for one of your playable factions.
|
||||
* Run without arguments to list all factions and colors that are available.
|
||||
* Faction options: raynor, kerrigan, primal, protoss, nova
|
||||
* Color options: white, red, blue, teal, purple, yellow, orange, green, lightpink, violet, lightgrey, darkgreen,
|
||||
brown, lightgreen, darkgrey, pink, rainbow, random, default
|
||||
* `/option [option_name] [option_value]` Sets an option normally controlled by your yaml after generation.
|
||||
* Run without arguments to list all options.
|
||||
* Options pertain to automatic cutscene skipping, Kerrigan presence, Spear of Adun presence, starting resource
|
||||
@@ -111,19 +100,6 @@ Additionally, upgrades are grouped beneath their corresponding units or building
|
||||
A filter parameter can be provided, e.g., `/received Thor`, to limit the number of items shown.
|
||||
Every item whose name, race, or group name contains the provided parameter will be shown.
|
||||
|
||||
## Particularities in a multiworld
|
||||
|
||||
### Collect on goal completion
|
||||
|
||||
One of the default options of multiworlds is that once a world has achieved its goal, it collects its items from all
|
||||
other worlds.
|
||||
If you do not want this to happen, you should ask the person generating the multiworld to set the `Collect Permission`
|
||||
option to something else, e.g., manual.
|
||||
If the generation is not done via the website, the person that does the generation should modify the `collect_mode`
|
||||
option in their `host.yaml` file prior to generation.
|
||||
If the multiworld has already been generated, the host can use the command `/option collect_mode [value]` to change
|
||||
this option.
|
||||
|
||||
## Known issues
|
||||
|
||||
- StarCraft 2 Archipelago does not support loading a saved game.
|
||||
@@ -132,7 +108,3 @@ For this reason, it is recommended to play on a difficulty level lower than what
|
||||
To restart a mission, use the StarCraft 2 Client.
|
||||
- A crash report is often generated when a mission is closed.
|
||||
This does not affect the game and can be ignored.
|
||||
- Currently, the StarCraft 2 client uses the Victory locations to determine which missions have been completed.
|
||||
As a result, the Archipelago collect feature can sometime grant access to missions that are connected to a mission that
|
||||
you did not complete.
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
## Quel est l'effet de la *randomization* sur ce jeu ?
|
||||
|
||||
### *Items* et *locations*
|
||||
|
||||
Les éléments qui suivent sont les *items* qui sont *randomized* et qui doivent être débloqués pour être utilisés dans
|
||||
le jeu:
|
||||
1. La capacité de produire des unités, excepté les drones/probes/scv.
|
||||
@@ -39,33 +37,21 @@ Quand vous recevez un *item*, il devient immédiatement disponible, même pendan
|
||||
la boîte de texte situé dans le coin en haut à droite de *StarCraft 2*.
|
||||
L'acquisition d'un *item* est aussi indiquée dans le client d'Archipelago.
|
||||
|
||||
### *Mission order*
|
||||
|
||||
Les missions et l'ordre dans lequel elles doivent être complétées, dénoté *mission order*, peuvent également être
|
||||
*randomized*.
|
||||
Les quatre campagnes de *StarCraft 2* peuvent être utilisées pour remplir le *mission order*.
|
||||
Notez que les missions d'évolution de *Heart of the Swarm* ne sont pas incluses dans le *randomizer*.
|
||||
Par défaut, le *mission order* suit la structure des campagnes sélectionnées, mais plusieurs autres options sont
|
||||
disponibles, comme *blitz*, *grid*, etc.
|
||||
|
||||
Les missions peuvent être lancées par le client *StarCraft 2 Archipelago*, via l'interface graphique de l'onglet
|
||||
*StarCraft 2 Launcher*.
|
||||
Les segments qui se passent sur l'*Hyperion*, un Léviathan et la *Spear of Adun* ne sont pas inclus.
|
||||
De plus, les points de progression, tels que les crédits ou la Solarite, ne sont pas utilisés dans *StarCraft 2
|
||||
De plus, les points de progression tels que les crédits ou la Solarite ne sont pas utilisés dans *StarCraft 2
|
||||
Archipelago*.
|
||||
Les missions accessibles ont leur nom en bleu, tandis que celles où toutes les *locations* ont été collectées
|
||||
apparaissent en blanc.
|
||||
En plaçant votre souris sur une mission, les *locations* non collectées s’affichent, classées par catégorie.
|
||||
Les missions qui ne sont pas accessibles ont leur nom en gris et leurs prérequis seront également affichés à cet endroit.
|
||||
|
||||
|
||||
## Quel est le but de ce jeu quand il est *randomized*?
|
||||
|
||||
Le but est de réussir la mission finale du *mission order* (e.g. *blitz*, *grid*, etc.).
|
||||
Le fichier de configuration yaml permet de spécifier le *mission order*, quelle combinaison des quatre campagnes de
|
||||
*StarCraft 2* peuvent être utilisée et comment les missions sont distribuées dans le *mission order*.
|
||||
Le fichier de configuration yaml permet de spécifier le *mission order*, lesquelles des quatre campagnes de
|
||||
*StarCraft 2* peuvent être utilisées pour remplir le *mission order* et comment les missions sont distribuées dans le
|
||||
*mission order*.
|
||||
Étant donné que les deux premières options déterminent le nombre de missions dans un monde de *StarCraft 2*, elles
|
||||
peuvent être utilisées pour moduler le temps nécessaire pour terminer le monde.
|
||||
Notez que les missions d'évolution de Heart of the Swarm ne sont pas incluses dans le *randomizer*.
|
||||
|
||||
## Quelles sont les modifications non aléatoires comparativement à la version de base de *StarCraft 2*
|
||||
|
||||
@@ -103,7 +89,9 @@ Les fichiers existants vont être écrasés.
|
||||
* `/game_speed [game_speed]` Remplace la vitesse du jeu pour le monde.
|
||||
* Les options sont *default*, *slower*, *slow*, *normal*, *fast*, and *faster*.
|
||||
* `/color [faction] [color]` Remplace la couleur d'une des *factions* qui est jouable.
|
||||
* Si la commande est lancée sans option, la liste des *factions* et des couleurs disponibles sera affichée.
|
||||
* Les options de *faction*: raynor, kerrigan, primal, protoss, nova.
|
||||
* Les options de couleur: *white*, *red*, *blue*, *teal*, *purple*, *yellow*, *orange*, *green*, *lightpink*,
|
||||
*violet*, *lightgrey*, *darkgreen*, *brown*, *lightgreen*, *darkgrey*, *pink*, *rainbow*, *random*, *default*.
|
||||
* `/option [option_name] [option_value]` Permet de changer un option normalement définit dans le *yaml*.
|
||||
* Si la commande est lancée sans option, la liste des options qui sont modifiables va être affichée.
|
||||
* Les options qui peuvent être changées avec cette commande incluent sauter les cinématiques automatiquement, la
|
||||
@@ -126,19 +114,6 @@ De plus, les améliorations sont regroupées sous leurs unités/bâtiments corre
|
||||
Un paramètre de filtrage peut aussi être fourni, e.g., `/received Thor`, pour limiter le nombre d'*items* affichés.
|
||||
Tous les *items* dont le nom, la race ou le nom de groupe contient le paramètre fourni seront affichés.
|
||||
|
||||
## Particularités dans un multiworld
|
||||
|
||||
### *Collect on goal completion*
|
||||
|
||||
L'une des options par défaut des *multiworlds* est qu'une fois qu'un monde a atteint son objectif final, il collecte
|
||||
tous ses *items*, incluant ceux dans les autres mondes.
|
||||
Si vous ne souhaitez pas que cela se produise, vous devez demander à la personne générant le *multiworld* de changer
|
||||
l'option *Collect Permission*.
|
||||
Si la génération n'est pas effectuée via le site web, la personne qui effectue la génération doit modifier l'option
|
||||
`collect_mode` dans son fichier *host.yaml* avant la génération.
|
||||
Si le *multiworld* a déjà été généré, l'hôte peut utiliser la commande `/option collect_mode [valeur]` pour modifier
|
||||
cette option.
|
||||
|
||||
## Problèmes connus
|
||||
|
||||
- *StarCraft 2 Archipelago* ne supporte pas le chargement d'une sauvegarde.
|
||||
@@ -148,7 +123,3 @@ normalement à l'aise.
|
||||
Pour redémarrer une mission, utilisez le client de *StarCraft 2 Archipelago*.
|
||||
- Un rapport d'erreur est souvent généré lorsqu'une mission est fermée.
|
||||
Cela n'affecte pas le jeu et peut être ignoré.
|
||||
- Actuellement, le client de *StarCraft 2* utilise la *location* associée à la victoire d'une mission pour déterminer
|
||||
si celle-ci a été complétée.
|
||||
En conséquence, la fonctionnalité *collect* d'*Archipelago* peut rendre accessible des missions connectées à une
|
||||
mission que vous n'avez pas terminée.
|
||||
@@ -41,7 +41,6 @@ Remember the name you enter in the options page or in the yaml file, you'll need
|
||||
Check out [Creating a YAML](/tutorial/Archipelago/setup/en#creating-a-yaml) for more game-agnostic information.
|
||||
|
||||
### Common yaml questions
|
||||
|
||||
#### How do I know I set my yaml up correctly?
|
||||
|
||||
The simplest way to check is to use the website [validator](/check).
|
||||
|
||||
@@ -49,7 +49,6 @@ Si vous désirez des informations et/ou instructions générales sur l'utilisati
|
||||
veuillez consulter [*Creating a YAML*](/tutorial/Archipelago/setup/en#creating-a-yaml).
|
||||
|
||||
### Questions récurrentes à propos du fichier *yaml*
|
||||
|
||||
#### Comment est-ce que je sais que mon *yaml* est bien défini?
|
||||
|
||||
La manière la plus simple de valider votre *yaml* est d'utiliser le
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import FrozenSet, List, NamedTuple
|
||||
|
||||
# A WitnessRule is just an or-chain of and-conditions.
|
||||
# It represents the set of all options that could fulfill this requirement.
|
||||
# E.g. if something requires "Dots or (Shapers and Stars)", it'd be represented as: {{"Dots"}, {"Shapers, "Stars"}}
|
||||
# {} is an unusable requirement.
|
||||
# {{}} is an always usable requirement.
|
||||
WitnessRule = FrozenSet[FrozenSet[str]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class AreaDefinition:
|
||||
name: str
|
||||
regions: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RegionDefinition:
|
||||
name: str
|
||||
short_name: str
|
||||
area: AreaDefinition
|
||||
logical_entities: List[str] = field(default_factory=list)
|
||||
physical_entities: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
class ConnectionDefinition(NamedTuple):
|
||||
target_region: str
|
||||
traversal_rule: WitnessRule
|
||||
|
||||
@property
|
||||
def can_be_traversed(self) -> bool:
|
||||
return bool(self.traversal_rule)
|
||||
@@ -486,5 +486,5 @@ for key, item in ALL_LOCATIONS_TO_IDS.items():
|
||||
ALL_LOCATIONS_TO_ID[key] = item
|
||||
|
||||
for loc in ALL_LOCATIONS_TO_IDS:
|
||||
area = static_witness_logic.ENTITIES_BY_NAME[loc]["area"].name
|
||||
area = static_witness_logic.ENTITIES_BY_NAME[loc]["area"]["name"]
|
||||
AREA_LOCATION_GROUPS.setdefault(area, set()).add(loc)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from collections import Counter, defaultdict
|
||||
from typing import Any, Dict, FrozenSet, List, Optional, Set
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from Utils import cache_argsless
|
||||
|
||||
from .definition_classes import AreaDefinition, ConnectionDefinition, RegionDefinition, WitnessRule
|
||||
from .item_definition_classes import (
|
||||
CATEGORY_NAME_MAPPINGS,
|
||||
DoorItemDefinition,
|
||||
@@ -14,6 +13,7 @@ from .item_definition_classes import (
|
||||
)
|
||||
from .settings.easter_eggs import EASTER_EGGS
|
||||
from .utils import (
|
||||
WitnessRule,
|
||||
define_new_region,
|
||||
get_items,
|
||||
get_sigma_expert_logic,
|
||||
@@ -21,7 +21,7 @@ from .utils import (
|
||||
get_umbra_variety_logic,
|
||||
get_vanilla_logic,
|
||||
logical_or_witness_rules,
|
||||
parse_witness_rule,
|
||||
parse_lambda,
|
||||
)
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ class StaticWitnessLogicObj:
|
||||
lines = get_sigma_normal_logic()
|
||||
|
||||
# All regions with a list of panels in them and the connections to other regions, before logic adjustments
|
||||
self.ALL_REGIONS_BY_NAME: Dict[str, RegionDefinition] = {}
|
||||
self.ALL_AREAS_BY_NAME: Dict[str, AreaDefinition] = {}
|
||||
self.CONNECTIONS_WITH_DUPLICATES: Dict[str, List[ConnectionDefinition]] = defaultdict(list)
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME: Dict[str, List[ConnectionDefinition]] = {}
|
||||
self.ALL_REGIONS_BY_NAME: Dict[str, Dict[str, Any]] = {}
|
||||
self.ALL_AREAS_BY_NAME: Dict[str, Dict[str, Any]] = {}
|
||||
self.CONNECTIONS_WITH_DUPLICATES: Dict[str, Dict[str, Set[WitnessRule]]] = defaultdict(lambda: defaultdict(set))
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME: Dict[str, Set[Tuple[str, WitnessRule]]] = {}
|
||||
|
||||
self.ENTITIES_BY_HEX: Dict[str, Dict[str, Any]] = {}
|
||||
self.ENTITIES_BY_NAME: Dict[str, Dict[str, Any]] = {}
|
||||
@@ -55,15 +55,15 @@ class StaticWitnessLogicObj:
|
||||
area_counts: Dict[str, int] = Counter()
|
||||
for region_name, entity_amount in EASTER_EGGS.items():
|
||||
region_object = self.ALL_REGIONS_BY_NAME[region_name]
|
||||
correct_area = region_object.area
|
||||
correct_area = region_object["area"]
|
||||
|
||||
for _ in range(entity_amount):
|
||||
location_id = 160200 + egg_counter
|
||||
entity_hex = hex(0xEE000 + egg_counter)
|
||||
egg_counter += 1
|
||||
|
||||
area_counts[correct_area.name] += 1
|
||||
full_entity_name = f"{correct_area.name} Easter Egg {area_counts[correct_area.name]}"
|
||||
area_counts[correct_area["name"]] += 1
|
||||
full_entity_name = f"{correct_area['name']} Easter Egg {area_counts[correct_area['name']]}"
|
||||
|
||||
self.ENTITIES_BY_HEX[entity_hex] = {
|
||||
"checkName": full_entity_name,
|
||||
@@ -81,11 +81,11 @@ class StaticWitnessLogicObj:
|
||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
||||
"entities": frozenset({frozenset({})})
|
||||
}
|
||||
region_object.logical_entities.append(entity_hex)
|
||||
region_object.physical_entities.append(entity_hex)
|
||||
region_object["entities"].append(entity_hex)
|
||||
region_object["physical_entities"].append(entity_hex)
|
||||
|
||||
easter_egg_region = self.ALL_REGIONS_BY_NAME["Easter Eggs"]
|
||||
easter_egg_area = easter_egg_region.area
|
||||
easter_egg_area = easter_egg_region["area"]
|
||||
for i in range(sum(EASTER_EGGS.values())):
|
||||
location_id = 160000 + i
|
||||
entity_hex = hex(0xEE200 + i)
|
||||
@@ -111,15 +111,19 @@ class StaticWitnessLogicObj:
|
||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
||||
"entities": frozenset({frozenset({})})
|
||||
}
|
||||
easter_egg_region.logical_entities.append(entity_hex)
|
||||
easter_egg_region.physical_entities.append(entity_hex)
|
||||
easter_egg_region["entities"].append(entity_hex)
|
||||
easter_egg_region["physical_entities"].append(entity_hex)
|
||||
|
||||
def read_logic_file(self, lines: List[str]) -> None:
|
||||
"""
|
||||
Reads the logic file and does the initial population of data structures
|
||||
"""
|
||||
current_area = AreaDefinition("Misc")
|
||||
current_region = RegionDefinition("Fake", "Fake", current_area) # Unused, but makes PyCharm & mypy shut up
|
||||
|
||||
current_region = {}
|
||||
current_area: Dict[str, Any] = {
|
||||
"name": "Misc",
|
||||
"regions": [],
|
||||
}
|
||||
self.ALL_AREAS_BY_NAME["Misc"] = current_area
|
||||
|
||||
for line in lines:
|
||||
@@ -129,16 +133,19 @@ class StaticWitnessLogicObj:
|
||||
if line[-1] == ":":
|
||||
new_region_and_connections = define_new_region(line, current_area)
|
||||
current_region = new_region_and_connections[0]
|
||||
region_name = current_region.name
|
||||
region_name = current_region["name"]
|
||||
self.ALL_REGIONS_BY_NAME[region_name] = current_region
|
||||
for connection in new_region_and_connections[1]:
|
||||
self.CONNECTIONS_WITH_DUPLICATES[region_name].append(connection)
|
||||
current_area.regions.append(region_name)
|
||||
self.CONNECTIONS_WITH_DUPLICATES[region_name][connection[0]].add(connection[1])
|
||||
current_area["regions"].append(region_name)
|
||||
continue
|
||||
|
||||
if line[0] == "=":
|
||||
area_name = line[2:-2]
|
||||
current_area = AreaDefinition(area_name, [])
|
||||
current_area = {
|
||||
"name": area_name,
|
||||
"regions": [],
|
||||
}
|
||||
self.ALL_AREAS_BY_NAME[area_name] = current_area
|
||||
continue
|
||||
|
||||
@@ -151,9 +158,9 @@ class StaticWitnessLogicObj:
|
||||
entity_hex = entity_name_full[0:7]
|
||||
entity_name = entity_name_full[9:-1]
|
||||
|
||||
entity_requirement_string = line_split.pop(0)
|
||||
required_panel_lambda = line_split.pop(0)
|
||||
|
||||
full_entity_name = current_region.short_name + " " + entity_name
|
||||
full_entity_name = current_region["shortName"] + " " + entity_name
|
||||
|
||||
if location_id == "Door" or location_id == "Laser":
|
||||
self.ENTITIES_BY_HEX[entity_hex] = {
|
||||
@@ -170,18 +177,18 @@ class StaticWitnessLogicObj:
|
||||
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
|
||||
|
||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
||||
"entities": parse_witness_rule(entity_requirement_string)
|
||||
"entities": parse_lambda(required_panel_lambda)
|
||||
}
|
||||
|
||||
# Lasers and Doors exist in a region, but don't have a regional *requirement*
|
||||
# If a laser is activated, you don't need to physically walk up to it for it to count
|
||||
# As such, logically, they behave more as if they were part of the "Entry" region
|
||||
self.ALL_REGIONS_BY_NAME["Entry"].logical_entities.append(entity_hex)
|
||||
self.ALL_REGIONS_BY_NAME["Entry"]["entities"].append(entity_hex)
|
||||
# However, it will also be important to keep track of their physical location for postgame purposes.
|
||||
current_region.physical_entities.append(entity_hex)
|
||||
current_region["physical_entities"].append(entity_hex)
|
||||
continue
|
||||
|
||||
item_requirement_string = line_split.pop(0)
|
||||
required_item_lambda = line_split.pop(0)
|
||||
|
||||
laser_names = {
|
||||
"Laser",
|
||||
@@ -217,18 +224,18 @@ class StaticWitnessLogicObj:
|
||||
entity_type = "Panel"
|
||||
location_type = "General"
|
||||
|
||||
required_items = parse_witness_rule(item_requirement_string)
|
||||
required_entities = parse_witness_rule(entity_requirement_string)
|
||||
required_items = parse_lambda(required_item_lambda)
|
||||
required_panels = parse_lambda(required_panel_lambda)
|
||||
|
||||
required_items = frozenset(required_items)
|
||||
|
||||
requirement = {
|
||||
"entities": required_entities,
|
||||
"entities": required_panels,
|
||||
"items": required_items
|
||||
}
|
||||
|
||||
if entity_type == "Obelisk Side":
|
||||
eps = set(next(iter(required_entities)))
|
||||
eps = set(next(iter(required_panels)))
|
||||
eps -= {"Theater to Tunnels"}
|
||||
|
||||
eps_ints = {int(h, 16) for h in eps}
|
||||
@@ -253,43 +260,39 @@ class StaticWitnessLogicObj:
|
||||
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
|
||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = requirement
|
||||
|
||||
current_region.logical_entities.append(entity_hex)
|
||||
current_region.physical_entities.append(entity_hex)
|
||||
current_region["entities"].append(entity_hex)
|
||||
current_region["physical_entities"].append(entity_hex)
|
||||
|
||||
self.add_easter_eggs()
|
||||
|
||||
def reverse_connection(self, source_region: str, connection: ConnectionDefinition) -> None:
|
||||
# Reverse this connection with all its possibilities, except the ones marked as "OneWay".
|
||||
remaining_options: Set[FrozenSet[str]] = set()
|
||||
for sub_option in connection.traversal_rule:
|
||||
if not any(req == "TrueOneWay" for req in sub_option):
|
||||
remaining_options.add(sub_option)
|
||||
def reverse_connection(self, source_region: str, connection: Tuple[str, Set[WitnessRule]]) -> None:
|
||||
target = connection[0]
|
||||
traversal_options = connection[1]
|
||||
|
||||
reversed_connection = ConnectionDefinition(source_region, frozenset(remaining_options))
|
||||
if reversed_connection.can_be_traversed:
|
||||
self.CONNECTIONS_WITH_DUPLICATES[connection.target_region].append(reversed_connection)
|
||||
# Reverse this connection with all its possibilities, except the ones marked as "OneWay".
|
||||
for requirement in traversal_options:
|
||||
remaining_options = set()
|
||||
for option in requirement:
|
||||
if not any(req == "TrueOneWay" for req in option):
|
||||
remaining_options.add(option)
|
||||
|
||||
if remaining_options:
|
||||
self.CONNECTIONS_WITH_DUPLICATES[target][source_region].add(frozenset(remaining_options))
|
||||
|
||||
def reverse_connections(self) -> None:
|
||||
# Iterate all connections
|
||||
for region_name, connections in list(self.CONNECTIONS_WITH_DUPLICATES.items()):
|
||||
for connection in connections:
|
||||
for connection in connections.items():
|
||||
self.reverse_connection(region_name, connection)
|
||||
|
||||
def combine_connections(self) -> None:
|
||||
# All regions need to be present, and this dict is copied later - Thus, defaultdict is not the correct choice.
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME = {region_name: [] for region_name in self.ALL_REGIONS_BY_NAME}
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME = {region_name: set() for region_name in self.ALL_REGIONS_BY_NAME}
|
||||
|
||||
for source, connections in self.CONNECTIONS_WITH_DUPLICATES.items():
|
||||
# Organize rules by target region
|
||||
traversal_options_by_target_region = defaultdict(list)
|
||||
for target_region, traversal_option in connections:
|
||||
traversal_options_by_target_region[target_region].append(traversal_option)
|
||||
|
||||
# Combine connections to the same target region into one connection
|
||||
for target, traversal_rules in traversal_options_by_target_region.items():
|
||||
combined_rule = logical_or_witness_rules(traversal_rules)
|
||||
combined_connection = ConnectionDefinition(target, combined_rule)
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME[source].append(combined_connection)
|
||||
for target, requirement in connections.items():
|
||||
combined_req = logical_or_witness_rules(requirement)
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME[source].add((target, combined_req))
|
||||
|
||||
|
||||
# Item data parsed from WitnessItems.txt
|
||||
|
||||
@@ -2,12 +2,17 @@ from datetime import date
|
||||
from math import floor
|
||||
from pkgutil import get_data
|
||||
from random import Random
|
||||
from typing import Collection, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar
|
||||
|
||||
from .definition_classes import AreaDefinition, ConnectionDefinition, RegionDefinition, WitnessRule
|
||||
from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# A WitnessRule is just an or-chain of and-conditions.
|
||||
# It represents the set of all options that could fulfill this requirement.
|
||||
# E.g. if something requires "Dots or (Shapers and Stars)", it'd be represented as: {{"Dots"}, {"Shapers, "Stars"}}
|
||||
# {} is an unusable requirement.
|
||||
# {{}} is an always usable requirement.
|
||||
WitnessRule = FrozenSet[FrozenSet[str]]
|
||||
|
||||
|
||||
def cast_not_none(value: Optional[T]) -> T:
|
||||
assert value is not None
|
||||
@@ -57,7 +62,7 @@ def build_weighted_int_list(inputs: Collection[float], total: int) -> List[int]:
|
||||
return rounded_output
|
||||
|
||||
|
||||
def define_new_region(region_string: str, area: AreaDefinition) -> Tuple[RegionDefinition, List[ConnectionDefinition]]:
|
||||
def define_new_region(region_string: str, area: dict[str, Any]) -> Tuple[Dict[str, Any], Set[Tuple[str, WitnessRule]]]:
|
||||
"""
|
||||
Returns a region object by parsing a line in the logic file
|
||||
"""
|
||||
@@ -72,28 +77,35 @@ def define_new_region(region_string: str, area: AreaDefinition) -> Tuple[RegionD
|
||||
region_name = region_name_split[0]
|
||||
region_name_simple = region_name_split[1][:-1]
|
||||
|
||||
options = []
|
||||
options = set()
|
||||
|
||||
for _ in range(len(line_split) // 2):
|
||||
connected_region = line_split.pop(0)
|
||||
traversal_rule_string = line_split.pop(0)
|
||||
corresponding_lambda = line_split.pop(0)
|
||||
|
||||
options.append(ConnectionDefinition(connected_region, parse_witness_rule(traversal_rule_string)))
|
||||
|
||||
region_obj = RegionDefinition(region_name, region_name_simple, area)
|
||||
options.add(
|
||||
(connected_region, parse_lambda(corresponding_lambda))
|
||||
)
|
||||
|
||||
region_obj = {
|
||||
"name": region_name,
|
||||
"shortName": region_name_simple,
|
||||
"entities": [],
|
||||
"physical_entities": [],
|
||||
"area": area,
|
||||
}
|
||||
return region_obj, options
|
||||
|
||||
|
||||
def parse_witness_rule(rule_string: str) -> WitnessRule:
|
||||
def parse_lambda(lambda_string: str) -> WitnessRule:
|
||||
"""
|
||||
Turns a rule string literal like this: a | b & c
|
||||
into a set of sets (called "WitnessRule") like this: {{a}, {b, c}}
|
||||
The rule string has to be in DNF.
|
||||
Turns a lambda String literal like this: a | b & c
|
||||
into a set of sets like this: {{a}, {b, c}}
|
||||
The lambda has to be in DNF.
|
||||
"""
|
||||
if rule_string == "True":
|
||||
if lambda_string == "True":
|
||||
return frozenset([frozenset()])
|
||||
split_ands = set(rule_string.split(" | "))
|
||||
split_ands = set(lambda_string.split(" | "))
|
||||
return frozenset({frozenset(a.split(" & ")) for a in split_ands})
|
||||
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ class EntityHuntPicker:
|
||||
|
||||
eligible_panels_by_area = defaultdict(set)
|
||||
for eligible_panel in all_eligible_panels:
|
||||
associated_area = static_witness_logic.ENTITIES_BY_HEX[eligible_panel]["area"].name
|
||||
associated_area = static_witness_logic.ENTITIES_BY_HEX[eligible_panel]["area"]["name"]
|
||||
eligible_panels_by_area[associated_area].add(eligible_panel)
|
||||
|
||||
return all_eligible_panels, eligible_panels_by_area
|
||||
|
||||
@@ -18,7 +18,7 @@ if __name__ == "__main__":
|
||||
for entity_id, entity_object in static_witness_logic.ENTITIES_BY_HEX.items():
|
||||
location_id = entity_object["id"]
|
||||
|
||||
area = entity_object["area"].name
|
||||
area = entity_object["area"]["name"]
|
||||
area_to_entity_ids[area].append(entity_id)
|
||||
|
||||
if location_id is None:
|
||||
|
||||
@@ -464,7 +464,7 @@ def choose_areas(world: "WitnessWorld", amount: int, locations_per_area: Dict[st
|
||||
|
||||
|
||||
def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]], Dict[str, List[Item]]]:
|
||||
potential_areas = list(static_witness_logic.ALL_AREAS_BY_NAME.values())
|
||||
potential_areas = list(static_witness_logic.ALL_AREAS_BY_NAME.keys())
|
||||
|
||||
locations_per_area = {}
|
||||
items_per_area = {}
|
||||
@@ -472,14 +472,14 @@ def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]]
|
||||
for area in potential_areas:
|
||||
regions = [
|
||||
world.get_region(region)
|
||||
for region in area.regions
|
||||
for region in static_witness_logic.ALL_AREAS_BY_NAME[area]["regions"]
|
||||
if region in world.player_regions.created_region_names
|
||||
]
|
||||
locations = [location for region in regions for location in region.get_locations() if not location.is_event]
|
||||
|
||||
if locations:
|
||||
locations_per_area[area.name] = locations
|
||||
items_per_area[area.name] = [location.item for location in locations]
|
||||
locations_per_area[area] = locations
|
||||
items_per_area[area] = [location.item for location in locations]
|
||||
|
||||
return locations_per_area, items_per_area
|
||||
|
||||
@@ -516,7 +516,7 @@ def word_area_hint(world: "WitnessWorld", hinted_area: str, area_items: List[Ite
|
||||
hunt_panels = None
|
||||
if world.options.victory_condition == "panel_hunt" and hinted_area != "Easter Eggs":
|
||||
hunt_panels = sum(
|
||||
static_witness_logic.ENTITIES_BY_HEX[hunt_entity]["area"].name == hinted_area
|
||||
static_witness_logic.ENTITIES_BY_HEX[hunt_entity]["area"]["name"] == hinted_area
|
||||
for hunt_entity in world.player_logic.HUNT_ENTITIES
|
||||
)
|
||||
|
||||
@@ -620,7 +620,7 @@ def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int,
|
||||
|
||||
already_hinted_locations |= {
|
||||
loc for loc in world.multiworld.get_reachable_locations(state, world.player)
|
||||
if loc.address and static_witness_logic.ENTITIES_BY_NAME[loc.name]["area"].name == "Tutorial (Inside)"
|
||||
if loc.address and static_witness_logic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)"
|
||||
}
|
||||
|
||||
intended_location_hints = hint_amount - area_hints
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Tuple
|
||||
|
||||
from schema import And, Schema
|
||||
|
||||
@@ -20,10 +20,10 @@ from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, Dict, List, Set, Tuple, cast
|
||||
|
||||
from .data import static_logic as static_witness_logic
|
||||
from .data.definition_classes import ConnectionDefinition, WitnessRule
|
||||
from .data.item_definition_classes import DoorItemDefinition, ItemCategory, ProgressiveItemDefinition
|
||||
from .data.static_logic import StaticWitnessLogicObj
|
||||
from .data.utils import (
|
||||
WitnessRule,
|
||||
get_boat,
|
||||
get_caves_except_path_to_challenge_exclusion_list,
|
||||
get_complex_additional_panels,
|
||||
@@ -47,7 +47,7 @@ from .data.utils import (
|
||||
get_vault_exclusion_list,
|
||||
logical_and_witness_rules,
|
||||
logical_or_witness_rules,
|
||||
parse_witness_rule,
|
||||
parse_lambda,
|
||||
)
|
||||
from .entity_hunt import EntityHuntPicker
|
||||
|
||||
@@ -97,10 +97,10 @@ class WitnessPlayerLogic:
|
||||
elif self.DIFFICULTY == "none":
|
||||
self.REFERENCE_LOGIC = static_witness_logic.vanilla
|
||||
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL: Dict[str, List[ConnectionDefinition]] = copy.deepcopy(
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL: Dict[str, Set[Tuple[str, WitnessRule]]] = copy.deepcopy(
|
||||
self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME
|
||||
)
|
||||
self.CONNECTIONS_BY_REGION_NAME: Dict[str, List[ConnectionDefinition]] = copy.deepcopy(
|
||||
self.CONNECTIONS_BY_REGION_NAME: Dict[str, Set[Tuple[str, WitnessRule]]] = copy.deepcopy(
|
||||
self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME
|
||||
)
|
||||
self.DEPENDENT_REQUIREMENTS_BY_HEX: Dict[str, Dict[str, WitnessRule]] = copy.deepcopy(
|
||||
@@ -178,7 +178,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
entity_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity_hex]
|
||||
|
||||
if entity_obj["region"] is not None and entity_obj["region"].name in self.UNREACHABLE_REGIONS:
|
||||
if entity_obj["region"] is not None and entity_obj["region"]["name"] in self.UNREACHABLE_REGIONS:
|
||||
return frozenset()
|
||||
|
||||
# For the requirement of an entity, we consider two things:
|
||||
@@ -270,7 +270,7 @@ class WitnessPlayerLogic:
|
||||
new_items = theoretical_new_items
|
||||
if dep_obj["region"] and entity_obj["region"] != dep_obj["region"]:
|
||||
new_items = frozenset(
|
||||
frozenset(possibility | {dep_obj["region"].name})
|
||||
frozenset(possibility | {dep_obj["region"]["name"]})
|
||||
for possibility in new_items
|
||||
)
|
||||
|
||||
@@ -359,11 +359,11 @@ class WitnessPlayerLogic:
|
||||
line_split = line.split(" - ")
|
||||
|
||||
requirement = {
|
||||
"entities": parse_witness_rule(line_split[1]),
|
||||
"entities": parse_lambda(line_split[1]),
|
||||
}
|
||||
|
||||
if len(line_split) > 2:
|
||||
required_items = parse_witness_rule(line_split[2])
|
||||
required_items = parse_lambda(line_split[2])
|
||||
items_actually_in_the_game = [
|
||||
item_name for item_name, item_definition in static_witness_logic.ALL_ITEMS.items()
|
||||
if item_definition.category is ItemCategory.SYMBOL
|
||||
@@ -394,31 +394,26 @@ class WitnessPlayerLogic:
|
||||
return
|
||||
|
||||
if adj_type == "New Connections":
|
||||
# This adjustment type does not actually reverse the connection if it could be reversed.
|
||||
# If needed, this might be added later
|
||||
line_split = line.split(" - ")
|
||||
source_region = line_split[0]
|
||||
target_region = line_split[1]
|
||||
panel_set_string = line_split[2]
|
||||
|
||||
for connection in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region]:
|
||||
if connection.target_region == target_region:
|
||||
if connection[0] == target_region:
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].remove(connection)
|
||||
|
||||
if panel_set_string == "TrueOneWay":
|
||||
# This means the connection can be completely replaced
|
||||
only_connection = ConnectionDefinition(target_region, frozenset({frozenset(["TrueOneWay"])}))
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].append(only_connection)
|
||||
else:
|
||||
combined_rule = logical_or_witness_rules(
|
||||
[connection.traversal_rule, parse_witness_rule(panel_set_string)]
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add(
|
||||
(target_region, frozenset({frozenset(["TrueOneWay"])}))
|
||||
)
|
||||
combined_connection = ConnectionDefinition(target_region, combined_rule)
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].append(combined_connection)
|
||||
else:
|
||||
new_lambda = logical_or_witness_rules([connection[1], parse_lambda(panel_set_string)])
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add((target_region, new_lambda))
|
||||
break
|
||||
else:
|
||||
new_connection = ConnectionDefinition(target_region, parse_witness_rule(panel_set_string))
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].append(new_connection)
|
||||
new_conn = (target_region, parse_lambda(panel_set_string))
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add(new_conn)
|
||||
|
||||
if adj_type == "Added Locations":
|
||||
if "0x" in line:
|
||||
@@ -563,7 +558,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
self.AVAILABLE_EASTER_EGGS_PER_REGION = defaultdict(int)
|
||||
for entity_hex in self.AVAILABLE_EASTER_EGGS:
|
||||
region_name = static_witness_logic.ENTITIES_BY_HEX[entity_hex]["region"].name
|
||||
region_name = static_witness_logic.ENTITIES_BY_HEX[entity_hex]["region"]["name"]
|
||||
self.AVAILABLE_EASTER_EGGS_PER_REGION[region_name] += 1
|
||||
|
||||
eggs_per_check, logically_required_eggs_per_check = world.options.easter_egg_hunt.get_step_and_logical_step()
|
||||
@@ -801,7 +796,7 @@ class WitnessPlayerLogic:
|
||||
next_region = regions_to_check.pop()
|
||||
|
||||
for region_exit in self.CONNECTIONS_BY_REGION_NAME[next_region]:
|
||||
target = region_exit.target_region
|
||||
target = region_exit[0]
|
||||
|
||||
if target in reachable_regions:
|
||||
continue
|
||||
@@ -849,7 +844,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
# First, entities in unreachable regions are obviously themselves unreachable.
|
||||
for region in new_unreachable_regions:
|
||||
for entity in static_witness_logic.ALL_REGIONS_BY_NAME[region].physical_entities:
|
||||
for entity in static_witness_logic.ALL_REGIONS_BY_NAME[region]["physical_entities"]:
|
||||
# Never disable the Victory Location.
|
||||
if entity == self.VICTORY_LOCATION:
|
||||
continue
|
||||
@@ -884,11 +879,11 @@ class WitnessPlayerLogic:
|
||||
if not new_unreachable_regions and not newly_discovered_disabled_entities:
|
||||
return
|
||||
|
||||
def reduce_connection_requirement(self, connection: ConnectionDefinition) -> ConnectionDefinition:
|
||||
def reduce_connection_requirement(self, connection: Tuple[str, WitnessRule]) -> WitnessRule:
|
||||
all_possibilities = []
|
||||
|
||||
# Check each traversal option individually
|
||||
for option in connection.traversal_rule:
|
||||
for option in connection[1]:
|
||||
individual_entity_requirements: List[WitnessRule] = []
|
||||
for entity in option:
|
||||
# If a connection requires solving a disabled entity, it is not valid.
|
||||
@@ -906,7 +901,7 @@ class WitnessPlayerLogic:
|
||||
entity_req = self.get_entity_requirement(entity)
|
||||
|
||||
if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]:
|
||||
region_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"].name
|
||||
region_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]["name"]
|
||||
entity_req = logical_and_witness_rules([entity_req, frozenset({frozenset({region_name})})])
|
||||
|
||||
individual_entity_requirements.append(entity_req)
|
||||
@@ -914,7 +909,7 @@ class WitnessPlayerLogic:
|
||||
# Merge all possible requirements into one DNF condition.
|
||||
all_possibilities.append(logical_and_witness_rules(individual_entity_requirements))
|
||||
|
||||
return ConnectionDefinition(connection.target_region, logical_or_witness_rules(all_possibilities))
|
||||
return logical_or_witness_rules(all_possibilities)
|
||||
|
||||
def make_dependency_reduced_checklist(self) -> None:
|
||||
"""
|
||||
@@ -947,14 +942,14 @@ class WitnessPlayerLogic:
|
||||
|
||||
# Make independent region connection requirements based on the entities they require
|
||||
for region, connections in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL.items():
|
||||
new_connections = []
|
||||
new_connections = set()
|
||||
|
||||
for connection in connections:
|
||||
reduced_connection = self.reduce_connection_requirement(connection)
|
||||
overall_requirement = self.reduce_connection_requirement(connection)
|
||||
|
||||
# If there is a way to use this connection, add it.
|
||||
if reduced_connection.can_be_traversed:
|
||||
new_connections.append(reduced_connection)
|
||||
if overall_requirement:
|
||||
new_connections.add((connection[0], overall_requirement))
|
||||
|
||||
self.CONNECTIONS_BY_REGION_NAME[region] = new_connections
|
||||
|
||||
|
||||
@@ -10,9 +10,8 @@ from BaseClasses import Entrance, Region
|
||||
from worlds.generic.Rules import CollectionRule
|
||||
|
||||
from .data import static_logic as static_witness_logic
|
||||
from .data.definition_classes import WitnessRule
|
||||
from .data.static_logic import StaticWitnessLogicObj
|
||||
from .data.utils import optimize_witness_rule
|
||||
from .data.utils import WitnessRule, optimize_witness_rule
|
||||
from .locations import WitnessPlayerLocations
|
||||
from .player_logic import WitnessPlayerLogic
|
||||
|
||||
@@ -115,7 +114,7 @@ class WitnessPlayerRegions:
|
||||
if k not in player_logic.UNREACHABLE_REGIONS
|
||||
}
|
||||
|
||||
event_locations_per_region: Dict[str, Dict[str, int]] = defaultdict(dict)
|
||||
event_locations_per_region = defaultdict(dict)
|
||||
|
||||
for event_location, event_item_and_entity in player_logic.EVENT_ITEM_PAIRS.items():
|
||||
entity_or_region = event_item_and_entity[1]
|
||||
@@ -127,13 +126,13 @@ class WitnessPlayerRegions:
|
||||
if region is None:
|
||||
region_name = "Entry"
|
||||
else:
|
||||
region_name = region.name
|
||||
region_name = region["name"]
|
||||
order = self.reference_logic.ENTITIES_BY_HEX[entity_or_region]["order"]
|
||||
event_locations_per_region[region_name][event_location] = order
|
||||
|
||||
for region_name, region in regions_to_create.items():
|
||||
location_entities_for_this_region = [
|
||||
self.reference_logic.ENTITIES_BY_HEX[entity] for entity in region.logical_entities
|
||||
self.reference_logic.ENTITIES_BY_HEX[entity] for entity in region["entities"]
|
||||
]
|
||||
locations_for_this_region = {
|
||||
entity["checkName"]: entity["order"] for entity in location_entities_for_this_region
|
||||
|
||||
@@ -10,7 +10,7 @@ from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import CollectionRule, set_rule
|
||||
|
||||
from .data import static_logic as static_witness_logic
|
||||
from .data.definition_classes import WitnessRule
|
||||
from .data.utils import WitnessRule
|
||||
from .player_logic import WitnessPlayerLogic
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -516,6 +516,6 @@ async def main() -> None:
|
||||
|
||||
|
||||
def launch() -> None:
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -177,7 +177,7 @@ def main() -> None:
|
||||
|
||||
import colorama
|
||||
|
||||
colorama.just_fix_windows_console()
|
||||
colorama.init()
|
||||
|
||||
asyncio.run(_main())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user