Merge branch 'ArchipelagoMW:main' into main

This commit is contained in:
CookieCat
2024-01-17 09:52:15 -05:00
committed by GitHub
46 changed files with 810 additions and 267 deletions

5
.coveragerc Normal file
View File

@@ -0,0 +1,5 @@
[report]
exclude_lines =
pragma: no cover
if TYPE_CHECKING:
if typing.TYPE_CHECKING:

View File

@@ -71,7 +71,7 @@ jobs:
continue-on-error: true
if: env.diff != '' && matrix.task == 'flake8'
run: |
flake8 --count --max-complexity=10 --max-doc-length=120 --max-line-length=120 --statistics ${{ env.diff }}
flake8 --count --max-complexity=14 --max-doc-length=120 --max-line-length=120 --statistics ${{ env.diff }}
- name: "mypy: Type check modified files"
continue-on-error: true

View File

@@ -1056,9 +1056,6 @@ class Location:
@property
def hint_text(self) -> str:
hint_text = getattr(self, "_hint_text", None)
if hint_text:
return hint_text
return "at " + self.name.replace("_", " ").replace("-", " ")

View File

@@ -26,6 +26,13 @@ class ALttPLocation(Location):
self.player_address = player_address
self._hint_text = hint_text
@property
def hint_text(self) -> str:
hint_text = getattr(self, "_hint_text", None)
if hint_text:
return hint_text
return "at " + self.name.replace("_", " ").replace("-", " ")
class ALttPItem(Item):
game: str = "A Link to the Past"

View File

@@ -7,16 +7,25 @@ from ..AutoWorld import WebWorld, World
class Bk_SudokuWebWorld(WebWorld):
options_page = "games/Sudoku/info/en"
theme = 'partyTime'
tutorials = [
Tutorial(
tutorial_name='Setup Guide',
description='A guide to playing BK Sudoku',
language='English',
file_name='setup_en.md',
link='setup/en',
authors=['Jarno']
)
]
setup_en = Tutorial(
tutorial_name='Setup Guide',
description='A guide to playing BK Sudoku',
language='English',
file_name='setup_en.md',
link='setup/en',
authors=['Jarno']
)
setup_de = Tutorial(
tutorial_name='Setup Anleitung',
description='Eine Anleitung um BK-Sudoku zu spielen',
language='Deutsch',
file_name='setup_de.md',
link='setup/de',
authors=['Held_der_Zeit']
)
tutorials = [setup_en, setup_de]
class Bk_SudokuWorld(World):

View File

@@ -0,0 +1,21 @@
# BK-Sudoku
## Was ist das für ein Spiel?
BK-Sudoku ist kein typisches Archipelago-Spiel; stattdessen ist es ein gewöhnlicher Sudoku-Client der sich zu jeder
beliebigen Multiworld verbinden kann. Einmal verbunden kannst du ein 9x9 Sudoku spielen um einen zufälligen Hinweis
für dein Spiel zu erhalten. Es ist zwar langsam, aber es gibt dir etwas zu tun, solltest du mal nicht in der Lage sein
weitere „Checks” zu erreichen.
(Wer mag kann auch einfach so Sudoku spielen. Man muss nicht mit einer Multiworld verbunden sein, um ein Sudoku zu
spielen/generieren.)
## Wie werden Hinweise freigeschalten?
Nach dem Lösen eines Sudokus wird für den verbundenen Slot ein zufällig ausgewählter Hinweis freigegeben, für einen
Gegenstand der noch nicht gefunden wurde.
## Wo ist die Seite für die Einstellungen?
Es gibt keine Seite für die Einstellungen. Dieses Spiel kann nicht in deinen YAML-Dateien benutzt werden. Stattdessen
kann sich der Client mit einem beliebigen Slot einer Multiworld verbinden. In dem Client selbst kann aber der
Schwierigkeitsgrad des Sudoku ausgewählt werden.

View File

