Merge remote-tracking branch 'origin/main'

# Conflicts:
#	worlds/ahit/Regions.py
This commit is contained in:
CookieCat
2024-05-22 12:29:22 -04:00
36 changed files with 872 additions and 368 deletions

64
Fill.py
View File

@@ -35,8 +35,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
"""
:param multiworld: Multiworld to be filled.
:param base_state: State assumed before fill.
:param locations: Locations to be filled with item_pool
:param item_pool: Items to fill into the locations
:param locations: Locations to be filled with item_pool, gets mutated by removing locations that get filled.
:param item_pool: Items to fill into the locations, gets mutated by removing items that get placed.
:param single_player_placement: if true, can speed up placement if everything belongs to a single player
:param lock: locations are set to locked as they are filled
:param swap: if true, swaps of already place items are done in the event of a dead end
@@ -220,7 +220,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
def remaining_fill(multiworld: MultiWorld,
locations: typing.List[Location],
itempool: typing.List[Item],
name: str = "Remaining") -> None:
name: str = "Remaining",
move_unplaceable_to_start_inventory: bool = False) -> None:
unplaced_items: typing.List[Item] = []
placements: typing.List[Location] = []
swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter()
@@ -284,13 +285,21 @@ def remaining_fill(multiworld: MultiWorld,
if unplaced_items and locations:
# There are leftover unplaceable items and locations that won't accept them
raise FillError(f"No more spots to place {len(unplaced_items)} items. Remaining locations are invalid.\n"
f"Unplaced items:\n"
f"{', '.join(str(item) for item in unplaced_items)}\n"
f"Unfilled locations:\n"
f"{', '.join(str(location) for location in locations)}\n"
f"Already placed {len(placements)}:\n"
f"{', '.join(str(place) for place in placements)}")
if move_unplaceable_to_start_inventory:
last_batch = []
for item in unplaced_items:
logging.debug(f"Moved {item} to start_inventory to prevent fill failure.")
multiworld.push_precollected(item)
last_batch.append(multiworld.worlds[item.player].create_filler())
remaining_fill(multiworld, locations, unplaced_items, name + " Start Inventory Retry")
else:
raise FillError(f"No more spots to place {len(unplaced_items)} items. Remaining locations are invalid.\n"
f"Unplaced items:\n"
f"{', '.join(str(item) for item in unplaced_items)}\n"
f"Unfilled locations:\n"
f"{', '.join(str(location) for location in locations)}\n"
f"Already placed {len(placements)}:\n"
f"{', '.join(str(place) for place in placements)}")
itempool.extend(unplaced_items)
@@ -420,7 +429,8 @@ def distribute_early_items(multiworld: MultiWorld,
return fill_locations, itempool
def distribute_items_restrictive(multiworld: MultiWorld) -> None:
def distribute_items_restrictive(multiworld: MultiWorld,
panic_method: typing.Literal["swap", "raise", "start_inventory"] = "swap") -> None:
fill_locations = sorted(multiworld.get_unfilled_locations())
multiworld.random.shuffle(fill_locations)
# get items to distribute
@@ -470,8 +480,29 @@ def distribute_items_restrictive(multiworld: MultiWorld) -> None:
if progitempool:
# "advancement/progression fill"
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, single_player_placement=multiworld.players == 1,
name="Progression")
if panic_method == "swap":
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
swap=True,
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
elif panic_method == "raise":
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
swap=False,
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
elif panic_method == "start_inventory":
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
swap=False, allow_partial=True,
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
if progitempool:
for item in progitempool:
logging.debug(f"Moved {item} to start_inventory to prevent fill failure.")
multiworld.push_precollected(item)
filleritempool.append(multiworld.worlds[item.player].create_filler())
logging.warning(f"{len(progitempool)} items moved to start inventory,"
f" due to failure in Progression fill step.")
progitempool[:] = []
else:
raise ValueError(f"Generator Panic Method {panic_method} not recognized.")
if progitempool:
raise FillError(
f"Not enough locations for progression items. "
@@ -486,7 +517,9 @@ def distribute_items_restrictive(multiworld: MultiWorld) -> None:
inaccessible_location_rules(multiworld, multiworld.state, defaultlocations)
remaining_fill(multiworld, excludedlocations, filleritempool, "Remaining Excluded")
remaining_fill(multiworld, excludedlocations, filleritempool, "Remaining Excluded",
move_unplaceable_to_start_inventory=panic_method=="start_inventory")
if excludedlocations:
raise FillError(
f"Not enough filler items for excluded locations. "
@@ -495,7 +528,8 @@ def distribute_items_restrictive(multiworld: MultiWorld) -> None:
restitempool = filleritempool + usefulitempool
remaining_fill(multiworld, defaultlocations, restitempool)
remaining_fill(multiworld, defaultlocations, restitempool,
move_unplaceable_to_start_inventory=panic_method=="start_inventory")
unplaced = restitempool
unfilled = defaultlocations

View File

@@ -13,7 +13,7 @@ import worlds
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region
from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items
from Options import StartInventoryPool
from Utils import __version__, output_path, version_tuple
from Utils import __version__, output_path, version_tuple, get_settings
from settings import get_settings
from worlds import AutoWorld
from worlds.generic.Rules import exclusion_rules, locality_rules
@@ -272,7 +272,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
if multiworld.algorithm == 'flood':
flood_items(multiworld) # different algo, biased towards early game progress items
elif multiworld.algorithm == 'balanced':
distribute_items_restrictive(multiworld)
distribute_items_restrictive(multiworld, get_settings().generator.panic_method)
AutoWorld.call_all(multiworld, 'post_fill')

View File

@@ -67,6 +67,7 @@ Currently, the following games are supported:
* Yoshi's Island
* Mario & Luigi: Superstar Saga
* Bomb Rush Cyberfunk
* Aquaria
* Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006
* A Hat in Time

View File

@@ -270,15 +270,19 @@ def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
await ctx.shutdown_task
except (KeyboardInterrupt, SystemExit):
pass
except Exception:
if ctx.saving:
ctx._save()
except Exception as e:
with db_session:
room = Room.get(id=room_id)
room.last_port = -1
logger.exception(e)
raise
else:
if ctx.saving:
ctx._save()
finally:
try:
ctx._save()
with (db_session):
# ensure the Room does not spin up again on its own, minute of safety buffer
room = Room.get(id=room_id)

View File

@@ -665,6 +665,14 @@ class GeneratorOptions(Group):
OFF = 0
ON = 1
class PanicMethod(str):
"""
What to do if the current item placements appear unsolvable.
raise -> Raise an exception and abort.
swap -> Attempt to fix it by swapping prior placements around. (Default)
start_inventory -> Move remaining items to start_inventory, generate additional filler items to fill locations.
"""
enemizer_path: EnemizerPath = EnemizerPath("EnemizerCLI/EnemizerCLI.Core") # + ".exe" is implied on Windows
player_files_path: PlayerFilesPath = PlayerFilesPath("Players")
players: Players = Players(0)
@@ -673,6 +681,7 @@ class GeneratorOptions(Group):
spoiler: Spoiler = Spoiler(3)
race: Race = Race(0)
plando_options: PlandoOptions = PlandoOptions("bosses, connections, texts")
panic_method: PanicMethod = PanicMethod("swap")
class SNIOptions(Group):

View File

@@ -8,14 +8,16 @@ from typing import Optional
from enum import Enum
from BaseClasses import Item, ItemClassification
class ItemType(Enum):
"""
Used to indicate to the multi-world if an item is usefull or not
Used to indicate to the multi-world if an item is useful or not
"""
NORMAL = 0
PROGRESSION = 1
JUNK = 2
class ItemGroup(Enum):
"""
Used to group items
@@ -28,6 +30,7 @@ class ItemGroup(Enum):
SONG = 5
TURTLE = 6
class AquariaItem(Item):
"""
A single item in the Aquaria game.
@@ -40,22 +43,23 @@ class AquariaItem(Item):
"""
Initialisation of the Item
:param name: The name of the item
:param classification: If the item is usefull or not
:param classification: If the item is useful or not
:param code: The ID of the item (if None, it is an event)
:param player: The ID of the player in the multiworld
"""
super().__init__(name, classification, code, player)
class ItemData:
"""
Data of an item.
"""
id:int
count:int
type:ItemType
group:ItemGroup
id: int
count: int
type: ItemType
group: ItemGroup
def __init__(self, id:int, count:int, type:ItemType, group:ItemGroup):
def __init__(self, id: int, count: int, type: ItemType, group: ItemGroup):
"""
Initialisation of the item data
@param id: The item ID
@@ -68,6 +72,7 @@ class ItemData:
self.type = type
self.group = group
"""Information data for every (not event) item."""
item_table = {
# name: ID, Nb, Item Type, Item Group
@@ -207,4 +212,3 @@ item_table = {
"Transturtle Simon says": ItemData(698132, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest05
"Transturtle Arnassi ruins": ItemData(698133, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_seahorse
}

View File

@@ -43,7 +43,7 @@ class AquariaLocations:
locations_home_water = {
"Home water, bulb below the grouper fish": 698058,
"Home water, bulb in the path bellow Nautilus Prime": 698059,
"Home water, bulb in the path below Nautilus Prime": 698059,
"Home water, bulb in the little room above the grouper fish": 698060,
"Home water, bulb in the end of the left path from the verse cave": 698061,
"Home water, bulb in the top left path": 698062,
@@ -129,7 +129,7 @@ class AquariaLocations:
locations_openwater_bl = {
"Open water bottom left area, bulb behind the chomper fish": 698011,
"Open water bottom left area, bulb inside the downest fish pass": 698010,
"Open water bottom left area, bulb inside the lowest fish pass": 698010,
}
locations_skeleton_path = {
@@ -226,7 +226,7 @@ class AquariaLocations:
"Mithalas cathedral, third urn in the path behind the flesh vein": 698146,
"Mithalas cathedral, one of the urns in the top right room": 698147,
"Mithalas cathedral, Mithalan Dress": 698189,
"Mithalas cathedral right area, urn bellow the left entrance": 698198,
"Mithalas cathedral right area, urn below the left entrance": 698198,
}
locations_cathedral_underground = {
@@ -457,7 +457,7 @@ class AquariaLocations:
locations_body_l = {
"The body left area, first bulb in the top face room": 698066,
"The body left area, second bulb in the top face room": 698069,
"The body left area, bulb bellow the water stream": 698067,
"The body left area, bulb below the water stream": 698067,
"The body left area, bulb in the top path to the top face room": 698068,
"The body left area, bulb in the bottom face room": 698070,
}

View File

@@ -10,9 +10,8 @@ from Options import Toggle, Choice, Range, DeathLink, PerGameCommonOptions, Defa
class IngredientRandomizer(Choice):
"""
Randomize Ingredients. Select if the simple ingredients (that does not have
a recipe) should be randomized. If 'common_ingredients' is selected, the
randomization will exclude the "Red Bulb", "Special Bulb" and "Rukh Egg".
Select if the simple ingredients (that do not have a recipe) should be randomized.
If "Common Ingredients" is selected, the randomization will exclude the "Red Bulb", "Special Bulb" and "Rukh Egg".
"""
display_name = "Randomize Ingredients"
option_off = 0
@@ -29,27 +28,25 @@ class DishRandomizer(Toggle):
class TurtleRandomizer(Choice):
"""Randomize the transportation turtle."""
display_name = "Turtle Randomizer"
option_no_turtle_randomization = 0
option_randomize_all_turtle = 1
option_randomize_turtle_other_than_the_final_one = 2
option_none = 0
option_all = 1
option_all_except_final = 2
default = 2
class EarlyEnergyForm(DefaultOnToggle):
"""
Force the Energy Form to be in a location before leaving the areas around the Home Water.
"""
""" Force the Energy Form to be in a location early in the game """
display_name = "Early Energy Form"
class AquarianTranslation(Toggle):
"""Translate to English the Aquarian scripture in the game."""
"""Translate the Aquarian scripture in the game into English."""
display_name = "Translate Aquarian"
class BigBossesToBeat(Range):
"""
A number of big bosses to beat before having access to the creator (the final boss). The big bosses are
The number of big bosses to beat before having access to the creator (the final boss). The big bosses are
"Fallen God", "Mithalan God", "Drunian God", "Sun God" and "The Golem".
"""
display_name = "Big bosses to beat"
@@ -60,12 +57,12 @@ class BigBossesToBeat(Range):
class MiniBossesToBeat(Range):
"""
A number of Minibosses to beat before having access to the creator (the final boss). Mini bosses are
The number of minibosses to beat before having access to the creator (the final boss). The minibosses are
"Nautilus Prime", "Blaster Peg Prime", "Mergog", "Mithalan priests", "Octopus Prime", "Crabbius Maximus",
"Mantis Shrimp Prime" and "King Jellyfish God Prime". Note that the Energy statue and Simon says are not
mini bosses.
"Mantis Shrimp Prime" and "King Jellyfish God Prime".
Note that the Energy Statue and Simon Says are not minibosses.
"""
display_name = "Mini bosses to beat"
display_name = "Minibosses to beat"
range_start = 0
range_end = 8
default = 0
@@ -73,47 +70,50 @@ class MiniBossesToBeat(Range):
class Objective(Choice):
"""
The game objective can be only to kill the creator or to kill the creator
and having obtained the three every secret memories
The game objective can be to kill the creator or to kill the creator after obtaining all three secret memories.
"""
display_name = "Objective"
option_kill_the_creator = 0
option_obtain_secrets_and_kill_the_creator = 1
default = 0
class SkipFirstVision(Toggle):
"""
The first vision in the game; where Naija transform to Energy Form and get fload by enemy; is quite cool but
The first vision in the game, where Naija transforms into Energy Form and gets flooded by enemies, is quite cool but
can be quite long when you already know what is going on. This option can be used to skip this vision.
"""
display_name = "Skip first Naija's vision"
display_name = "Skip Naija's first vision"
class NoProgressionHardOrHiddenLocation(Toggle):
"""
Make sure that there is no progression items at hard to get or hard to find locations.
Those locations that will be very High location (that need beast form, soup and skill to get), every
location in the bubble cave, locations that need you to cross a false wall without any indication, Arnassi
race, bosses and mini-bosses. Usefull for those that want a casual run.
Make sure that there are no progression items at hard-to-reach or hard-to-find locations.
Those locations are very High locations (that need beast form, soup and skill to get),
every location in the bubble cave, locations where need you to cross a false wall without any indication,
the Arnassi race, bosses and minibosses. Useful for those that want a more casual run.
"""
display_name = "No progression in hard or hidden locations"
class LightNeededToGetToDarkPlaces(DefaultOnToggle):
"""
Make sure that the sun form or the dumbo pet can be aquired before getting to dark places. Be aware that navigating
in dark place without light is extremely difficult.
Make sure that the sun form or the dumbo pet can be acquired before getting to dark places.
Be aware that navigating in dark places without light is extremely difficult.
"""
display_name = "Light needed to get to dark places"
class BindSongNeededToGetUnderRockBulb(Toggle):
"""
Make sure that the bind song can be aquired before having to obtain sing bulb under rocks.
Make sure that the bind song can be acquired before having to obtain sing bulbs under rocks.
"""
display_name = "Bind song needed to get sing bulbs under rocks"
class UnconfineHomeWater(Choice):
"""
Open the way out of Home water area so that Naija can go to open water and beyond without the bind song.
Open the way out of the Home water area so that Naija can go to open water and beyond without the bind song.
"""
display_name = "Unconfine Home Water Area"
option_off = 0

View File

@@ -5,7 +5,7 @@ Description: Used to manage Regions in the Aquaria game multiworld randomizer
"""
from typing import Dict, Optional
from BaseClasses import MultiWorld, Region, Entrance, ItemClassification, LocationProgressType, CollectionState
from BaseClasses import MultiWorld, Region, Entrance, ItemClassification, CollectionState
from .Items import AquariaItem
from .Locations import AquariaLocations, AquariaLocation
from .Options import AquariaOptions
@@ -223,8 +223,6 @@ class AquariaRegions:
region.add_locations(locations, AquariaLocation)
return region
def __create_home_water_area(self) -> None:
"""
Create the `verse_cave`, `home_water` and `song_cave*` regions
@@ -941,7 +939,7 @@ class AquariaRegions:
"""
Add secrets events to the `world`
"""
self.__add_event_location(self.first_secret, # Doit ajouter une région pour le "first secret"
self.__add_event_location(self.first_secret, # Doit ajouter une région pour le "first secret"
"First secret",
"First secret obtained")
self.__add_event_location(self.mithalas_city,
@@ -1095,12 +1093,10 @@ class AquariaRegions:
add_rule(self.multiworld.get_entrance("Veil left of sun temple to Sun temple left area", self.player),
lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player))
def __adjusting_manual_rules(self) -> None:
add_rule(self.multiworld.get_location("Mithalas cathedral, Mithalan Dress", self.player),
lambda state: _has_beast_form(state, self.player))
add_rule(self.multiworld.get_location("Open water bottom left area, bulb inside the downest fish pass", self.player),
add_rule(self.multiworld.get_location("Open water bottom left area, bulb inside the lowest fish pass", self.player),
lambda state: _has_fish_form(state, self.player))
add_rule(self.multiworld.get_location("Kelp forest bottom left area, Walker baby", self.player),
lambda state: _has_spirit_form(state, self.player))
@@ -1122,7 +1118,7 @@ class AquariaRegions:
self.player), lambda state: _has_energy_form(state, self.player))
add_rule(self.multiworld.get_location("Home water, bulb in the bottom left room", self.player),
lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Home water, bulb in the path bellow Nautilus Prime", self.player),
add_rule(self.multiworld.get_location("Home water, bulb in the path below Nautilus Prime", self.player),
lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Naija's home, bulb after the energy door", self.player),
lambda state: _has_energy_form(state, self.player))
@@ -1133,9 +1129,6 @@ class AquariaRegions:
lambda state: _has_fish_form(state, self.player) and
_has_spirit_form(state, self.player))
def __no_progression_hard_or_hidden_location(self) -> None:
self.multiworld.get_location("Energy temple boss area, Fallen god tooth",
self.player).item_rule =\
@@ -1242,11 +1235,7 @@ class AquariaRegions:
add_rule(self.multiworld.get_entrance("Home Water to Open water top left area", self.player),
lambda state: _has_bind_song(state, self.player) and _has_energy_form(state, self.player))
if options.early_energy_form:
add_rule(self.multiworld.get_entrance("Home Water to Home water transturtle room", self.player),
lambda state: _has_energy_form(state, self.player))
if options.early_energy_form:
add_rule(self.multiworld.get_entrance("Home Water to Open water top left area", self.player),
lambda state: _has_energy_form(state, self.player))
self.multiworld.early_items[self.player]["Energy form"] = 1
if options.no_progression_hard_or_hidden_locations:
self.__no_progression_hard_or_hidden_location()

View File

@@ -5,7 +5,7 @@ Description: Main module for Aquaria game multiworld randomizer
"""
from typing import List, Dict, ClassVar, Any
from ..AutoWorld import World, WebWorld
from worlds.AutoWorld import World, WebWorld
from BaseClasses import Tutorial, MultiWorld, ItemClassification
from .Items import item_table, AquariaItem, ItemType, ItemGroup
from .Locations import location_table
@@ -114,7 +114,7 @@ class AquariaWorld(World):
def create_item(self, name: str) -> AquariaItem:
"""
Create an AquariaItem using `name' as item name.
Create an AquariaItem using 'name' as item name.
"""
result: AquariaItem
try:

View File

@@ -11,39 +11,39 @@ options page link: [Aquaria Player Options Page](../player-options).
## What does randomization do to this game?
The locations in the randomizer are:
- All sing bulbs;
- All Mithalas Urns;
- All Sunken City crates;
- Collectible treasure locations (including pet eggs and costumes);
- Beating Simon says;
- Li cave;
- Every Transportation Turtle (also called transturtle);
- Locations where you get songs,
* Erulian spirit cristal,
* Energy status mini-boss,
* Beating Mithalan God boss,
* Fish cave puzzle,
* Beating Drunian God boss,
* Beating Sun God boss,
* Breaking Li cage in the body
- All sing bulbs
- All Mithalas Urns
- All Sunken City crates
- Collectible treasure locations (including pet eggs and costumes)
- Beating Simon says
- Li cave
- Every Transportation Turtle (also called transturtle)
- Locations where you get songs:
* Erulian spirit cristal
* Energy status mini-boss
* Beating Mithalan God boss
* Fish cave puzzle
* Beating Drunian God boss
* Beating Sun God boss
* Breaking Li cage in the body
Note that, unlike the vanilla game, when opening sing bulbs, Mithalas urns and Sunken City crates,
nothing will come out of them. The moment those bulbs, urns and crates are opened, the location is considered received.
nothing will come out of them. The moment those bulbs, urns and crates are opened, the location is considered checked.
The items in the randomizer are:
- Dishes (used to learn recipes*);
- Some ingredients;
- The Wok (third plate used to cook 3 ingredients recipes everywhere);
- All collectible treasure (including pet eggs and costumes);
- Li and Li song;
- All songs (other than Li's song since it is learned when Li is obtained);
- Transportation to transturtles.
- Dishes (used to learn recipes)<sup>*</sup>
- Some ingredients
- The Wok (third plate used to cook 3-ingredient recipes everywhere)
- All collectible treasure (including pet eggs and costumes)
- Li and Li's song
- All songs (other than Li's song since it is learned when Li is obtained)
- Transportation to transturtles
Also, there is the option to randomize every ingredient drops (from fishes, monsters
or plants).
*Note that, unlike in the vanilla game, the recipes for dishes (other than the Sea Loaf)
cannot be cooked (and learn) before being obtained as randomized items. Also, enemies and plants
<sup>*</sup> Note that, unlike in the vanilla game, the recipes for dishes (other than the Sea Loaf)
cannot be cooked (or learned) before being obtained as randomized items. Also, enemies and plants
that drop dishes that have not been learned before will drop ingredients of this dish instead.
## What is the goal of the game?
@@ -57,8 +57,8 @@ Any items specified above can be in another player's world.
No visuals are shown when finding locations other than collectible treasure.
For those treasures, the visual of the treasure is visually unchanged.
After collecting a location check, a message will be shown to inform the player
what has been collected, and who will receive it.
what has been collected and who will receive it.
## When the player receives an item, what happens?
When you receive an item, a message will pop up to inform you where you received
the item from, and which one it is.
the item from and which one it was.

View File

@@ -2,9 +2,12 @@
## Required Software
- The original Aquaria Game (buyable from a lot of online game seller);
- The original Aquaria Game (purchasable from most online game stores)
- The [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases)
- Optional, for sending [commands](/tutorial/Archipelago/commands/en) like `!hint`: the TextClient from [the most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases)
## Optional Software
- For sending [commands](/tutorial/Archipelago/commands/en) like `!hint`: the TextClient from [the most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases)
## Installation and execution Procedures
@@ -13,10 +16,9 @@
First, you should copy the original Aquaria folder game. The randomizer will possibly modify the game so that
the original game will stop working. Copying the folder will guarantee that the original game keeps on working.
Also, in Windows, the save files are stored in the Aquaria folder. So copying the Aquaria folder for every Multiworld
game you play will make sure that every game has their own save game.
game you play will make sure that every game has its own save game.
Unzip the Aquaria randomizer release and copy all unzipped files in the Aquaria game folder. The unzipped files
are those:
Unzip the Aquaria randomizer release and copy all unzipped files in the Aquaria game folder. The unzipped files are:
- aquaria_randomizer.exe
- OpenAL32.dll
- override (directory)
@@ -25,11 +27,11 @@ are those:
- wrap_oal.dll
- cacert.pem
If there is a conflict between file in the original game folder and the unzipped files, you should override
the original files with the one of the unzipped randomizer.
If there is a conflict between files in the original game folder and the unzipped files, you should overwrite
the original files with the ones from the unzipped randomizer.
Finally, to launch the randomizer, you must use the command line interface (you can open the command line interface
by writing `cmd` in the address bar of the Windows file explorer). Here is the command line to use to start the
by typing `cmd` in the address bar of the Windows File Explorer). Here is the command line used to start the
randomizer:
```bash
@@ -44,8 +46,8 @@ aquaria_randomizer.exe --name YourName --server theServer:thePort --password th
### Linux when using the AppImage
If you use the AppImage, just copy it in the Aquaria game folder. You then have to make it executable. You
can do that from command line by using
If you use the AppImage, just copy it into the Aquaria game folder. You then have to make it executable. You
can do that from command line by using:
```bash
chmod +x Aquaria_Randomizer-*.AppImage
@@ -65,7 +67,7 @@ or, if the room has a password:
./Aquaria_Randomizer-*.AppImage --name YourName --server theServer:thePort --password thePassword
```
Note that you should not have multiple Aquaria_Randomizer AppImage file in the same folder. If this situation occurred,
Note that you should not have multiple Aquaria_Randomizer AppImage file in the same folder. If this situation occurs,
the preceding commands will launch the game multiple times.
### Linux when using the tar file
@@ -73,24 +75,23 @@ the preceding commands will launch the game multiple times.
First, you should copy the original Aquaria folder game. The randomizer will possibly modify the game so that
the original game will stop working. Copying the folder will guarantee that the original game keeps on working.
Untar the Aquaria randomizer release and copy all extracted files in the Aquaria game folder. The extracted
files are those:
Untar the Aquaria randomizer release and copy all extracted files in the Aquaria game folder. The extracted files are:
- aquaria_randomizer
- override (directory)
- usersettings.xml
- cacert.pem
If there is a conflict between file in the original game folder and the extracted files, you should override
the original files with the one of the extracted randomizer files.
If there is a conflict between files in the original game folder and the extracted files, you should overwrite
the original files with the ones from the extracted randomizer files.
Then, you should use your system package manager to install liblua5, libogg, libvorbis, libopenal and libsdl2.
Then, you should use your system package manager to install `liblua5`, `libogg`, `libvorbis`, `libopenal` and `libsdl2`.
On Debian base system (like Ubuntu), you can use the following command:
```bash
sudo apt install liblua5.1-0-dev libogg-dev libvorbis-dev libopenal-dev libsdl2-dev
```
Also, if there is some `.so` files in the Aquaria original game folder (`libgcc_s.so.1`, `libopenal.so.1`,
Also, if there are certain `.so` files in the original Aquaria game folder (`libgcc_s.so.1`, `libopenal.so.1`,
`libSDL-1.2.so.0` and `libstdc++.so.6`), you should remove them from the Aquaria Randomizer game folder. Those are
old libraries that will not work on the recent build of the randomizer.
@@ -106,7 +107,7 @@ or, if the room has a password:
./aquaria_randomizer --name YourName --server theServer:thePort --password thePassword
```
Note: If you have a permission denied error when using the command line, you can use this command line to be
Note: If you get a permission denied error when using the command line, you can use this command to be
sure that your executable has executable permission:
```bash

View File

@@ -25,7 +25,7 @@ after_home_water_locations = [
"Open water top right area, bulb in the turtle room",
"Open water top right area, Transturtle",
"Open water bottom left area, bulb behind the chomper fish",
"Open water bottom left area, bulb inside the downest fish pass",
"Open water bottom left area, bulb inside the lowest fish pass",
"Open water skeleton path, bulb close to the right exit",
"Open water skeleton path, bulb behind the chomper fish",
"Open water skeleton path, King skull",
@@ -82,7 +82,7 @@ after_home_water_locations = [
"Mithalas cathedral, third urn in the path behind the flesh vein",
"Mithalas cathedral, one of the urns in the top right room",
"Mithalas cathedral, Mithalan Dress",
"Mithalas cathedral right area, urn bellow the left entrance",
"Mithalas cathedral right area, urn below the left entrance",
"Cathedral underground, bulb in the center part",
"Cathedral underground, first bulb in the top left part",
"Cathedral underground, second bulb in the top left part",
@@ -178,7 +178,7 @@ after_home_water_locations = [
"The body main area, bulb on the main path blocking tube",
"The body left area, first bulb in the top face room",
"The body left area, second bulb in the top face room",
"The body left area, bulb bellow the water stream",
"The body left area, bulb below the water stream",
"The body left area, bulb in the top path to the top face room",
"The body left area, bulb in the bottom face room",
"The body right area, bulb in the top face room",

View File

@@ -18,7 +18,7 @@ class BindSongAccessTest(AquariaTestBase):
"""Test locations that require Bind song"""
locations = [
"Verse cave right area, Big Seed",
"Home water, bulb in the path bellow Nautilus Prime",
"Home water, bulb in the path below Nautilus Prime",
"Home water, bulb in the bottom left room",
"Home water, Nautilus Egg",
"Song cave, Verse egg",

View File

@@ -24,7 +24,7 @@ class BindSongOptionAccessTest(AquariaTestBase):
"Song cave, bulb under the rock close to the song door",
"Song cave, bulb under the rock in the path to the singing statues",
"Naija's home, bulb under the rock at the right of the main path",
"Home water, bulb in the path bellow Nautilus Prime",
"Home water, bulb in the path below Nautilus Prime",
"Home water, bulb in the bottom left room",
"Home water, Nautilus Egg",
"Song cave, Verse egg",

View File

@@ -39,7 +39,7 @@ class EnergyFormAccessTest(AquariaTestBase):
"Mithalas cathedral, third urn in the path behind the flesh vein",
"Mithalas cathedral, one of the urns in the top right room",
"Mithalas cathedral, Mithalan Dress",
"Mithalas cathedral right area, urn bellow the left entrance",
"Mithalas cathedral right area, urn below the left entrance",
"Cathedral boss area, beating Mithalan God",
"Kelp Forest top left area, bulb close to the Verse egg",
"Kelp forest top left area, Verse egg",
@@ -67,7 +67,6 @@ class EnergyFormAccessTest(AquariaTestBase):
"First secret",
"Sunken City cleared",
"Objective complete",
]
items = [["Energy form"]]
self.assertAccessDependency(locations, items)

View File

@@ -1,31 +0,0 @@
"""
Author: Louis M
Date: Thu, 18 Apr 2024 18:45:56 +0000
Description: Unit test used to test accessibility of locations with and without the bind song (with the early
energy form option)
"""
from worlds.aquaria.test import AquariaTestBase, after_home_water_locations
class EnergyFormAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of locations with and without the energy form"""
options = {
"early_energy_form": True,
}
def test_energy_form_location(self) -> None:
"""Test locations that require Energy form with early energy song enable"""
locations = [
"Home water, Nautilus Egg",
"Naija's home, bulb after the energy door",
"Energy temple first area, bulb in the bottom room blocked by a rock",
"Energy temple second area, bulb under the rock",
"Energy temple bottom entrance, Krotite armor",
"Energy temple third area, bulb in the bottom path",
"Energy temple boss area, Fallen god tooth",
"Energy temple blaster room, Blaster egg",
*after_home_water_locations
]
items = [["Energy form"]]
self.assertAccessDependency(locations, items)

View File

@@ -21,7 +21,7 @@ class FishFormAccessTest(AquariaTestBase):
"Mithalas city, urn inside a home fish pass",
"Kelp Forest top right area, bulb in the top fish pass",
"The veil bottom area, Verse egg",
"Open water bottom left area, bulb inside the downest fish pass",
"Open water bottom left area, bulb inside the lowest fish pass",
"Kelp Forest top left area, bulb close to the Verse egg",
"Kelp forest top left area, Verse egg",
"Mermog cave, bulb in the left part of the cave",

View File

@@ -27,7 +27,7 @@ class LiAccessTest(AquariaTestBase):
"The body main area, bulb on the main path blocking tube",
"The body left area, first bulb in the top face room",
"The body left area, second bulb in the top face room",
"The body left area, bulb bellow the water stream",
"The body left area, bulb below the water stream",
"The body left area, bulb in the top path to the top face room",
"The body left area, bulb in the bottom face room",
"The body right area, bulb in the top face room",

View File

@@ -41,7 +41,7 @@ class NatureFormAccessTest(AquariaTestBase):
"The body main area, bulb on the main path blocking tube",
"The body left area, first bulb in the top face room",
"The body left area, second bulb in the top face room",
"The body left area, bulb bellow the water stream",
"The body left area, bulb below the water stream",
"The body left area, bulb in the top path to the top face room",
"The body left area, bulb in the bottom face room",
"The body right area, bulb in the top face room",

View File

@@ -9,7 +9,9 @@ else:
class Logic(Choice):
"""Choose the logic used by the randomizer."""
"""
Choose the logic used by the randomizer.
"""
display_name = "Logic"
option_glitchless = 0
option_glitched = 1
@@ -17,26 +19,38 @@ class Logic(Choice):
class SkipIntro(DefaultOnToggle):
"""Skips escaping the police station.
Graffiti spots tagged during the intro will not unlock items."""
"""
Skips escaping the police station.
Graffiti spots tagged during the intro will not unlock items.
"""
display_name = "Skip Intro"
class SkipDreams(Toggle):
"""Skips the dream sequences at the end of each chapter.
This can be changed later in the options menu inside the Archipelago phone app."""
"""
Skips the dream sequences at the end of each chapter.
This can be changed later in the options menu inside the Archipelago phone app.
"""
display_name = "Skip Dreams"
class SkipHands(Toggle):
"""Skips spraying the lion statue hands after the dream in Chapter 5."""
"""
Skips spraying the lion statue hands after the dream in Chapter 5.
"""
display_name = "Skip Statue Hands"
class TotalRep(Range):
"""Change the total amount of REP in your world.
"""
Change the total amount of REP in your world.
At least 960 REP is needed to finish the game.
Will be rounded to the nearest number divisible by 8."""
Will be rounded to the nearest number divisible by 8.
"""
display_name = "Total REP"
range_start = 1000
range_end = 2000
@@ -74,12 +88,16 @@ class TotalRep(Range):
class EndingREP(Toggle):
"""Changes the final boss to require 1000 REP instead of 960 REP to start."""
"""
Changes the final boss to require 1000 REP instead of 960 REP to start.
"""
display_name = "Extra REP Required"
class StartStyle(Choice):
"""Choose which movestyle to start with."""
"""
Choose which movestyle to start with.
"""
display_name = "Starting Movestyle"
option_skateboard = 2
option_inline_skates = 3
@@ -88,17 +106,22 @@ class StartStyle(Choice):
class LimitedGraffiti(Toggle):
"""Each graffiti design can only be used a limited number of times before being removed from your inventory.
In some cases, such as completing a dream, using graffiti to defeat enemies, or spraying over your own graffiti,
uses will not be counted.
If enabled, doing graffiti is disabled during crew battles, to prevent softlocking."""
"""
Each graffiti design can only be used a limited number of times before being removed from your inventory.
In some cases, such as completing a dream, using graffiti to defeat enemies, or spraying over your own graffiti, uses will not be counted.
If enabled, doing graffiti is disabled during crew battles, to prevent softlocking.
"""
display_name = "Limited Graffiti"
class SGraffiti(Choice):
"""Choose if small graffiti should be separate, meaning that you will need to switch characters every time you run
out, or combined, meaning that unlocking new characters will add 5 uses that any character can use.
Has no effect if Limited Graffiti is disabled."""
"""
Choose if small graffiti should be separate, meaning that you will need to switch characters every time you run out, or combined, meaning that unlocking new characters will add 5 uses that any character can use.
Has no effect if Limited Graffiti is disabled.
"""
display_name = "Small Graffiti Uses"
option_separate = 0
option_combined = 1
@@ -106,19 +129,27 @@ class SGraffiti(Choice):
class JunkPhotos(Toggle):
"""Skip taking pictures of Polo for items."""
"""
Skip taking pictures of Polo for items.
"""
display_name = "Skip Polo Photos"
class DontSavePhotos(Toggle):
"""Photos taken with the Camera app will not be saved.
This can be changed later in the options menu inside the Archipelago phone app."""
"""
Photos taken with the Camera app will not be saved.
This can be changed later in the options menu inside the Archipelago phone app.
"""
display_name = "Don't Save Photos"
class ScoreDifficulty(Choice):
"""Alters the score required to win score challenges and crew battles.
This can be changed later in the options menu inside the Archipelago phone app."""
"""
Alters the score required to win score challenges and crew battles.
This can be changed later in the options menu inside the Archipelago phone app.
"""
display_name = "Score Difficulty"
option_normal = 0
option_medium = 1
@@ -129,10 +160,14 @@ class ScoreDifficulty(Choice):
class DamageMultiplier(Range):
"""Multiplies all damage received.
"""
Multiplies all damage received.
At 3x, most damage will OHKO the player, including falling into pits.
At 6x, all damage will OHKO the player.
This can be changed later in the options menu inside the Archipelago phone app."""
This can be changed later in the options menu inside the Archipelago phone app.
"""
display_name = "Damage Multiplier"
range_start = 1
range_end = 6
@@ -140,8 +175,11 @@ class DamageMultiplier(Range):
class BRCDeathLink(DeathLink):
"""When you die, everyone dies. The reverse is also true.
This can be changed later in the options menu inside the Archipelago phone app."""
"""
When you die, everyone dies. The reverse is also true.
This can be changed later in the options menu inside the Archipelago phone app.
"""
@dataclass

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass
from Options import Choice, Range, Toggle, DeathLink, PerGameCommonOptions
from Options import Choice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions
class DeathLinkAmnesty(Range):
@@ -47,7 +47,9 @@ class MoveShuffle(Toggle):
- Air Dash
- Skid Jump
- Climb
NOTE: Having Move Shuffle and Standard Logic Difficulty will guarantee that one of the four Move items will be immediately accessible
WARNING: Combining Move Shuffle and Hard Logic Difficulty can require very difficult tricks
"""
display_name = "Move Shuffle"
@@ -75,7 +77,9 @@ class Carsanity(Toggle):
class BadelineChaserSource(Choice):
"""
What type of action causes more Badeline Chasers to start spawning
Locations: The number of locations you've checked contributes to Badeline Chasers
Strawberries: The number of Strawberry items you've received contributes to Badeline Chasers
"""
display_name = "Badeline Chaser Source"
@@ -86,7 +90,9 @@ class BadelineChaserSource(Choice):
class BadelineChaserFrequency(Range):
"""
How many of the `Badeline Chaser Source` actions must occur to make each Badeline Chaser start spawning
NOTE: Choosing `0` disables Badeline Chasers entirely
WARNING: Turning on Badeline Chasers alongside Move Shuffle could result in extremely difficult situations
"""
display_name = "Badeline Chaser Frequency"
@@ -104,6 +110,24 @@ class BadelineChaserSpeed(Range):
default = 3
celeste_64_option_groups = [
OptionGroup("Goal Options", [
TotalStrawberries,
StrawberriesRequiredPercentage,
]),
OptionGroup("Sanity Options", [
Friendsanity,
Signsanity,
Carsanity,
]),
OptionGroup("Badeline Chasers", [
BadelineChaserSource,
BadelineChaserFrequency,
BadelineChaserSpeed,
]),
]
@dataclass
class Celeste64Options(PerGameCommonOptions):
death_link: DeathLink

View File

@@ -7,7 +7,7 @@ from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_tab
from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\
sign_location_data_table, car_location_data_table, location_table
from .Names import ItemName, LocationName
from .Options import Celeste64Options
from .Options import Celeste64Options, celeste_64_option_groups
class Celeste64WebWorld(WebWorld):
@@ -24,6 +24,8 @@ class Celeste64WebWorld(WebWorld):
tutorials = [setup_en]
option_groups = celeste_64_option_groups
class Celeste64World(World):
"""Relive the magic of Celeste Mountain alongside Madeline in this small, heartfelt 3D platformer.

View File

@@ -1,13 +1,15 @@
from dataclasses import dataclass
import typing
from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList, PerGameCommonOptions
from Options import Choice, Range, Toggle, DeathLink, DefaultOnToggle, OptionGroup, PerGameCommonOptions
class Goal(Choice):
"""
Determines the goal of the seed
Knautilus: Scuttle the Knautilus in Krematoa and defeat Baron K. Roolenstein
Banana Bird Hunt: Find a certain number of Banana Birds and rescue their mother
"""
display_name = "Goal"
@@ -26,6 +28,7 @@ class IncludeTradeSequence(Toggle):
class DKCoinsForGyrocopter(Range):
"""
How many DK Coins are needed to unlock the Gyrocopter
Note: Achieving this number before unlocking the Turbo Ski will cause the game to grant you a
one-time upgrade to the next non-unlocked boat, until you return to Funky. Logic does not assume
that you will use this.
@@ -93,6 +96,7 @@ class LevelShuffle(Toggle):
class Difficulty(Choice):
"""
Which Difficulty Level to use
NORML: The Normal Difficulty
HARDR: Many DK Barrels are removed
TUFST: Most DK Barrels and all Midway Barrels are removed
@@ -159,19 +163,40 @@ class StartingLifeCount(Range):
default = 5
dkc3_option_groups = [
OptionGroup("Goal Options", [
Goal,
KrematoaBonusCoinCost,
PercentageOfExtraBonusCoins,
NumberOfBananaBirds,
PercentageOfBananaBirds,
]),
OptionGroup("Aesthetics", [
Autosave,
MERRY,
MusicShuffle,
KongPaletteSwap,
StartingLifeCount,
]),
]
@dataclass
class DKC3Options(PerGameCommonOptions):
#death_link: DeathLink # Disabled
goal: Goal
#include_trade_sequence: IncludeTradeSequence # Disabled
dk_coins_for_gyrocopter: DKCoinsForGyrocopter
goal: Goal
krematoa_bonus_coin_cost: KrematoaBonusCoinCost
percentage_of_extra_bonus_coins: PercentageOfExtraBonusCoins
number_of_banana_birds: NumberOfBananaBirds
percentage_of_banana_birds: PercentageOfBananaBirds
dk_coins_for_gyrocopter: DKCoinsForGyrocopter
kongsanity: KONGsanity
level_shuffle: LevelShuffle
difficulty: Difficulty
autosave: Autosave
merry: MERRY
music_shuffle: MusicShuffle

View File

@@ -4,20 +4,21 @@ import typing
import math
import threading
import settings
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
from Options import PerGameCommonOptions
from .Items import DKC3Item, ItemData, item_table, inventory_table, junk_table
from .Locations import DKC3Location, all_locations, setup_locations
from .Options import DKC3Options
from .Regions import create_regions, connect_regions
from .Levels import level_list
from .Rules import set_rules
from .Names import ItemName, LocationName
from .Client import DKC3SNIClient
from worlds.AutoWorld import WebWorld, World
from .Rom import LocalRom, patch_rom, get_base_rom_path, DKC3DeltaPatch
import Patch
import settings
from worlds.AutoWorld import WebWorld, World
from .Client import DKC3SNIClient
from .Items import DKC3Item, ItemData, item_table, inventory_table, junk_table
from .Levels import level_list
from .Locations import DKC3Location, all_locations, setup_locations
from .Names import ItemName, LocationName
from .Options import DKC3Options, dkc3_option_groups
from .Regions import create_regions, connect_regions
from .Rom import LocalRom, patch_rom, get_base_rom_path, DKC3DeltaPatch
from .Rules import set_rules
class DK3Settings(settings.Group):
@@ -41,9 +42,11 @@ class DKC3Web(WebWorld):
"setup/en",
["PoryGone"]
)
tutorials = [setup_en]
option_groups = dkc3_option_groups
class DKC3World(World):
"""

View File

@@ -1,25 +1,38 @@
from dataclasses import dataclass
from Options import Choice, Removed, Toggle, DefaultOnToggle, DeathLink, PerGameCommonOptions
class PartyShuffle(Toggle):
"""Shuffles party members into the pool.
Note that enabling this can potentially increase both the difficulty and length of a run."""
"""
Shuffles party members into the item pool.
Note that enabling this can significantly increase both the difficulty and length of a run.
"""
display_name = "Shuffle Party Members"
class GestureShuffle(Choice):
"""Choose where gestures will appear in the item pool."""
"""
Choose where gestures will appear in the item pool.
"""
display_name = "Shuffle Gestures"
option_anywhere = 0
option_tvs_only = 1
option_default_locations = 2
default = 0
class MedallionShuffle(Toggle):
"""Shuffles red medallions into the pool."""
"""
Shuffles red medallions into the item pool.
"""
display_name = "Shuffle Red Medallions"
class StartLocation(Choice):
"""Select the starting location from 1 of 4 positions."""
"""
Select the starting location from 1 of 4 positions.
"""
display_name = "Start Location"
option_waynehouse = 0
option_viewaxs_edifice = 1
@@ -35,14 +48,23 @@ class StartLocation(Choice):
return "TV Island"
return super().get_option_name(value)
class ExtraLogic(DefaultOnToggle):
"""Include some extra items in logic (CHARGE UP, 1x PAPER CUP) to prevent the game from becoming too difficult."""
"""
Include some extra items in logic (CHARGE UP, 1x PAPER CUP) to prevent the game from becoming too difficult.
"""
display_name = "Extra Items in Logic"
class Hylics2DeathLink(DeathLink):
"""When you die, everyone dies. The reverse is also true.
"""
When you die, everyone dies. The reverse is also true.
Note that this also includes death by using the PERISH gesture.
Can be toggled via in-game console command "/deathlink"."""
Can be toggled via in-game console command "/deathlink".
"""
@dataclass
class Hylics2Options(PerGameCommonOptions):

View File

@@ -48,10 +48,6 @@ class MLSSClient(BizHawkClient):
rom_name_bytes = await bizhawk.read(ctx.bizhawk_ctx, [(0xA0, 14, "ROM")])
rom_name = bytes([byte for byte in rom_name_bytes[0] if byte != 0]).decode("UTF-8")
if not rom_name.startswith("MARIO&LUIGIUA8"):
logger.info(
"ERROR: You have opened a game that is not Mario & Luigi Superstar Saga. "
"Please make sure you are opening the correct ROM."
)
return False
except UnicodeDecodeError:
return False

View File

@@ -1,18 +1,26 @@
import typing
from dataclasses import dataclass
from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList
from Options import Choice, Range, Toggle, DeathLink, DefaultOnToggle, OptionGroup, PerGameCommonOptions
class Goal(Choice):
"""
Determines the goal of the seed
Biolizard: Finish Cannon's Core and defeat the Biolizard and Finalhazard
Chaos Emerald Hunt: Find the Seven Chaos Emeralds and reach Green Hill Zone
Finalhazard Chaos Emerald Hunt: Find the Seven Chaos Emeralds and reach Green Hill Zone, then defeat Finalhazard
Grand Prix: Win every race in Kart Race Mode (all standard levels are disabled)
Boss Rush: Beat all of the bosses in the Boss Rush, ending with Finalhazard
Cannon's Core Boss Rush: Beat Cannon's Core, then beat all of the bosses in the Boss Rush, ending with Finalhazard
Boss Rush Chaos Emerald Hunt: Find the Seven Chaos Emeralds, then beat all of the bosses in the Boss Rush, ending with Finalhazard
Chaos Chao: Raise a Chaos Chao to win
"""
display_name = "Goal"
@@ -46,9 +54,13 @@ class MissionShuffle(Toggle):
class BossRushShuffle(Choice):
"""
Determines how bosses in Boss Rush Mode are shuffled
Vanilla: Bosses appear in the Vanilla ordering
Shuffled: The same bosses appear, but in a random order
Chaos: Each boss is randomly chosen separately (one will always be King Boom Boo)
Singularity: One boss is chosen and placed in every slot (one will always be replaced with King Boom Boo)
"""
display_name = "Boss Rush Shuffle"
@@ -196,9 +208,13 @@ class Keysanity(Toggle):
class Whistlesanity(Choice):
"""
Determines whether whistling at various spots grants checks
None: No Whistle Spots grant checks
Pipes: Whistling at Pipes grants checks (97 Locations)
Hidden: Whistling at Hidden Whistle Spots grants checks (32 Locations)
Both: Whistling at both Pipes and Hidden Whistle Spots grants checks (129 Locations)
"""
display_name = "Whistlesanity"
@@ -228,8 +244,9 @@ class Omosanity(Toggle):
class Animalsanity(Toggle):
"""
Determines whether unique counts of animals grant checks.
ALL animals must be collected in a single run of a mission to get all checks.
(421 Locations)
ALL animals must be collected in a single run of a mission to get all checks.
"""
display_name = "Animalsanity"
@@ -237,8 +254,11 @@ class Animalsanity(Toggle):
class KartRaceChecks(Choice):
"""
Determines whether Kart Race Mode grants checks
None: No Kart Races grant checks
Mini: Each Kart Race difficulty must be beaten only once
Full: Every Character must separately beat each Kart Race difficulty
"""
display_name = "Kart Race Checks"
@@ -271,8 +291,11 @@ class NumberOfLevelGates(Range):
class LevelGateDistribution(Choice):
"""
Determines how levels are distributed between level gate regions
Early: Earlier regions will have more levels than later regions
Even: Levels will be evenly distributed between all regions
Late: Later regions will have more levels than earlier regions
"""
display_name = "Level Gate Distribution"
@@ -296,7 +319,9 @@ class LevelGateCosts(Choice):
class MaximumEmblemCap(Range):
"""
Determines the maximum number of emblems that can be in the item pool.
If fewer available locations exist in the pool than this number, the number of available locations will be used instead.
Gate and Cannon's Core costs will be calculated based off of that number.
"""
display_name = "Max Emblem Cap"
@@ -321,9 +346,13 @@ class RequiredRank(Choice):
class ChaoRaceDifficulty(Choice):
"""
Determines the number of Chao Race difficulty levels included. Easier difficulty settings means fewer Chao Race checks
None: No Chao Races have checks
Beginner: Beginner Races
Intermediate: Beginner, Challenge, Hero, and Dark Races
Expert: Beginner, Challenge, Hero, Dark and Jewel Races
"""
display_name = "Chao Race Difficulty"
@@ -350,9 +379,10 @@ class ChaoKarateDifficulty(Choice):
class ChaoStadiumChecks(Choice):
"""
Determines which Chao Stadium activities grant checks
All: Each individual race and karate fight grants a check
Prize: Only the races which grant Chao Toys grant checks (final race of each Beginner and Jewel cup, 4th, 8th, and
12th Challenge Races, 2nd and 4th Hero and Dark Races, final fight of each Karate difficulty)
Prize: Only the races which grant Chao Toys grant checks (final race of each Beginner and Jewel cup, 4th, 8th, and 12th Challenge Races, 2nd and 4th Hero and Dark Races, final fight of each Karate difficulty)
"""
display_name = "Chao Stadium Checks"
option_all = 0
@@ -374,6 +404,7 @@ class ChaoStats(Range):
class ChaoStatsFrequency(Range):
"""
Determines how many levels in each Chao Stat grant checks (up to the maximum set in the `chao_stats` option)
`1` means every level is included, `2` means every other level is included, `3` means every third, and so on
"""
display_name = "Chao Stats Frequency"
@@ -408,8 +439,11 @@ class ChaoKindergarten(Choice):
"""
Determines whether learning the lessons from the Kindergarten Classroom grants checks
(WARNING: VERY SLOW)
None: No Kindergarten classes have checks
Basics: One class from each category (Drawing, Dance, Song, and Instrument) is a check (4 Locations)
Full: Every class is a check (23 Locations)
"""
display_name = "Chao Kindergarten Checks"
@@ -443,8 +477,8 @@ class BlackMarketUnlockCosts(Choice):
class BlackMarketPriceMultiplier(Range):
"""
Determines how many rings the Black Market items cost
The base ring costs of items in the Black Market range from 50-100,
and are then multiplied by this value
The base ring costs of items in the Black Market range from 50-100, and are then multiplied by this value
"""
display_name = "Black Market Price Multiplier"
range_start = 0
@@ -469,7 +503,9 @@ class ChaoEntranceRandomization(Toggle):
class RequiredCannonsCoreMissions(Choice):
"""
Determines how many Cannon's Core missions must be completed (for Biolizard or Cannon's Core goals)
First: Only the first mission must be completed
All Active: All active Cannon's Core missions must be completed
"""
display_name = "Required Cannon's Core Missions"
@@ -665,8 +701,11 @@ class CannonsCoreMission5(DefaultOnToggle):
class RingLoss(Choice):
"""
How taking damage is handled
Classic: You lose all of your rings when hit
Modern: You lose 20 rings when hit
OHKO: You die immediately when hit (NOTE: Some Hard Logic tricks may require damage boosts!)
"""
display_name = "Ring Loss"
@@ -693,9 +732,13 @@ class RingLink(Toggle):
class SADXMusic(Choice):
"""
Whether the randomizer will include Sonic Adventure DX Music in the music pool
SA2B: Only SA2B music will be played
SADX: Only SADX music will be played
Both: Both SA2B and SADX music will be played
NOTE: This option requires the player to own a PC copy of SADX and to follow the addition steps in the setup guide.
"""
display_name = "SADX Music"
@@ -715,9 +758,13 @@ class SADXMusic(Choice):
class MusicShuffle(Choice):
"""
What type of Music Shuffle is used
None: No music is shuffled.
Levels: Level music is shuffled.
Full: Level, Menu, and Additional music is shuffled.
Singularity: Level, Menu, and Additional music is all replaced with a single random song.
"""
display_name = "Music Shuffle Type"
@@ -731,10 +778,15 @@ class MusicShuffle(Choice):
class VoiceShuffle(Choice):
"""
What type of Voice Shuffle is used
None: No voices are shuffled.
Shuffled: Voices are shuffled.
Rude: Voices are shuffled, but some are replaced with rude words.
Chao: All voices are replaced with chao sounds.
Singularity: All voices are replaced with a single random voice.
"""
display_name = "Voice Shuffle Type"
@@ -768,7 +820,9 @@ class Narrator(Choice):
class LogicDifficulty(Choice):
"""
What set of Upgrade Requirement logic to use
Standard: The logic assumes the "intended" usage of Upgrades to progress through levels
Hard: Some simple skips or sequence breaks may be required
"""
display_name = "Logic Difficulty"
@@ -777,96 +831,195 @@ class LogicDifficulty(Choice):
default = 0
sa2b_options: typing.Dict[str, type(Option)] = {
"goal": Goal,
sa2b_option_groups = [
OptionGroup("General Options", [
Goal,
BossRushShuffle,
LogicDifficulty,
RequiredRank,
MaximumEmblemCap,
RingLoss,
]),
OptionGroup("Stages", [
MissionShuffle,
EmblemPercentageForCannonsCore,
RequiredCannonsCoreMissions,
NumberOfLevelGates,
LevelGateCosts,
LevelGateDistribution,
]),
OptionGroup("Sanity Options", [
Keysanity,
Whistlesanity,
Beetlesanity,
Omosanity,
Animalsanity,
KartRaceChecks,
]),
OptionGroup("Chao", [
BlackMarketSlots,
BlackMarketUnlockCosts,
BlackMarketPriceMultiplier,
ChaoRaceDifficulty,
ChaoKarateDifficulty,
ChaoStadiumChecks,
ChaoAnimalParts,
ChaoStats,
ChaoStatsFrequency,
ChaoStatsStamina,
ChaoStatsHidden,
ChaoKindergarten,
ShuffleStartingChaoEggs,
ChaoEntranceRandomization,
]),
OptionGroup("Junk and Traps", [
JunkFillPercentage,
TrapFillPercentage,
OmochaoTrapWeight,
TimestopTrapWeight,
ConfusionTrapWeight,
TinyTrapWeight,
GravityTrapWeight,
ExpositionTrapWeight,
IceTrapWeight,
SlowTrapWeight,
CutsceneTrapWeight,
ReverseTrapWeight,
PongTrapWeight,
MinigameTrapDifficulty,
]),
OptionGroup("Speed Missions", [
SpeedMissionCount,
SpeedMission2,
SpeedMission3,
SpeedMission4,
SpeedMission5,
]),
OptionGroup("Mech Missions", [
MechMissionCount,
MechMission2,
MechMission3,
MechMission4,
MechMission5,
]),
OptionGroup("Hunt Missions", [
HuntMissionCount,
HuntMission2,
HuntMission3,
HuntMission4,
HuntMission5,
]),
OptionGroup("Kart Missions", [
KartMissionCount,
KartMission2,
KartMission3,
KartMission4,
KartMission5,
]),
OptionGroup("Cannon's Core Missions", [
CannonsCoreMissionCount,
CannonsCoreMission2,
CannonsCoreMission3,
CannonsCoreMission4,
CannonsCoreMission5,
]),
OptionGroup("Aesthetics", [
SADXMusic,
MusicShuffle,
VoiceShuffle,
Narrator,
]),
]
"mission_shuffle": MissionShuffle,
"boss_rush_shuffle": BossRushShuffle,
"keysanity": Keysanity,
"whistlesanity": Whistlesanity,
"beetlesanity": Beetlesanity,
"omosanity": Omosanity,
"animalsanity": Animalsanity,
"kart_race_checks": KartRaceChecks,
@dataclass
class SA2BOptions(PerGameCommonOptions):
goal: Goal
boss_rush_shuffle: BossRushShuffle
logic_difficulty: LogicDifficulty
required_rank: RequiredRank
max_emblem_cap: MaximumEmblemCap
ring_loss: RingLoss
"logic_difficulty": LogicDifficulty,
"required_rank": RequiredRank,
"required_cannons_core_missions": RequiredCannonsCoreMissions,
mission_shuffle: MissionShuffle
required_cannons_core_missions: RequiredCannonsCoreMissions
emblem_percentage_for_cannons_core: EmblemPercentageForCannonsCore
number_of_level_gates: NumberOfLevelGates
level_gate_distribution: LevelGateDistribution
level_gate_costs: LevelGateCosts
"emblem_percentage_for_cannons_core": EmblemPercentageForCannonsCore,
"number_of_level_gates": NumberOfLevelGates,
"level_gate_distribution": LevelGateDistribution,
"level_gate_costs": LevelGateCosts,
"max_emblem_cap": MaximumEmblemCap,
keysanity: Keysanity
whistlesanity: Whistlesanity
beetlesanity: Beetlesanity
omosanity: Omosanity
animalsanity: Animalsanity
kart_race_checks: KartRaceChecks
"chao_race_difficulty": ChaoRaceDifficulty,
"chao_karate_difficulty": ChaoKarateDifficulty,
"chao_stadium_checks": ChaoStadiumChecks,
"chao_stats": ChaoStats,
"chao_stats_frequency": ChaoStatsFrequency,
"chao_stats_stamina": ChaoStatsStamina,
"chao_stats_hidden": ChaoStatsHidden,
"chao_animal_parts": ChaoAnimalParts,
"chao_kindergarten": ChaoKindergarten,
"black_market_slots": BlackMarketSlots,
"black_market_unlock_costs": BlackMarketUnlockCosts,
"black_market_price_multiplier": BlackMarketPriceMultiplier,
"shuffle_starting_chao_eggs": ShuffleStartingChaoEggs,
"chao_entrance_randomization": ChaoEntranceRandomization,
black_market_slots: BlackMarketSlots
black_market_unlock_costs: BlackMarketUnlockCosts
black_market_price_multiplier: BlackMarketPriceMultiplier
chao_race_difficulty: ChaoRaceDifficulty
chao_karate_difficulty: ChaoKarateDifficulty
chao_stadium_checks: ChaoStadiumChecks
chao_animal_parts: ChaoAnimalParts
chao_stats: ChaoStats
chao_stats_frequency: ChaoStatsFrequency
chao_stats_stamina: ChaoStatsStamina
chao_stats_hidden: ChaoStatsHidden
chao_kindergarten: ChaoKindergarten
shuffle_starting_chao_eggs: ShuffleStartingChaoEggs
chao_entrance_randomization: ChaoEntranceRandomization
"junk_fill_percentage": JunkFillPercentage,
"trap_fill_percentage": TrapFillPercentage,
"omochao_trap_weight": OmochaoTrapWeight,
"timestop_trap_weight": TimestopTrapWeight,
"confusion_trap_weight": ConfusionTrapWeight,
"tiny_trap_weight": TinyTrapWeight,
"gravity_trap_weight": GravityTrapWeight,
"exposition_trap_weight": ExpositionTrapWeight,
#"darkness_trap_weight": DarknessTrapWeight,
"ice_trap_weight": IceTrapWeight,
"slow_trap_weight": SlowTrapWeight,
"cutscene_trap_weight": CutsceneTrapWeight,
"reverse_trap_weight": ReverseTrapWeight,
"pong_trap_weight": PongTrapWeight,
"minigame_trap_difficulty": MinigameTrapDifficulty,
junk_fill_percentage: JunkFillPercentage
trap_fill_percentage: TrapFillPercentage
omochao_trap_weight: OmochaoTrapWeight
timestop_trap_weight: TimestopTrapWeight
confusion_trap_weight: ConfusionTrapWeight
tiny_trap_weight: TinyTrapWeight
gravity_trap_weight: GravityTrapWeight
exposition_trap_weight: ExpositionTrapWeight
#darkness_trap_weight: DarknessTrapWeight
ice_trap_weight: IceTrapWeight
slow_trap_weight: SlowTrapWeight
cutscene_trap_weight: CutsceneTrapWeight
reverse_trap_weight: ReverseTrapWeight
pong_trap_weight: PongTrapWeight
minigame_trap_difficulty: MinigameTrapDifficulty
"sadx_music": SADXMusic,
"music_shuffle": MusicShuffle,
"voice_shuffle": VoiceShuffle,
"narrator": Narrator,
"ring_loss": RingLoss,
sadx_music: SADXMusic
music_shuffle: MusicShuffle
voice_shuffle: VoiceShuffle
narrator: Narrator
"speed_mission_count": SpeedMissionCount,
"speed_mission_2": SpeedMission2,
"speed_mission_3": SpeedMission3,
"speed_mission_4": SpeedMission4,
"speed_mission_5": SpeedMission5,
speed_mission_count: SpeedMissionCount
speed_mission_2: SpeedMission2
speed_mission_3: SpeedMission3
speed_mission_4: SpeedMission4
speed_mission_5: SpeedMission5
"mech_mission_count": MechMissionCount,
"mech_mission_2": MechMission2,
"mech_mission_3": MechMission3,
"mech_mission_4": MechMission4,
"mech_mission_5": MechMission5,
mech_mission_count: MechMissionCount
mech_mission_2: MechMission2
mech_mission_3: MechMission3
mech_mission_4: MechMission4
mech_mission_5: MechMission5
"hunt_mission_count": HuntMissionCount,
"hunt_mission_2": HuntMission2,
"hunt_mission_3": HuntMission3,
"hunt_mission_4": HuntMission4,
"hunt_mission_5": HuntMission5,
hunt_mission_count: HuntMissionCount
hunt_mission_2: HuntMission2
hunt_mission_3: HuntMission3
hunt_mission_4: HuntMission4
hunt_mission_5: HuntMission5
"kart_mission_count": KartMissionCount,
"kart_mission_2": KartMission2,
"kart_mission_3": KartMission3,
"kart_mission_4": KartMission4,
"kart_mission_5": KartMission5,
kart_mission_count: KartMissionCount
kart_mission_2: KartMission2
kart_mission_3: KartMission3
kart_mission_4: KartMission4
kart_mission_5: KartMission5
"cannons_core_mission_count": CannonsCoreMissionCount,
"cannons_core_mission_2": CannonsCoreMission2,
"cannons_core_mission_3": CannonsCoreMission3,
"cannons_core_mission_4": CannonsCoreMission4,
"cannons_core_mission_5": CannonsCoreMission5,
cannons_core_mission_count: CannonsCoreMissionCount
cannons_core_mission_2: CannonsCoreMission2
cannons_core_mission_3: CannonsCoreMission3
cannons_core_mission_4: CannonsCoreMission4
cannons_core_mission_5: CannonsCoreMission5
"ring_link": RingLink,
"death_link": DeathLink,
}
ring_link: RingLink
death_link: DeathLink

View File

@@ -3,20 +3,20 @@ import math
import logging
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
from worlds.AutoWorld import WebWorld, World
from .AestheticData import chao_name_conversion, sample_chao_names, totally_real_item_names, \
all_exits, all_destinations, multi_rooms, single_rooms, room_to_exits_map, exit_to_room_map, valid_kindergarten_exits
from .GateBosses import get_gate_bosses, get_boss_rush_bosses, get_boss_name
from .Items import SA2BItem, ItemData, item_table, upgrades_table, emeralds_table, junk_table, trap_table, item_groups, \
eggs_table, fruits_table, seeds_table, hats_table, animals_table, chaos_drives_table
from .Locations import SA2BLocation, all_locations, setup_locations, chao_animal_event_location_table, black_market_location_table
from .Options import sa2b_options
from .Missions import get_mission_table, get_mission_count_table, get_first_and_last_cannons_core_missions
from .Names import ItemName, LocationName
from .Options import SA2BOptions, sa2b_option_groups
from .Regions import create_regions, shuffleable_regions, connect_regions, LevelGate, gate_0_whitelist_regions, \
gate_0_blacklist_regions
from .Rules import set_rules
from .Names import ItemName, LocationName
from .AestheticData import chao_name_conversion, sample_chao_names, totally_real_item_names, \
all_exits, all_destinations, multi_rooms, single_rooms, room_to_exits_map, exit_to_room_map, valid_kindergarten_exits
from worlds.AutoWorld import WebWorld, World
from .GateBosses import get_gate_bosses, get_boss_rush_bosses, get_boss_name
from .Missions import get_mission_table, get_mission_count_table, get_first_and_last_cannons_core_missions
import Patch
class SA2BWeb(WebWorld):
@@ -30,8 +30,9 @@ class SA2BWeb(WebWorld):
"setup/en",
["RaspberrySpaceJam", "PoryGone", "Entiss"]
)
tutorials = [setup_en]
option_groups = sa2b_option_groups
def check_for_impossible_shuffle(shuffled_levels: typing.List[int], gate_0_range: int, multiworld: MultiWorld):
@@ -54,7 +55,8 @@ class SA2BWorld(World):
Sonic Adventure 2 Battle is an action platforming game. Play as Sonic, Tails, Knuckles, Shadow, Rouge, and Eggman across 31 stages and prevent the destruction of the earth.
"""
game: str = "Sonic Adventure 2 Battle"
option_definitions = sa2b_options
options_dataclass = SA2BOptions
options: SA2BOptions
topology_present = False
data_version = 7

View File

@@ -1,12 +1,14 @@
from dataclasses import dataclass
from Options import Choice, Range, Toggle, DeathLink, DefaultOnToggle, PerGameCommonOptions
from Options import Choice, Range, Toggle, DeathLink, DefaultOnToggle, OptionGroup, PerGameCommonOptions
class Goal(Choice):
"""
Determines the goal of the seed
Bowser: Defeat Koopalings, reach Bowser's Castle and defeat Bowser
Yoshi Egg Hunt: Find a certain number of Yoshi Eggs
"""
display_name = "Goal"
@@ -28,7 +30,9 @@ class BossesRequired(Range):
class NumberOfYoshiEggs(Range):
"""
Maximum possible number of Yoshi Eggs that will be in the item pool
If fewer available locations exist in the pool than this number, the number of available locations will be used instead.
Required Percentage of Yoshi Eggs will be calculated based off of that number.
"""
display_name = "Max Number of Yoshi Eggs"
@@ -64,7 +68,9 @@ class MoonChecks(Toggle):
class Hidden1UpChecks(Toggle):
"""
Whether collecting a hidden 1-Up mushroom in a level will grant a check
These checks are considered cryptic as there's no actual indicator that they're in their respective places
Enable this option at your own risk
"""
display_name = "Hidden 1-Up Checks"
@@ -80,7 +86,9 @@ class BonusBlockChecks(Toggle):
class Blocksanity(Toggle):
"""
Whether hitting a block with an item or coin inside will grant a check
Note that some blocks are excluded due to how the option and the game works!
Exclusion list:
* Blocks in Top Secret Area & Front Door/Bowser Castle
* Blocks that are unreachable unless you glitch your way in
@@ -91,10 +99,15 @@ class Blocksanity(Toggle):
class BowserCastleDoors(Choice):
"""
How the doors of Bowser's Castle behave
Vanilla: Front and Back Doors behave as vanilla
Fast: Both doors behave as the Back Door
Slow: Both doors behave as the Front Door
"Front Door" rooms depend on the `bowser_castle_rooms` option
"Back Door" only requires going through the dark hallway to Bowser
"""
display_name = "Bowser Castle Doors"
@@ -107,10 +120,15 @@ class BowserCastleDoors(Choice):
class BowserCastleRooms(Choice):
"""
How the rooms of Bowser's Castle Front Door behave
Vanilla: You can choose which rooms to enter, as in vanilla
Random Two Room: Two random rooms are chosen
Random Five Room: Five random rooms are chosen
Gauntlet: All eight rooms must be cleared
Labyrinth: Which room leads to Bowser?
"""
display_name = "Bowser Castle Rooms"
@@ -125,9 +143,13 @@ class BowserCastleRooms(Choice):
class BossShuffle(Choice):
"""
How bosses are shuffled
None: Bosses are not shuffled
Simple: Four Reznors and the seven Koopalings are shuffled around
Full: Each boss location gets a fully random boss
Singularity: One or two bosses are chosen and placed at every boss location
"""
display_name = "Boss Shuffle"
@@ -148,6 +170,7 @@ class LevelShuffle(Toggle):
class ExcludeSpecialZone(Toggle):
"""
If active, this option will prevent any progression items from being placed in Special Zone levels.
Additionally, if Level Shuffle is active, Special Zone levels will not be shuffled away from their vanilla tiles.
"""
display_name = "Exclude Special Zone"
@@ -155,9 +178,10 @@ class ExcludeSpecialZone(Toggle):
class SwapDonutGhostHouseExits(Toggle):
"""
If enabled, this option will swap which overworld direction the two exits of the level at the Donut Ghost House
overworld tile go:
If enabled, this option will swap which overworld direction the two exits of the level at the Donut Ghost House overworld tile go:
False: Normal Exit goes up, Secret Exit goes right.
True: Normal Exit goes right, Secret Exit goes up.
"""
display_name = "Swap Donut GH Exits"
@@ -258,6 +282,7 @@ class Autosave(DefaultOnToggle):
class EarlyClimb(Toggle):
"""
Force Climb to appear early in the seed as a local item.
This is particularly useful to prevent BK when Level Shuffle is disabled
"""
display_name = "Early Climb"
@@ -277,9 +302,13 @@ class OverworldSpeed(Choice):
class MusicShuffle(Choice):
"""
Music shuffle type
None: No Music is shuffled
Consistent: Each music track is consistently shuffled throughout the game
Full: Each individual level has a random music track
Singularity: The entire game uses one song for overworld and one song for levels
"""
display_name = "Music Shuffle"
@@ -293,9 +322,13 @@ class MusicShuffle(Choice):
class SFXShuffle(Choice):
"""
Shuffles almost every instance of sound effect playback
Archipelago elements that play sound effects aren't randomized
None: No SFX are shuffled
Full: Each individual SFX call has a random SFX
Singularity: The entire game uses one SFX for every SFX call
"""
display_name = "Sound Effect Shuffle"
@@ -324,8 +357,11 @@ class MarioPalette(Choice):
class LevelPaletteShuffle(Choice):
"""
Whether to shuffle level palettes
Off: Do not shuffle palettes
On Legacy: Uses only the palette sets from the original game
On Curated: Uses custom, hand-crafted palette sets
"""
display_name = "Level Palette Shuffle"
@@ -338,8 +374,11 @@ class LevelPaletteShuffle(Choice):
class OverworldPaletteShuffle(Choice):
"""
Whether to shuffle overworld palettes
Off: Do not shuffle palettes
On Legacy: Uses only the palette sets from the original game
On Curated: Uses custom, hand-crafted palette sets
"""
display_name = "Overworld Palette Shuffle"
@@ -359,6 +398,52 @@ class StartingLifeCount(Range):
default = 5
smw_option_groups = [
OptionGroup("Goal Options", [
Goal,
BossesRequired,
NumberOfYoshiEggs,
PercentageOfYoshiEggs,
]),
OptionGroup("Sanity Options", [
DragonCoinChecks,
MoonChecks,
Hidden1UpChecks,
BonusBlockChecks,
Blocksanity,
]),
OptionGroup("Level Shuffling", [
LevelShuffle,
ExcludeSpecialZone,
BowserCastleDoors,
BowserCastleRooms,
BossShuffle,
SwapDonutGhostHouseExits,
]),
OptionGroup("Junk and Traps", [
JunkFillPercentage,
TrapFillPercentage,
IceTrapWeight,
StunTrapWeight,
LiteratureTrapWeight,
TimerTrapWeight,
ReverseTrapWeight,
ThwimpTrapWeight,
]),
OptionGroup("Aesthetics", [
DisplayReceivedItemPopups,
Autosave,
OverworldSpeed,
MusicShuffle,
SFXShuffle,
MarioPalette,
LevelPaletteShuffle,
OverworldPaletteShuffle,
StartingLifeCount,
]),
]
@dataclass
class SMWOptions(PerGameCommonOptions):
death_link: DeathLink

57
worlds/smw/Presets.py Normal file
View File

@@ -0,0 +1,57 @@
from typing import Dict, Any
all_random = {
"goal": "random",
"bosses_required": "random",
"max_yoshi_egg_cap": "random",
"percentage_of_yoshi_eggs": "random",
"dragon_coin_checks": "random",
"moon_checks": "random",
"hidden_1up_checks": "random",
"bonus_block_checks": "random",
"blocksanity": "random",
"bowser_castle_doors": "random",
"bowser_castle_rooms": "random",
"level_shuffle": "random",
"exclude_special_zone": "random",
"boss_shuffle": "random",
"swap_donut_gh_exits": "random",
"display_received_item_popups": "random",
"junk_fill_percentage": "random",
"trap_fill_percentage": "random",
"ice_trap_weight": "random",
"stun_trap_weight": "random",
"literature_trap_weight": "random",
"timer_trap_weight": "random",
"reverse_trap_weight": "random",
"thwimp_trap_weight": "random",
"autosave": "random",
"early_climb": "random",
"overworld_speed": "random",
"music_shuffle": "random",
"sfx_shuffle": "random",
"mario_palette": "random",
"level_palette_shuffle": "random",
"overworld_palette_shuffle": "random",
"starting_life_count": "random",
}
allsanity = {
"dragon_coin_checks": True,
"moon_checks": True,
"hidden_1up_checks": True,
"bonus_block_checks": True,
"blocksanity": True,
"level_shuffle": True,
"boss_shuffle": "full",
"music_shuffle": "full",
"sfx_shuffle": "full",
"mario_palette": "random",
"level_palette_shuffle": "on_curated",
"overworld_palette_shuffle": "on_curated",
}
smw_options_presets: Dict[str, Dict[str, Any]] = {
"All Random": all_random,
"Allsanity": allsanity,
}

View File

@@ -6,17 +6,19 @@ import settings
import threading
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
from .Items import SMWItem, ItemData, item_table, junk_table
from .Locations import SMWLocation, all_locations, setup_locations, special_zone_level_names, special_zone_dragon_coin_names, special_zone_hidden_1up_names, special_zone_blocksanity_names
from .Options import SMWOptions
from .Regions import create_regions, connect_regions
from .Levels import full_level_list, generate_level_list, location_id_to_level_id
from .Rules import set_rules
from worlds.generic.Rules import add_rule, exclusion_rules
from .Names import ItemName, LocationName
from .Client import SMWSNIClient
from worlds.AutoWorld import WebWorld, World
from worlds.generic.Rules import add_rule, exclusion_rules
from .Client import SMWSNIClient
from .Items import SMWItem, ItemData, item_table, junk_table
from .Levels import full_level_list, generate_level_list, location_id_to_level_id
from .Locations import SMWLocation, all_locations, setup_locations, special_zone_level_names, special_zone_dragon_coin_names, special_zone_hidden_1up_names, special_zone_blocksanity_names
from .Names import ItemName, LocationName
from .Options import SMWOptions, smw_option_groups
from .Presets import smw_options_presets
from .Regions import create_regions, connect_regions
from .Rom import LocalRom, patch_rom, get_base_rom_path, SMWDeltaPatch
from .Rules import set_rules
class SMWSettings(settings.Group):
@@ -40,9 +42,12 @@ class SMWWeb(WebWorld):
"setup/en",
["PoryGone"]
)
tutorials = [setup_en]
option_groups = smw_option_groups
options_presets = smw_options_presets
class SMWWorld(World):
"""

View File

@@ -8,7 +8,7 @@ from .er_rules import set_er_location_rules
from .regions import tunic_regions
from .er_scripts import create_er_regions
from .er_data import portal_mapping
from .options import TunicOptions, EntranceRando
from .options import TunicOptions, EntranceRando, tunic_option_groups
from worlds.AutoWorld import WebWorld, World
from worlds.generic import PlandoConnection
from decimal import Decimal, ROUND_HALF_UP
@@ -27,6 +27,7 @@ class TunicWeb(WebWorld):
]
theme = "grassFlowers"
game = "TUNIC"
option_groups = tunic_option_groups
class TunicItem(Item):

View File

@@ -1,30 +1,38 @@
from dataclasses import dataclass
from Options import DefaultOnToggle, Toggle, StartInventoryPool, Choice, Range, TextChoice, PerGameCommonOptions
from Options import (DefaultOnToggle, Toggle, StartInventoryPool, Choice, Range, TextChoice, PerGameCommonOptions,
OptionGroup)
class SwordProgression(DefaultOnToggle):
"""Adds four sword upgrades to the item pool that will progressively grant stronger melee weapons, including two new swords with increased range and attack power."""
"""
Adds four sword upgrades to the item pool that will progressively grant stronger melee weapons, including two new swords with increased range and attack power.
"""
internal_name = "sword_progression"
display_name = "Sword Progression"
class StartWithSword(Toggle):
"""Start with a sword in the player's inventory. Does not count towards Sword Progression."""
"""
Start with a sword in the player's inventory. Does not count towards Sword Progression.
"""
internal_name = "start_with_sword"
display_name = "Start With Sword"
class KeysBehindBosses(Toggle):
"""Places the three hexagon keys behind their respective boss fight in your world."""
"""
Places the three hexagon keys behind their respective boss fight in your world.
"""
internal_name = "keys_behind_bosses"
display_name = "Keys Behind Bosses"
class AbilityShuffling(Toggle):
"""Locks the usage of Prayer, Holy Cross*, and the Icebolt combo until the relevant pages of the manual have been found.
"""
Locks the usage of Prayer, Holy Cross*, and the Icebolt combo until the relevant pages of the manual have been found.
If playing Hexagon Quest, abilities are instead randomly unlocked after obtaining 25%, 50%, and 75% of the required Hexagon goal amount.
*Certain Holy Cross usages are still allowed, such as the free bomb codes, the seeking spell, and other player-facing codes.
* Certain Holy Cross usages are still allowed, such as the free bomb codes, the seeking spell, and other player-facing codes.
"""
internal_name = "ability_shuffling"
display_name = "Shuffle Abilities"
@@ -37,9 +45,9 @@ class LogicRules(Choice):
No Major Glitches: Sneaky Laurels zips, ice grapples through doors, shooting the west bell, and boss quick kills are included in logic.
* Ice grappling through the Ziggurat door is not in logic since you will get stuck in there without Prayer.
Unrestricted: Logic in No Major Glitches, as well as ladder storage to get to certain places early.
*Torch is given to the player at the start of the game due to the high softlock potential with various tricks. Using the torch is not required in logic.
*Using Ladder Storage to get to individual chests is not in logic to avoid tedium.
*Getting knocked out of the air by enemies during Ladder Storage to reach places is not in logic, except for in Rooted Ziggurat Lower. This is so you're not punished for playing with enemy rando on.
* Torch is given to the player at the start of the game due to the high softlock potential with various tricks. Using the torch is not required in logic.
* Using Ladder Storage to get to individual chests is not in logic to avoid tedium.
* Getting knocked out of the air by enemies during Ladder Storage to reach places is not in logic, except for in Rooted Ziggurat Lower. This is so you're not punished for playing with enemy rando on.
"""
internal_name = "logic_rules"
display_name = "Logic Rules"
@@ -52,21 +60,27 @@ class LogicRules(Choice):
class Lanternless(Toggle):
"""Choose whether you require the Lantern for dark areas.
When enabled, the Lantern is marked as Useful instead of Progression."""
"""
Choose whether you require the Lantern for dark areas.
When enabled, the Lantern is marked as Useful instead of Progression.
"""
internal_name = "lanternless"
display_name = "Lanternless"
class Maskless(Toggle):
"""Choose whether you require the Scavenger's Mask for Lower Quarry.
When enabled, the Scavenger's Mask is marked as Useful instead of Progression."""
"""
Choose whether you require the Scavenger's Mask for Lower Quarry.
When enabled, the Scavenger's Mask is marked as Useful instead of Progression.
"""
internal_name = "maskless"
display_name = "Maskless"
class FoolTraps(Choice):
"""Replaces low-to-medium value money rewards in the item pool with fool traps, which cause random negative effects to the player."""
"""
Replaces low-to-medium value money rewards in the item pool with fool traps, which cause random negative effects to the player.
"""
internal_name = "fool_traps"
display_name = "Fool Traps"
option_off = 0
@@ -77,13 +91,17 @@ class FoolTraps(Choice):
class HexagonQuest(Toggle):
"""An alternate goal that shuffles Gold "Questagon" items into the item pool and allows the game to be completed after collecting the required number of them."""
"""
An alternate goal that shuffles Gold "Questagon" items into the item pool and allows the game to be completed after collecting the required number of them.
"""
internal_name = "hexagon_quest"
display_name = "Hexagon Quest"
class HexagonGoal(Range):
"""How many Gold Questagons are required to complete the game on Hexagon Quest."""
"""
How many Gold Questagons are required to complete the game on Hexagon Quest.
"""
internal_name = "hexagon_goal"
display_name = "Gold Hexagons Required"
range_start = 15
@@ -92,7 +110,9 @@ class HexagonGoal(Range):
class ExtraHexagonPercentage(Range):
"""How many extra Gold Questagons are shuffled into the item pool, taken as a percentage of the goal amount."""
"""
How many extra Gold Questagons are shuffled into the item pool, taken as a percentage of the goal amount.
"""
internal_name = "extra_hexagon_percentage"
display_name = "Percentage of Extra Gold Hexagons"
range_start = 0
@@ -118,16 +138,20 @@ class EntranceRando(TextChoice):
class FixedShop(Toggle):
"""Forces the Windmill entrance to lead to a shop, and removes the remaining shops from the pool.
"""
Forces the Windmill entrance to lead to a shop, and removes the remaining shops from the pool.
Adds another entrance in Rooted Ziggurat Lower to keep an even number of entrances.
Has no effect if Entrance Rando is not enabled."""
Has no effect if Entrance Rando is not enabled.
"""
internal_name = "fixed_shop"
display_name = "Fewer Shops in Entrance Rando"
class LaurelsLocation(Choice):
"""Force the Hero's Laurels to be placed at a location in your world.
For if you want to avoid or specify early or late Laurels."""
"""
Force the Hero's Laurels to be placed at a location in your world.
For if you want to avoid or specify early or late Laurels.
"""
internal_name = "laurels_location"
display_name = "Laurels Location"
option_anywhere = 0
@@ -138,9 +162,12 @@ class LaurelsLocation(Choice):
class ShuffleLadders(Toggle):
"""Turns several ladders in the game into items that must be found before they can be climbed on.
"""
Turns several ladders in the game into items that must be found before they can be climbed on.
Adds more layers of progression to the game by blocking access to many areas early on.
"Ladders were a mistake." —Andrew Shouldice"""
"Ladders were a mistake."
—Andrew Shouldice
"""
internal_name = "shuffle_ladders"
display_name = "Shuffle Ladders"
@@ -163,3 +190,12 @@ class TunicOptions(PerGameCommonOptions):
lanternless: Lanternless
maskless: Maskless
laurels_location: LaurelsLocation
tunic_option_groups = [
OptionGroup("Logic Options", [
LogicRules,
Lanternless,
Maskless,
])
]

View File

@@ -16,7 +16,7 @@ from .data.item_definition_classes import DoorItemDefinition, ItemData
from .data.utils import get_audio_logs
from .hints import CompactItemData, create_all_hints, make_compact_hint_data, make_laser_hints
from .locations import WitnessPlayerLocations, static_witness_locations
from .options import TheWitnessOptions
from .options import TheWitnessOptions, witness_option_groups
from .player_items import WitnessItem, WitnessPlayerItems
from .player_logic import WitnessPlayerLogic
from .presets import witness_option_presets
@@ -36,6 +36,7 @@ class WitnessWebWorld(WebWorld):
)]
options_presets = witness_option_presets
option_groups = witness_option_groups
class WitnessWorld(World):

View File

@@ -2,7 +2,7 @@ from dataclasses import dataclass
from schema import And, Schema
from Options import Choice, DefaultOnToggle, OptionDict, PerGameCommonOptions, Range, Toggle
from Options import Choice, DefaultOnToggle, OptionDict, OptionGroup, PerGameCommonOptions, Range, Toggle
from .data import static_logic as static_witness_logic
from .data.item_definition_classes import ItemCategory, WeightedItemDefinition
@@ -61,9 +61,9 @@ class ShuffleLasers(Choice):
class ShuffleDoors(Choice):
"""
If on, opening doors, moving bridges etc. will require a "key".
If set to "panels", the panel on the door will be locked until receiving its corresponding key.
If set to "doors", the door will open immediately upon receiving its key. Door panels are added as location checks.
"Mixed" includes all doors from "doors", and all control panels (bridges, elevators etc.) from "panels".
- Panels: The panel on the door will be locked until receiving its corresponding key.
- Doors: The door will open immediately upon receiving its key. Door panels are added as location checks.
- Mixed: Includes all doors from "doors", and all control panels (bridges, elevators etc.) from "panels".
"""
display_name = "Shuffle Doors"
option_off = 0
@@ -74,8 +74,10 @@ class ShuffleDoors(Choice):
class DoorGroupings(Choice):
"""
If set to "none", there will be one key for each door, potentially resulting in upwards of 120 keys being added to the item pool.
If set to "regional", all doors in the same general region will open at once with a single key, reducing the amount of door items and complexity.
Controls how door items are grouped.
- None: There will be one key for each door, potentially resulting in upwards of 120 keys being added to the item pool.
- Regional: - All doors in the same general region will open at once with a single key, reducing the amount of door items and complexity.
"""
display_name = "Door Groupings"
option_off = 0
@@ -108,8 +110,8 @@ class ShuffleVaultBoxes(Toggle):
class ShuffleEnvironmentalPuzzles(Choice):
"""
Adds Environmental/Obelisk Puzzles into the location pool.
If set to "individual", every Environmental Puzzle sends an item.
If set to "Obelisk Sides", completing every puzzle on one side of an Obelisk sends an item.
- Individual: Every Environmental Puzzle sends an item.
- Obelisk Sides: Completing every puzzle on one side of an Obelisk sends an item.
Note: In Obelisk Sides, any EPs excluded through another option will be pre-completed on their Obelisk.
"""
@@ -129,9 +131,9 @@ class ShuffleDog(Toggle):
class EnvironmentalPuzzlesDifficulty(Choice):
"""
When "Shuffle Environmental Puzzles" is on, this setting governs which EPs are eligible for the location pool.
If set to "eclipse", every EP in the game is eligible, including the 1-hour-long "Theater Eclipse EP".
If set to "tedious", Theater Eclipse EP is excluded from the location pool.
If set to "normal", several other difficult or long EPs are excluded as well.
- Eclipse: Every EP in the game is eligible, including the 1-hour-long "Theater Eclipse EP".
- Tedious Theater Eclipse EP is excluded from the location pool.
- Normal: several other difficult or long EPs are excluded as well.
"""
display_name = "Environmental Puzzles Difficulty"
option_normal = 0
@@ -159,10 +161,10 @@ class ShufflePostgame(Toggle):
class VictoryCondition(Choice):
"""
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).
- 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.
@@ -332,3 +334,45 @@ class TheWitnessOptions(PerGameCommonOptions):
laser_hints: LaserHints
death_link: DeathLink
death_link_amnesty: DeathLinkAmnesty
witness_option_groups = [
OptionGroup("Puzzles & Goal", [
PuzzleRandomization,
VictoryCondition,
MountainLasers,
ChallengeLasers,
]),
OptionGroup("Locations", [
ShuffleDiscardedPanels,
ShuffleVaultBoxes,
ShuffleEnvironmentalPuzzles,
EnvironmentalPuzzlesDifficulty,
ShufflePostgame,
DisableNonRandomizedPuzzles,
]),
OptionGroup("Progression Items", [
ShuffleSymbols,
ShuffleDoors,
DoorGroupings,
ShuffleLasers,
ShuffleBoat,
ObeliskKeys,
]),
OptionGroup("Filler Items", [
PuzzleSkipAmount,
TrapPercentage,
TrapWeights
]),
OptionGroup("Hints", [
HintAmount,
AreaHintPercentage,
LaserHints
]),
OptionGroup("Misc", [
EarlyCaves,
ElevatorsComeToYou,
DeathLink,
DeathLinkAmnesty,
])
]