Compare commits

..

1 Commits

Author SHA1 Message Date
Exempt-Medic
6c5418d708 Include that an empty string is expected 2025-03-11 11:47:21 -04:00
43 changed files with 190 additions and 273 deletions

View File

@@ -511,7 +511,7 @@ if __name__ == '__main__':
import colorama
colorama.just_fix_windows_console()
colorama.init()
asyncio.run(main())
colorama.deinit()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -370,7 +370,7 @@ if __name__ == "__main__":
import colorama
colorama.just_fix_windows_console()
colorama.init()
asyncio.run(main())
colorama.deinit()

View File

@@ -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):

View File

@@ -346,7 +346,7 @@ if __name__ == '__main__':
import colorama
colorama.just_fix_windows_console()
colorama.init()
asyncio.run(main())
colorama.deinit()

View File

@@ -735,6 +735,6 @@ async def main() -> None:
if __name__ == '__main__':
colorama.just_fix_windows_console()
colorama.init()
asyncio.run(main())
colorama.deinit()

View File

@@ -500,7 +500,7 @@ def main():
import colorama
colorama.just_fix_windows_console()
colorama.init()
asyncio.run(_main())
colorama.deinit()

View File

@@ -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()

View File

@@ -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" }} />
🎲

View File

@@ -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 %}

View File

@@ -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()

View File

@@ -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. |

View File

@@ -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()

View File

@@ -261,6 +261,6 @@ def launch():
# options = Utils.get_options()
import colorama
colorama.just_fix_windows_console()
colorama.init()
asyncio.run(main())
colorama.deinit()

View File

@@ -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)

View File

@@ -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.

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -1625,6 +1625,6 @@ def get_location_offset(mission_id):
def launch():
colorama.just_fix_windows_console()
colorama.init()
asyncio.run(main())
colorama.deinit()

View File

@@ -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.

View File

@@ -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 saffichent, 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.

View File

@@ -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).

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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})

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -1,4 +1,5 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Tuple
from schema import And, Schema

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -516,6 +516,6 @@ async def main() -> None:
def launch() -> None:
colorama.just_fix_windows_console()
colorama.init()
asyncio.run(main())
colorama.deinit()

View File

@@ -177,7 +177,7 @@ def main() -> None:
import colorama
colorama.just_fix_windows_console()
colorama.init()
asyncio.run(_main())