@@ -0,0 +1,27 @@
# BK-Sudoku Setup Anleitung
## Benötigte Software
- [Bk-Sudoku](https://github.com/Jarno458/sudoku)
- Windows 8 oder höher
## Generelles Konzept
Dies ist ein Client, der sich mit jedem beliebigen Slot einer Multiworld verbinden kann. Er lässt dich ein (9x9) Sudoku
spielen, um zufällige Hinweise für den verbundenen Slot freizuschalten.
Aufgrund des Fakts, dass der Sudoku-Client sich zu jedem beliebigen Slot verbinden kann, ist es daher nicht notwendig
eine YAML für dieses Spiel zu generieren, da es keinen neuen Slot zur Multiworld-Session hinzufügt.
## Installationsprozess
Gehe zu der aktuellsten (latest) Veröffentlichung der [BK-Sudoku Releases](https://github.com/Jarno458/sudoku/releases).
Downloade und extrahiere/entpacke die `Bk_Sudoku.zip`-Datei.
## Verbinden mit einer Multiworld
1. Starte `Bk_Sudoku.exe`
2. Trage den Namen des Slots ein, mit dem du dich verbinden möchtest
3. Trage die Server-URL und den Port ein
4. Drücke auf Verbinden (connect)
5. Wähle deinen Schwierigkeitsgrad
6. Versuche das Sudoku zu Lösen

View File

@@ -11,16 +11,26 @@ from .Rules import get_button_rule
class CliqueWebWorld(WebWorld):
theme = "partyTime"
tutorials = [
Tutorial(
tutorial_name="Start Guide",
description="A guide to playing Clique.",
language="English",
file_name="guide_en.md",
link="guide/en",
authors=["Phar"]
)
]
setup_en = Tutorial(
tutorial_name="Start Guide",
description="A guide to playing Clique.",
language="English",
file_name="guide_en.md",
link="guide/en",
authors=["Phar"]
)
setup_de = Tutorial(
tutorial_name="Anleitung zum Anfangen",
description="Eine Anleitung um Clique zu spielen.",
language="Deutsch",
file_name="guide_de.md",
link="guide/de",
authors=["Held_der_Zeit"]
)
tutorials = [setup_en, setup_de]
class CliqueWorld(World):

View File

@@ -0,0 +1,18 @@
# Clique
## Was ist das für ein Spiel?
~~Clique ist ein psychologisches Überlebens-Horror Spiel, in dem der Spieler der Versuchung wiederstehen muss große~~
~~(rote) Knöpfe zu drücken.~~
Clique ist ein scherzhaftes Spiel, welches für Archipelago im März 2023 entwickelt wurde, um zu zeigen, wie einfach
es sein kann eine Welt für Archipelago zu entwicklen. Das Ziel des Spiels ist es den großen (standardmäßig) roten
Knopf zu drücken. Wenn ein Spieler auf dem `hard_mode` (schwieriger Modus) spielt, muss dieser warten bis jemand
anderes in der Multiworld den Knopf aktiviert, damit er gedrückt werden kann.
Clique kann auf den meisten modernen, HTML5-fähigen Browsern gespielt werden.
## Wo ist die Seite für die Einstellungen?
Die [Seite für die Spielereinstellungen dieses Spiels](../player-options) enthält alle Optionen die man benötigt um
eine YAML-Datei zu konfigurieren und zu exportieren.

View File

@@ -0,0 +1,25 @@
# Clique Anleitung
Nachdem dein Seed generiert wurde, gehe auf die Website von [Clique dem Spiel](http://clique.pharware.com/) und gib
Server-Daten, deinen Slot-Namen und ein Passwort (falls vorhanden) ein. Klicke dann auf "Connect" (Verbinden).
Wenn du auf "Einfach" spielst, kannst du unbedenklich den Knopf drücken und deine "Befriedigung" erhalten.
Wenn du auf "Schwer" spielst, ist es sehr wahrscheinlich, dass du warten musst bevor du dein Ziel erreichen kannst.
Glücklicherweise läuft Click auf den meißten großen Browsern, die HTML5 unterstützen. Das heißt du kannst Clique auf
deinem Handy starten und produktiv sein während du wartest!
Falls du einige Ideen brauchst was du tun kannst, während du wartest bis der Knopf aktiviert wurde, versuche
(mindestens) eins der Folgenden:
- Dein Zimmer aufräumen.
- Die Wäsche machen.
- Etwas Essen von einem X-Belieben Fast Food Restaruant holen.
- Das tägliche Wordle machen.
- ~~Deine Seele an **Phar** verkaufen.~~
- Deine Hausaufgaben erledigen.
- Deine Post abholen.
~~Solltest du auf irgendwelche Probleme in diesem Spiel stoßen, solltest du keinesfalls nicht **thephar** auf~~
~~Discord kontaktieren. *zwinker* *zwinker*~~

View File

@@ -821,7 +821,8 @@ class KH2Context(CommonContext):
def finishedGame(ctx: KH2Context, message):
if ctx.kh2slotdata['FinalXemnas'] == 1:
if not ctx.final_xemnas and ctx.kh2_loc_name_to_id[LocationName.FinalXemnas] in ctx.locations_checked:
if not ctx.final_xemnas and ctx.kh2_read_byte(ctx.Save + all_world_locations[LocationName.FinalXemnas].addrObtained) \
& 0x1 << all_world_locations[LocationName.FinalXemnas].bitIndex > 0:
ctx.final_xemnas = True
# three proofs
if ctx.kh2slotdata['Goal'] == 0:

View File

@@ -2,22 +2,7 @@ import typing
from BaseClasses import Item
from .Names import ItemName
class KH2Item(Item):
game: str = "Kingdom Hearts 2"
class ItemData(typing.NamedTuple):
quantity: int = 0
kh2id: int = 0
# Save+ mem addr
memaddr: int = 0
# some items have bitmasks. if bitmask>0 bitor to give item else
bitmask: int = 0
# if ability then
ability: bool = False
from .Subclasses import ItemData
# 0x130000
Reports_Table = {
@@ -209,7 +194,7 @@ Armor_Table = {
ItemName.GrandRibbon: ItemData(1, 157, 0x35D4),
}
Usefull_Table = {
ItemName.MickeyMunnyPouch: ItemData(1, 535, 0x3695), # 5000 munny per
ItemName.MickeyMunnyPouch: ItemData(1, 535, 0x3695), # 5000 munny per
ItemName.OletteMunnyPouch: ItemData(2, 362, 0x363C), # 2500 munny per
ItemName.HadesCupTrophy: ItemData(1, 537, 0x3696),
ItemName.UnknownDisk: ItemData(1, 462, 0x365F),
@@ -349,7 +334,7 @@ GoofyAbility_Table = {
Wincon_Table = {
ItemName.LuckyEmblem: ItemData(kh2id=367, memaddr=0x3641), # letter item
ItemName.Victory: ItemData(kh2id=263, memaddr=0x111),
# ItemName.Victory: ItemData(kh2id=263, memaddr=0x111),
ItemName.Bounty: ItemData(kh2id=461, memaddr=0x365E), # Dummy 14
# ItemName.UniversalKey:ItemData(,365,0x363F,0)#Tournament Poster
}

View File

@@ -1,19 +1,9 @@
import typing
from BaseClasses import Location
from .Names import LocationName, ItemName
class KH2Location(Location):
game: str = "Kingdom Hearts 2"
class LocationData(typing.NamedTuple):
locid: int
yml: str
charName: str = "Sora"
charNumber: int = 1
from .Names import LocationName, ItemName, RegionName
from .Subclasses import LocationData
from .Regions import KH2REGIONS
# data's addrcheck sys3 addr obtained roomid bit index is eventid
LoD_Checks = {
@@ -541,7 +531,7 @@ TWTNW_Checks = {
LocationName.Xemnas1: LocationData(26, "Double Get Bonus"),
LocationName.Xemnas1GetBonus: LocationData(26, "Second Get Bonus"),
LocationName.Xemnas1SecretAnsemReport13: LocationData(537, "Chest"),
LocationName.FinalXemnas: LocationData(71, "Get Bonus"),
# LocationName.FinalXemnas: LocationData(71, "Get Bonus"),
LocationName.XemnasDataPowerBoost: LocationData(554, "Chest"),
}
@@ -806,74 +796,75 @@ Atlantica_Checks = {
}
event_location_to_item = {
LocationName.HostileProgramEventLocation: ItemName.HostileProgramEvent,
LocationName.McpEventLocation: ItemName.McpEvent,
LocationName.HostileProgramEventLocation: ItemName.HostileProgramEvent,
LocationName.McpEventLocation: ItemName.McpEvent,
# LocationName.ASLarxeneEventLocation: ItemName.ASLarxeneEvent,
LocationName.DataLarxeneEventLocation: ItemName.DataLarxeneEvent,
LocationName.BarbosaEventLocation: ItemName.BarbosaEvent,
LocationName.GrimReaper1EventLocation: ItemName.GrimReaper1Event,
LocationName.GrimReaper2EventLocation: ItemName.GrimReaper2Event,
LocationName.DataLuxordEventLocation: ItemName.DataLuxordEvent,
LocationName.DataAxelEventLocation: ItemName.DataAxelEvent,
LocationName.CerberusEventLocation: ItemName.CerberusEvent,
LocationName.OlympusPeteEventLocation: ItemName.OlympusPeteEvent,
LocationName.HydraEventLocation: ItemName.HydraEvent,
LocationName.DataLarxeneEventLocation: ItemName.DataLarxeneEvent,
LocationName.BarbosaEventLocation: ItemName.BarbosaEvent,
LocationName.GrimReaper1EventLocation: ItemName.GrimReaper1Event,
LocationName.GrimReaper2EventLocation: ItemName.GrimReaper2Event,
LocationName.DataLuxordEventLocation: ItemName.DataLuxordEvent,
LocationName.DataAxelEventLocation: ItemName.DataAxelEvent,
LocationName.CerberusEventLocation: ItemName.CerberusEvent,
LocationName.OlympusPeteEventLocation: ItemName.OlympusPeteEvent,
LocationName.HydraEventLocation: ItemName.HydraEvent,
LocationName.OcPainAndPanicCupEventLocation: ItemName.OcPainAndPanicCupEvent,
LocationName.OcCerberusCupEventLocation: ItemName.OcCerberusCupEvent,
LocationName.HadesEventLocation: ItemName.HadesEvent,
LocationName.OcCerberusCupEventLocation: ItemName.OcCerberusCupEvent,
LocationName.HadesEventLocation: ItemName.HadesEvent,
# LocationName.ASZexionEventLocation: ItemName.ASZexionEvent,
LocationName.DataZexionEventLocation: ItemName.DataZexionEvent,
LocationName.Oc2TitanCupEventLocation: ItemName.Oc2TitanCupEvent,
LocationName.Oc2GofCupEventLocation: ItemName.Oc2GofCupEvent,
LocationName.DataZexionEventLocation: ItemName.DataZexionEvent,
LocationName.Oc2TitanCupEventLocation: ItemName.Oc2TitanCupEvent,
LocationName.Oc2GofCupEventLocation: ItemName.Oc2GofCupEvent,
# LocationName.Oc2CupsEventLocation: ItemName.Oc2CupsEventLocation,
LocationName.HadesCupEventLocations: ItemName.HadesCupEvents,
LocationName.PrisonKeeperEventLocation: ItemName.PrisonKeeperEvent,
LocationName.OogieBoogieEventLocation: ItemName.OogieBoogieEvent,
LocationName.ExperimentEventLocation: ItemName.ExperimentEvent,
LocationName.HadesCupEventLocations: ItemName.HadesCupEvents,
LocationName.PrisonKeeperEventLocation: ItemName.PrisonKeeperEvent,
LocationName.OogieBoogieEventLocation: ItemName.OogieBoogieEvent,
LocationName.ExperimentEventLocation: ItemName.ExperimentEvent,
# LocationName.ASVexenEventLocation: ItemName.ASVexenEvent,
LocationName.DataVexenEventLocation: ItemName.DataVexenEvent,
LocationName.ShanYuEventLocation: ItemName.ShanYuEvent,
LocationName.AnsemRikuEventLocation: ItemName.AnsemRikuEvent,
LocationName.StormRiderEventLocation: ItemName.StormRiderEvent,
LocationName.DataXigbarEventLocation: ItemName.DataXigbarEvent,
LocationName.RoxasEventLocation: ItemName.RoxasEvent,
LocationName.XigbarEventLocation: ItemName.XigbarEvent,
LocationName.LuxordEventLocation: ItemName.LuxordEvent,
LocationName.SaixEventLocation: ItemName.SaixEvent,
LocationName.XemnasEventLocation: ItemName.XemnasEvent,
LocationName.ArmoredXemnasEventLocation: ItemName.ArmoredXemnasEvent,
LocationName.ArmoredXemnas2EventLocation: ItemName.ArmoredXemnas2Event,
LocationName.DataVexenEventLocation: ItemName.DataVexenEvent,
LocationName.ShanYuEventLocation: ItemName.ShanYuEvent,
LocationName.AnsemRikuEventLocation: ItemName.AnsemRikuEvent,
LocationName.StormRiderEventLocation: ItemName.StormRiderEvent,
LocationName.DataXigbarEventLocation: ItemName.DataXigbarEvent,
LocationName.RoxasEventLocation: ItemName.RoxasEvent,
LocationName.XigbarEventLocation: ItemName.XigbarEvent,
LocationName.LuxordEventLocation: ItemName.LuxordEvent,
LocationName.SaixEventLocation: ItemName.SaixEvent,
LocationName.XemnasEventLocation: ItemName.XemnasEvent,
LocationName.ArmoredXemnasEventLocation: ItemName.ArmoredXemnasEvent,
LocationName.ArmoredXemnas2EventLocation: ItemName.ArmoredXemnas2Event,
# LocationName.FinalXemnasEventLocation: ItemName.FinalXemnasEvent,
LocationName.DataXemnasEventLocation: ItemName.DataXemnasEvent,
LocationName.ThresholderEventLocation: ItemName.ThresholderEvent,
LocationName.BeastEventLocation: ItemName.BeastEvent,
LocationName.DarkThornEventLocation: ItemName.DarkThornEvent,
LocationName.XaldinEventLocation: ItemName.XaldinEvent,
LocationName.DataXaldinEventLocation: ItemName.DataXaldinEvent,
LocationName.TwinLordsEventLocation: ItemName.TwinLordsEvent,
LocationName.GenieJafarEventLocation: ItemName.GenieJafarEvent,
LocationName.DataXemnasEventLocation: ItemName.DataXemnasEvent,
LocationName.ThresholderEventLocation: ItemName.ThresholderEvent,
LocationName.BeastEventLocation: ItemName.BeastEvent,
LocationName.DarkThornEventLocation: ItemName.DarkThornEvent,
LocationName.XaldinEventLocation: ItemName.XaldinEvent,
LocationName.DataXaldinEventLocation: ItemName.DataXaldinEvent,
LocationName.TwinLordsEventLocation: ItemName.TwinLordsEvent,
LocationName.GenieJafarEventLocation: ItemName.GenieJafarEvent,
# LocationName.ASLexaeusEventLocation: ItemName.ASLexaeusEvent,
LocationName.DataLexaeusEventLocation: ItemName.DataLexaeusEvent,
LocationName.ScarEventLocation: ItemName.ScarEvent,
LocationName.GroundShakerEventLocation: ItemName.GroundShakerEvent,
LocationName.DataSaixEventLocation: ItemName.DataSaixEvent,
LocationName.HBDemyxEventLocation: ItemName.HBDemyxEvent,
LocationName.ThousandHeartlessEventLocation: ItemName.ThousandHeartlessEvent,
LocationName.Mushroom13EventLocation: ItemName.Mushroom13Event,
LocationName.SephiEventLocation: ItemName.SephiEvent,
LocationName.DataDemyxEventLocation: ItemName.DataDemyxEvent,
LocationName.CorFirstFightEventLocation: ItemName.CorFirstFightEvent,
LocationName.CorSecondFightEventLocation: ItemName.CorSecondFightEvent,
LocationName.TransportEventLocation: ItemName.TransportEvent,
LocationName.OldPeteEventLocation: ItemName.OldPeteEvent,
LocationName.FuturePeteEventLocation: ItemName.FuturePeteEvent,
LocationName.DataLexaeusEventLocation: ItemName.DataLexaeusEvent,
LocationName.ScarEventLocation: ItemName.ScarEvent,
LocationName.GroundShakerEventLocation: ItemName.GroundShakerEvent,
LocationName.DataSaixEventLocation: ItemName.DataSaixEvent,
LocationName.HBDemyxEventLocation: ItemName.HBDemyxEvent,
LocationName.ThousandHeartlessEventLocation: ItemName.ThousandHeartlessEvent,
LocationName.Mushroom13EventLocation: ItemName.Mushroom13Event,
LocationName.SephiEventLocation: ItemName.SephiEvent,
LocationName.DataDemyxEventLocation: ItemName.DataDemyxEvent,
LocationName.CorFirstFightEventLocation: ItemName.CorFirstFightEvent,
LocationName.CorSecondFightEventLocation: ItemName.CorSecondFightEvent,
LocationName.TransportEventLocation: ItemName.TransportEvent,
LocationName.OldPeteEventLocation: ItemName.OldPeteEvent,
LocationName.FuturePeteEventLocation: ItemName.FuturePeteEvent,
# LocationName.ASMarluxiaEventLocation: ItemName.ASMarluxiaEvent,
LocationName.DataMarluxiaEventLocation: ItemName.DataMarluxiaEvent,
LocationName.TerraEventLocation: ItemName.TerraEvent,
LocationName.TwilightThornEventLocation: ItemName.TwilightThornEvent,
LocationName.Axel1EventLocation: ItemName.Axel1Event,
LocationName.Axel2EventLocation: ItemName.Axel2Event,
LocationName.DataRoxasEventLocation: ItemName.DataRoxasEvent,
LocationName.DataMarluxiaEventLocation: ItemName.DataMarluxiaEvent,
LocationName.TerraEventLocation: ItemName.TerraEvent,
LocationName.TwilightThornEventLocation: ItemName.TwilightThornEvent,
LocationName.Axel1EventLocation: ItemName.Axel1Event,
LocationName.Axel2EventLocation: ItemName.Axel2Event,
LocationName.DataRoxasEventLocation: ItemName.DataRoxasEvent,
LocationName.FinalXemnasEventLocation: ItemName.Victory,
}
all_weapon_slot = {
LocationName.FAKESlot,
@@ -1361,3 +1352,9 @@ exclusion_table = {
location for location, data in all_locations.items() if location not in event_location_to_item.keys() and location not in popups_set and location != LocationName.StationofSerenityPotion and data.yml == "Chest"
}
}
location_groups: typing.Dict[str, list]
location_groups = {
Region_Name: [loc for loc in Region_Locs if "Event" not in loc]
for Region_Name, Region_Locs in KH2REGIONS.items() if Region_Locs
}

View File

@@ -1,9 +1,11 @@
import typing
from BaseClasses import MultiWorld, Region
from . import Locations
from .Locations import KH2Location, event_location_to_item
from . import LocationName, RegionName, Events_Table
from .Subclasses import KH2Location
from .Names import LocationName, RegionName
from .Items import Events_Table
KH2REGIONS: typing.Dict[str, typing.List[str]] = {
"Menu": [],
@@ -788,7 +790,7 @@ KH2REGIONS: typing.Dict[str, typing.List[str]] = {
LocationName.ArmoredXemnas2EventLocation
],
RegionName.FinalXemnas: [
LocationName.FinalXemnas
LocationName.FinalXemnasEventLocation
],
RegionName.DataXemnas: [
LocationName.XemnasDataPowerBoost,
@@ -1020,7 +1022,8 @@ def create_regions(self):
multiworld.regions += [create_region(multiworld, player, active_locations, region, locations) for region, locations in
KH2REGIONS.items()]
# fill the event locations with events
for location, item in event_location_to_item.items():
for location, item in Locations.event_location_to_item.items():
multiworld.get_location(location, player).place_locked_item(
multiworld.worlds[player].create_event_item(item))

View File

@@ -83,6 +83,8 @@ class KH2Rules:
return state.has(ItemName.TornPages, self.player, amount)
def level_locking_unlock(self, state: CollectionState, amount):
if self.world.options.Promise_Charm and state.has(ItemName.PromiseCharm, self.player):
return True
return amount <= sum([state.count(item_name, self.player) for item_name in visit_locking_dict["2VisitLocking"]])
def summon_levels_unlocked(self, state: CollectionState, amount) -> bool:
@@ -270,7 +272,7 @@ class KH2WorldRules(KH2Rules):
add_item_rule(location, lambda item: item.player == self.player and item.name in SupportAbility_Table.keys())
def set_kh2_goal(self):
final_xemnas_location = self.multiworld.get_location(LocationName.FinalXemnas, self.player)
final_xemnas_location = self.multiworld.get_location(LocationName.FinalXemnasEventLocation, self.player)
if self.multiworld.Goal[self.player] == "three_proofs":
final_xemnas_location.access_rule = lambda state: self.kh2_has_all(three_proofs, state)
if self.multiworld.FinalXemnas[self.player]:

29
worlds/kh2/Subclasses.py Normal file
View File

@@ -0,0 +1,29 @@
import typing
from BaseClasses import Location, Item
class KH2Location(Location):
game: str = "Kingdom Hearts 2"
class LocationData(typing.NamedTuple):
locid: int
yml: str
charName: str = "Sora"
charNumber: int = 1
class KH2Item(Item):
game: str = "Kingdom Hearts 2"
class ItemData(typing.NamedTuple):
quantity: int = 0
kh2id: int = 0
# Save+ mem addr
memaddr: int = 0
# some items have bitmasks. if bitmask>0 bitor to give item else
bitmask: int = 0
# if ability then
ability: bool = False

View File

@@ -12,6 +12,7 @@ from .OpenKH import patch_kh2
from .Options import KingdomHearts2Options
from .Regions import create_regions, connect_regions
from .Rules import *
from .Subclasses import KH2Item
def launch_client():
@@ -49,7 +50,9 @@ class KH2World(World):
for item_id, item in enumerate(item_dictionary_table.keys(), 0x130000)}
location_name_to_id = {item: location
for location, item in enumerate(all_locations.keys(), 0x130000)}
item_name_groups = item_groups
location_name_groups = location_groups
visitlocking_dict: Dict[str, int]
plando_locations: Dict[str, str]
@@ -253,11 +256,8 @@ class KH2World(World):
self.goofy_gen_early()
self.keyblade_gen_early()
if self.multiworld.FinalXemnas[self.player]:
self.plando_locations[LocationName.FinalXemnas] = ItemName.Victory
else:
self.plando_locations[LocationName.FinalXemnas] = self.create_filler().name
self.total_locations -= 1
# final xemnas isn't a location anymore
# self.total_locations -= 1
if self.options.WeaponSlotStartHint:
for location in all_weapon_slot:

View File

@@ -2635,12 +2635,6 @@
panels:
- OBSTACLE
The Colorful:
# The set of required_doors in the achievement panel should prevent
# generation from asking you to solve The Colorful before opening all of the
# doors. Access from the roof is included so that the painting here could be
# an entrance. The client will have to be hardcoded to not open the door to
# the achievement until all of the doors are open, whether by solving the
# panels or through receiving items.
entrances:
The Colorful (Gray):
room: The Colorful (Gray)
@@ -2651,31 +2645,53 @@
id: Countdown Panels/Panel_colorful_colorful
check: True
tag: forbid
required_door:
required_panel:
- room: The Colorful (White)
door: Progress Door
panel: BEGIN
- room: The Colorful (Black)
door: Progress Door
panel: FOUND
- room: The Colorful (Red)
door: Progress Door
panel: LOAF
- room: The Colorful (Yellow)
door: Progress Door
panel: CREAM
- room: The Colorful (Blue)
door: Progress Door
panel: SUN
- room: The Colorful (Purple)
door: Progress Door
panel: SPOON
- room: The Colorful (Orange)
door: Progress Door
panel: LETTERS
- room: The Colorful (Green)
door: Progress Door
panel: WALLS
- room: The Colorful (Brown)
door: Progress Door
panel: IRON
- room: The Colorful (Gray)
door: Progress Door
panel: OBSTACLE
achievement: The Colorful
paintings:
- id: arrows_painting_12
orientation: north
progression:
Progressive Colorful:
- room: The Colorful (White)
door: Progress Door
- room: The Colorful (Black)
door: Progress Door
- room: The Colorful (Red)
door: Progress Door
- room: The Colorful (Yellow)
door: Progress Door
- room: The Colorful (Blue)
door: Progress Door
- room: The Colorful (Purple)
door: Progress Door
- room: The Colorful (Orange)
door: Progress Door
- room: The Colorful (Green)
door: Progress Door
- room: The Colorful (Brown)
door: Progress Door
- room: The Colorful (Gray)
door: Progress Door
Welcome Back Area:
entrances:
Starting Room:
@@ -4202,9 +4218,6 @@
SIX:
id: Backside Room/Panel_six_six_5
tag: midwhite
colors:
- red
- yellow
hunt: True
required_door:
room: Number Hunt
@@ -4280,9 +4293,6 @@
SIX:
id: Backside Room/Panel_six_six_6
tag: midwhite
colors:
- red
- yellow
hunt: True
required_door:
room: Number Hunt

View File

@@ -1452,3 +1452,4 @@ progression:
Progressive Fearless: 444470
Progressive Orange Tower: 444482
Progressive Art Gallery: 444563
Progressive Colorful: 444580

View File

@@ -28,6 +28,10 @@ class ItemData(NamedTuple):
# door shuffle is on and tower isn't progressive
return world.options.shuffle_doors != ShuffleDoors.option_none \
and not world.options.progressive_orange_tower
elif self.mode == "the colorful":
# complex door shuffle is on and colorful isn't progressive
return world.options.shuffle_doors == ShuffleDoors.option_complex \
and not world.options.progressive_colorful
elif self.mode == "complex door":
return world.options.shuffle_doors == ShuffleDoors.option_complex
elif self.mode == "door group":
@@ -70,6 +74,8 @@ def load_item_data():
if room_name in PROGRESSION_BY_ROOM and door_name in PROGRESSION_BY_ROOM[room_name]:
if room_name == "Orange Tower":
door_mode = "orange tower"
elif room_name == "The Colorful":
door_mode = "the colorful"
else:
door_mode = "special"

View File

@@ -21,6 +21,13 @@ class ProgressiveOrangeTower(DefaultOnToggle):
display_name = "Progressive Orange Tower"
class ProgressiveColorful(DefaultOnToggle):
"""When "Shuffle Doors" is on "complex", this setting governs the manner in which The Colorful opens up.
If off, there is an item for each room of The Colorful, meaning that random rooms in the middle of the sequence can open up without giving you access to them.
If on, there are ten progressive items, which open up the sequence from White forward."""
display_name = "Progressive Colorful"
class LocationChecks(Choice):
"""On "normal", there will be a location check for each panel set that would ordinarily open a door, as well as for
achievement panels and a small handful of other panels.
@@ -117,6 +124,7 @@ class DeathLink(Toggle):
class LingoOptions(PerGameCommonOptions):
shuffle_doors: ShuffleDoors
progressive_orange_tower: ProgressiveOrangeTower
progressive_colorful: ProgressiveColorful
location_checks: LocationChecks
shuffle_colors: ShuffleColors
shuffle_panels: ShufflePanels

View File

@@ -83,7 +83,8 @@ class LingoPlayerLogic:
def handle_non_grouped_door(self, room_name: str, door_data: Door, world: "LingoWorld"):
if room_name in PROGRESSION_BY_ROOM and door_data.name in PROGRESSION_BY_ROOM[room_name]:
if room_name == "Orange Tower" and not world.options.progressive_orange_tower:
if (room_name == "Orange Tower" and not world.options.progressive_orange_tower)\
or (room_name == "The Colorful" and not world.options.progressive_colorful):
self.set_door_item(room_name, door_data.name, door_data.item_name)
else:
progressive_item_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name
@@ -223,7 +224,7 @@ class LingoPlayerLogic:
"kind of logic error.")
if door_shuffle != ShuffleDoors.option_none and location_classification != LocationClassification.insanity \
and not early_color_hallways is False:
and not early_color_hallways:
# If shuffle doors is on, force a useful item onto the HI panel. This may not necessarily get you out of BK,
# but the goal is to allow you to reach at least one more check. The non-painting ones are hardcoded right
# now. We only allow the entrance to the Pilgrim Room if color shuffle is off, because otherwise there are

View File

@@ -118,7 +118,16 @@ class OOTWeb(WebWorld):
["TheLynk"]
)
tutorials = [setup, setup_es, setup_fr]
setup_de = Tutorial(
setup.tutorial_name,
setup.description,
"Deutsch",
"setup_de.md",
"setup/de",
["Held_der_Zeit"]
)
tutorials = [setup, setup_es, setup_fr, setup_de]
class OOTWorld(World):

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -0,0 +1,41 @@
# The Legend of Zelda: Ocarina of Time
## Wo ist die Seite für die Einstellungen?
Die [Seite für die Spielereinstellungen dieses Spiels](../player-options) enthält alle Optionen die man benötigt um
eine YAML-Datei zu konfigurieren und zu exportieren.
## Was macht der Randomizer in diesem Spiel?
Items, welche der Spieler für gewöhnlich im Verlauf des Spiels erhalten würde, wurden umhergemischt. Die Logik bleit
bestehen, damit ist das Spiel immer durchspielbar. Doch weil die Items durch das ganze Spiel gemischt wurden, müssen
manche Bereiche früher bescuht werden, als man es in Vanilla tun würde.
Eine Liste von implementierter Logik, die unoffensichtlich erscheinen kann, kann
[hier (Englisch)](https://wiki.ootrandomizer.com/index.php?title=Logic) gefunden werden.
## Welche Items und Bereiche werden gemischt?
Alle ausrüstbare und sammelbare Gegenstände, sowie Munition können gemischt werden. Und alle Bereiche, die einen
dieser Items enthalten könnten, haben (sehr wahrscheinlich) ihren Inhalt verändert. Goldene Skulltulas können ebenfalls
dazugezählt werden, je nach Wunsch des Spielers.
## Welche Items können in sich in der Welt eines anderen Spielers befinden?
Jedes dieser Items, die gemicht werden können, können in einer Multiworld auch in der Welt eines anderen Spielers
fallen. Es ist jedoch möglich ausgewählte Items auf deine eigene Welt zu beschränken.
## Wie sieht ein Item einer anderen Welt in OoT aus?
Items, die zu einer anderen Welt gehören, werden repräsentiert durch Zelda's Brief.
## Was passiert, wenn der Spieler ein Item erhält?
Sobald der Spieler ein Item erhält, wird Link das Item über seinen Kopf halten und der ganzen Welt präsentieren.
Gut für's Geschäft!
## Einzigartige Lokale Befehle
Die folgenden Befehle stehen nur im OoTClient, um mit Archipelago zu spielen, zur Verfügung:
- `/n64` Überprüffe den Verbindungsstatus deiner N64
- `/deathlink` Schalte den "Deathlink" des Clients um. Überschreibt die zuvor konfigurierten Einstellungen.

108
worlds/oot/docs/setup_de.md Normal file
View File

@@ -0,0 +1,108 @@
# Setup Anleitung für Ocarina of Time: Archipelago Edition
## WICHTIG
Da wir BizHawk benutzen, gilt diese Anleitung nur für Windows und Linux.
## Benötigte Software
- BizHawk: [BizHawk Veröffentlichungen von TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory)
- Version 2.3.1 und später werden unterstützt. Version 2.9 ist empfohlen.
- Detailierte Installtionsanweisungen für BizHawk können über den obrigen Link gefunden werden.
- Windows-Benutzer müssen die Prerequisiten installiert haben. Diese können ebenfalls über
den obrigen Link gefunden werden.
- Der integrierte Archipelago-Client, welcher [hier](https://github.com/ArchipelagoMW/Archipelago/releases) installiert
werden kann.
- Eine `Ocarina of Time v1.0 US(?) ROM`. (Nicht aus Europa und keine Master-Quest oder Debug-Rom!)
## Konfigurieren von BizHawk
Sobald Bizhawk einmal installiert wurde, öffne **EmuHawk** und ändere die folgenen Einsteluungen:
- (≤ 2.8) Gehe zu `Config > Customize`. Wechlse zu dem `Advanced`-Reiter, wechsle dann den `Lua Core` von "NLua+KopiLua" zu
`"Lua+LuaInterface"`. Starte danach EmuHawk neu. Dies ist zwingend notwendig, damit die Lua-Scripts, mit denen man sich mit dem Client verbindet, ordnungsgemäß funktionieren.
**ANMERKUNG: Selbst wenn "Lua+LuaInterface" bereits ausgewählt ist, wechsle zwischen den beiden Optionen umher und**
**wähle es erneut aus. Neue Installationen oder Versionen von EmuHawk neigen dazu "Lua+LuaInterface" als die**
**Standard-Option anzuzeigen, aber laden dennoch "NLua+KopiLua", bis dieser Schritt getan ist.**
- Unter `Config > Customize > Advanced`, gehe sicher dass der Haken bei `AutoSaveRAM` ausgeählt ist, und klicke dann
den 5s-Knopf. Dies verringert die Wahrscheinlichkeit den Speicherfrotschritt zu verlieren, sollte der Emulator mal
abstürzen.
- **(Optional)** Unter `Config > Customize` kannst du die Haken in den "Run in background"
(Laufe weiter im Hintergrund) und "Accept background input" (akzeptiere Tastendruck im Hintergrund) Kästchen setzen.
Dies erlaubt dir das Spiel im Hintergrund weiter zu spielen, selbst wenn ein anderes Fenster aktiv ist. (Nützlich bei
mehreren oder eher großen Bildschrimen/Monitoren.)
- Unter `Config > Hotkeys` sind viele Hotkeys, die mit oft genuten Tasten belegt worden sind. Es wird empfohlen die
meisten (oder alle) Hotkeys zu deaktivieren. Dies kann schnell mit `Esc` erledigt werden.
- Wird mit einem Kontroller gespielt, bei der Tastenbelegung (bei einem Laufendem Spiel, unter
`Config > Controllers...`), deaktiviere "P1 A Up", "P1 A Down", "P1 A Left", and "P1 A Right" und gehe stattdessen in
den Reiter `Analog Controls` um den Stick zu belegen, da sonst Probleme beim Zielen auftreten (mit dem Bogen oder
ähnliches). Y-Axis ist für Oben und Unten, und die X-Axis ist für Links und Rechts.
- Unter `N64` setze einen Haken bei "Use Expansion Slot" (Benutze Erweiterungs-Slot). Dies wird benötigt damit
savestates/schnellspeichern funktioniert. (Das N64-Menü taucht nur **nach** dem laden einer N64-ROM auf.)
Es wird sehr empfohlen N64 Rom-Erweiterungen (\*.n64, \*.z64) mit dem Emuhawk - welcher zuvor installiert wurde - zu
verknüpfen.
Um dies zu tun, muss eine beliebige N64 Rom aufgefunden werden, welche in deinem Besitz ist, diese Rechtsklicken und
dann auf "Öffnen mit..." gehen. Gehe dann auf "Andere App auswählen" und suche nach deinen BizHawk-Ordner, in der
sich der Emulator befindet, und wähle dann `EmuHawk.exe` **(NICHT "DiscoHawk.exe"!)** aus.
Eine Alternative BizHawk Setup Anleitung (auf Englisch), sowie weitere Hilfe bei Problemen kann
[hier](https://wiki.ootrandomizer.com/index.php?title=Bizhawk) gefunden werden.
## Erstelle eine YAML-Datei
### Was ist eine YAML-Datei und Warum brauch ich eine?
Eine YAML-Datie enthält einen Satz an einstellbaren Optionen, die dem Generator mitteilen, wie
dein Spiel generiert werden soll. In einer Multiworld stellt jeder Spieler eine eigene YAML-Datei zur Verfügung. Dies
erlaubt jeden Spieler eine personalisierte Erfahrung nach derem Geschmack. Damit kann auch jeder Spieler in einer
Multiworld (des gleichen Spiels) völlig unterschiedliche Einstellungen haben.
Für weitere Informationen, besuche die allgemeine Anleitung zum Erstellen einer
YAML-Datei: [Archipelago Setup Anleitung](/tutorial/Archipelago/setup/en)
### Woher bekomme ich eine YAML-Datei?
Die Seite für die Spielereinstellungen auf dieser Website erlaubt es dir deine persönlichen Einstellungen nach
vorlieben zu konfigurieren und eine YAML-Datei zu exportieren.
Seite für die Spielereinstellungen:
[Seite für die Spielereinstellungen von Ocarina of Time](/games/Ocarina%20of%20Time/player-options)
### Überprüfen deiner YAML-Datei
Wenn du deine YAML-Datei überprüfen möchtest, um sicher zu gehen, dass sie funktioniert, kannst du dies auf der
YAML-Überprüfungsseite tun.
YAML-Überprüfungsseite: [YAML-Überprüfungsseite](/check)
## Beitreten einer Multiworld
### Erhalte deinen OoT-Patch
(Der folgende Prozess ist bei den meisten ROM-basierenden Spielen sehr ähnlich.)
Wenn du einer Multiworld beitrittst, wirst du gefordert eine YAML-Datei bei dem Host abzugeben. Ist dies getan,
erhälst du (in der Regel) einen Link vom Host der Multiworld. Dieser führt dich zu einem Raum, in dem alle
teilnehmenden Spieler (bzw. Welten) aufgelistet sind. Du solltest dich dann auf **deine** Welt konzentrieren
und klicke dann auf `Download APZ5 File...`.
![Screenshot of a Multiworld Room with an Ocarina of Time Player](/static/generated/docs/Ocarina%20of%20Time/MultiWorld-room_oot.png)
Führe die `.apz5`-Datei mit einem Doppelklick aus, um deinen Ocarina Of Time-Client zu starten, sowie das patchen
deiner ROM. Ist dieser Prozess fertig (kann etwas dauern), startet sich der Client und der Emulator automatisch
(sofern das "Öffnen mit..." ausgewählt wurde).
### Verbinde zum Multiserver
Sind einmal der Client und der Emulator gestartet, müssen sie nur noch miteinander verbunden werden. Gehe dazu in
deinen Archipelago-Ordner, dann zu `data/lua`, und füge das `connector_oot.lua` Script per Drag&Drop (ziehen und
fallen lassen) auf das EmuHawk-Fenster. (Alternativ kannst du die Lua-Konsole manuell öffnen, gehe dazu auf
`Script > Open Script` und durchsuche die Ordner nach `data/lua/connector_oot.lua`.)
Um den Client mit dem Multiserver zu verbinden, füge einfach `<Adresse>:<Port>` in das Textfeld ganz oben im
Client ein und drücke Enter oder "Connect" (verbinden). Wird ein Passwort benötigt, musst du es danach unten in das
Textfeld (für den Chat und Befehle) eingeben.
Alternativ kannst du auch in dem unterem Textfeld den folgenden Befehl schreiben:
`/connect <Adresse>:<Port> [Passwort]` (wie die Adresse und der Port lautet steht in dem Raum, oder wird von deinem
Host an dich weitergegeben.)
Beispiel: `/connect archipelago.gg:12345 Passw123`
Du bist nun bereit für dein Zeitreise-Abenteuer in Hyrule.

View File

@@ -7,7 +7,7 @@ import logging
import os
from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar
from BaseClasses import ItemClassification, MultiWorld, Tutorial
from BaseClasses import ItemClassification, MultiWorld, Tutorial, LocationProgressType
from Fill import FillError, fill_restrictive
from Options import Toggle
import settings
@@ -20,7 +20,7 @@ from .items import (ITEM_GROUPS, PokemonEmeraldItem, create_item_label_to_code_m
offset_item_value)
from .locations import (LOCATION_GROUPS, PokemonEmeraldLocation, create_location_label_to_id_map,
create_locations_with_tags)
from .options import (ItemPoolType, RandomizeWildPokemon, RandomizeBadges, RandomizeTrainerParties, RandomizeHms,
from .options import (Goal, ItemPoolType, RandomizeWildPokemon, RandomizeBadges, RandomizeTrainerParties, RandomizeHms,
RandomizeStarters, LevelUpMoves, RandomizeAbilities, RandomizeTypes, TmCompatibility,
HmCompatibility, RandomizeStaticEncounters, NormanRequirement, PokemonEmeraldOptions)
from .pokemon import get_random_species, get_random_move, get_random_damaging_move, get_random_type
@@ -146,6 +146,60 @@ class PokemonEmeraldWorld(World):
self.multiworld.regions.extend(regions.values())
# Exclude locations which are always locked behind the player's goal
def exclude_locations(location_names: List[str]):
for location_name in location_names:
try:
self.multiworld.get_location(location_name,
self.player).progress_type = LocationProgressType.EXCLUDED
except KeyError:
continue # Location not in multiworld
if self.options.goal == Goal.option_champion:
# Always required to beat champion before receiving this
exclude_locations([
"Littleroot Town - S.S. Ticket from Norman"
])
# S.S. Ticket requires beating champion, so ferry is not accessible until after goal
if not self.options.enable_ferry:
exclude_locations([
"SS Tidal - Hidden Item in Lower Deck Trash Can",
"SS Tidal - TM49 from Thief"
])
# Construction workers don't move until champion is defeated
if "Safari Zone Construction Workers" not in self.options.remove_roadblocks.value:
exclude_locations([
"Safari Zone NE - Hidden Item North",
"Safari Zone NE - Hidden Item East",
"Safari Zone NE - Item on Ledge",
"Safari Zone SE - Hidden Item in South Grass 1",
"Safari Zone SE - Hidden Item in South Grass 2",
"Safari Zone SE - Item in Grass"
])
elif self.options.goal == Goal.option_norman:
# If the player sets their options such that Surf or the Balance
# Badge is vanilla, a very large number of locations become
# "post-Norman". Similarly, access to the E4 may require you to
# defeat Norman as an event or to get his badge, making postgame
# locations inaccessible. Detecting these situations isn't trivial
# and excluding all locations requiring Surf would be a bad idea.
# So for now we just won't touch it and blame the user for
# constructing their options in this way. Players usually expect
# to only partially complete their world when playing this goal
# anyway.
# Locations which are directly unlocked by defeating Norman.
exclude_locations([
"Petalburg Gym - Balance Badge",
"Petalburg Gym - TM42 from Norman",
"Petalburg City - HM03 from Wally's Uncle",
"Dewford Town - TM36 from Sludge Bomb Man",
"Mauville City - Basement Key from Wattson",
"Mauville City - TM24 from Wattson"
])
def create_items(self) -> None:
item_locations: List[PokemonEmeraldLocation] = [
location

View File

@@ -1,8 +1,10 @@
import typing
from Options import Option, DeathLink, Range, Toggle
from dataclasses import dataclass
from Options import Option, DeathLink, Range, Toggle, PerGameCommonOptions
class DoorCost(Range):
"""Amount of Trinkets required to enter Areas. Set to 0 to disable artificial locks."""
display_name = "Door Cost"
range_start = 0
range_end = 3
default = 3
@@ -13,6 +15,7 @@ class AreaCostRandomizer(Toggle):
class DeathLinkAmnesty(Range):
"""Amount of Deaths to take before sending a DeathLink signal, for balancing difficulty"""
display_name = "Death Link Amnesty"
range_start = 0
range_end = 30
default = 15
@@ -25,11 +28,11 @@ class MusicRandomizer(Toggle):
"""Randomize Music"""
display_name = "Music Randomizer"
v6_options: typing.Dict[str,type(Option)] = {
"MusicRandomizer": MusicRandomizer,
"AreaRandomizer": AreaRandomizer,
"DoorCost": DoorCost,
"AreaCostRandomizer": AreaCostRandomizer,
"death_link": DeathLink,
"DeathLinkAmnesty": DeathLinkAmnesty
}
@dataclass
class V6Options(PerGameCommonOptions):
music_rando: MusicRandomizer
area_rando: AreaRandomizer
door_cost: DoorCost
area_cost: AreaCostRandomizer
death_link: DeathLink
death_link_amnesty: DeathLinkAmnesty

View File

@@ -31,14 +31,3 @@ def create_regions(world: MultiWorld, player: int):
locWrp_names = ["Edge Games"]
regWrp.locations += [V6Location(player, loc_name, location_table[loc_name], regWrp) for loc_name in locWrp_names]
world.regions.append(regWrp)
def connect_regions(world: MultiWorld, player: int, source: str, target: str, rule):
sourceRegion = world.get_region(source, player)
targetRegion = world.get_region(target, player)
connection = Entrance(player,'', sourceRegion)
connection.access_rule = rule
sourceRegion.exits.append(connection)
connection.connect(targetRegion)

View File

@@ -1,6 +1,6 @@
import typing
from ..generic.Rules import add_rule
from .Regions import connect_regions, v6areas
from .Regions import v6areas
def _has_trinket_range(state, player, start, end) -> bool:
@@ -10,34 +10,36 @@ def _has_trinket_range(state, player, start, end) -> bool:
return True
def set_rules(world, player, area_connections: typing.Dict[int, int], area_cost_map: typing.Dict[int, int]):
def set_rules(multiworld, options, player, area_connections: typing.Dict[int, int], area_cost_map: typing.Dict[int, int]):
areashuffle = list(range(len(v6areas)))
if world.AreaRandomizer[player].value:
world.random.shuffle(areashuffle)
if options.area_rando:
multiworld.random.shuffle(areashuffle)
area_connections.update({(index + 1): (value + 1) for index, value in enumerate(areashuffle)})
area_connections.update({0: 0})
if world.AreaCostRandomizer[player].value:
world.random.shuffle(areashuffle)
if options.area_cost:
multiworld.random.shuffle(areashuffle)
area_cost_map.update({(index + 1): (value + 1) for index, value in enumerate(areashuffle)})
area_cost_map.update({0: 0})
menu_region = multiworld.get_region("Menu", player)
for i in range(1, 5):
connect_regions(world, player, "Menu", v6areas[area_connections[i] - 1],
lambda state, i=i: _has_trinket_range(state, player,
world.DoorCost[player].value * (area_cost_map[i] - 1),
world.DoorCost[player].value * area_cost_map[i]))
target_region = multiworld.get_region(v6areas[area_connections[i] - 1], player)
menu_region.connect(connecting_region=target_region,
rule=lambda state, i=i: _has_trinket_range(state, player,
options.door_cost * (area_cost_map[i] - 1),
options.door_cost * area_cost_map[i]))
# Special Rule for V
add_rule(world.get_location("V", player), lambda state: state.can_reach("Laboratory", 'Region', player) and
add_rule(multiworld.get_location("V", player), lambda state: state.can_reach("Laboratory", 'Region', player) and
state.can_reach("The Tower", 'Region', player) and
state.can_reach("Space Station 2", 'Region', player) and
state.can_reach("Warp Zone", 'Region', player))
# Special Rule for NPC Trinket
add_rule(world.get_location("NPC Trinket", player),
add_rule(multiworld.get_location("NPC Trinket", player),
lambda state: state.can_reach("Laboratory", 'Region', player) or
(state.can_reach("The Tower", 'Region', player) and
state.can_reach("Space Station 2", 'Region', player) and
state.can_reach("Warp Zone", 'Region', player)))
world.completion_condition[player] = lambda state: state.can_reach("V", 'Location', player)
multiworld.completion_condition[player] = lambda state: state.can_reach("V", 'Location', player)

View File

@@ -2,7 +2,7 @@ import typing
import os, json
from .Items import item_table, V6Item
from .Locations import location_table, V6Location
from .Options import v6_options
from .Options import V6Options
from .Rules import set_rules
from .Regions import create_regions
from BaseClasses import Item, ItemClassification, Tutorial
@@ -41,7 +41,7 @@ class V6World(World):
music_map: typing.Dict[int,int]
option_definitions = v6_options
options_dataclass = V6Options
def create_regions(self):
create_regions(self.multiworld, self.player)
@@ -49,7 +49,7 @@ class V6World(World):
def set_rules(self):
self.area_connections = {}
self.area_cost_map = {}
set_rules(self.multiworld, self.player, self.area_connections, self.area_cost_map)
set_rules(self.multiworld, self.options, self.player, self.area_connections, self.area_cost_map)
def create_item(self, name: str) -> Item:
return V6Item(name, ItemClassification.progression, item_table[name], self.player)
@@ -61,7 +61,7 @@ class V6World(World):
def generate_basic(self):
musiclist_o = [1,2,3,4,9,12]
musiclist_s = musiclist_o.copy()
if self.multiworld.MusicRandomizer[self.player].value:
if self.options.music_rando:
self.multiworld.random.shuffle(musiclist_s)
self.music_map = dict(zip(musiclist_o, musiclist_s))
@@ -69,10 +69,10 @@ class V6World(World):
return {
"MusicRando": self.music_map,
"AreaRando": self.area_connections,
"DoorCost": self.multiworld.DoorCost[self.player].value,
"DoorCost": self.options.door_cost.value,
"AreaCostRando": self.area_cost_map,
"DeathLink": self.multiworld.death_link[self.player].value,
"DeathLink_Amnesty": self.multiworld.DeathLinkAmnesty[self.player].value
"DeathLink": self.options.death_link.value,
"DeathLink_Amnesty": self.options.death_link_amnesty.value
}
def generate_output(self, output_directory: str):

View File

@@ -16,6 +16,7 @@ Symbols:
72 - Colored Squares
80 - Arrows
200 - Progressive Dots - Dots,Full Dots
210 - Progressive Symmetry - Symmetry,Colored Dots
260 - Progressive Stars - Stars,Stars + Same Colored Symbol
Useful:
@@ -29,8 +30,9 @@ Filler:
#503 - Energy Fill (Max) - 1
Traps:
600 - Slowness - 8
600 - Slowness - 6
610 - Power Surge - 2
615 - Bonk - 1
Jokes:
650 - Functioning Brain
@@ -41,10 +43,13 @@ Doors:
1102 - Tutorial Outpost Exit (Panel) - 0x04CA4
1105 - Symmetry Island Lower (Panel) - 0x000B0
1107 - Symmetry Island Upper (Panel) - 0x1C349
1108 - Desert Surface 3 Control (Panel) - 0x09FA0
1109 - Desert Surface 8 Control (Panel) - 0x09F86
1110 - Desert Light Room Entry (Panel) - 0x0C339
1111 - Desert Flood Controls (Panel) - 0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B
1112 - Desert Light Control (Panel) - 0x09FAA
1113 - Desert Flood Room Entry (Panel) - 0x0A249
1114 - Desert Elevator Room Hexagonal Control (Panel) - 0x0A015
1115 - Quarry Elevator Control (Panel) - 0x17CC4
1117 - Quarry Entry 1 (Panel) - 0x09E57
1118 - Quarry Entry 2 (Panel) - 0x17C09
@@ -69,6 +74,7 @@ Doors:
1167 - Town Maze Rooftop Bridge (Panel) - 0x2896A
1169 - Town Windmill Entry (Panel) - 0x17F5F
1172 - Town Cargo Box Entry (Panel) - 0x0A0C8
1173 - Town Desert Laser Redirect Control (Panel) - 0x09F98
1182 - Windmill Turn Control (Panel) - 0x17D02
1184 - Theater Entry (Panel) - 0x17F89
1185 - Theater Video Input (Panel) - 0x00815
@@ -231,10 +237,10 @@ Doors:
1984 - Caves Shortcuts - 0x2D859,0x2D73F
1987 - Tunnels Doors - 0x27739,0x27263,0x09E87,0x0348A
2000 - Desert Control Panels - 0x09FAA,0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B
2000 - Desert Control Panels - 0x09FAA,0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B,0x0A015,0x09FA0,0x09F86
2005 - Quarry Stoneworks Control Panels - 0x03678,0x03676,0x03679,0x03675
2010 - Quarry Boathouse Control Panels - 0x03852,0x03858,0x275FA
2015 - Town Control Panels - 0x2896A,0x334D8
2015 - Town Control Panels - 0x2896A,0x334D8,0x09F98
2020 - Windmill & Theater Control Panels - 0x17D02,0x00815
2025 - Bunker Control Panels - 0x34BC5,0x34BC6,0x0A079
2030 - Swamp Control Panels - 0x00609,0x18488,0x181F5,0x17E2B,0x17C0A,0x17E07
@@ -242,7 +248,7 @@ Doors:
2100 - Symmetry Island Panels - 0x1C349,0x000B0
2101 - Tutorial Outpost Panels - 0x0A171,0x04CA4
2105 - Desert Panels - 0x09FAA,0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B,0x0C339,0x0A249
2105 - Desert Panels - 0x09FAA,0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B,0x0C339,0x0A249,0x0A015,0x09FA0,0x09F86
2110 - Quarry Outside Panels - 0x17C09,0x09E57,0x17CC4
2115 - Quarry Stoneworks Panels - 0x01E5A,0x01E59,0x03678,0x03676,0x03679,0x03675
2120 - Quarry Boathouse Panels - 0x03852,0x03858,0x275FA
@@ -250,6 +256,7 @@ Doors:
2125 - Monastery Panels - 0x09D9B,0x00C92,0x00B10
2130 - Town Church & RGB House Panels - 0x28998,0x28A0D,0x334D8
2135 - Town Maze Panels - 0x2896A,0x28A79
2137 - Town Dockside House Panels - 0x0A0C8,0x09F98
2140 - Windmill & Theater Panels - 0x17D02,0x00815,0x17F5F,0x17F89,0x0A168,0x33AB2
2145 - Treehouse Panels - 0x0A182,0x0288C,0x02886,0x2700B,0x17CBC,0x037FF
2150 - Bunker Panels - 0x34BC5,0x34BC6,0x0A079,0x0A099,0x17C2E

View File

@@ -209,12 +209,12 @@ Door - 0x0C316 (Elevator Room Entry) - 0x18076
159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True
Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317:
158111 - 0x17C31 (Final Transparent) - True - True
158113 - 0x012D7 (Final Hexagonal) - 0x17C31 & 0x0A015 - True
158114 - 0x0A015 (Final Hexagonal Control) - 0x17C31 - True
158115 - 0x0A15C (Final Bent 1) - True - True
158116 - 0x09FFF (Final Bent 2) - 0x0A15C - True
158117 - 0x0A15F (Final Bent 3) - 0x09FFF - True
158111 - 0x17C31 (Elevator Room Transparent) - True - True
158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True
158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True
158115 - 0x0A15C (Elevator Room Bent 1) - True - True
158116 - 0x09FFF (Elevator Room Bent 2) - 0x0A15C - True
158117 - 0x0A15F (Elevator Room Bent 3) - 0x09FFF - True
159035 - 0x037BB (Elevator EP) - 0x01317 - True
Door - 0x01317 (Elevator) - 0x03608
@@ -474,7 +474,7 @@ Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x2
158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat
158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Black/White Squares & Shapers
Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8
158707 - 0x09F98 (Desert Laser Redirect) - True - True
158707 - 0x09F98 (Desert Laser Redirect Control) - True - True
158220 - 0x18590 (Transparent) - True - Symmetry
158221 - 0x28AE3 (Vines) - 0x18590 - True
158222 - 0x28938 (Apple Tree) - 0x28AE3 - True
@@ -895,9 +895,9 @@ Mountainside Vault (Mountainside):
Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34:
158405 - 0x0042D (River Shape) - True - True
158406 - 0x09F7F (Box Short) - 7 Lasers - True
158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True
158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Stars & Black/White Squares & Stars + Same Colored Symbol
158800 - 0xFFF00 (Box Long) - 11 Lasers & 0x17C34 - True
158800 - 0xFFF00 (Box Long) - 11 Lasers + Redirect & 0x17C34 - True
159300 - 0x001A3 (River Shape EP) - True - True
159320 - 0x3370E (Arch Black EP) - True - True
159324 - 0x336C8 (Arch White Right EP) - True - True

View File

@@ -209,12 +209,12 @@ Door - 0x0C316 (Elevator Room Entry) - 0x18076
159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True
Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317:
158111 - 0x17C31 (Final Transparent) - True - True
158113 - 0x012D7 (Final Hexagonal) - 0x17C31 & 0x0A015 - True
158114 - 0x0A015 (Final Hexagonal Control) - 0x17C31 - True
158115 - 0x0A15C (Final Bent 1) - True - True
158116 - 0x09FFF (Final Bent 2) - 0x0A15C - True
158117 - 0x0A15F (Final Bent 3) - 0x09FFF - True
158111 - 0x17C31 (Elevator Room Transparent) - True - True
158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True
158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True
158115 - 0x0A15C (Elevator Room Bent 1) - True - True
158116 - 0x09FFF (Elevator Room Bent 2) - 0x0A15C - True
158117 - 0x0A15F (Elevator Room Bent 3) - 0x09FFF - True
159035 - 0x037BB (Elevator EP) - 0x01317 - True
Door - 0x01317 (Elevator) - 0x03608
@@ -474,7 +474,7 @@ Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x2
158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat
158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Squares & Black/White Squares & Shapers & Triangles
Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8
158707 - 0x09F98 (Desert Laser Redirect) - True - True
158707 - 0x09F98 (Desert Laser Redirect Control) - True - True
158220 - 0x18590 (Transparent) - True - Symmetry
158221 - 0x28AE3 (Vines) - 0x18590 - True
158222 - 0x28938 (Apple Tree) - 0x28AE3 - True
@@ -895,9 +895,9 @@ Mountainside Vault (Mountainside):
Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34:
158405 - 0x0042D (River Shape) - True - True
158406 - 0x09F7F (Box Short) - 7 Lasers - True
158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True
158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Stars & Black/White Squares & Stars + Same Colored Symbol & Triangles
158800 - 0xFFF00 (Box Long) - 11 Lasers & 0x17C34 - True
158800 - 0xFFF00 (Box Long) - 11 Lasers + Redirect & 0x17C34 - True
159300 - 0x001A3 (River Shape EP) - True - True
159320 - 0x3370E (Arch Black EP) - True - True
159324 - 0x336C8 (Arch White Right EP) - True - True

View File

@@ -209,12 +209,12 @@ Door - 0x0C316 (Elevator Room Entry) - 0x18076
159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True
Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317:
158111 - 0x17C31 (Final Transparent) - True - True
158113 - 0x012D7 (Final Hexagonal) - 0x17C31 & 0x0A015 - True
158114 - 0x0A015 (Final Hexagonal Control) - 0x17C31 - True
158115 - 0x0A15C (Final Bent 1) - True - True
158116 - 0x09FFF (Final Bent 2) - 0x0A15C - True
158117 - 0x0A15F (Final Bent 3) - 0x09FFF - True
158111 - 0x17C31 (Elevator Room Transparent) - True - True
158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True
158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True
158115 - 0x0A15C (Elevator Room Bent 1) - True - True
158116 - 0x09FFF (Elevator Room Bent 2) - 0x0A15C - True
158117 - 0x0A15F (Elevator Room Bent 3) - 0x09FFF - True
159035 - 0x037BB (Elevator EP) - 0x01317 - True
Door - 0x01317 (Elevator) - 0x03608
@@ -474,7 +474,7 @@ Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x2
158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat
158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Black/White Squares & Shapers
Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8
158707 - 0x09F98 (Desert Laser Redirect) - True - True
158707 - 0x09F98 (Desert Laser Redirect Control) - True - True
158220 - 0x18590 (Transparent) - True - Symmetry
158221 - 0x28AE3 (Vines) - 0x18590 - True
158222 - 0x28938 (Apple Tree) - 0x28AE3 - True
@@ -895,9 +895,9 @@ Mountainside Vault (Mountainside):
Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34:
158405 - 0x0042D (River Shape) - True - True
158406 - 0x09F7F (Box Short) - 7 Lasers - True
158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True
158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Black/White Squares
158800 - 0xFFF00 (Box Long) - 11 Lasers & 0x17C34 - True
158800 - 0xFFF00 (Box Long) - 11 Lasers + Redirect & 0x17C34 - True
159300 - 0x001A3 (River Shape EP) - True - True
159320 - 0x3370E (Arch Black EP) - True - True
159324 - 0x336C8 (Arch White Right EP) - True - True

View File

@@ -6,6 +6,7 @@ from typing import Dict, Optional
from BaseClasses import Region, Location, MultiWorld, Item, Entrance, Tutorial, CollectionState
from Options import PerGameCommonOptions, Toggle
from .presets import witness_option_presets
from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \
get_priority_hint_items, make_hints, generate_joke_hints
from worlds.AutoWorld import World, WebWorld
@@ -15,7 +16,7 @@ from .locations import WitnessPlayerLocations, StaticWitnessLocations
from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems, ItemData
from .regions import WitnessRegions
from .rules import set_rules
from .Options import TheWitnessOptions
from .options import TheWitnessOptions
from .utils import get_audio_logs
from logging import warning, error
@@ -31,6 +32,8 @@ class WitnessWebWorld(WebWorld):
["NewSoupVi", "Jarno"]
)]
options_presets = witness_option_presets
class WitnessWorld(World):
"""
@@ -40,7 +43,6 @@ class WitnessWorld(World):
"""
game = "The Witness"
topology_present = False
data_version = 14
StaticWitnessLogic()
StaticWitnessLocations()
@@ -88,10 +90,10 @@ class WitnessWorld(World):
}
def generate_early(self):
disabled_locations = self.multiworld.exclude_locations[self.player].value
disabled_locations = self.options.exclude_locations.value
self.player_logic = WitnessPlayerLogic(
self, disabled_locations, self.multiworld.start_inventory[self.player].value
self, disabled_locations, self.options.start_inventory.value
)
self.locat: WitnessPlayerLocations = WitnessPlayerLocations(self, self.player_logic)
@@ -102,14 +104,29 @@ class WitnessWorld(World):
self.log_ids_to_hints = dict()
if not (self.options.shuffle_symbols or self.options.shuffle_doors or self.options.shuffle_lasers):
if self.multiworld.players == 1:
warning(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have any progression"
f" items. Please turn on Symbol Shuffle, Door Shuffle or Laser Shuffle if that doesn't"
f" seem right.")
else:
raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have any"
f" progression items. Please turn on Symbol Shuffle, Door Shuffle or Laser Shuffle.")
interacts_with_multiworld = (
self.options.shuffle_symbols or
self.options.shuffle_doors or
self.options.shuffle_lasers == "anywhere"
)
has_progression = (
interacts_with_multiworld
or self.options.shuffle_lasers == "local"
or self.options.shuffle_boat
or self.options.early_caves == "add_to_pool"
)
if not has_progression and self.multiworld.players == 1:
warning(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have any progression"
f" items. Please turn on Symbol Shuffle, Door Shuffle or Laser Shuffle if that doesn't seem right.")
elif not interacts_with_multiworld and self.multiworld.players > 1:
raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have enough"
f" progression items that can be placed in other players' worlds. Please turn on Symbol"
f" Shuffle, Door Shuffle or non-local Laser Shuffle.")
if self.options.shuffle_lasers == "local":
self.options.local_items.value |= self.item_name_groups["Lasers"]
def create_regions(self):
self.regio.create_regions(self, self.player_logic)
@@ -176,7 +193,8 @@ class WitnessWorld(World):
extra_checks = [
("First Hallway Room", "First Hallway Bend"),
("First Hallway", "First Hallway Straight"),
("Desert Outside", "Desert Surface 3"),
("Desert Outside", "Desert Surface 1"),
("Desert Outside", "Desert Surface 2"),
]
for i in range(num_early_locs, needed_size):
@@ -253,7 +271,7 @@ class WitnessWorld(World):
self.own_itempool += new_items
self.multiworld.itempool += new_items
if self.items.item_data[item_name].local_only:
self.multiworld.local_items[self.player].value.add(item_name)
self.options.local_items.value.add(item_name)
def fill_slot_data(self) -> dict:
hint_amount = self.options.hint_amount.value

View File

@@ -187,8 +187,8 @@ def get_always_hint_items(world: "WitnessWorld") -> List[str]:
return always
def get_always_hint_locations(_: "WitnessWorld") -> List[str]:
return [
def get_always_hint_locations(world: "WitnessWorld") -> List[str]:
always = [
"Challenge Vault Box",
"Mountain Bottom Floor Discard",
"Theater Eclipse EP",
@@ -196,6 +196,16 @@ def get_always_hint_locations(_: "WitnessWorld") -> List[str]:
"Mountainside Cloud Cycle EP",
]
# Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side
if world.options.EP_difficulty == "eclipse":
always.append("Town Obelisk Side 6") # Eclipse EP
if world.options.EP_difficulty != "normal":
always.append("Treehouse Obelisk Side 4") # Couch EP
always.append("River Obelisk Side 1") # Cloud Cycle EP. Needs to be changed to "Mountainside Obelisk" soon
return always
def get_priority_hint_items(world: "WitnessWorld") -> List[str]:
priority = {
@@ -217,9 +227,8 @@ def get_priority_hint_items(world: "WitnessWorld") -> List[str]:
"Eraser",
"Black/White Squares",
"Colored Squares",
"Colored Dots",
"Sound Dots",
"Symmetry"
"Progressive Symmetry"
]
priority.update(world.random.sample(symbols, 5))
@@ -249,8 +258,8 @@ def get_priority_hint_items(world: "WitnessWorld") -> List[str]:
return sorted(priority)
def get_priority_hint_locations(_: "WitnessWorld") -> List[str]:
return [
def get_priority_hint_locations(world: "WitnessWorld") -> List[str]:
priority = [
"Swamp Purple Underwater",
"Shipwreck Vault Box",
"Town RGB Room Left",
@@ -265,6 +274,13 @@ def get_priority_hint_locations(_: "WitnessWorld") -> List[str]:
"Boat Shipwreck Green EP",
"Quarry Stoneworks Control Room Left",
]
# Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side
if world.options.EP_difficulty != "normal":
priority.append("Town Obelisk Side 6") # Theater Flowers EP
priority.append("Treehouse Obelisk Side 4") # Shipwreck Green EP
return priority
def make_hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: List[Item]):

View File

@@ -55,8 +55,8 @@ class StaticWitnessLocations:
"Desert Light Room 3",
"Desert Pond Room 5",
"Desert Flood Room 6",
"Desert Final Hexagonal",
"Desert Final Bent 3",
"Desert Elevator Room Hexagonal",
"Desert Elevator Room Bent 3",
"Desert Laser Panel",
"Quarry Entry 1 Panel",

View File

@@ -28,11 +28,14 @@ class ShuffleSymbols(DefaultOnToggle):
display_name = "Shuffle Symbols"
class ShuffleLasers(Toggle):
class ShuffleLasers(Choice):
"""If on, the 11 lasers are turned into items and will activate on their own upon receiving them.
Note: There is a visual bug that can occur with the Desert Laser. It does not affect gameplay - The Laser can still
be redirected as normal, for both applications of redirection."""
display_name = "Shuffle Lasers"
option_off = 0
option_local = 1
option_anywhere = 2
class ShuffleDoors(Choice):
@@ -114,9 +117,13 @@ class ShufflePostgame(Toggle):
class VictoryCondition(Choice):
"""Change the victory condition from the original game's ending (elevator) to beating the Challenge
or solving the mountaintop box, either using the short solution
(7 lasers or whatever you've changed it to) or the long solution (11 lasers or whatever you've changed it to)."""
"""Set the victory condition for this world.
Elevator: Start the elevator at the bottom of the mountain (requires Mountain Lasers).
Challenge: Beat the secret Challenge (requires Challenge Lasers).
Mountain Box Short: Input the short solution to the Mountaintop Box (requires Mountain Lasers).
Mountain Box Long: Input the long solution to the Mountaintop Box (requires Challenge Lasers).
It is important to note that while the Mountain Box requires Desert Laser to be redirected in Town for that laser
to count, the laser locks on the Elevator and Challenge Timer panels do not."""
display_name = "Victory Condition"
option_elevator = 0
option_challenge = 1
@@ -133,10 +140,13 @@ class PuzzleRandomization(Choice):
class MountainLasers(Range):
"""Sets the amount of beams required to enter the final area."""
"""Sets the amount of lasers required to enter the Mountain.
If set to a higher amount than 7, the mountaintop box will be slightly rotated to make it possible to solve without
the hatch being opened.
This change will also be applied logically to the long solution ("Challenge Lasers" setting)."""
display_name = "Required Lasers for Mountain Entry"
range_start = 1
range_end = 7
range_end = 11
default = 7
@@ -182,10 +192,19 @@ class HintAmount(Range):
class DeathLink(Toggle):
"""If on: Whenever you fail a puzzle (with some exceptions), everyone who is also on Death Link dies.
The effect of a "death" in The Witness is a Power Surge."""
The effect of a "death" in The Witness is a Bonk Trap."""
display_name = "Death Link"
class DeathLinkAmnesty(Range):
"""Number of panel fails to allow before sending a death through Death Link.
0 means every panel fail will send a death, 1 means every other panel fail will send a death, etc."""
display_name = "Death Link Amnesty"
range_start = 0
range_end = 5
default = 1
@dataclass
class TheWitnessOptions(PerGameCommonOptions):
puzzle_randomization: PuzzleRandomization
@@ -209,3 +228,4 @@ class TheWitnessOptions(PerGameCommonOptions):
puzzle_skip_amount: PuzzleSkipAmount
hint_amount: HintAmount
death_link: DeathLink
death_link_amnesty: DeathLinkAmnesty

View File

@@ -103,7 +103,8 @@ class WitnessPlayerLogic:
if option_entity in self.EVENT_NAMES_BY_HEX:
new_items = frozenset({frozenset([option_entity])})
elif option_entity in {"7 Lasers", "11 Lasers", "PP2 Weirdness", "Theater to Tunnels"}:
elif option_entity in {"7 Lasers", "11 Lasers", "7 Lasers + Redirect", "11 Lasers + Redirect",
"PP2 Weirdness", "Theater to Tunnels"}:
new_items = frozenset({frozenset([option_entity])})
else:
new_items = self.reduce_req_within_region(option_entity)
@@ -322,7 +323,10 @@ class WitnessPlayerLogic:
elif victory == 3:
self.VICTORY_LOCATION = "0xFFF00"
if chal_lasers <= 7:
# Long box can usually only be solved by opening Mountain Entry. However, if it requires 7 lasers or less
# (challenge_lasers <= 7), you can now solve it without opening Mountain Entry first.
# Furthermore, if the user sets mountain_lasers > 7, the box is rotated to not require Mountain Entry either.
if chal_lasers <= 7 or mnt_lasers > 7:
adjustment_linesets_in_order.append([
"Requirement Changes:",
"0xFFF00 - 11 Lasers - True",

101
worlds/witness/presets.py Normal file
View File

@@ -0,0 +1,101 @@
from typing import Any, Dict
from .options import *
witness_option_presets: Dict[str, Dict[str, Any]] = {
# Great for short syncs & scratching that "speedrun with light routing elements" itch.
"Short & Dense": {
"progression_balancing": 30,
"puzzle_randomization": PuzzleRandomization.option_sigma_normal,
"shuffle_symbols": False,
"shuffle_doors": ShuffleDoors.option_panels,
"door_groupings": DoorGroupings.option_off,
"shuffle_boat": True,
"shuffle_lasers": ShuffleLasers.option_local,
"disable_non_randomized_puzzles": True,
"shuffle_discarded_panels": False,
"shuffle_vault_boxes": False,
"shuffle_EPs": ShuffleEnvironmentalPuzzles.option_off,
"EP_difficulty": EnvironmentalPuzzlesDifficulty.option_normal,
"shuffle_postgame": False,
"victory_condition": VictoryCondition.option_mountain_box_short,
"mountain_lasers": 7,
"challenge_lasers": 11,
"early_caves": EarlyCaves.option_off,
"elevators_come_to_you": False,
"trap_percentage": TrapPercentage.default,
"puzzle_skip_amount": PuzzleSkipAmount.default,
"hint_amount": HintAmount.default,
"death_link": DeathLink.default,
},
# For relative beginners who want to move to the next step.
"Advanced, But Chill": {
"progression_balancing": 30,
"puzzle_randomization": PuzzleRandomization.option_sigma_normal,
"shuffle_symbols": True,
"shuffle_doors": ShuffleDoors.option_doors,
"door_groupings": DoorGroupings.option_regional,
"shuffle_boat": True,
"shuffle_lasers": ShuffleLasers.option_off,
"disable_non_randomized_puzzles": False,
"shuffle_discarded_panels": True,
"shuffle_vault_boxes": True,
"shuffle_EPs": ShuffleEnvironmentalPuzzles.option_obelisk_sides,
"EP_difficulty": EnvironmentalPuzzlesDifficulty.option_normal,
"shuffle_postgame": False,
"victory_condition": VictoryCondition.option_mountain_box_long,
"mountain_lasers": 6,
"challenge_lasers": 9,
"early_caves": EarlyCaves.option_off,
"elevators_come_to_you": False,
"trap_percentage": TrapPercentage.default,
"puzzle_skip_amount": 15,
"hint_amount": HintAmount.default,
"death_link": DeathLink.default,
},
# Allsanity but without the BS (no expert, no tedious EPs).
"Nice Allsanity": {
"progression_balancing": 50,
"puzzle_randomization": PuzzleRandomization.option_sigma_normal,
"shuffle_symbols": True,
"shuffle_doors": ShuffleDoors.option_mixed,
"door_groupings": DoorGroupings.option_off,
"shuffle_boat": True,
"shuffle_lasers": ShuffleLasers.option_anywhere,
"disable_non_randomized_puzzles": False,
"shuffle_discarded_panels": True,
"shuffle_vault_boxes": True,
"shuffle_EPs": ShuffleEnvironmentalPuzzles.option_individual,
"EP_difficulty": EnvironmentalPuzzlesDifficulty.option_normal,
"shuffle_postgame": False,
"victory_condition": VictoryCondition.option_challenge,
"mountain_lasers": 6,
"challenge_lasers": 9,
"early_caves": EarlyCaves.option_off,
"elevators_come_to_you": True,
"trap_percentage": TrapPercentage.default,
"puzzle_skip_amount": 15,
"hint_amount": HintAmount.default,
"death_link": DeathLink.default,
},
}

View File

@@ -29,8 +29,9 @@ laser_hexes = [
]
def _has_laser(laser_hex: str, world: "WitnessWorld", player: int) -> Callable[[CollectionState], bool]:
if laser_hex == "0x012FB":
def _has_laser(laser_hex: str, world: "WitnessWorld", player: int,
redirect_required: bool) -> Callable[[CollectionState], bool]:
if laser_hex == "0x012FB" and redirect_required:
return lambda state: (
_can_solve_panel(laser_hex, world, world.player, world.player_logic, world.locat)(state)
and state.has("Desert Laser Redirection", player)
@@ -39,11 +40,11 @@ def _has_laser(laser_hex: str, world: "WitnessWorld", player: int) -> Callable[[
return _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.locat)
def _has_lasers(amount: int, world: "WitnessWorld") -> Callable[[CollectionState], bool]:
def _has_lasers(amount: int, world: "WitnessWorld", redirect_required: bool) -> Callable[[CollectionState], bool]:
laser_lambdas = []
for laser_hex in laser_hexes:
has_laser_lambda = _has_laser(laser_hex, world, world.player)
has_laser_lambda = _has_laser(laser_hex, world, world.player, redirect_required)
laser_lambdas.append(has_laser_lambda)
@@ -155,10 +156,16 @@ def _has_item(item: str, world: "WitnessWorld", player: int,
return lambda state: state.can_reach(item, "Region", player)
if item == "7 Lasers":
laser_req = world.options.mountain_lasers.value
return _has_lasers(laser_req, world)
return _has_lasers(laser_req, world, False)
if item == "7 Lasers + Redirect":
laser_req = world.options.mountain_lasers.value
return _has_lasers(laser_req, world, True)
if item == "11 Lasers":
laser_req = world.options.challenge_lasers.value
return _has_lasers(laser_req, world)
return _has_lasers(laser_req, world, False)
if item == "11 Lasers + Redirect":
laser_req = world.options.challenge_lasers.value
return _has_lasers(laser_req, world, True)
elif item == "PP2 Weirdness":
return lambda state: _can_do_expert_pp2(state, world)
elif item == "Theater to Tunnels":

View File

@@ -1,4 +1,7 @@
Items:
Desert Surface 3 Control (Panel)
Desert Surface 8 Control (Panel)
Desert Elevator Room Hexagonal Control (Panel)
Desert Flood Controls (Panel)
Desert Light Control (Panel)
Quarry Elevator Control (Panel)
@@ -10,6 +13,7 @@ Quarry Boathouse Hook Control (Panel)
Monastery Shutters Control (Panel)
Town Maze Rooftop Bridge (Panel)
Town RGB Control (Panel)
Town Desert Laser Redirect Control (Panel)
Windmill Turn Control (Panel)
Theater Video Input (Panel)
Bunker Drop-Down Door Controls (Panel)

View File

@@ -10,7 +10,7 @@ Monastery Panels
Town Church & RGB House Panels
Town Maze Panels
Windmill & Theater Panels
Town Cargo Box Entry (Panel)
Town Dockside House Panels
Treehouse Panels
Bunker Panels
Swamp Panels

View File

@@ -1,9 +1,8 @@
Items:
Arrows
Progressive Dots
Colored Dots
Sound Dots
Symmetry
Progressive Symmetry
Triangles
Eraser
Shapers

View File

@@ -109,7 +109,6 @@ class StaticWitnessLogicObj:
"Laser",
"Laser Hedges",
"Laser Pressure Plates",
"Desert Laser Redirect"
}
is_vault_or_video = "Vault" in entity_name or "Video" in entity_name