Merge branch 'main' into player-tracker

This commit is contained in:
Chris Wilson
2022-09-24 18:35:59 -04:00
144 changed files with 4851 additions and 3263 deletions

View File

@@ -27,7 +27,8 @@ class AutoWorldRegister(type):
# build rest
dct["item_names"] = frozenset(dct["item_name_to_id"])
dct["item_name_groups"] = dct.get("item_name_groups", {})
dct["item_name_groups"] = {group_name: frozenset(group_set) for group_name, group_set
in dct.get("item_name_groups", {}).items()}
dct["item_name_groups"]["Everything"] = dct["item_names"]
dct["location_names"] = frozenset(dct["location_name_to_id"])
dct["all_item_and_group_names"] = frozenset(dct["item_names"] | set(dct.get("item_name_groups", {})))
@@ -97,22 +98,22 @@ def call_stage(world: "MultiWorld", method_name: str, *args: Any) -> None:
class WebWorld:
"""Webhost integration"""
# display a settings page. Can be a link to an out-of-ap settings tool too.
settings_page: Union[bool, str] = True
"""display a settings page. Can be a link to a specific page or external tool."""
# docs folder will be scanned for game info pages using this list in the format '{language}_{game_name}.md'
game_info_languages: List[str] = ['en']
"""docs folder will be scanned for game info pages using this list in the format '{language}_{game_name}.md'"""
# docs folder will also be scanned for tutorial guides given the relevant information in this list. Each Tutorial
# class is to be used for one guide.
tutorials: List["Tutorial"]
"""docs folder will also be scanned for tutorial guides. Each Tutorial class is to be used for one guide."""
# Choose a theme for your /game/* pages
# Available: dirt, grass, grassFlowers, ice, jungle, ocean, partyTime
theme: str = "grass"
theme = "grass"
"""Choose a theme for you /game/* pages.
Available: dirt, grass, grassFlowers, ice, jungle, ocean, partyTime, stone"""
# display a link to a bug report page, most likely a link to a GitHub issue page.
bug_report_page: Optional[str]
"""display a link to a bug report page, most likely a link to a GitHub issue page."""
if TYPE_CHECKING:
from WebHostLib.tracker import PlayerTracker
@@ -230,10 +231,8 @@ class World(metaclass=AutoWorldRegister):
@classmethod
def fill_hook(cls,
progitempool: List["Item"],
nonexcludeditempool: List["Item"],
localrestitempool: Dict[int, List["Item"]],
nonlocalrestitempool: Dict[int, List["Item"]],
restitempool: List["Item"],
usefulitempool: List["Item"],
filleritempool: List["Item"],
fill_locations: List["Location"]) -> None:
"""Special method that gets called as part of distribute_items_restrictive (main fill).
This gets called once per present world type."""
@@ -251,6 +250,11 @@ class World(metaclass=AutoWorldRegister):
"""Fill in the slot_data field in the Connected network package."""
return {}
def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]):
"""Fill in additional entrance information text into locations, which is displayed when hinted.
structure is {player_id: {location_id: text}} You will need to insert your own player_id."""
pass
def modify_multidata(self, multidata: Dict[str, Any]) -> None: # TODO: TypedDict for multidata?
"""For deeper modification of server multidata."""
pass

View File

@@ -27,7 +27,8 @@ class WorldSource(typing.NamedTuple):
world_sources: typing.List[WorldSource] = []
file: os.DirEntry # for me (Berserker) at least, PyCharm doesn't seem to infer the type correctly
for file in os.scandir(folder):
if not file.name.startswith("_"): # prevent explicitly loading __pycache__ and allow _* names for non-world folders
# prevent loading of __pycache__ and allow _* for non-world folders, disable files/folders starting with "."
if not file.name.startswith(("_", ".")):
if file.is_dir():
world_sources.append(WorldSource(file.name))
elif file.is_file() and file.name.endswith(".apworld"):

View File

@@ -1,8 +1,9 @@
import logging
from typing import Optional
from typing import Optional, Union, List, Tuple, Callable, Dict
from BaseClasses import Boss
from Fill import FillError
from .Options import Bosses
def BossFactory(boss: str, player: int) -> Optional[Boss]:
@@ -12,7 +13,7 @@ def BossFactory(boss: str, player: int) -> Optional[Boss]:
raise Exception('Unknown Boss: %s', boss)
def ArmosKnightsDefeatRule(state, player: int):
def ArmosKnightsDefeatRule(state, player: int) -> bool:
# Magic amounts are probably a bit overkill
return (
state.has_melee_weapon(player) or
@@ -25,7 +26,7 @@ def ArmosKnightsDefeatRule(state, player: int):
state.has('Red Boomerang', player))
def LanmolasDefeatRule(state, player: int):
def LanmolasDefeatRule(state, player: int) -> bool:
return (
state.has_melee_weapon(player) or
state.has('Fire Rod', player) or
@@ -35,16 +36,16 @@ def LanmolasDefeatRule(state, player: int):
state.can_shoot_arrows(player))
def MoldormDefeatRule(state, player: int):
def MoldormDefeatRule(state, player: int) -> bool:
return state.has_melee_weapon(player)
def HelmasaurKingDefeatRule(state, player: int):
def HelmasaurKingDefeatRule(state, player: int) -> bool:
# TODO: technically possible with the hammer
return state.has_sword(player) or state.can_shoot_arrows(player)
def ArrghusDefeatRule(state, player: int):
def ArrghusDefeatRule(state, player: int) -> bool:
if not state.has('Hookshot', player):
return False
# TODO: ideally we would have a check for bow and silvers, which combined with the
@@ -58,7 +59,7 @@ def ArrghusDefeatRule(state, player: int):
(state.has('Ice Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 16))))
def MothulaDefeatRule(state, player: int):
def MothulaDefeatRule(state, player: int) -> bool:
return (
state.has_melee_weapon(player) or
(state.has('Fire Rod', player) and state.can_extend_magic(player, 10)) or
@@ -70,11 +71,11 @@ def MothulaDefeatRule(state, player: int):
)
def BlindDefeatRule(state, player: int):
def BlindDefeatRule(state, player: int) -> bool:
return state.has_melee_weapon(player) or state.has('Cane of Somaria', player) or state.has('Cane of Byrna', player)
def KholdstareDefeatRule(state, player: int):
def KholdstareDefeatRule(state, player: int) -> bool:
return (
(
state.has('Fire Rod', player) or
@@ -96,11 +97,11 @@ def KholdstareDefeatRule(state, player: int):
)
def VitreousDefeatRule(state, player: int):
def VitreousDefeatRule(state, player: int) -> bool:
return state.can_shoot_arrows(player) or state.has_melee_weapon(player)
def TrinexxDefeatRule(state, player: int):
def TrinexxDefeatRule(state, player: int) -> bool:
if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)):
return False
return state.has('Hammer', player) or state.has('Tempered Sword', player) or state.has('Golden Sword', player) or \
@@ -108,11 +109,11 @@ def TrinexxDefeatRule(state, player: int):
(state.has_sword(player) and state.can_extend_magic(player, 32))
def AgahnimDefeatRule(state, player: int):
def AgahnimDefeatRule(state, player: int) -> bool:
return state.has_sword(player) or state.has('Hammer', player) or state.has('Bug Catching Net', player)
def GanonDefeatRule(state, player: int):
def GanonDefeatRule(state, player: int) -> bool:
if state.world.swordless[player]:
return state.has('Hammer', player) and \
state.has_fire_source(player) and \
@@ -132,7 +133,7 @@ def GanonDefeatRule(state, player: int):
return common and state.has('Silver Bow', player) and state.can_shoot_arrows(player)
boss_table = {
boss_table: Dict[str, Tuple[str, Optional[Callable]]] = {
'Armos Knights': ('Armos', ArmosKnightsDefeatRule),
'Lanmolas': ('Lanmola', LanmolasDefeatRule),
'Moldorm': ('Moldorm', MoldormDefeatRule),
@@ -147,7 +148,7 @@ boss_table = {
'Agahnim2': ('Agahnim2', AgahnimDefeatRule)
}
boss_location_table = [
boss_location_table: List[Tuple[str, str]] = [
('Ganons Tower', 'top'),
('Tower of Hera', None),
('Skull Woods', None),
@@ -164,6 +165,34 @@ boss_location_table = [
]
def place_plando_bosses(bosses: List[str], world, player: int) -> Tuple[List[str], List[Tuple[str, str]]]:
# Most to least restrictive order
boss_locations = boss_location_table.copy()
world.random.shuffle(boss_locations)
boss_locations.sort(key=lambda location: -int(restrictive_boss_locations[location]))
already_placed_bosses: List[str] = []
for boss in bosses:
if "-" in boss: # handle plando locations
loc, boss = boss.split("-")
boss = boss.title()
level: str = None
if loc.split(" ")[-1] in {"top", "middle", "bottom"}:
# split off level
loc = loc.split(" ")
level = loc[-1]
loc = " ".join(loc[:-1])
loc = loc.title().replace("Of", "of")
place_boss(world, player, boss, loc, level)
already_placed_bosses.append(boss)
boss_locations.remove((loc, level))
else: # boss chosen with no specified locations
boss = boss.title()
boss_locations, already_placed_bosses = place_where_possible(world, player, boss, boss_locations)
return already_placed_bosses, boss_locations
def can_place_boss(boss: str, dungeon_name: str, level: Optional[str] = None) -> bool:
# blacklist approach
if boss in {"Agahnim", "Agahnim2", "Ganon"}:
@@ -187,62 +216,50 @@ def can_place_boss(boss: str, dungeon_name: str, level: Optional[str] = None) ->
return True
restrictive_boss_locations = {}
restrictive_boss_locations: Dict[Tuple[str, str], bool] = {}
for location in boss_location_table:
restrictive_boss_locations[location] = not all(can_place_boss(boss, *location)
for boss in boss_table if not boss.startswith("Agahnim"))
def place_boss(world, player: int, boss: str, location: str, level: Optional[str]):
def place_boss(world, player: int, boss: str, location: str, level: Optional[str]) -> None:
if location == 'Ganons Tower' and world.mode[player] == 'inverted':
location = 'Inverted Ganons Tower'
logging.debug('Placing boss %s at %s', boss, location + (' (' + level + ')' if level else ''))
world.get_dungeon(location, player).bosses[level] = BossFactory(boss, player)
def format_boss_location(location, level):
def format_boss_location(location: str, level: str) -> str:
return location + (' (' + level + ')' if level else '')
def place_bosses(world, player: int):
if world.boss_shuffle[player] == 'none':
def place_bosses(world, player: int) -> None:
# will either be an int or a lower case string with ';' between options
boss_shuffle: Union[str, int] = world.boss_shuffle[player].value
already_placed_bosses: List[str] = []
remaining_locations: List[Tuple[str, str]] = []
# handle plando
if isinstance(boss_shuffle, str):
# figure out our remaining mode, convert it to an int and remove it from plando_args
options = boss_shuffle.split(";")
boss_shuffle = Bosses.options[options.pop()]
# place our plando bosses
already_placed_bosses, remaining_locations = place_plando_bosses(options, world, player)
if boss_shuffle == Bosses.option_none: # vanilla boss locations
return
# Most to least restrictive order
boss_locations = boss_location_table.copy()
world.random.shuffle(boss_locations)
boss_locations.sort(key= lambda location: -int(restrictive_boss_locations[location]))
if not remaining_locations and not already_placed_bosses:
remaining_locations = boss_location_table.copy()
world.random.shuffle(remaining_locations)
remaining_locations.sort(key=lambda location: -int(restrictive_boss_locations[location]))
all_bosses = sorted(boss_table.keys()) # sorted to be deterministic on older pythons
placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]
shuffle_mode = world.boss_shuffle[player]
already_placed_bosses = []
if ";" in shuffle_mode:
bosses = shuffle_mode.split(";")
shuffle_mode = bosses.pop()
for boss in bosses:
if "-" in boss:
loc, boss = boss.split("-")
boss = boss.title()
level = None
if loc.split(" ")[-1] in {"top", "middle", "bottom"}:
# split off level
loc = loc.split(" ")
level = loc[-1]
loc = " ".join(loc[:-1])
loc = loc.title().replace("Of", "of")
if can_place_boss(boss, loc, level) and (loc, level) in boss_locations:
place_boss(world, player, boss, loc, level)
already_placed_bosses.append(boss)
boss_locations.remove((loc, level))
else:
raise Exception(f"Cannot place {boss} at {format_boss_location(loc, level)} for player {player}.")
else:
boss = boss.title()
boss_locations, already_placed_bosses = place_where_possible(world, player, boss, boss_locations)
if shuffle_mode == "none":
return # vanilla bosses come pre-placed
if shuffle_mode in ["basic", "full"]:
if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled
if boss_shuffle == Bosses.option_basic or boss_shuffle == Bosses.option_full:
if boss_shuffle == Bosses.option_basic: # vanilla bosses shuffled
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
else: # all bosses present, the three duplicates chosen at random
bosses = placeable_bosses + world.random.sample(placeable_bosses, 3)
@@ -258,7 +275,7 @@ def place_bosses(world, player: int):
logging.debug('Bosses chosen %s', bosses)
world.random.shuffle(bosses)
for loc, level in boss_locations:
for loc, level in remaining_locations:
for _ in range(len(bosses)):
boss = bosses.pop()
if can_place_boss(boss, loc, level):
@@ -272,8 +289,8 @@ def place_bosses(world, player: int):
place_boss(world, player, boss, loc, level)
elif shuffle_mode == "chaos": # all bosses chosen at random
for loc, level in boss_locations:
elif boss_shuffle == Bosses.option_chaos: # all bosses chosen at random
for loc, level in remaining_locations:
try:
boss = world.random.choice(
[b for b in placeable_bosses if can_place_boss(b, loc, level)])
@@ -282,9 +299,9 @@ def place_bosses(world, player: int):
else:
place_boss(world, player, boss, loc, level)
elif shuffle_mode == "singularity":
elif boss_shuffle == Bosses.option_singularity:
primary_boss = world.random.choice(placeable_bosses)
remaining_boss_locations, _ = place_where_possible(world, player, primary_boss, boss_locations)
remaining_boss_locations, _ = place_where_possible(world, player, primary_boss, remaining_locations)
if remaining_boss_locations:
# pick a boss to go into the remaining locations
remaining_boss = world.random.choice([boss for boss in placeable_bosses if all(
@@ -293,12 +310,12 @@ def place_bosses(world, player: int):
if remaining_boss_locations:
raise Exception("Unfilled boss locations!")
else:
raise FillError(f"Could not find boss shuffle mode {shuffle_mode}")
raise FillError(f"Could not find boss shuffle mode {boss_shuffle}")
def place_where_possible(world, player: int, boss: str, boss_locations):
remainder = []
placed_bosses = []
def place_where_possible(world, player: int, boss: str, boss_locations) -> Tuple[List[Tuple[str, str]], List[str]]:
remainder: List[Tuple[str, str]] = []
placed_bosses: List[str] = []
for loc, level in boss_locations:
# place that boss where it can go
if can_place_boss(boss, loc, level):

View File

@@ -212,9 +212,7 @@ def parse_arguments(argv, no_defaults=False):
Alternatively, can be a ALttP Rom patched with a Link
sprite that will be extracted.
''')
parser.add_argument('--gui', help='Launch the GUI', action='store_true')
parser.add_argument('--enemizercli', default=defval('EnemizerCLI/EnemizerCLI.Core'))
parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos',
"singularity"])

View File

@@ -480,7 +480,7 @@ def set_up_take_anys(world, player):
old_man_take_any.shop = TakeAny(old_man_take_any, 0x0112, 0xE2, True, True, total_shop_slots)
world.shops.append(old_man_take_any.shop)
swords = [item for item in world.itempool if item.type == 'Sword' and item.player == player]
swords = [item for item in world.itempool if item.player == player and item.type == 'Sword']
if swords:
sword = world.random.choice(swords)
world.itempool.remove(sword)

View File

@@ -1,7 +1,7 @@
import typing
from BaseClasses import MultiWorld
from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink
from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, TextChoice
class Logic(Choice):
@@ -39,8 +39,6 @@ class OpenPyramid(Choice):
option_auto = 3
default = option_goal
alias_true = option_open
alias_false = option_closed
alias_yes = option_open
alias_no = option_closed
@@ -140,13 +138,143 @@ class WorldState(Choice):
option_inverted = 2
class Bosses(Choice):
option_vanilla = 0
option_simple = 1
class Bosses(TextChoice):
"""Shuffles bosses around to different locations.
Basic will shuffle all bosses except Ganon and Agahnim anywhere they can be placed.
Full chooses 3 bosses at random to be placed twice instead of Lanmolas, Moldorm, and Helmasaur.
Chaos allows any boss to appear any number of times.
Singularity places a single boss in as many places as possible, and a second boss in any remaining locations.
Supports plando placement. Formatting here: https://archipelago.gg/tutorial/A%20Link%20to%20the%20Past/plando/en"""
display_name = "Boss Shuffle"
option_none = 0
option_basic = 1
option_full = 2
option_chaos = 3
option_singularity = 4
bosses: set = {
"Armos Knights",
"Lanmolas",
"Moldorm",
"Helmasaur King",
"Arrghus",
"Mothula",
"Blind",
"Kholdstare",
"Vitreous",
"Trinexx",
}
locations: set = {
"Ganons Tower Top",
"Tower of Hera",
"Skull Woods",
"Ganons Tower Middle",
"Eastern Palace",
"Desert Palace",
"Palace of Darkness",
"Swamp Palace",
"Thieves Town",
"Ice Palace",
"Misery Mire",
"Turtle Rock",
"Ganons Tower Bottom"
}
def __init__(self, value: typing.Union[str, int]):
assert isinstance(value, str) or isinstance(value, int), \
f"{value} is not a valid option for {self.__class__.__name__}"
self.value = value
@classmethod
def from_text(cls, text: str):
import random
# set all of our text to lower case for name checking
text = text.lower()
cls.bosses = {boss_name.lower() for boss_name in cls.bosses}
cls.locations = {boss_location.lower() for boss_location in cls.locations}
if text == "random":
return cls(random.choice(list(cls.options.values())))
for option_name, value in cls.options.items():
if option_name == text:
return cls(value)
options = text.split(";")
# since plando exists in the option verify the plando values given are valid
cls.validate_plando_bosses(options)
# find out what type of boss shuffle we should use for placing bosses after plando
# and add as a string to look nice in the spoiler
if "random" in options:
shuffle = random.choice(list(cls.options))
options.remove("random")
options = ";".join(options) + ";" + shuffle
boss_class = cls(options)
else:
for option in options:
if option in cls.options:
boss_class = cls(";".join(options))
break
else:
if len(options) == 1:
if cls.valid_boss_name(options[0]):
options = options[0] + ";singularity"
boss_class = cls(options)
else:
options = options[0] + ";none"
boss_class = cls(options)
else:
options = ";".join(options) + ";none"
boss_class = cls(options)
return boss_class
@classmethod
def validate_plando_bosses(cls, options: typing.List[str]) -> None:
from .Bosses import can_place_boss, format_boss_location
for option in options:
if option == "random" or option in cls.options:
if option != options[-1]:
raise ValueError(f"{option} option must be at the end of the boss_shuffle options!")
continue
if "-" in option:
location, boss = option.split("-")
level = ''
if not cls.valid_boss_name(boss):
raise ValueError(f"{boss} is not a valid boss name for location {location}.")
if not cls.valid_location_name(location):
raise ValueError(f"{location} is not a valid boss location name.")
if location.split(" ")[-1] in ("top", "middle", "bottom"):
location = location.split(" ")
level = location[-1]
location = " ".join(location[:-1])
location = location.title().replace("Of", "of")
if not can_place_boss(boss.title(), location, level):
raise ValueError(f"{format_boss_location(location, level)} "
f"is not a valid location for {boss.title()}.")
else:
if not cls.valid_boss_name(option):
raise ValueError(f"{option} is not a valid boss name.")
@classmethod
def valid_boss_name(cls, value: str) -> bool:
return value.lower() in cls.bosses
@classmethod
def valid_location_name(cls, value: str) -> bool:
return value in cls.locations
def verify(self, world, player_name: str, plando_options) -> None:
if isinstance(self.value, int):
return
from Generate import PlandoSettings
if not(PlandoSettings.bosses & plando_options):
import logging
# plando is disabled but plando options were given so pull the option and change it to an int
option = self.value.split(";")[-1]
self.value = self.options[option]
logging.warning(f"The plando bosses module is turned off, so {self.name_lookup[self.value].title()} "
f"boss shuffle will be used for player {player_name}.")
class Enemies(Choice):
option_vanilla = 0
@@ -159,8 +287,6 @@ class Progressive(Choice):
option_off = 0
option_grouped_random = 1
option_on = 2
alias_false = 0
alias_true = 2
default = 2
def want_progressives(self, random):
@@ -168,8 +294,8 @@ class Progressive(Choice):
class Swordless(Toggle):
"""No swords. Curtains in Skull Woods and Agahnim\'s
Tower are removed, Agahnim\'s Tower barrier can be
"""No swords. Curtains in Skull Woods and Agahnim's
Tower are removed, Agahnim's Tower barrier can be
destroyed with hammer. Misery Mire and Turtle Rock
can be opened without a sword. Hammer damages Ganon.
Ether and Bombos Tablet can be activated with Hammer
@@ -202,8 +328,6 @@ class Hints(Choice):
option_on = 2
option_full = 3
default = 2
alias_false = 0
alias_true = 2
class Scams(Choice):
@@ -213,7 +337,6 @@ class Scams(Choice):
option_king_zora = 1
option_bottle_merchant = 2
option_all = 3
alias_false = 0
@property
def gives_king_zora_hint(self):
@@ -282,8 +405,8 @@ class ShieldPalette(Palette):
display_name = "Shield Palette"
class LinkPalette(Palette):
display_name = "Link Palette"
# class LinkPalette(Palette):
# display_name = "Link Palette"
class HeartBeep(Choice):
@@ -293,7 +416,6 @@ class HeartBeep(Choice):
option_half = 2
option_quarter = 3
option_off = 4
alias_false = 4
class HeartColor(Choice):
@@ -375,6 +497,7 @@ alttp_options: typing.Dict[str, type(Option)] = {
"hints": Hints,
"scams": Scams,
"restrict_dungeon_item_on_boss": RestrictBossItem,
"boss_shuffle": Bosses,
"pot_shuffle": PotShuffle,
"enemy_shuffle": EnemyShuffle,
"killable_thieves": KillableThieves,
@@ -387,7 +510,7 @@ alttp_options: typing.Dict[str, type(Option)] = {
"hud_palettes": HUDPalette,
"sword_palettes": SwordPalette,
"shield_palettes": ShieldPalette,
"link_palettes": LinkPalette,
# "link_palettes": LinkPalette,
"heartbeep": HeartBeep,
"heartcolor": HeartColor,
"quickswap": QuickSwap,

View File

@@ -4,6 +4,10 @@ import typing
from BaseClasses import Region, Entrance, RegionType
def is_main_entrance(entrance: Entrance) -> bool:
return entrance.parent_region.type in {RegionType.DarkWorld, RegionType.LightWorld, RegionType.Generic}
def create_regions(world, player):
world.regions += [

View File

@@ -34,7 +34,7 @@ from worlds.alttp.Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts
DeathMountain_texts, \
LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \
SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen
from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen, parse_yaml
from worlds.alttp.Items import ItemFactory, item_table, item_name_groups, progression_items
from worlds.alttp.EntranceShuffle import door_addresses
from worlds.alttp.Options import smallkey_shuffle
@@ -551,18 +551,22 @@ class Sprite():
Sprite.base_data = Sprite.sprite + Sprite.palette + Sprite.glove_palette
def from_ap_sprite(self, filedata):
filedata = filedata.decode("utf-8-sig")
import yaml
obj = yaml.safe_load(filedata)
if obj["min_format_version"] > 1:
raise Exception("Sprite file requires an updated reader.")
self.author_name = obj["author"]
self.name = obj["name"]
if obj["data"]: # skip patching for vanilla content
data = bsdiff4.patch(Sprite.base_data, obj["data"])
self.sprite = data[:self.sprite_size]
self.palette = data[self.sprite_size:self.palette_size]
self.glove_palette = data[self.sprite_size + self.palette_size:]
# noinspection PyBroadException
try:
obj = parse_yaml(filedata.decode("utf-8-sig"))
if obj["min_format_version"] > 1:
raise Exception("Sprite file requires an updated reader.")
self.author_name = obj["author"]
self.name = obj["name"]
if obj["data"]: # skip patching for vanilla content
data = bsdiff4.patch(Sprite.base_data, obj["data"])
self.sprite = data[:self.sprite_size]
self.palette = data[self.sprite_size:self.palette_size]
self.glove_palette = data[self.sprite_size + self.palette_size:]
except Exception:
logger = logging.getLogger("apsprite")
logger.exception("Error parsing apsprite file")
self.valid = False
@property
def author_game_display(self) -> str:
@@ -659,7 +663,7 @@ class Sprite():
@staticmethod
def parse_zspr(filedata, expected_kind):
logger = logging.getLogger('ZSPR')
logger = logging.getLogger("ZSPR")
headerstr = "<4xBHHIHIHH6x"
headersize = struct.calcsize(headerstr)
if len(filedata) < headersize:
@@ -667,7 +671,7 @@ class Sprite():
version, csum, icsum, sprite_offset, sprite_size, palette_offset, palette_size, kind = struct.unpack_from(
headerstr, filedata)
if version not in [1]:
logger.error('Error parsing ZSPR file: Version %g not supported', version)
logger.error("Error parsing ZSPR file: Version %g not supported", version)
return None
if kind != expected_kind:
return None
@@ -676,36 +680,42 @@ class Sprite():
stream.seek(headersize)
def read_utf16le(stream):
"Decodes a null-terminated UTF-16_LE string of unknown size from a stream"
"""Decodes a null-terminated UTF-16_LE string of unknown size from a stream"""
raw = bytearray()
while True:
char = stream.read(2)
if char in [b'', b'\x00\x00']:
if char in [b"", b"\x00\x00"]:
break
raw += char
return raw.decode('utf-16_le')
return raw.decode("utf-16_le")
sprite_name = read_utf16le(stream)
author_name = read_utf16le(stream)
author_credits_name = stream.read().split(b"\x00", 1)[0].decode()
# noinspection PyBroadException
try:
sprite_name = read_utf16le(stream)
author_name = read_utf16le(stream)
author_credits_name = stream.read().split(b"\x00", 1)[0].decode()
# Ignoring the Author Rom name for the time being.
# Ignoring the Author Rom name for the time being.
real_csum = sum(filedata) % 0x10000
if real_csum != csum or real_csum ^ 0xFFFF != icsum:
logger.warning('ZSPR file has incorrect checksum. It may be corrupted.')
real_csum = sum(filedata) % 0x10000
if real_csum != csum or real_csum ^ 0xFFFF != icsum:
logger.warning("ZSPR file has incorrect checksum. It may be corrupted.")
sprite = filedata[sprite_offset:sprite_offset + sprite_size]
palette = filedata[palette_offset:palette_offset + palette_size]
sprite = filedata[sprite_offset:sprite_offset + sprite_size]
palette = filedata[palette_offset:palette_offset + palette_size]
if len(sprite) != sprite_size or len(palette) != palette_size:
logger.error('Error parsing ZSPR file: Unexpected end of file')
if len(sprite) != sprite_size or len(palette) != palette_size:
logger.error("Error parsing ZSPR file: Unexpected end of file")
return None
return sprite, palette, sprite_name, author_name, author_credits_name
except Exception:
logger.exception("Error parsing ZSPR file")
return None
return (sprite, palette, sprite_name, author_name, author_credits_name)
def decode_palette(self):
"Returns the palettes as an array of arrays of 15 colors"
"""Returns the palettes as an array of arrays of 15 colors"""
def array_chunk(arr, size):
return list(zip(*[iter(arr)] * size))

View File

@@ -4,6 +4,7 @@ import random
import threading
import typing
import Utils
from BaseClasses import Item, CollectionState, Tutorial
from .Dungeons import create_dungeons
from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
@@ -11,7 +12,8 @@ from .InvertedRegions import create_inverted_regions, mark_dark_world_regions
from .ItemPool import generate_itempool, difficulties
from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem
from .Options import alttp_options, smallkey_shuffle
from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions
from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions, lookup_vanilla_location_to_entrance, \
is_main_entrance
from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enemizer, apply_rom_settings, \
get_hash_string, get_base_rom_path, LttPDeltaPatch
from .Rules import set_rules
@@ -23,6 +25,7 @@ lttp_logger = logging.getLogger("A Link to the Past")
extras_list = sum(difficulties['normal'].extras[0:5], [])
class ALTTPWeb(WebWorld):
setup_en = Tutorial(
"Multiworld Setup Tutorial",
@@ -362,6 +365,10 @@ class ALTTPWorld(World):
create_items = generate_itempool
enemizer_path: str = Utils.get_options()["generator"]["enemizer_path"] \
if os.path.isabs(Utils.get_options()["generator"]["enemizer_path"]) \
else Utils.local_path(Utils.get_options()["generator"]["enemizer_path"])
def __init__(self, *args, **kwargs):
self.dungeon_local_item_names = set()
self.dungeon_specific_item_names = set()
@@ -376,12 +383,12 @@ class ALTTPWorld(World):
raise FileNotFoundError(rom_file)
def generate_early(self):
if self.use_enemizer():
check_enemizer(self.enemizer_path)
player = self.player
world = self.world
if self.use_enemizer():
check_enemizer(world.enemizer)
# system for sharing ER layouts
self.er_seed = str(world.random.randint(0, 2 ** 64))
@@ -570,7 +577,7 @@ class ALTTPWorld(World):
def use_enemizer(self):
world = self.world
player = self.player
return (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player]
return (world.boss_shuffle[player] or world.enemy_shuffle[player]
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
or world.pot_shuffle[player] or world.bush_shuffle[player]
or world.killable_thieves[player])
@@ -586,7 +593,7 @@ class ALTTPWorld(World):
patch_rom(world, rom, player, use_enemizer)
if use_enemizer:
patch_enemizer(world, player, rom, world.enemizer, output_directory)
patch_enemizer(world, player, rom, self.enemizer_path, output_directory)
if world.is_race:
patch_race_rom(rom, world, player)
@@ -599,7 +606,7 @@ class ALTTPWorld(World):
'hud': world.hud_palettes[player],
'sword': world.sword_palettes[player],
'shield': world.shield_palettes[player],
'link': world.link_palettes[player]
# 'link': world.link_palettes[player]
}
palettes_options = {key: option.current_key for key, option in palettes_options.items()}
@@ -631,6 +638,20 @@ class ALTTPWorld(World):
finally:
self.rom_name_available_event.set() # make sure threading continues and errors are collected
@classmethod
def stage_extend_hint_information(cls, world, hint_data: typing.Dict[int, typing.Dict[int, str]]):
er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if
world.shuffle[player] != "vanilla" or world.retro_caves[player]}
for region in world.regions:
if region.player in er_hint_data and region.locations:
main_entrance = region.get_connecting_entrance(is_main_entrance)
for location in region.locations:
if type(location.address) == int: # skips events and crystals
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
er_hint_data[region.player][location.address] = main_entrance.name
hint_data.update(er_hint_data)
def modify_multidata(self, multidata: dict):
import base64
# wait for self.rom_name to be available.
@@ -645,8 +666,7 @@ class ALTTPWorld(World):
return ALttPItem(name, self.player, **item_init_table[name])
@classmethod
def stage_fill_hook(cls, world, progitempool, nonexcludeditempool, localrestitempool, nonlocalrestitempool,
restitempool, fill_locations):
def stage_fill_hook(cls, world, progitempool, usefulitempool, filleritempool, fill_locations):
trash_counts = {}
standard_keyshuffle_players = set()
for player in world.get_game_players("A Link to the Past"):
@@ -693,26 +713,15 @@ class ALTTPWorld(World):
for player, trash_count in trash_counts.items():
gtower_locations = locations_mapping[player]
world.random.shuffle(gtower_locations)
localrest = localrestitempool[player]
if localrest:
gt_item_pool = restitempool + localrest
world.random.shuffle(gt_item_pool)
else:
gt_item_pool = restitempool.copy()
while gtower_locations and gt_item_pool and trash_count > 0:
while gtower_locations and filleritempool and trash_count > 0:
spot_to_fill = gtower_locations.pop()
item_to_place = gt_item_pool.pop()
item_to_place = filleritempool.pop()
if spot_to_fill.item_rule(item_to_place):
if item_to_place in localrest:
localrest.remove(item_to_place)
else:
restitempool.remove(item_to_place)
world.push_item(spot_to_fill, item_to_place, False)
fill_locations.remove(spot_to_fill) # very slow, unfortunately
trash_count -= 1
def get_filler_item_name(self) -> str:
if self.world.goal[self.player] == "icerodhunt":
item = "Nothing"

View File

@@ -26,10 +26,14 @@
- Example: `Trinexx`
- Takes a particular boss and places that boss in any remaining slots in which this boss can function.
- In this example, it would fill Desert Palace, but not Tower of Hera.
- If no other options are provided this will follow normal singularity rules with that boss.
- Boss Shuffle:
- Example: `simple`
- Example: `basic`
- Runs a particular boss shuffle mode to finish construction instead of vanilla placement, typically used as
a last instruction.
- Supports `random` which will choose a random option from the normal choices.
- If one is not supplied any remaining locations will be unshuffled unless a single specific boss is
supplied in which case it will use singularity as noted above.
- [Available Bosses](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/Bosses.py#L135)
- [Available Arenas](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/Bosses.py#L150)

View File

@@ -66,7 +66,7 @@ async def dkc3_game_watcher(ctx: Context):
return
new_checks = []
from worlds.dkc3.Rom import location_rom_data, item_rom_data
from worlds.dkc3.Rom import location_rom_data, item_rom_data, boss_location_ids, level_unlock_map
for loc_id, loc_data in location_rom_data.items():
if loc_id not in ctx.locations_checked:
data = await snes_read(ctx, WRAM_START + loc_data[0], 1)
@@ -186,22 +186,40 @@ async def dkc3_game_watcher(ctx: Context):
# DKC3_TODO: This method of collect should work, however it does not unlock the next level correctly when previous is flagged
# Handle Collected Locations
#for loc_id in ctx.checked_locations:
# if loc_id not in ctx.locations_checked:
# loc_data = location_rom_data[loc_id]
# data = await snes_read(ctx, WRAM_START + loc_data[0], 1)
# invert_bit = ((len(loc_data) >= 3) and loc_data[2])
# if not invert_bit:
# masked_data = data[0] | (1 << loc_data[1])
# print("Collected Location: ", hex(loc_data[0]), " | ", loc_data[1])
# snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data]))
# await snes_flush_writes(ctx)
# else:
# masked_data = data[0] & ~(1 << loc_data[1])
# print("Collected Inverted Location: ", hex(loc_data[0]), " | ", loc_data[1])
# snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data]))
# await snes_flush_writes(ctx)
# ctx.locations_checked.add(loc_id)
for loc_id in ctx.checked_locations:
if loc_id not in ctx.locations_checked and loc_id not in boss_location_ids:
loc_data = location_rom_data[loc_id]
data = await snes_read(ctx, WRAM_START + loc_data[0], 1)
invert_bit = ((len(loc_data) >= 3) and loc_data[2])
if not invert_bit:
masked_data = data[0] | (1 << loc_data[1])
#print("Collected Location: ", hex(loc_data[0]), " | ", loc_data[1])
snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data]))
if (loc_data[1] == 1):
# Make the next levels accessible
level_id = loc_data[0] - 0x632
levels_to_tiles = await snes_read(ctx, ROM_START + 0x3FF800, 0x60)
tiles_to_levels = await snes_read(ctx, ROM_START + 0x3FF860, 0x60)
tile_id = levels_to_tiles[level_id] if levels_to_tiles[level_id] != 0xFF else level_id
tile_id = tile_id + 0x632
#print("Tile ID: ", hex(tile_id))
if tile_id in level_unlock_map:
for next_level_address in level_unlock_map[tile_id]:
next_level_id = next_level_address - 0x632
next_tile_id = tiles_to_levels[next_level_id] if tiles_to_levels[next_level_id] != 0xFF else next_level_id
next_tile_id = next_tile_id + 0x632
#print("Next Level ID: ", hex(next_tile_id))
next_data = await snes_read(ctx, WRAM_START + next_tile_id, 1)
snes_buffered_write(ctx, WRAM_START + next_tile_id, bytes([next_data[0] | 0x01]))
await snes_flush_writes(ctx)
else:
masked_data = data[0] & ~(1 << loc_data[1])
print("Collected Inverted Location: ", hex(loc_data[0]), " | ", loc_data[1])
snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data]))
await snes_flush_writes(ctx)
ctx.locations_checked.add(loc_id)
# Calculate Boomer Cost Text
boomer_cost_text = await snes_read(ctx, WRAM_START + 0xAAFD, 2)

View File

@@ -221,6 +221,55 @@ level_location_table = {
LocationName.rocket_rush_dk: 0xDC30A0,
}
kong_location_table = {
LocationName.lakeside_limbo_kong: 0xDC3100,
LocationName.doorstop_dash_kong: 0xDC3104,
LocationName.tidal_trouble_kong: 0xDC3108,
LocationName.skiddas_row_kong: 0xDC310C,
LocationName.murky_mill_kong: 0xDC3110,
LocationName.barrel_shield_bust_up_kong: 0xDC3114,
LocationName.riverside_race_kong: 0xDC3118,
LocationName.squeals_on_wheels_kong: 0xDC311C,
LocationName.springin_spiders_kong: 0xDC3120,
LocationName.bobbing_barrel_brawl_kong: 0xDC3124,
LocationName.bazzas_blockade_kong: 0xDC3128,
LocationName.rocket_barrel_ride_kong: 0xDC312C,
LocationName.kreeping_klasps_kong: 0xDC3130,
LocationName.tracker_barrel_trek_kong: 0xDC3134,
LocationName.fish_food_frenzy_kong: 0xDC3138,
LocationName.fire_ball_frenzy_kong: 0xDC313C,
LocationName.demolition_drain_pipe_kong: 0xDC3140,
LocationName.ripsaw_rage_kong: 0xDC3144,
LocationName.blazing_bazookas_kong: 0xDC3148,
LocationName.low_g_labyrinth_kong: 0xDC314C,
LocationName.krevice_kreepers_kong: 0xDC3150,
LocationName.tearaway_toboggan_kong: 0xDC3154,
LocationName.barrel_drop_bounce_kong: 0xDC3158,
LocationName.krack_shot_kroc_kong: 0xDC315C,
LocationName.lemguin_lunge_kong: 0xDC3160,
LocationName.buzzer_barrage_kong: 0xDC3164,
LocationName.kong_fused_cliffs_kong: 0xDC3168,
LocationName.floodlit_fish_kong: 0xDC316C,
LocationName.pothole_panic_kong: 0xDC3170,
LocationName.ropey_rumpus_kong: 0xDC3174,
LocationName.konveyor_rope_clash_kong: 0xDC3178,
LocationName.creepy_caverns_kong: 0xDC317C,
LocationName.lightning_lookout_kong: 0xDC3180,
LocationName.koindozer_klamber_kong: 0xDC3184,
LocationName.poisonous_pipeline_kong: 0xDC3188,
LocationName.stampede_sprint_kong: 0xDC318C,
LocationName.criss_cross_cliffs_kong: 0xDC3191,
LocationName.tyrant_twin_tussle_kong: 0xDC3195,
LocationName.swoopy_salvo_kong: 0xDC319A,
}
boss_location_table = {
LocationName.belchas_barn: 0xDC30A1,
@@ -266,6 +315,7 @@ all_locations = {
**boss_location_table,
**secret_cave_location_table,
**brothers_bear_location_table,
**kong_location_table,
}
location_table = {}
@@ -277,6 +327,9 @@ def setup_locations(world, player: int):
if False:#world.include_trade_sequence[player].value:
location_table.update({**brothers_bear_location_table})
if world.kongsanity[player].value:
location_table.update({**kong_location_table})
return location_table

View File

@@ -1,197 +1,236 @@
# Level Definitions
lakeside_limbo_flag = "Lakeside Limbo - Flag"
lakeside_limbo_kong = "Lakeside Limbo - KONG"
lakeside_limbo_bonus_1 = "Lakeside Limbo - Bonus 1"
lakeside_limbo_bonus_2 = "Lakeside Limbo - Bonus 2"
lakeside_limbo_dk = "Lakeside Limbo - DK Coin"
doorstop_dash_flag = "Doorstop Dash - Flag"
doorstop_dash_kong = "Doorstop Dash - KONG"
doorstop_dash_bonus_1 = "Doorstop Dash - Bonus 1"
doorstop_dash_bonus_2 = "Doorstop Dash - Bonus 2"
doorstop_dash_dk = "Doorstop Dash - DK Coin"
tidal_trouble_flag = "Tidal Trouble - Flag"
tidal_trouble_kong = "Tidal Trouble - KONG"
tidal_trouble_bonus_1 = "Tidal Trouble - Bonus 1"
tidal_trouble_bonus_2 = "Tidal Trouble - Bonus 2"
tidal_trouble_dk = "Tidal Trouble - DK Coin"
skiddas_row_flag = "Skidda's Row - Flag"
skiddas_row_kong = "Skidda's Row - KONG"
skiddas_row_bonus_1 = "Skidda's Row - Bonus 1"
skiddas_row_bonus_2 = "Skidda's Row - Bonus 2"
skiddas_row_dk = "Skidda's Row - DK Coin"
murky_mill_flag = "Murky Mill - Flag"
murky_mill_kong = "Murky Mill - KONG"
murky_mill_bonus_1 = "Murky Mill - Bonus 1"
murky_mill_bonus_2 = "Murky Mill - Bonus 2"
murky_mill_dk = "Murky Mill - DK Coin"
barrel_shield_bust_up_flag = "Barrel Shield Bust-Up - Flag"
barrel_shield_bust_up_kong = "Barrel Shield Bust-Up - KONG"
barrel_shield_bust_up_bonus_1 = "Barrel Shield Bust-Up - Bonus 1"
barrel_shield_bust_up_bonus_2 = "Barrel Shield Bust-Up - Bonus 2"
barrel_shield_bust_up_dk = "Barrel Shield Bust-Up - DK Coin"
riverside_race_flag = "Riverside Race - Flag"
riverside_race_kong = "Riverside Race - KONG"
riverside_race_bonus_1 = "Riverside Race - Bonus 1"
riverside_race_bonus_2 = "Riverside Race - Bonus 2"
riverside_race_dk = "Riverside Race - DK Coin"
squeals_on_wheels_flag = "Squeals On Wheels - Flag"
squeals_on_wheels_kong = "Squeals On Wheels - KONG"
squeals_on_wheels_bonus_1 = "Squeals On Wheels - Bonus 1"
squeals_on_wheels_bonus_2 = "Squeals On Wheels - Bonus 2"
squeals_on_wheels_dk = "Squeals On Wheels - DK Coin"
springin_spiders_flag = "Springin' Spiders - Flag"
springin_spiders_kong = "Springin' Spiders - KONG"
springin_spiders_bonus_1 = "Springin' Spiders - Bonus 1"
springin_spiders_bonus_2 = "Springin' Spiders - Bonus 2"
springin_spiders_dk = "Springin' Spiders - DK Coin"
bobbing_barrel_brawl_flag = "Bobbing Barrel Brawl - Flag"
bobbing_barrel_brawl_kong = "Bobbing Barrel Brawl - KONG"
bobbing_barrel_brawl_bonus_1 = "Bobbing Barrel Brawl - Bonus 1"
bobbing_barrel_brawl_bonus_2 = "Bobbing Barrel Brawl - Bonus 2"
bobbing_barrel_brawl_dk = "Bobbing Barrel Brawl - DK Coin"
bazzas_blockade_flag = "Bazza's Blockade - Flag"
bazzas_blockade_kong = "Bazza's Blockade - KONG"
bazzas_blockade_bonus_1 = "Bazza's Blockade - Bonus 1"
bazzas_blockade_bonus_2 = "Bazza's Blockade - Bonus 2"
bazzas_blockade_dk = "Bazza's Blockade - DK Coin"
rocket_barrel_ride_flag = "Rocket Barrel Ride - Flag"
rocket_barrel_ride_kong = "Rocket Barrel Ride - KONG"
rocket_barrel_ride_bonus_1 = "Rocket Barrel Ride - Bonus 1"
rocket_barrel_ride_bonus_2 = "Rocket Barrel Ride - Bonus 2"
rocket_barrel_ride_dk = "Rocket Barrel Ride - DK Coin"
kreeping_klasps_flag = "Kreeping Klasps - Flag"
kreeping_klasps_kong = "Kreeping Klasps - KONG"
kreeping_klasps_bonus_1 = "Kreeping Klasps - Bonus 1"
kreeping_klasps_bonus_2 = "Kreeping Klasps - Bonus 2"
kreeping_klasps_dk = "Kreeping Klasps - DK Coin"
tracker_barrel_trek_flag = "Tracker Barrel Trek - Flag"
tracker_barrel_trek_kong = "Tracker Barrel Trek - KONG"
tracker_barrel_trek_bonus_1 = "Tracker Barrel Trek - Bonus 1"
tracker_barrel_trek_bonus_2 = "Tracker Barrel Trek - Bonus 2"
tracker_barrel_trek_dk = "Tracker Barrel Trek - DK Coin"
fish_food_frenzy_flag = "Fish Food Frenzy - Flag"
fish_food_frenzy_kong = "Fish Food Frenzy - KONG"
fish_food_frenzy_bonus_1 = "Fish Food Frenzy - Bonus 1"
fish_food_frenzy_bonus_2 = "Fish Food Frenzy - Bonus 2"
fish_food_frenzy_dk = "Fish Food Frenzy - DK Coin"
fire_ball_frenzy_flag = "Fire-Ball Frenzy - Flag"
fire_ball_frenzy_kong = "Fire-Ball Frenzy - KONG"
fire_ball_frenzy_bonus_1 = "Fire-Ball Frenzy - Bonus 1"
fire_ball_frenzy_bonus_2 = "Fire-Ball Frenzy - Bonus 2"
fire_ball_frenzy_dk = "Fire-Ball Frenzy - DK Coin"
demolition_drain_pipe_flag = "Demolition Drain-Pipe - Flag"
demolition_drain_pipe_kong = "Demolition Drain-Pipe - KONG"
demolition_drain_pipe_bonus_1 = "Demolition Drain-Pipe - Bonus 1"
demolition_drain_pipe_bonus_2 = "Demolition Drain-Pipe - Bonus 2"
demolition_drain_pipe_dk = "Demolition Drain-Pipe - DK Coin"
ripsaw_rage_flag = "Ripsaw Rage - Flag"
ripsaw_rage_kong = "Ripsaw Rage - KONG"
ripsaw_rage_bonus_1 = "Ripsaw Rage - Bonus 1"
ripsaw_rage_bonus_2 = "Ripsaw Rage - Bonus 2"
ripsaw_rage_dk = "Ripsaw Rage - DK Coin"
blazing_bazookas_flag = "Blazing Bazookas - Flag"
blazing_bazookas_bonus_1 = "Blazing Bazookas - Bonus 1"
blazing_bazookas_bonus_2 = "Blazing Bazookas - Bonus 2"
blazing_bazookas_dk = "Blazing Bazookas - DK Coin"
blazing_bazookas_flag = "Blazing Bazukas - Flag"
blazing_bazookas_kong = "Blazing Bazukas - KONG"
blazing_bazookas_bonus_1 = "Blazing Bazukas - Bonus 1"
blazing_bazookas_bonus_2 = "Blazing Bazukas - Bonus 2"
blazing_bazookas_dk = "Blazing Bazukas - DK Coin"
low_g_labyrinth_flag = "Low-G Labyrinth - Flag"
low_g_labyrinth_kong = "Low-G Labyrinth - KONG"
low_g_labyrinth_bonus_1 = "Low-G Labyrinth - Bonus 1"
low_g_labyrinth_bonus_2 = "Low-G Labyrinth - Bonus 2"
low_g_labyrinth_dk = "Low-G Labyrinth - DK Coin"
krevice_kreepers_flag = "Krevice Kreepers - Flag"
krevice_kreepers_kong = "Krevice Kreepers - KONG"
krevice_kreepers_bonus_1 = "Krevice Kreepers - Bonus 1"
krevice_kreepers_bonus_2 = "Krevice Kreepers - Bonus 2"
krevice_kreepers_dk = "Krevice Kreepers - DK Coin"
tearaway_toboggan_flag = "Tearaway Toboggan - Flag"
tearaway_toboggan_kong = "Tearaway Toboggan - KONG"
tearaway_toboggan_bonus_1 = "Tearaway Toboggan - Bonus 1"
tearaway_toboggan_bonus_2 = "Tearaway Toboggan - Bonus 2"
tearaway_toboggan_dk = "Tearaway Toboggan - DK Coin"
barrel_drop_bounce_flag = "Barrel Drop Bounce - Flag"
barrel_drop_bounce_kong = "Barrel Drop Bounce - KONG"
barrel_drop_bounce_bonus_1 = "Barrel Drop Bounce - Bonus 1"
barrel_drop_bounce_bonus_2 = "Barrel Drop Bounce - Bonus 2"
barrel_drop_bounce_dk = "Barrel Drop Bounce - DK Coin"
krack_shot_kroc_flag = "Krack-Shot Kroc - Flag"
krack_shot_kroc_kong = "Krack-Shot Kroc - KONG"
krack_shot_kroc_bonus_1 = "Krack-Shot Kroc - Bonus 1"
krack_shot_kroc_bonus_2 = "Krack-Shot Kroc - Bonus 2"
krack_shot_kroc_dk = "Krack-Shot Kroc - DK Coin"
lemguin_lunge_flag = "Lemguin Lunge - Flag"
lemguin_lunge_kong = "Lemguin Lunge - KONG"
lemguin_lunge_bonus_1 = "Lemguin Lunge - Bonus 1"
lemguin_lunge_bonus_2 = "Lemguin Lunge - Bonus 2"
lemguin_lunge_dk = "Lemguin Lunge - DK Coin"
buzzer_barrage_flag = "Buzzer Barrage - Flag"
buzzer_barrage_kong = "Buzzer Barrage - KONG"
buzzer_barrage_bonus_1 = "Buzzer Barrage - Bonus 1"
buzzer_barrage_bonus_2 = "Buzzer Barrage - Bonus 2"
buzzer_barrage_dk = "Buzzer Barrage - DK Coin"
kong_fused_cliffs_flag = "Kong-Fused Cliffs - Flag"
kong_fused_cliffs_kong = "Kong-Fused Cliffs - KONG"
kong_fused_cliffs_bonus_1 = "Kong-Fused Cliffs - Bonus 1"
kong_fused_cliffs_bonus_2 = "Kong-Fused Cliffs - Bonus 2"
kong_fused_cliffs_dk = "Kong-Fused Cliffs - DK Coin"
floodlit_fish_flag = "Floodlit Fish - Flag"
floodlit_fish_kong = "Floodlit Fish - KONG"
floodlit_fish_bonus_1 = "Floodlit Fish - Bonus 1"
floodlit_fish_bonus_2 = "Floodlit Fish - Bonus 2"
floodlit_fish_dk = "Floodlit Fish - DK Coin"
pothole_panic_flag = "Pothole Panic - Flag"
pothole_panic_kong = "Pothole Panic - KONG"
pothole_panic_bonus_1 = "Pothole Panic - Bonus 1"
pothole_panic_bonus_2 = "Pothole Panic - Bonus 2"
pothole_panic_dk = "Pothole Panic - DK Coin"
ropey_rumpus_flag = "Ropey Rumpus - Flag"
ropey_rumpus_kong = "Ropey Rumpus - KONG"
ropey_rumpus_bonus_1 = "Ropey Rumpus - Bonus 1"
ropey_rumpus_bonus_2 = "Ropey Rumpus - Bonus 2"
ropey_rumpus_dk = "Ropey Rumpus - DK Coin"
konveyor_rope_clash_flag = "Konveyor Rope Klash - Flag"
konveyor_rope_clash_kong = "Konveyor Rope Klash - KONG"
konveyor_rope_clash_bonus_1 = "Konveyor Rope Klash - Bonus 1"
konveyor_rope_clash_bonus_2 = "Konveyor Rope Klash - Bonus 2"
konveyor_rope_clash_dk = "Konveyor Rope Klash - DK Coin"
creepy_caverns_flag = "Creepy Caverns - Flag"
creepy_caverns_kong = "Creepy Caverns - KONG"
creepy_caverns_bonus_1 = "Creepy Caverns - Bonus 1"
creepy_caverns_bonus_2 = "Creepy Caverns - Bonus 2"
creepy_caverns_dk = "Creepy Caverns - DK Coin"
lightning_lookout_flag = "Lightning Lookout - Flag"
lightning_lookout_kong = "Lightning Lookout - KONG"
lightning_lookout_bonus_1 = "Lightning Lookout - Bonus 1"
lightning_lookout_bonus_2 = "Lightning Lookout - Bonus 2"
lightning_lookout_dk = "Lightning Lookout - DK Coin"
koindozer_klamber_flag = "Koindozer Klamber - Flag"
koindozer_klamber_kong = "Koindozer Klamber - KONG"
koindozer_klamber_bonus_1 = "Koindozer Klamber - Bonus 1"
koindozer_klamber_bonus_2 = "Koindozer Klamber - Bonus 2"
koindozer_klamber_dk = "Koindozer Klamber - DK Coin"
poisonous_pipeline_flag = "Poisonous Pipeline - Flag"
poisonous_pipeline_kong = "Poisonous Pipeline - KONG"
poisonous_pipeline_bonus_1 = "Poisonous Pipeline - Bonus 1"
poisonous_pipeline_bonus_2 = "Poisonous Pipeline - Bonus 2"
poisonous_pipeline_dk = "Poisonous Pipeline - DK Coin"
stampede_sprint_flag = "Stampede Sprint - Flag"
stampede_sprint_kong = "Stampede Sprint - KONG"
stampede_sprint_bonus_1 = "Stampede Sprint - Bonus 1"
stampede_sprint_bonus_2 = "Stampede Sprint - Bonus 2"
stampede_sprint_bonus_3 = "Stampede Sprint - Bonus 3"
stampede_sprint_dk = "Stampede Sprint - DK Coin"
criss_cross_cliffs_flag = "Criss Kross Cliffs - Flag"
criss_cross_cliffs_kong = "Criss Kross Cliffs - KONG"
criss_cross_cliffs_bonus_1 = "Criss Kross Cliffs - Bonus 1"
criss_cross_cliffs_bonus_2 = "Criss Kross Cliffs - Bonus 2"
criss_cross_cliffs_dk = "Criss Kross Cliffs - DK Coin"
tyrant_twin_tussle_flag = "Tyrant Twin Tussle - Flag"
tyrant_twin_tussle_kong = "Tyrant Twin Tussle - KONG"
tyrant_twin_tussle_bonus_1 = "Tyrant Twin Tussle - Bonus 1"
tyrant_twin_tussle_bonus_2 = "Tyrant Twin Tussle - Bonus 2"
tyrant_twin_tussle_bonus_3 = "Tyrant Twin Tussle - Bonus 3"
tyrant_twin_tussle_dk = "Tyrant Twin Tussle - DK Coin"
swoopy_salvo_flag = "Swoopy Salvo - Flag"
swoopy_salvo_kong = "Swoopy Salvo - KONG"
swoopy_salvo_bonus_1 = "Swoopy Salvo - Bonus 1"
swoopy_salvo_bonus_2 = "Swoopy Salvo - Bonus 2"
swoopy_salvo_bonus_3 = "Swoopy Salvo - Bonus 3"

View File

@@ -6,7 +6,7 @@ from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, O
class Goal(Choice):
"""
Determines the goal of the seed
Knautilus: Reach the Knautilus and defeat Baron K. Roolenstein
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"
@@ -75,6 +75,13 @@ class PercentageOfBananaBirds(Range):
default = 100
class KONGsanity(Toggle):
"""
Whether collecting all four KONG letters in each level grants a check
"""
display_name = "KONGsanity"
class LevelShuffle(Toggle):
"""
Whether levels are shuffled
@@ -82,6 +89,41 @@ class LevelShuffle(Toggle):
display_name = "Level Shuffle"
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
"""
display_name = "Difficulty"
option_norml = 0
option_hardr = 1
option_tufst = 2
default = 0
@classmethod
def get_option_name(cls, value) -> str:
if cls.auto_display_name:
return cls.name_lookup[value].upper()
else:
return cls.name_lookup[value]
class Autosave(DefaultOnToggle):
"""
Whether the game should autosave after each level
"""
display_name = "Autosave"
class MERRY(Toggle):
"""
Whether the Bonus Barrels will be Christmas-themed
"""
display_name = "MERRY"
class MusicShuffle(Toggle):
"""
Whether music is shuffled
@@ -125,7 +167,11 @@ dkc3_options: typing.Dict[str, type(Option)] = {
"percentage_of_extra_bonus_coins": PercentageOfExtraBonusCoins,
"number_of_banana_birds": NumberOfBananaBirds,
"percentage_of_banana_birds": PercentageOfBananaBirds,
"kongsanity": KONGsanity,
"level_shuffle": LevelShuffle,
"difficulty": Difficulty,
"autosave": Autosave,
"merry": MERRY,
"music_shuffle": MusicShuffle,
"kong_palette_swap": KongPaletteSwap,
"starting_life_count": StartingLifeCount,

View File

@@ -44,6 +44,8 @@ def create_regions(world, player: int, active_locations):
LocationName.lakeside_limbo_bonus_2 : [0x657, 3],
LocationName.lakeside_limbo_dk : [0x657, 5],
}
if world.kongsanity[player]:
lakeside_limbo_region_locations[LocationName.lakeside_limbo_kong] = []
lakeside_limbo_region = create_region(world, player, active_locations, LocationName.lakeside_limbo_region,
lakeside_limbo_region_locations, None)
@@ -53,6 +55,8 @@ def create_regions(world, player: int, active_locations):
LocationName.doorstop_dash_bonus_2 : [0x65A, 3],
LocationName.doorstop_dash_dk : [0x65A, 5],
}
if world.kongsanity[player]:
doorstop_dash_region_locations[LocationName.doorstop_dash_kong] = []
doorstop_dash_region = create_region(world, player, active_locations, LocationName.doorstop_dash_region,
doorstop_dash_region_locations, None)
@@ -62,6 +66,8 @@ def create_regions(world, player: int, active_locations):
LocationName.tidal_trouble_bonus_2 : [0x659, 3],
LocationName.tidal_trouble_dk : [0x659, 5],
}
if world.kongsanity[player]:
tidal_trouble_region_locations[LocationName.tidal_trouble_kong] = []
tidal_trouble_region = create_region(world, player, active_locations, LocationName.tidal_trouble_region,
tidal_trouble_region_locations, None)
@@ -71,6 +77,8 @@ def create_regions(world, player: int, active_locations):
LocationName.skiddas_row_bonus_2 : [0x65D, 3],
LocationName.skiddas_row_dk : [0x65D, 5],
}
if world.kongsanity[player]:
skiddas_row_region_locations[LocationName.skiddas_row_kong] = []
skiddas_row_region = create_region(world, player, active_locations, LocationName.skiddas_row_region,
skiddas_row_region_locations, None)
@@ -80,6 +88,8 @@ def create_regions(world, player: int, active_locations):
LocationName.murky_mill_bonus_2 : [0x65C, 3],
LocationName.murky_mill_dk : [0x65C, 5],
}
if world.kongsanity[player]:
murky_mill_region_locations[LocationName.murky_mill_kong] = []
murky_mill_region = create_region(world, player, active_locations, LocationName.murky_mill_region,
murky_mill_region_locations, None)
@@ -89,6 +99,8 @@ def create_regions(world, player: int, active_locations):
LocationName.barrel_shield_bust_up_bonus_2 : [0x662, 3],
LocationName.barrel_shield_bust_up_dk : [0x662, 5],
}
if world.kongsanity[player]:
barrel_shield_bust_up_region_locations[LocationName.barrel_shield_bust_up_kong] = []
barrel_shield_bust_up_region = create_region(world, player, active_locations, LocationName.barrel_shield_bust_up_region,
barrel_shield_bust_up_region_locations, None)
@@ -98,6 +110,8 @@ def create_regions(world, player: int, active_locations):
LocationName.riverside_race_bonus_2 : [0x664, 3],
LocationName.riverside_race_dk : [0x664, 5],
}
if world.kongsanity[player]:
riverside_race_region_locations[LocationName.riverside_race_kong] = []
riverside_race_region = create_region(world, player, active_locations, LocationName.riverside_race_region,
riverside_race_region_locations, None)
@@ -107,6 +121,8 @@ def create_regions(world, player: int, active_locations):
LocationName.squeals_on_wheels_bonus_2 : [0x65B, 3],
LocationName.squeals_on_wheels_dk : [0x65B, 5],
}
if world.kongsanity[player]:
squeals_on_wheels_region_locations[LocationName.squeals_on_wheels_kong] = []
squeals_on_wheels_region = create_region(world, player, active_locations, LocationName.squeals_on_wheels_region,
squeals_on_wheels_region_locations, None)
@@ -116,6 +132,8 @@ def create_regions(world, player: int, active_locations):
LocationName.springin_spiders_bonus_2 : [0x661, 3],
LocationName.springin_spiders_dk : [0x661, 5],
}
if world.kongsanity[player]:
springin_spiders_region_locations[LocationName.springin_spiders_kong] = []
springin_spiders_region = create_region(world, player, active_locations, LocationName.springin_spiders_region,
springin_spiders_region_locations, None)
@@ -125,6 +143,8 @@ def create_regions(world, player: int, active_locations):
LocationName.bobbing_barrel_brawl_bonus_2 : [0x666, 3],
LocationName.bobbing_barrel_brawl_dk : [0x666, 5],
}
if world.kongsanity[player]:
bobbing_barrel_brawl_region_locations[LocationName.bobbing_barrel_brawl_kong] = []
bobbing_barrel_brawl_region = create_region(world, player, active_locations, LocationName.bobbing_barrel_brawl_region,
bobbing_barrel_brawl_region_locations, None)
@@ -134,6 +154,8 @@ def create_regions(world, player: int, active_locations):
LocationName.bazzas_blockade_bonus_2 : [0x667, 3],
LocationName.bazzas_blockade_dk : [0x667, 5],
}
if world.kongsanity[player]:
bazzas_blockade_region_locations[LocationName.bazzas_blockade_kong] = []
bazzas_blockade_region = create_region(world, player, active_locations, LocationName.bazzas_blockade_region,
bazzas_blockade_region_locations, None)
@@ -143,6 +165,8 @@ def create_regions(world, player: int, active_locations):
LocationName.rocket_barrel_ride_bonus_2 : [0x66A, 3],
LocationName.rocket_barrel_ride_dk : [0x66A, 5],
}
if world.kongsanity[player]:
rocket_barrel_ride_region_locations[LocationName.rocket_barrel_ride_kong] = []
rocket_barrel_ride_region = create_region(world, player, active_locations, LocationName.rocket_barrel_ride_region,
rocket_barrel_ride_region_locations, None)
@@ -152,6 +176,8 @@ def create_regions(world, player: int, active_locations):
LocationName.kreeping_klasps_bonus_2 : [0x658, 3],
LocationName.kreeping_klasps_dk : [0x658, 5],
}
if world.kongsanity[player]:
kreeping_klasps_region_locations[LocationName.kreeping_klasps_kong] = []
kreeping_klasps_region = create_region(world, player, active_locations, LocationName.kreeping_klasps_region,
kreeping_klasps_region_locations, None)
@@ -161,6 +187,8 @@ def create_regions(world, player: int, active_locations):
LocationName.tracker_barrel_trek_bonus_2 : [0x66B, 3],
LocationName.tracker_barrel_trek_dk : [0x66B, 5],
}
if world.kongsanity[player]:
tracker_barrel_trek_region_locations[LocationName.tracker_barrel_trek_kong] = []
tracker_barrel_trek_region = create_region(world, player, active_locations, LocationName.tracker_barrel_trek_region,
tracker_barrel_trek_region_locations, None)
@@ -170,6 +198,8 @@ def create_regions(world, player: int, active_locations):
LocationName.fish_food_frenzy_bonus_2 : [0x668, 3],
LocationName.fish_food_frenzy_dk : [0x668, 5],
}
if world.kongsanity[player]:
fish_food_frenzy_region_locations[LocationName.fish_food_frenzy_kong] = []
fish_food_frenzy_region = create_region(world, player, active_locations, LocationName.fish_food_frenzy_region,
fish_food_frenzy_region_locations, None)
@@ -179,6 +209,8 @@ def create_regions(world, player: int, active_locations):
LocationName.fire_ball_frenzy_bonus_2 : [0x66D, 3],
LocationName.fire_ball_frenzy_dk : [0x66D, 5],
}
if world.kongsanity[player]:
fire_ball_frenzy_region_locations[LocationName.fire_ball_frenzy_kong] = []
fire_ball_frenzy_region = create_region(world, player, active_locations, LocationName.fire_ball_frenzy_region,
fire_ball_frenzy_region_locations, None)
@@ -188,6 +220,8 @@ def create_regions(world, player: int, active_locations):
LocationName.demolition_drain_pipe_bonus_2 : [0x672, 3],
LocationName.demolition_drain_pipe_dk : [0x672, 5],
}
if world.kongsanity[player]:
demolition_drain_pipe_region_locations[LocationName.demolition_drain_pipe_kong] = []
demolition_drain_pipe_region = create_region(world, player, active_locations, LocationName.demolition_drain_pipe_region,
demolition_drain_pipe_region_locations, None)
@@ -197,6 +231,8 @@ def create_regions(world, player: int, active_locations):
LocationName.ripsaw_rage_bonus_2 : [0x660, 3],
LocationName.ripsaw_rage_dk : [0x660, 5],
}
if world.kongsanity[player]:
ripsaw_rage_region_locations[LocationName.ripsaw_rage_kong] = []
ripsaw_rage_region = create_region(world, player, active_locations, LocationName.ripsaw_rage_region,
ripsaw_rage_region_locations, None)
@@ -206,6 +242,8 @@ def create_regions(world, player: int, active_locations):
LocationName.blazing_bazookas_bonus_2 : [0x66E, 3],
LocationName.blazing_bazookas_dk : [0x66E, 5],
}
if world.kongsanity[player]:
blazing_bazookas_region_locations[LocationName.blazing_bazookas_kong] = []
blazing_bazookas_region = create_region(world, player, active_locations, LocationName.blazing_bazookas_region,
blazing_bazookas_region_locations, None)
@@ -215,6 +253,8 @@ def create_regions(world, player: int, active_locations):
LocationName.low_g_labyrinth_bonus_2 : [0x670, 3],
LocationName.low_g_labyrinth_dk : [0x670, 5],
}
if world.kongsanity[player]:
low_g_labyrinth_region_locations[LocationName.low_g_labyrinth_kong] = []
low_g_labyrinth_region = create_region(world, player, active_locations, LocationName.low_g_labyrinth_region,
low_g_labyrinth_region_locations, None)
@@ -224,6 +264,8 @@ def create_regions(world, player: int, active_locations):
LocationName.krevice_kreepers_bonus_2 : [0x673, 3],
LocationName.krevice_kreepers_dk : [0x673, 5],
}
if world.kongsanity[player]:
krevice_kreepers_region_locations[LocationName.krevice_kreepers_kong] = []
krevice_kreepers_region = create_region(world, player, active_locations, LocationName.krevice_kreepers_region,
krevice_kreepers_region_locations, None)
@@ -233,6 +275,8 @@ def create_regions(world, player: int, active_locations):
LocationName.tearaway_toboggan_bonus_2 : [0x65F, 3],
LocationName.tearaway_toboggan_dk : [0x65F, 5],
}
if world.kongsanity[player]:
tearaway_toboggan_region_locations[LocationName.tearaway_toboggan_kong] = []
tearaway_toboggan_region = create_region(world, player, active_locations, LocationName.tearaway_toboggan_region,
tearaway_toboggan_region_locations, None)
@@ -242,6 +286,8 @@ def create_regions(world, player: int, active_locations):
LocationName.barrel_drop_bounce_bonus_2 : [0x66C, 3],
LocationName.barrel_drop_bounce_dk : [0x66C, 5],
}
if world.kongsanity[player]:
barrel_drop_bounce_region_locations[LocationName.barrel_drop_bounce_kong] = []
barrel_drop_bounce_region = create_region(world, player, active_locations, LocationName.barrel_drop_bounce_region,
barrel_drop_bounce_region_locations, None)
@@ -251,6 +297,8 @@ def create_regions(world, player: int, active_locations):
LocationName.krack_shot_kroc_bonus_2 : [0x66F, 3],
LocationName.krack_shot_kroc_dk : [0x66F, 5],
}
if world.kongsanity[player]:
krack_shot_kroc_region_locations[LocationName.krack_shot_kroc_kong] = []
krack_shot_kroc_region = create_region(world, player, active_locations, LocationName.krack_shot_kroc_region,
krack_shot_kroc_region_locations, None)
@@ -260,6 +308,8 @@ def create_regions(world, player: int, active_locations):
LocationName.lemguin_lunge_bonus_2 : [0x65E, 3],
LocationName.lemguin_lunge_dk : [0x65E, 5],
}
if world.kongsanity[player]:
lemguin_lunge_region_locations[LocationName.lemguin_lunge_kong] = []
lemguin_lunge_region = create_region(world, player, active_locations, LocationName.lemguin_lunge_region,
lemguin_lunge_region_locations, None)
@@ -269,6 +319,8 @@ def create_regions(world, player: int, active_locations):
LocationName.buzzer_barrage_bonus_2 : [0x676, 3],
LocationName.buzzer_barrage_dk : [0x676, 5],
}
if world.kongsanity[player]:
buzzer_barrage_region_locations[LocationName.buzzer_barrage_kong] = []
buzzer_barrage_region = create_region(world, player, active_locations, LocationName.buzzer_barrage_region,
buzzer_barrage_region_locations, None)
@@ -278,6 +330,8 @@ def create_regions(world, player: int, active_locations):
LocationName.kong_fused_cliffs_bonus_2 : [0x674, 3],
LocationName.kong_fused_cliffs_dk : [0x674, 5],
}
if world.kongsanity[player]:
kong_fused_cliffs_region_locations[LocationName.kong_fused_cliffs_kong] = []
kong_fused_cliffs_region = create_region(world, player, active_locations, LocationName.kong_fused_cliffs_region,
kong_fused_cliffs_region_locations, None)
@@ -287,6 +341,8 @@ def create_regions(world, player: int, active_locations):
LocationName.floodlit_fish_bonus_2 : [0x669, 3],
LocationName.floodlit_fish_dk : [0x669, 5],
}
if world.kongsanity[player]:
floodlit_fish_region_locations[LocationName.floodlit_fish_kong] = []
floodlit_fish_region = create_region(world, player, active_locations, LocationName.floodlit_fish_region,
floodlit_fish_region_locations, None)
@@ -296,6 +352,8 @@ def create_regions(world, player: int, active_locations):
LocationName.pothole_panic_bonus_2 : [0x677, 3],
LocationName.pothole_panic_dk : [0x677, 5],
}
if world.kongsanity[player]:
pothole_panic_region_locations[LocationName.pothole_panic_kong] = []
pothole_panic_region = create_region(world, player, active_locations, LocationName.pothole_panic_region,
pothole_panic_region_locations, None)
@@ -305,6 +363,8 @@ def create_regions(world, player: int, active_locations):
LocationName.ropey_rumpus_bonus_2 : [0x675, 3],
LocationName.ropey_rumpus_dk : [0x675, 5],
}
if world.kongsanity[player]:
ropey_rumpus_region_locations[LocationName.ropey_rumpus_kong] = []
ropey_rumpus_region = create_region(world, player, active_locations, LocationName.ropey_rumpus_region,
ropey_rumpus_region_locations, None)
@@ -314,6 +374,8 @@ def create_regions(world, player: int, active_locations):
LocationName.konveyor_rope_clash_bonus_2 : [0x657, 3],
LocationName.konveyor_rope_clash_dk : [0x657, 5],
}
if world.kongsanity[player]:
konveyor_rope_clash_region_locations[LocationName.konveyor_rope_clash_kong] = []
konveyor_rope_clash_region = create_region(world, player, active_locations, LocationName.konveyor_rope_clash_region,
konveyor_rope_clash_region_locations, None)
@@ -323,6 +385,8 @@ def create_regions(world, player: int, active_locations):
LocationName.creepy_caverns_bonus_2 : [0x678, 3],
LocationName.creepy_caverns_dk : [0x678, 5],
}
if world.kongsanity[player]:
creepy_caverns_region_locations[LocationName.creepy_caverns_kong] = []
creepy_caverns_region = create_region(world, player, active_locations, LocationName.creepy_caverns_region,
creepy_caverns_region_locations, None)
@@ -332,6 +396,8 @@ def create_regions(world, player: int, active_locations):
LocationName.lightning_lookout_bonus_2 : [0x665, 3],
LocationName.lightning_lookout_dk : [0x665, 5],
}
if world.kongsanity[player]:
lightning_lookout_region_locations[LocationName.lightning_lookout_kong] = []
lightning_lookout_region = create_region(world, player, active_locations, LocationName.lightning_lookout_region,
lightning_lookout_region_locations, None)
@@ -341,6 +407,8 @@ def create_regions(world, player: int, active_locations):
LocationName.koindozer_klamber_bonus_2 : [0x679, 3],
LocationName.koindozer_klamber_dk : [0x679, 5],
}
if world.kongsanity[player]:
koindozer_klamber_region_locations[LocationName.koindozer_klamber_kong] = []
koindozer_klamber_region = create_region(world, player, active_locations, LocationName.koindozer_klamber_region,
koindozer_klamber_region_locations, None)
@@ -350,6 +418,8 @@ def create_regions(world, player: int, active_locations):
LocationName.poisonous_pipeline_bonus_2 : [0x671, 3],
LocationName.poisonous_pipeline_dk : [0x671, 5],
}
if world.kongsanity[player]:
poisonous_pipeline_region_locations[LocationName.poisonous_pipeline_kong] = []
poisonous_pipeline_region = create_region(world, player, active_locations, LocationName.poisonous_pipeline_region,
poisonous_pipeline_region_locations, None)
@@ -360,6 +430,8 @@ def create_regions(world, player: int, active_locations):
LocationName.stampede_sprint_bonus_3 : [0x67B, 4],
LocationName.stampede_sprint_dk : [0x67B, 5],
}
if world.kongsanity[player]:
stampede_sprint_region_locations[LocationName.stampede_sprint_kong] = []
stampede_sprint_region = create_region(world, player, active_locations, LocationName.stampede_sprint_region,
stampede_sprint_region_locations, None)
@@ -369,6 +441,8 @@ def create_regions(world, player: int, active_locations):
LocationName.criss_cross_cliffs_bonus_2 : [0x67C, 3],
LocationName.criss_cross_cliffs_dk : [0x67C, 5],
}
if world.kongsanity[player]:
criss_cross_cliffs_region_locations[LocationName.criss_cross_cliffs_kong] = []
criss_cross_cliffs_region = create_region(world, player, active_locations, LocationName.criss_cross_cliffs_region,
criss_cross_cliffs_region_locations, None)
@@ -379,6 +453,8 @@ def create_regions(world, player: int, active_locations):
LocationName.tyrant_twin_tussle_bonus_3 : [0x67D, 4],
LocationName.tyrant_twin_tussle_dk : [0x67D, 5],
}
if world.kongsanity[player]:
tyrant_twin_tussle_region_locations[LocationName.tyrant_twin_tussle_kong] = []
tyrant_twin_tussle_region = create_region(world, player, active_locations, LocationName.tyrant_twin_tussle_region,
tyrant_twin_tussle_region_locations, None)
@@ -389,6 +465,8 @@ def create_regions(world, player: int, active_locations):
LocationName.swoopy_salvo_bonus_3 : [0x663, 4],
LocationName.swoopy_salvo_dk : [0x663, 5],
}
if world.kongsanity[player]:
swoopy_salvo_region_locations[LocationName.swoopy_salvo_kong] = []
swoopy_salvo_region = create_region(world, player, active_locations, LocationName.swoopy_salvo_region,
swoopy_salvo_region_locations, None)
@@ -503,9 +581,7 @@ def create_regions(world, player: int, active_locations):
sky_high_secret_region_locations = {}
if False:#world.include_trade_sequence[player]:
sky_high_secret_region_locations.update({
LocationName.sky_high_secret: [0x64B, 1],
})
sky_high_secret_region_locations[LocationName.sky_high_secret] = [0x64B, 1]
sky_high_secret_region = create_region(world, player, active_locations, LocationName.sky_high_secret_region,
sky_high_secret_region_locations, None)
@@ -517,9 +593,7 @@ def create_regions(world, player: int, active_locations):
cifftop_cache_region_locations = {}
if False:#world.include_trade_sequence[player]:
cifftop_cache_region_locations.update({
LocationName.cifftop_cache: [0x64D, 1],
})
cifftop_cache_region_locations[LocationName.cifftop_cache] = [0x64D, 1]
cifftop_cache_region = create_region(world, player, active_locations, LocationName.cifftop_cache_region,
cifftop_cache_region_locations, None)
@@ -622,29 +696,19 @@ def create_regions(world, player: int, active_locations):
LocationName.bazaars_general_store_2: [0x615, 3, True],
})
bramble_region_locations.update({
LocationName.brambles_bungalow: [0x619, 2],
})
bramble_region_locations[LocationName.brambles_bungalow] = [0x619, 2]
#flower_spot_region_locations.update({
# LocationName.flower_spot: [0x615, 3, True],
#})
barter_region_locations.update({
LocationName.barters_swap_shop: [0x61B, 3],
})
barter_region_locations[LocationName.barters_swap_shop] = [0x61B, 3]
barnacle_region_locations.update({
LocationName.barnacles_island: [0x61D, 2],
})
barnacle_region_locations[LocationName.barnacles_island] = [0x61D, 2]
blue_region_locations.update({
LocationName.blues_beach_hut: [0x621, 4],
})
blue_region_locations[LocationName.blues_beach_hut] = [0x621, 4]
blizzard_region_locations.update({
LocationName.blizzards_basecamp: [0x625, 4, True],
})
blizzard_region_locations[LocationName.blizzards_basecamp] = [0x625, 4, True]
bazaar_region = create_region(world, player, active_locations, LocationName.bazaar_region,
bazaar_region_locations, None)
@@ -817,7 +881,6 @@ def connect_regions(world, player, level_list):
level_list[32],
level_list[33],
level_list[34],
LocationName.kastle_kaos_region,
LocationName.sewer_stockpile_region,
]
@@ -835,10 +898,16 @@ def connect_regions(world, player, level_list):
for i in range(0, len(krematoa_levels)):
connect(world, player, names, LocationName.krematoa_region, krematoa_levels[i],
lambda state: (state.has(ItemName.bonus_coin, player, world.krematoa_bonus_coin_cost[player].value * (i+1))))
connect(world, player, names, LocationName.krematoa_region, LocationName.knautilus_region,
lambda state: (state.has(ItemName.krematoa_cog, player, 5)))
lambda state, i=i: (state.has(ItemName.bonus_coin, player, world.krematoa_bonus_coin_cost[player].value * (i+1))))
if world.goal[player] == "knautilus":
connect(world, player, names, LocationName.kaos_kore_region, LocationName.knautilus_region)
connect(world, player, names, LocationName.krematoa_region, LocationName.kastle_kaos_region,
lambda state: (state.has(ItemName.krematoa_cog, player, 5)))
else:
connect(world, player, names, LocationName.kaos_kore_region, LocationName.kastle_kaos_region)
connect(world, player, names, LocationName.krematoa_region, LocationName.knautilus_region,
lambda state: (state.has(ItemName.krematoa_cog, player, 5)))
def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None, exits=None):

View File

@@ -11,187 +11,270 @@ import os
import math
level_unlock_map = {
0x657: [0x65A],
0x65A: [0x680, 0x639, 0x659],
0x659: [0x65D],
0x65D: [0x65C],
0x65C: [0x688, 0x64F],
0x662: [0x681, 0x664],
0x664: [0x65B],
0x65B: [0x689, 0x661],
0x661: [0x63A, 0x666],
0x666: [0x650, 0x649],
0x667: [0x66A],
0x66A: [0x682, 0x658],
0x658: [0x68A, 0x66B],
0x66B: [0x668],
0x668: [0x651],
0x66D: [0x63C, 0x672],
0x672: [0x68B, 0x660],
0x660: [0x683, 0x66E],
0x66E: [0x670],
0x670: [0x652],
0x673: [0x684, 0x65F],
0x65F: [0x66C],
0x66C: [0x66F],
0x66F: [0x65E],
0x65E: [0x63D, 0x653, 0x68C, 0x64C],
0x676: [0x63E, 0x674, 0x685],
0x674: [0x63F, 0x669],
0x669: [0x677],
0x677: [0x68D, 0x675],
0x675: [0x654],
0x67A: [0x640, 0x678],
0x678: [0x665],
0x665: [0x686, 0x679],
0x679: [0x68E, 0x671],
0x67B: [0x67C],
0x67C: [0x67D],
0x67D: [0x663],
0x663: [0x67E],
}
location_rom_data = {
0xDC3000: [0x657, 1], # Lakeside Limbo
0xDC3001: [0x657, 2],
0xDC3002: [0x657, 3],
0xDC3003: [0x657, 5],
0xDC3100: [0x657, 7],
0xDC3004: [0x65A, 1], # Doorstop Dash
0xDC3005: [0x65A, 2],
0xDC3006: [0x65A, 3],
0xDC3007: [0x65A, 5],
0xDC3104: [0x65A, 7],
0xDC3008: [0x659, 1], # Tidal Trouble
0xDC3009: [0x659, 2],
0xDC300A: [0x659, 3],
0xDC300B: [0x659, 5],
0xDC3108: [0x659, 7],
0xDC300C: [0x65D, 1], # Skidda's Row
0xDC300D: [0x65D, 2],
0xDC300E: [0x65D, 3],
0xDC300F: [0x65D, 5],
0xDC310C: [0x65D, 7],
0xDC3010: [0x65C, 1], # Murky Mill
0xDC3011: [0x65C, 2],
0xDC3012: [0x65C, 3],
0xDC3013: [0x65C, 5],
0xDC3110: [0x65C, 7],
0xDC3014: [0x662, 1], # Barrel Shield Bust-Up
0xDC3015: [0x662, 2],
0xDC3016: [0x662, 3],
0xDC3017: [0x662, 5],
0xDC3114: [0x662, 7],
0xDC3018: [0x664, 1], # Riverside Race
0xDC3019: [0x664, 2],
0xDC301A: [0x664, 3],
0xDC301B: [0x664, 5],
0xDC3118: [0x664, 7],
0xDC301C: [0x65B, 1], # Squeals on Wheels
0xDC301D: [0x65B, 2],
0xDC301E: [0x65B, 3],
0xDC301F: [0x65B, 5],
0xDC311C: [0x65B, 7],
0xDC3020: [0x661, 1], # Springin' Spiders
0xDC3021: [0x661, 2],
0xDC3022: [0x661, 3],
0xDC3023: [0x661, 5],
0xDC3120: [0x661, 7],
0xDC3024: [0x666, 1], # Bobbing Barrel Brawl
0xDC3025: [0x666, 2],
0xDC3026: [0x666, 3],
0xDC3027: [0x666, 5],
0xDC3124: [0x666, 7],
0xDC3028: [0x667, 1], # Bazza's Blockade
0xDC3029: [0x667, 2],
0xDC302A: [0x667, 3],
0xDC302B: [0x667, 5],
0xDC3128: [0x667, 7],
0xDC302C: [0x66A, 1], # Rocket Barrel Ride
0xDC302D: [0x66A, 2],
0xDC302E: [0x66A, 3],
0xDC302F: [0x66A, 5],
0xDC312C: [0x66A, 7],
0xDC3030: [0x658, 1], # Kreeping Klasps
0xDC3031: [0x658, 2],
0xDC3032: [0x658, 3],
0xDC3033: [0x658, 5],
0xDC3130: [0x658, 7],
0xDC3034: [0x66B, 1], # Tracker Barrel Trek
0xDC3035: [0x66B, 2],
0xDC3036: [0x66B, 3],
0xDC3037: [0x66B, 5],
0xDC3134: [0x66B, 7],
0xDC3038: [0x668, 1], # Fish Food Frenzy
0xDC3039: [0x668, 2],
0xDC303A: [0x668, 3],
0xDC303B: [0x668, 5],
0xDC3138: [0x668, 7],
0xDC303C: [0x66D, 1], # Fire-ball Frenzy
0xDC303D: [0x66D, 2],
0xDC303E: [0x66D, 3],
0xDC303F: [0x66D, 5],
0xDC313C: [0x66D, 7],
0xDC3040: [0x672, 1], # Demolition Drainpipe
0xDC3041: [0x672, 2],
0xDC3042: [0x672, 3],
0xDC3043: [0x672, 5],
0xDC3140: [0x672, 7],
0xDC3044: [0x660, 1], # Ripsaw Rage
0xDC3045: [0x660, 2],
0xDC3046: [0x660, 3],
0xDC3047: [0x660, 5],
0xDC3144: [0x660, 7],
0xDC3048: [0x66E, 1], # Blazing Bazukas
0xDC3049: [0x66E, 2],
0xDC304A: [0x66E, 3],
0xDC304B: [0x66E, 5],
0xDC3148: [0x66E, 7],
0xDC304C: [0x670, 1], # Low-G Labyrinth
0xDC304D: [0x670, 2],
0xDC304E: [0x670, 3],
0xDC304F: [0x670, 5],
0xDC314C: [0x670, 7],
0xDC3050: [0x673, 1], # Krevice Kreepers
0xDC3051: [0x673, 2],
0xDC3052: [0x673, 3],
0xDC3053: [0x673, 5],
0xDC3150: [0x673, 7],
0xDC3054: [0x65F, 1], # Tearaway Toboggan
0xDC3055: [0x65F, 2],
0xDC3056: [0x65F, 3],
0xDC3057: [0x65F, 5],
0xDC3154: [0x65F, 7],
0xDC3058: [0x66C, 1], # Barrel Drop Bounce
0xDC3059: [0x66C, 2],
0xDC305A: [0x66C, 3],
0xDC305B: [0x66C, 5],
0xDC3158: [0x66C, 7],
0xDC305C: [0x66F, 1], # Krack-Shot Kroc
0xDC305D: [0x66F, 2],
0xDC305E: [0x66F, 3],
0xDC305F: [0x66F, 5],
0xDC315C: [0x66F, 7],
0xDC3060: [0x65E, 1], # Lemguin Lunge
0xDC3061: [0x65E, 2],
0xDC3062: [0x65E, 3],
0xDC3063: [0x65E, 5],
0xDC3160: [0x65E, 7],
0xDC3064: [0x676, 1], # Buzzer Barrage
0xDC3065: [0x676, 2],
0xDC3066: [0x676, 3],
0xDC3067: [0x676, 5],
0xDC3164: [0x676, 7],
0xDC3068: [0x674, 1], # Kong-Fused Cliffs
0xDC3069: [0x674, 2],
0xDC306A: [0x674, 3],
0xDC306B: [0x674, 5],
0xDC3168: [0x674, 7],
0xDC306C: [0x669, 1], # Floodlit Fish
0xDC306D: [0x669, 2],
0xDC306E: [0x669, 3],
0xDC306F: [0x669, 5],
0xDC316C: [0x669, 7],
0xDC3070: [0x677, 1], # Pothole Panic
0xDC3071: [0x677, 2],
0xDC3072: [0x677, 3],
0xDC3073: [0x677, 5],
0xDC3170: [0x677, 7],
0xDC3074: [0x675, 1], # Ropey Rumpus
0xDC3075: [0x675, 2],
0xDC3076: [0x675, 3],
0xDC3077: [0x675, 5],
0xDC3174: [0x675, 7],
0xDC3078: [0x67A, 1], # Konveyor Rope Klash
0xDC3079: [0x67A, 2],
0xDC307A: [0x67A, 3],
0xDC307B: [0x67A, 5],
0xDC3178: [0x67A, 7],
0xDC307C: [0x678, 1], # Creepy Caverns
0xDC307D: [0x678, 2],
0xDC307E: [0x678, 3],
0xDC307F: [0x678, 5],
0xDC317C: [0x678, 7],
0xDC3080: [0x665, 1], # Lightning Lookout
0xDC3081: [0x665, 2],
0xDC3082: [0x665, 3],
0xDC3083: [0x665, 5],
0xDC3180: [0x665, 7],
0xDC3084: [0x679, 1], # Koindozer Klamber
0xDC3085: [0x679, 2],
0xDC3086: [0x679, 3],
0xDC3087: [0x679, 5],
0xDC3184: [0x679, 7],
0xDC3088: [0x671, 1], # Poisonous Pipeline
0xDC3089: [0x671, 2],
0xDC308A: [0x671, 3],
0xDC308B: [0x671, 5],
0xDC3188: [0x671, 7],
0xDC308C: [0x67B, 1], # Stampede Sprint
@@ -199,23 +282,27 @@ location_rom_data = {
0xDC308E: [0x67B, 3],
0xDC308F: [0x67B, 4],
0xDC3090: [0x67B, 5],
0xDC318C: [0x67B, 7],
0xDC3091: [0x67C, 1], # Criss Kross Cliffs
0xDC3092: [0x67C, 2],
0xDC3093: [0x67C, 3],
0xDC3094: [0x67C, 5],
0xDC3191: [0x67C, 7],
0xDC3095: [0x67D, 1], # Tyrant Twin Tussle
0xDC3096: [0x67D, 2],
0xDC3097: [0x67D, 3],
0xDC3098: [0x67D, 4],
0xDC3099: [0x67D, 5],
0xDC3195: [0x67D, 7],
0xDC309A: [0x663, 1], # Swoopy Salvo
0xDC309B: [0x663, 2],
0xDC309C: [0x663, 3],
0xDC309D: [0x663, 4],
0xDC309E: [0x663, 5],
0xDC319A: [0x663, 7],
0xDC309F: [0x67E, 1], # Rocket Rush
0xDC30A0: [0x67E, 5],
@@ -243,7 +330,7 @@ location_rom_data = {
#0xDC30B4: [0x64D, 1], # Disabled until Trade Sequence
0xDC30B5: [0x64E, 1],
0xDC30B6: [0x5FD, 4], # Banana Bird Mother
0xDC30B6: [0x5FE, 4], # Banana Bird Mother
# DKC3_TODO: Disabled until Trade Sequence
#0xDC30B7: [0x615, 2, True],
@@ -256,6 +343,18 @@ location_rom_data = {
#0xDC30BE: [0x625, 4, True],
}
boss_location_ids = [
0xDC30A1,
0xDC30A2,
0xDC30A3,
0xDC30A4,
0xDC30A5,
0xDC30A6,
0xDC30A7,
0xDC30A8,
0xDC30B6,
]
item_rom_data = {
0xDC3001: [0x5D5], # 1-Up Balloon
@@ -400,10 +499,13 @@ def patch_rom(world, rom, player, active_level_list):
rom.write_byte(0x3484DE, 0xEA)
rom.write_byte(0x348528, 0x80) # Prevent Single-Ski Lock
# Make Swanky free
rom.write_byte(0x348C48, 0x00)
rom.write_bytes(0x34AB70, bytearray([0xEA, 0xEA]))
rom.write_bytes(0x34ABF7, bytearray([0xEA, 0xEA]))
rom.write_bytes(0x34ACD0, bytearray([0xEA, 0xEA]))
# Banana Bird Costs
if world.goal[player] == "banana_bird_hunt":
banana_bird_cost = math.floor(world.number_of_banana_birds[player] * world.percentage_of_banana_birds[player] / 100.0)
@@ -462,6 +564,25 @@ def patch_rom(world, rom, player, active_level_list):
rom.write_byte(0x9130, world.starting_life_count[player].value)
rom.write_byte(0x913B, world.starting_life_count[player].value)
# Cheat options
cheat_bytes = [0x00, 0x00]
if world.merry[player]:
cheat_bytes[0] |= 0x01
if world.autosave[player]:
cheat_bytes[0] |= 0x02
if world.difficulty[player] == "tufst":
cheat_bytes[0] |= 0x80
cheat_bytes[1] |= 0x80
elif world.difficulty[player] == "hardr":
cheat_bytes[0] |= 0x00
cheat_bytes[1] |= 0x00
elif world.difficulty[player] == "norml":
cheat_bytes[1] |= 0x40
rom.write_bytes(0x8303, bytearray(cheat_bytes))
# Handle Level Shuffle Here
if world.level_shuffle[player]:
@@ -469,6 +590,9 @@ def patch_rom(world, rom, player, active_level_list):
rom.write_byte(level_dict[level_list[i]].nameIDAddress, level_dict[active_level_list[i]].nameID)
rom.write_byte(level_dict[level_list[i]].levelIDAddress, level_dict[active_level_list[i]].levelID)
rom.write_byte(0x3FF800 + level_dict[active_level_list[i]].levelID, level_dict[level_list[i]].levelID)
rom.write_byte(0x3FF860 + level_dict[level_list[i]].levelID, level_dict[active_level_list[i]].levelID)
# First levels of each world
rom.write_byte(0x34BC3E, (0x32 + level_dict[active_level_list[0]].levelID))
rom.write_byte(0x34BC47, (0x32 + level_dict[active_level_list[5]].levelID))
@@ -495,6 +619,52 @@ def patch_rom(world, rom, player, active_level_list):
rom.write_byte(0x32F339, 0x55)
# Handle KONGsanity Here
if world.kongsanity[player]:
# Arich's Hoard KONGsanity fix
rom.write_bytes(0x34BA8C, bytearray([0xEA, 0xEA]))
# Don't hide the level flag if the 0x80 bit is set
rom.write_bytes(0x34CE92, bytearray([0x80]))
# Use the `!` next to level name for indicating KONG letters
rom.write_bytes(0x34B8F0, bytearray([0x80]))
rom.write_bytes(0x34B8F3, bytearray([0x80]))
# Hijack to code to set the 0x80 flag for the level when you complete KONG
rom.write_bytes(0x3BCD4B, bytearray([0x22, 0x80, 0xFA, 0XB8])) # JSL $B8FA80
rom.write_bytes(0x38FA80, bytearray([0xDA])) # PHX
rom.write_bytes(0x38FA81, bytearray([0x48])) # PHA
rom.write_bytes(0x38FA82, bytearray([0x08])) # PHP
rom.write_bytes(0x38FA83, bytearray([0xE2, 0x20])) # SEP #20
rom.write_bytes(0x38FA85, bytearray([0x48])) # PHA
rom.write_bytes(0x38FA86, bytearray([0x18])) # CLC
rom.write_bytes(0x38FA87, bytearray([0x6D, 0xD3, 0x18])) # ADC $18D3
rom.write_bytes(0x38FA8A, bytearray([0x8D, 0xD3, 0x18])) # STA $18D3
rom.write_bytes(0x38FA8D, bytearray([0x68])) # PLA
rom.write_bytes(0x38FA8E, bytearray([0xC2, 0x20])) # REP 20
rom.write_bytes(0x38FA90, bytearray([0X18])) # CLC
rom.write_bytes(0x38FA91, bytearray([0x6D, 0xD5, 0x05])) # ADC $05D5
rom.write_bytes(0x38FA94, bytearray([0x8D, 0xD5, 0x05])) # STA $05D5
rom.write_bytes(0x38FA97, bytearray([0xAE, 0xB9, 0x05])) # LDX $05B9
rom.write_bytes(0x38FA9A, bytearray([0xBD, 0x32, 0x06])) # LDA $0632, X
rom.write_bytes(0x38FA9D, bytearray([0x09, 0x80, 0x00])) # ORA #8000
rom.write_bytes(0x38FAA0, bytearray([0x9D, 0x32, 0x06])) # STA $0632, X
rom.write_bytes(0x38FAA3, bytearray([0xAD, 0xD5, 0x18])) # LDA $18D5
rom.write_bytes(0x38FAA6, bytearray([0xD0, 0x03])) # BNE $80EA
rom.write_bytes(0x38FAA8, bytearray([0x9C, 0xD9, 0x18])) # STZ $18D9
rom.write_bytes(0x38FAAB, bytearray([0xA9, 0x78, 0x00])) # LDA #0078
rom.write_bytes(0x38FAAE, bytearray([0x8D, 0xD5, 0x18])) # STA $18D5
rom.write_bytes(0x38FAB1, bytearray([0x28])) # PLP
rom.write_bytes(0x38FAB2, bytearray([0x68])) # PLA
rom.write_bytes(0x38FAB3, bytearray([0xFA])) # PLX
rom.write_bytes(0x38FAB4, bytearray([0x6B])) # RTL
# End Handle KONGsanity
# Handle Credits
rom.write_bytes(0x32A5DF, bytearray([0x41, 0x52, 0x43, 0x48, 0x49, 0x50, 0x45, 0x4C, 0x41, 0x47, 0x4F, 0x20, 0x4D, 0x4F, 0xC4])) # "ARCHIPELAGO MOD"
rom.write_bytes(0x32A5EE, bytearray([0x00, 0x03, 0x50, 0x4F, 0x52, 0x59, 0x47, 0x4F, 0x4E, 0xC5])) # "PORYGONE"
from Main import __version__
rom.name = bytearray(f'D3{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21]
@@ -516,6 +686,17 @@ def patch_rom(world, rom, player, active_level_list):
rom.write_byte(0x32DD63, 0xEA)
rom.write_byte(0x32DD64, 0xEA)
# Don't grant Banana Birds at Bears
rom.write_byte(0x3492DB, 0xEA)
rom.write_byte(0x3492DC, 0xEA)
rom.write_byte(0x3492DD, 0xEA)
rom.write_byte(0x3493F4, 0xEA)
rom.write_byte(0x3493F5, 0xEA)
rom.write_byte(0x3493F6, 0xEA)
# Don't grant present at Blizzard
rom.write_byte(0x8454, 0x00)
# Don't grant Patch and Skis from their bosses
rom.write_byte(0x3F3762, 0x00)
rom.write_byte(0x3F377B, 0x00)

View File

@@ -4,7 +4,7 @@ import math
import threading
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
from .Items import DKC3Item, ItemData, item_table, inventory_table
from .Items import DKC3Item, ItemData, item_table, inventory_table, junk_table
from .Locations import DKC3Location, all_locations, setup_locations
from .Options import dkc3_options
from .Regions import create_regions, connect_regions
@@ -40,7 +40,7 @@ class DKC3World(World):
game: str = "Donkey Kong Country 3"
option_definitions = dkc3_options
topology_present = False
data_version = 1
data_version = 2
#hint_blacklist = {LocationName.rocket_rush_flag}
item_name_to_id = {name: data.code for name, data in item_table.items()}
@@ -99,10 +99,13 @@ class DKC3World(World):
# Bosses
total_required_locations += number_of_bosses
# Secret Caves
total_required_locations += 13
if self.world.kongsanity[self.player]:
total_required_locations += 39
## Brothers Bear
if False:#self.world.include_trade_sequence[self.player]:
total_required_locations += 10
@@ -118,7 +121,11 @@ class DKC3World(World):
total_junk_count = total_required_locations - len(itempool)
itempool += [self.create_item(ItemName.bear_coin)] * total_junk_count
junk_pool = []
for item_name in self.world.random.choices(list(junk_table.keys()), k=total_junk_count):
junk_pool += [self.create_item(item_name)]
itempool += junk_pool
self.active_level_list = level_list.copy()

View File

@@ -34,7 +34,8 @@ base_info = {
"factorio_version": "1.1",
"dependencies": [
"base >= 1.1.0",
"? science-not-invited"
"? science-not-invited",
"? factory-levels"
]
}
@@ -107,7 +108,7 @@ def generate_mod(world, output_directory: str):
random = multiworld.slot_seeds[player]
def flop_random(low, high, base=None):
"""Guarentees 50% below base and 50% above base, uniform distribution in each direction."""
"""Guarantees 50% below base and 50% above base, uniform distribution in each direction."""
if base:
distance = random.random()
if random.randint(0, 1):

View File

@@ -137,8 +137,6 @@ class Progressive(Choice):
option_off = 0
option_grouped_random = 1
option_on = 2
alias_false = 0
alias_true = 2
default = 2
def want_progressives(self, random):

View File

@@ -7,7 +7,8 @@
"description": "Integration client for the Archipelago Randomizer",
"factorio_version": "1.1",
"dependencies": [
"base >= 1.1.0",
"? science-not-invited"
]
"base >= 1.1.0",
"? science-not-invited",
"? factory-levels"
]
}

View File

@@ -249,6 +249,10 @@ script.on_event(defines.events.on_player_main_inventory_changed, update_player_e
function add_samples(force, name, count)
local function add_to_table(t)
if count <= 0 then
-- Fixes a bug with single craft, if a recipe gives 0 of a given item.
return
end
t[name] = (t[name] or 0) + count
end
-- Add to global table of earned samples for future new players

View File

@@ -183,6 +183,18 @@ end
data.raw["assembling-machine"]["assembling-machine-1"].crafting_categories = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-3"].crafting_categories)
data.raw["assembling-machine"]["assembling-machine-2"].crafting_categories = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-3"].crafting_categories)
data.raw["assembling-machine"]["assembling-machine-1"].fluid_boxes = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-2"].fluid_boxes)
if mods["factory-levels"] then
-- Factory-Levels allows the assembling machines to get faster (and depending on settings), more productive at crafting products, the more the
-- assembling machine crafts the product. If the machine crafts enough, it may auto-upgrade to the next tier.
for i = 1, 25, 1 do
data.raw["assembling-machine"]["assembling-machine-1-level-" .. i].crafting_categories = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-3"].crafting_categories)
data.raw["assembling-machine"]["assembling-machine-1-level-" .. i].fluid_boxes = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-2"].fluid_boxes)
end
for i = 1, 50, 1 do
data.raw["assembling-machine"]["assembling-machine-2-level-" .. i].crafting_categories = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-3"].crafting_categories)
end
end
data.raw["ammo"]["artillery-shell"].stack_size = 10
{# each randomized tech gets set to be invisible, with new nodes added that trigger those #}

View File

@@ -1,4 +1,15 @@
{% from "macros.lua" import dict_to_lua %}
-- TODO: Replace the tinting code with an actual rendered picture of the energy bridge icon.
-- This tint is so that one is less likely to accidentally mass-produce energy-bridges, then wonder why their rocket is not building.
function energy_bridge_tint()
return { r = 0, g = 1, b = 0.667, a = 1}
end
function tint_icon(obj, tint)
obj.icons = { {icon = obj.icon, icon_size = obj.icon_size, icon_mipmaps = obj.icon_mipmaps, tint = tint} }
obj.icon = nil
obj.icon_size = nil
obj.icon_mipmaps = nil
end
local energy_bridge = table.deepcopy(data.raw["accumulator"]["accumulator"])
energy_bridge.name = "ap-energy-bridge"
energy_bridge.minable.result = "ap-energy-bridge"
@@ -6,12 +17,20 @@ energy_bridge.localised_name = "Archipelago EnergyLink Bridge"
energy_bridge.energy_source.buffer_capacity = "5MJ"
energy_bridge.energy_source.input_flow_limit = "1MW"
energy_bridge.energy_source.output_flow_limit = "1MW"
tint_icon(energy_bridge, energy_bridge_tint())
energy_bridge.picture.layers[1].tint = energy_bridge_tint()
energy_bridge.picture.layers[1].hr_version.tint = energy_bridge_tint()
energy_bridge.charge_animation.layers[1].layers[1].tint = energy_bridge_tint()
energy_bridge.charge_animation.layers[1].layers[1].hr_version.tint = energy_bridge_tint()
energy_bridge.discharge_animation.layers[1].layers[1].tint = energy_bridge_tint()
energy_bridge.discharge_animation.layers[1].layers[1].hr_version.tint = energy_bridge_tint()
data.raw["accumulator"]["ap-energy-bridge"] = energy_bridge
local energy_bridge_item = table.deepcopy(data.raw["item"]["accumulator"])
energy_bridge_item.name = "ap-energy-bridge"
energy_bridge_item.localised_name = "Archipelago EnergyLink Bridge"
energy_bridge_item.place_result = energy_bridge.name
tint_icon(energy_bridge_item, energy_bridge_tint())
data.raw["item"]["ap-energy-bridge"] = energy_bridge_item
local energy_bridge_recipe = table.deepcopy(data.raw["recipe"]["accumulator"])

View File

@@ -1,3 +1,21 @@
-- Find out if more than one AP mod is loaded, and if so, error out.
function mod_is_AP(str)
-- lua string.match is way more restrictive than regex. Regex would be "^AP-W?\d{20}-P[1-9]\d*-.+$"
local result = string.match(str, "^AP%-W?%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%-P[1-9]%d-%-.+$")
if result ~= nil then
log("Archipelago Mod: " .. result .. " is loaded.")
end
return result ~= nil
end
local ap_mod_count = 0
for name, _ in pairs(mods) do
if mod_is_AP(name) then
ap_mod_count = ap_mod_count + 1
if ap_mod_count > 1 then
error("More than one Archipelago Factorio mod is loaded.")
end
end
end
data:extend({
{
type = "bool-setting",

View File

@@ -20,7 +20,7 @@ FF1_STARTER_ITEMS = [
FF1_PROGRESSION_LIST = [
"Rod", "Cube", "Lute", "Key", "Chime", "Oxyale",
"Ship", "Canoe", "Floater", "Canal",
"Ship", "Canoe", "Floater", "Mark", "Sigil", "Canal",
"Crown", "Crystal", "Herb", "Tnt", "Adamant", "Slab", "Ruby", "Bottle",
"Shard",
"EarthOrb", "FireOrb", "WaterOrb", "AirOrb"

View File

@@ -31,7 +31,7 @@ class FF1World(World):
game = "Final Fantasy"
topology_present = False
remote_items = True
data_version = 1
data_version = 2
remote_start_inventory = True
ff1_items = FF1Items()
@@ -66,7 +66,10 @@ class FF1World(World):
def goal_rule_and_shards(state):
return goal_rule(state) and state.has("Shard", self.player, 32)
terminated_event.access_rule = goal_rule_and_shards
if "MARK" in items.keys():
# Fail generation for Noverworld and provide link to old FFR website
raise Exception("FFR Noverworld seeds must be generated on an older version of FFR. Please ensure you generated the settings using "
"4-4-0.finalfantasyrandomizer.com")
menu_region.locations.append(terminated_event)
self.world.regions += [menu_region]

View File

@@ -190,5 +190,7 @@
"Ship": 480,
"Bridge": 488,
"Canal": 492,
"Canoe": 498
"Canoe": 498,
"Sigil": 499,
"Mark": 500
}

View File

@@ -49,11 +49,11 @@ def exclusion_rules(world, player: int, exclude_locations: typing.Set[str]):
location.progress_type = LocationProgressType.EXCLUDED
def set_rule(spot, rule: CollectionRule):
def set_rule(spot: typing.Union["BaseClasses.Location", "BaseClasses.Entrance"], rule: CollectionRule):
spot.access_rule = rule
def add_rule(spot, rule: CollectionRule, combine='and'):
def add_rule(spot: typing.Union["BaseClasses.Location", "BaseClasses.Entrance"], rule: CollectionRule, combine='and'):
old_rule = spot.access_rule
if combine == 'or':
spot.access_rule = lambda state: rule(state) or old_rule(state)
@@ -61,35 +61,37 @@ def add_rule(spot, rule: CollectionRule, combine='and'):
spot.access_rule = lambda state: rule(state) and old_rule(state)
def forbid_item(location, item: str, player: int):
def forbid_item(location: "BaseClasses.Location", item: str, player: int):
old_rule = location.item_rule
location.item_rule = lambda i: (i.name != item or i.player != player) and old_rule(i)
def forbid_items_for_player(location, items: typing.Set[str], player: int):
def forbid_items_for_player(location: "BaseClasses.Location", items: typing.Set[str], player: int):
old_rule = location.item_rule
location.item_rule = lambda i: (i.player != player or i.name not in items) and old_rule(i)
def forbid_items(location, items: typing.Set[str]):
def forbid_items(location: "BaseClasses.Location", items: typing.Set[str]):
"""unused, but kept as a debugging tool."""
old_rule = location.item_rule
location.item_rule = lambda i: i.name not in items and old_rule(i)
def add_item_rule(location, rule: ItemRule):
def add_item_rule(location: "BaseClasses.Location", rule: ItemRule):
old_rule = location.item_rule
location.item_rule = lambda item: rule(item) and old_rule(item)
def item_in_locations(state, item: str, player: int, locations: typing.Sequence):
def item_in_locations(state: "BaseClasses.CollectionState", item: str, player: int,
locations: typing.Sequence["BaseClasses.Location"]) -> bool:
for location in locations:
if item_name(state, location[0], location[1]) == (item, player):
return True
return False
def item_name(state, location: str, player: int) -> typing.Optional[typing.Tuple[str, int]]:
def item_name(state: "BaseClasses.CollectionState", location: str, player: int) -> \
typing.Optional[typing.Tuple[str, int]]:
location = state.world.get_location(location, player)
if location.item is None:
return None

View File

@@ -409,7 +409,6 @@ class DeathLink(Choice):
shade: DeathLink functions like a normal death if you do not already have a shade, shadeless otherwise.
"""
option_off = 0
alias_false = 0
alias_no = 0
alias_true = 1
alias_on = 1
@@ -435,10 +434,8 @@ class CostSanity(Choice):
These costs can be in Geo (except Grubfather, Seer and Eggshop), Grubs, Charms, Essence and/or Rancid Eggs
"""
option_off = 0
alias_false = 0
alias_no = 0
option_on = 1
alias_true = 1
alias_yes = 1
option_shopsonly = 2
option_notshops = 3

View File

@@ -276,10 +276,10 @@ def set_advancement_rules(world: MultiWorld, player: int):
# 1.19 advancements
# can make a cake, and can reach a pillager outposts for allays
set_rule(world.get_location("Birthday Song", player), lambda state: state.can_reach("The Lie", "Location", player))
# find allay and craft a noteblock
set_rule(world.get_location("You've Got a Friend in Me", player), lambda state: state.has("Progressive Tools", player, 2) and state._mc_has_iron_ingots(player))
# can make a cake, and a noteblock, and can reach a pillager outposts for allays
set_rule(world.get_location("Birthday Song", player), lambda state: state.can_reach("The Lie", "Location", player) and state.has("Progressive Tools", player, 2) and state._mc_has_iron_ingots(player))
# can get to outposts.
# set_rule(world.get_location("You've Got a Friend in Me", player), lambda state: True)
# craft bucket and adventure to find frog spawning biome
set_rule(world.get_location("Bukkit Bukkit", player), lambda state: state.has("Bucket", player) and state._mc_has_iron_ingots(player) and state._mc_can_adventure(player))
# I don't like this one its way to easy to get. just a pain to find.

View File

@@ -7,9 +7,9 @@ config file.
## What does randomization do to this game?
Recipes are removed from the crafting book and shuffled into the item pool. It can also optionally change which
Some recipes are locked from being able to be crafted and shuffled into the item pool. It can also optionally change which
structures appear in each dimension. Crafting recipes are re-learned when they are received from other players as item
checks, and occasionally when completing your own achievements.
checks, and occasionally when completing your own achievements. See below for which recipes are shuffled.
## What is considered a location check in minecraft?
@@ -25,3 +25,86 @@ inventory directly.
Victory is achieved when the player kills the Ender Dragon, enters the portal in The End, and completes the credits
sequence either by skipping it or watching hit play out.
## Which recipes are locked?
* Archery
* Bow
* Arrow
* Crossbow
* Brewing
* Blaze Powder
* Brewing Stand
* Enchanting
* Enchanting Table
* Bookshelf
* Bucket
* Flint & Steel
* All Beds
* Bottles
* Shield
* Fishing Rod
* Fishing Rod
* Carrot on a Stick
* Warped Fungus on a Stick
* Campfire
* Campfire
* Soul Campfire
* Spyglass
* Lead
* Progressive Weapons
* Tier I
* Stone Sword
* Stone Axe
* Tier II
* Iron Sword
* Iron Axe
* Tier III
* Diamond Sword
* Diamond Axe
* Progessive Tools
* Tier I
* Stone Shovel
* Stone Hoe
* Tier II
* Iron Shovel
* Iron Hoe
* Tier III
* Diamond Shovel
* Diamond Hoe
* Netherite Ingot
* Progressive Armor
* Tier I
* Iron Helmet
* Iron Chestplate
* Iron Leggings
* Iron Boots
* Tier II
* Diamond Helmet
* Diamond Chestplate
* Diamond Leggings
* Diamond Boots
* Progressive Resource Crafting
* Tier I
* Iron Ingot from Nuggets
* Iron Nugget
* Gold Ingot from Nuggets
* Gold Nugget
* Furnace
* Blast Furnace
* Tier II
* Redstone
* Redstone Block
* Glowstone
* Iron Ingot from Iron Block
* Iron Block
* Gold Ingot from Gold Block
* Gold Block
* Diamond
* Diamond Block
* Netherite Block
* Netherite Ingot from Netherite Block
* Anvil
* Emerald
* Emerald Block
* Copper Block

View File

@@ -129,6 +129,8 @@ def getItemGenericName(item):
def isRestrictedDungeonItem(dungeon, item):
if not isinstance(item, OOTItem):
return False
if (item.map or item.compass) and dungeon.world.shuffle_mapcompass == 'dungeon':
return item in dungeon.dungeon_items
if item.type == 'SmallKey' and dungeon.world.shuffle_smallkeys == 'dungeon':

View File

@@ -1388,6 +1388,10 @@ def get_pool_core(world):
remove_junk_pool = list(remove_junk_pool) + ['Recovery Heart', 'Bombs (20)', 'Arrows (30)', 'Ice Trap']
junk_candidates = [item for item in pool if item in remove_junk_pool]
if len(pending_junk_pool) > len(junk_candidates):
excess = len(pending_junk_pool) - len(junk_candidates)
if world.triforce_hunt:
raise RuntimeError(f"Items in the pool for player {world.player} exceed locations. Add {excess} location(s) or remove {excess} triforce piece(s).")
while pending_junk_pool:
pending_item = pending_junk_pool.pop()
if not junk_candidates:

View File

@@ -22,6 +22,12 @@ def ap_id_to_oot_data(ap_id):
raise Exception(f'Could not find desired item ID: {ap_id}')
def oot_is_item_of_type(item, item_type):
if not isinstance(item, OOTItem):
return False
return item.type == item_type
class OOTItem(Item):
game: str = "Ocarina of Time"
type: str
@@ -43,7 +49,6 @@ class OOTItem(Item):
self.type = type
self.index = index
self.special = special or {}
self.looks_like_item = None
self.price = special.get('price', None) if special else None
self.internal = False

View File

@@ -101,7 +101,6 @@ class InteriorEntrances(Choice):
option_off = 0
option_simple = 1
option_all = 2
alias_false = 0
alias_true = 2
@@ -141,7 +140,6 @@ class MixEntrancePools(Choice):
option_off = 0
option_indoor = 1
option_all = 2
alias_false = 0
class DecoupleEntrances(Toggle):
@@ -158,12 +156,12 @@ class TriforceGoal(Range):
"""Number of Triforce pieces required to complete the game."""
display_name = "Required Triforce Pieces"
range_start = 1
range_end = 100
range_end = 80
default = 20
class ExtraTriforces(Range):
"""Percentage of additional Triforce pieces in the pool, separate from the item pool setting."""
"""Percentage of additional Triforce pieces in the pool. With high numbers, you may need to randomize additional locations to have enough items."""
display_name = "Percentage of Extra Triforce Pieces"
range_start = 0
range_end = 100
@@ -308,7 +306,6 @@ class ShopShuffle(Choice):
option_off = 0
option_fixed_number = 1
option_random_number = 2
alias_false = 0
class ShopSlots(Range):
@@ -326,7 +323,6 @@ class TokenShuffle(Choice):
option_dungeons = 1
option_overworld = 2
option_all = 3
alias_false = 0
class ScrubShuffle(Choice):
@@ -336,7 +332,6 @@ class ScrubShuffle(Choice):
option_low = 1
option_regular = 2
option_random_prices = 3
alias_false = 0
alias_affordable = 1
alias_expensive = 2
@@ -569,7 +564,6 @@ class Hints(Choice):
option_agony = 2
option_always = 3
default = 3
alias_false = 0
class MiscHints(DefaultOnToggle):
@@ -673,8 +667,6 @@ class IceTraps(Choice):
option_mayhem = 3
option_onslaught = 4
default = 1
alias_false = 0
alias_true = 2
alias_extra = 2
@@ -742,7 +734,6 @@ class Music(Choice):
option_normal = 0
option_off = 1
option_randomized = 2
alias_false = 1
class BackgroundMusic(Music):

View File

@@ -1844,7 +1844,7 @@ def write_rom_item(rom, item_id, item):
def get_override_table(world):
return list(filter(lambda val: val != None, map(partial(get_override_entry, world.player), world.world.get_filled_locations(world.player))))
return list(filter(lambda val: val != None, map(partial(get_override_entry, world), world.world.get_filled_locations(world.player))))
override_struct = struct.Struct('>xBBBHBB') # match override_t in get_items.c
@@ -1852,10 +1852,10 @@ def get_override_table_bytes(override_table):
return b''.join(sorted(itertools.starmap(override_struct.pack, override_table)))
def get_override_entry(player_id, location):
def get_override_entry(ootworld, location):
scene = location.scene
default = location.default
player_id = 0 if player_id == location.item.player else min(location.item.player, 255)
player_id = 0 if ootworld.player == location.item.player else min(location.item.player, 255)
if location.item.game != 'Ocarina of Time':
# This is an AP sendable. It's guaranteed to not be None.
if location.item.advancement:
@@ -1869,7 +1869,7 @@ def get_override_entry(player_id, location):
if location.item.trap:
item_id = 0x7C # Ice Trap ID, to get "X is a fool" message
looks_like_item_id = location.item.looks_like_item.index
looks_like_item_id = ootworld.trap_appearances[location.address].index
else:
looks_like_item_id = 0
@@ -2091,7 +2091,8 @@ def get_locked_doors(rom, world):
return [0x00D4 + scene * 0x1C + 0x04 + flag_byte, flag_bits]
# If boss door, set the door's unlock flag
if (world.shuffle_bosskeys == 'remove' and scene != 0x0A) or (world.shuffle_ganon_bosskey == 'remove' and scene == 0x0A):
if (world.shuffle_bosskeys == 'remove' and scene != 0x0A) or (
world.shuffle_ganon_bosskey == 'remove' and scene == 0x0A and not world.triforce_hunt):
if actor_id == 0x002E and actor_type == 0x05:
return [0x00D4 + scene * 0x1C + 0x04 + flag_byte, flag_bits]
@@ -2109,23 +2110,20 @@ def place_shop_items(rom, world, shop_items, messages, locations, init_shop_id=F
rom.write_int16(location.address1, location.item.index)
else:
if location.item.trap:
item_display = location.item.looks_like_item
elif location.item.game != "Ocarina of Time":
item_display = location.item
if location.item.advancement:
item_display.index = 0xCB
else:
item_display.index = 0xCC
item_display.special = {}
item_display = world.trap_appearances[location.address]
else:
item_display = location.item
# bottles in shops should look like empty bottles
# so that that are different than normal shop refils
if 'shop_object' in item_display.special:
rom_item = read_rom_item(rom, item_display.special['shop_object'])
if location.item.trap or location.item.game == "Ocarina of Time":
if 'shop_object' in item_display.special:
rom_item = read_rom_item(rom, item_display.special['shop_object'])
else:
rom_item = read_rom_item(rom, item_display.index)
else:
rom_item = read_rom_item(rom, item_display.index)
display_index = 0xCB if location.item.advancement else 0xCC
rom_item = read_rom_item(rom, display_index)
shop_objs.add(rom_item['object_id'])
shop_id = world.current_shop_id

View File

@@ -3,6 +3,7 @@ import logging
from .SaveContext import SaveContext
from .Regions import TimeOfDay
from .Items import oot_is_item_of_type
from BaseClasses import CollectionState
from worlds.generic.Rules import set_rule, add_rule, add_item_rule, forbid_item
@@ -138,7 +139,7 @@ def set_rules(ootworld):
# Sheik in Ice Cavern is the only song location in a dungeon; need to ensure that it cannot be anything else.
# This is required if map/compass included, or any_dungeon shuffle.
location = world.get_location('Sheik in Ice Cavern', player)
add_item_rule(location, lambda item: item.player == player and item.type == 'Song')
add_item_rule(location, lambda item: item.player == player and oot_is_item_of_type(item, 'Song'))
if ootworld.skip_child_zelda:
# If skip child zelda is on, the item at Song from Impa must be giveable by the save context.
@@ -181,7 +182,7 @@ def set_shop_rules(ootworld):
wallet = ootworld.parser.parse_rule('Progressive_Wallet')
wallet2 = ootworld.parser.parse_rule('(Progressive_Wallet, 2)')
for location in filter(lambda location: location.item and location.item.type == 'Shop', ootworld.get_locations()):
for location in filter(lambda location: location.item and oot_is_item_of_type(location.item, 'Shop'), ootworld.get_locations()):
# Add wallet requirements
if location.item.name in ['Buy Arrows (50)', 'Buy Fish', 'Buy Goron Tunic', 'Buy Bombchu (20)', 'Buy Bombs (30)']:
add_rule(location, wallet)

View File

@@ -8,7 +8,7 @@ logger = logging.getLogger("Ocarina of Time")
from .Location import OOTLocation, LocationFactory, location_name_to_id
from .Entrance import OOTEntrance
from .EntranceShuffle import shuffle_random_entrances, entrance_shuffle_table, EntranceShuffleError
from .Items import OOTItem, item_table, oot_data_to_ap_id
from .Items import OOTItem, item_table, oot_data_to_ap_id, oot_is_item_of_type
from .ItemPool import generate_itempool, add_dungeon_items, get_junk_item, get_junk_pool, normal_bottles
from .Regions import OOTRegion, TimeOfDay
from .Rules import set_rules, set_shop_rules, set_entrances_based_rules
@@ -305,6 +305,10 @@ class OOTWorld(World):
if self.skip_child_zelda:
self.shuffle_weird_egg = False
# Ganon boss key should not be in itempool in triforce hunt
if self.triforce_hunt:
self.shuffle_ganon_bosskey = 'remove'
# Determine skipped trials in GT
# This needs to be done before the logic rules in GT are parsed
trial_list = ['Forest', 'Fire', 'Water', 'Spirit', 'Shadow', 'Light']
@@ -313,7 +317,11 @@ class OOTWorld(World):
# Determine which dungeons are MQ
# Possible future plan: allow user to pick which dungeons are MQ
mq_dungeons = self.world.random.sample(dungeon_table, self.mq_dungeons)
if self.logic_rules == 'glitchless':
mq_dungeons = self.world.random.sample(dungeon_table, self.mq_dungeons)
else:
self.mq_dungeons = 0
mq_dungeons = []
self.dungeon_mq = {item['name']: (item in mq_dungeons) for item in dungeon_table}
# Determine tricks in logic
@@ -920,7 +928,7 @@ class OOTWorld(World):
# This includes all locations for which show_in_spoiler is false, and shuffled shop items.
for loc in self.get_locations():
if loc.address is not None and (
not loc.show_in_spoiler or (loc.item is not None and loc.item.type == 'Shop')
not loc.show_in_spoiler or oot_is_item_of_type(loc.item, 'Shop')
or (self.skip_child_zelda and loc.name in ['HC Zeldas Letter', 'Song from Impa'])):
loc.address = None
@@ -930,9 +938,10 @@ class OOTWorld(World):
with i_o_limiter:
# Make traps appear as other random items
ice_traps = [loc.item for loc in self.get_locations() if loc.item.trap]
for trap in ice_traps:
trap.looks_like_item = self.create_item(self.world.slot_seeds[self.player].choice(self.fake_items).name)
trap_location_ids = [loc.address for loc in self.get_locations() if loc.item.trap]
self.trap_appearances = {}
for loc_id in trap_location_ids:
self.trap_appearances[loc_id] = self.create_item(self.world.slot_seeds[self.player].choice(self.fake_items).name)
# Seed hint RNG, used for ganon text lines also
self.hint_rng = self.world.slot_seeds[self.player]
@@ -996,11 +1005,11 @@ class OOTWorld(World):
autoworld.major_item_locations.append(loc)
if loc.game == "Ocarina of Time" and loc.item.code and (not loc.locked or
(loc.item.type == 'Song' or
(loc.item.type == 'SmallKey' and world.worlds[loc.player].shuffle_smallkeys == 'any_dungeon') or
(loc.item.type == 'HideoutSmallKey' and world.worlds[loc.player].shuffle_fortresskeys == 'any_dungeon') or
(loc.item.type == 'BossKey' and world.worlds[loc.player].shuffle_bosskeys == 'any_dungeon') or
(loc.item.type == 'GanonBossKey' and world.worlds[loc.player].shuffle_ganon_bosskey == 'any_dungeon'))):
(oot_is_item_of_type(loc.item, 'Song') or
(oot_is_item_of_type(loc.item, 'SmallKey') and world.worlds[loc.player].shuffle_smallkeys == 'any_dungeon') or
(oot_is_item_of_type(loc.item, 'HideoutSmallKey') and world.worlds[loc.player].shuffle_fortresskeys == 'any_dungeon') or
(oot_is_item_of_type(loc.item, 'BossKey') and world.worlds[loc.player].shuffle_bosskeys == 'any_dungeon') or
(oot_is_item_of_type(loc.item, 'GanonBossKey') and world.worlds[loc.player].shuffle_ganon_bosskey == 'any_dungeon'))):
if loc.player in barren_hint_players:
hint_area = get_hint_area(loc)
items_by_region[loc.player][hint_area]['weight'] += 1
@@ -1015,7 +1024,7 @@ class OOTWorld(World):
elif barren_hint_players or woth_hint_players: # Check only relevant oot locations for barren/woth
for player in (barren_hint_players | woth_hint_players):
for loc in world.worlds[player].get_locations():
if loc.item.code and (not loc.locked or loc.item.type == 'Song'):
if loc.item.code and (not loc.locked or oot_is_item_of_type(loc.item, 'Song')):
if player in barren_hint_players:
hint_area = get_hint_area(loc)
items_by_region[player][hint_area]['weight'] += 1

View File

@@ -1,8 +1,4 @@
from Options import Range, Toggle, DefaultOnToggle, Choice
class UseResourcePacks(DefaultOnToggle):
"""Uses Resource Packs to fill out the item pool from Raft. Resource Packs have basic earlygame items such as planks, plastic, or food."""
display_name = "Use resource packs"
from Options import Range, Toggle, DefaultOnToggle, Choice, DeathLink
class MinimumResourcePackAmount(Range):
"""The minimum amount of resources available in a resource pack"""
@@ -19,23 +15,30 @@ class MaximumResourcePackAmount(Range):
default = 5
class DuplicateItems(Choice):
"""Adds duplicates of items to the item pool. These will be selected alongside
Resource Packs (if configured). Note that there are not many progression items,
and selecting Progression may produce many of the same duplicate item."""
"""Adds duplicates of items to the item pool (if configured in Filler items). These will be selected alongside Resource Packs (if configured). Note that there are not many progression items, and selecting Progression may produce many of the same duplicate item."""
display_name = "Duplicate items"
option_disabled = 0
option_progression = 1
option_non_progression = 2
option_any = 3
option_progression = 0
option_non_progression = 1
option_any = 2
default = 2
class FillerItemTypes(Choice):
"""Determines whether to use Resource Packs, Duplicate Items (as configured), or both."""
display_name = "Filler items"
option_resource_packs = 0
option_duplicates = 1
option_both = 2
class IslandFrequencyLocations(Choice):
"""Sets where frequencies for story islands are located."""
display_name = "Frequency locations"
option_vanilla = 0
option_random_on_island = 1
option_progressive = 2
option_anywhere = 3
default = 1
option_random_island_order = 2
option_random_on_island_random_order = 3
option_progressive = 4
option_anywhere = 5
default = 2
class IslandGenerationDistance(Choice):
"""Sets how far away islands spawn from you when you input their coordinates into the Receiver."""
@@ -56,7 +59,7 @@ class ProgressiveItems(DefaultOnToggle):
display_name = "Progressive items"
class BigIslandEarlyCrafting(Toggle):
"""Allows recipes that require items from big islands (eg leather) to lock earlygame items like the Receiver, Bolt, or Smelter."""
"""Allows recipes that require items from big islands (eg leather) to lock earlygame items like the Receiver, Bolt, or Smelter. Big islands are available from the start of the game, however it can take a long time to find them."""
display_name = "Early recipes behind big islands"
class PaddleboardMode(Toggle):
@@ -64,14 +67,15 @@ class PaddleboardMode(Toggle):
display_name = "Paddleboard Mode"
raft_options = {
"use_resource_packs": UseResourcePacks,
"minimum_resource_pack_amount": MinimumResourcePackAmount,
"maximum_resource_pack_amount": MaximumResourcePackAmount,
"duplicate_items": DuplicateItems,
"filler_item_types": FillerItemTypes,
"island_frequency_locations": IslandFrequencyLocations,
"island_generation_distance": IslandGenerationDistance,
"expensive_research": ExpensiveResearch,
"progressive_items": ProgressiveItems,
"big_island_early_crafting": BigIslandEarlyCrafting,
"paddleboard_mode": PaddleboardMode
"paddleboard_mode": PaddleboardMode,
"death_link": DeathLink
}

View File

@@ -12,9 +12,6 @@ class RaftLogic(LogicMixin):
def raft_can_smelt_items(self, player):
return self.has("Smelter", player)
def raft_can_find_titanium(self, player):
return self.has("Metal detector", player)
def raft_can_craft_bolt(self, player):
return self.raft_can_smelt_items(player) and self.has("Bolt", player)
@@ -27,12 +24,19 @@ class RaftLogic(LogicMixin):
def raft_can_craft_circuitBoard(self, player):
return self.raft_can_smelt_items(player) and self.has("Circuit board", player)
def raft_can_craft_shovel(self, player):
return self.raft_can_smelt_items(player) and self.has("Shovel", player) and self.raft_can_craft_bolt(player)
def raft_can_craft_reciever(self, player):
return self.raft_can_craft_circuitBoard(player) and self.raft_can_craft_hinge(player) and self.has("Receiver", player)
def raft_can_craft_antenna(self, player):
return self.raft_can_craft_circuitBoard(player) and self.raft_can_craft_bolt(player) and self.has("Antenna", player)
def raft_can_find_titanium(self, player):
return (self.has("Metal detector", player) and self.raft_can_craft_battery(player)
and self.raft_can_craft_shovel(player))
def raft_can_craft_plasticBottle(self, player):
return self.raft_can_smelt_items(player) and self.has("Empty bottle", player)
@@ -60,7 +64,7 @@ class RaftLogic(LogicMixin):
return self.raft_can_craft_hinge(player) and self.raft_can_craft_bolt(player) and self.has("Zipline tool", player)
def raft_can_get_dirt(self, player):
return self.raft_can_smelt_items(player) and self.raft_can_craft_bolt(player) and self.has("Shovel", player)
return self.raft_can_craft_shovel(player) and self.raft_big_islands_available(player)
def raft_can_craft_grassPlot(self, player):
return self.raft_can_get_dirt(player) and self.has("Grass plot", player)
@@ -88,60 +92,69 @@ class RaftLogic(LogicMixin):
return self.raft_can_access_radio_tower(player)
def raft_can_access_vasagatan(self, player):
return self.raft_can_complete_radio_tower(player) and self.raft_can_navigate(player) and self.has("Vasagatan Frequency", player)
return self.raft_can_navigate(player) and self.has("Vasagatan Frequency", player)
def raft_can_complete_vasagatan(self, player):
return self.raft_can_access_vasagatan(player)
def raft_can_access_balboa_island(self, player):
return (self.raft_can_complete_vasagatan(player)
and self.raft_can_drive(player)
and self.has("Balboa Island Frequency", player))
return self.raft_can_drive(player) and self.has("Balboa Island Frequency", player)
def raft_can_complete_balboa_island(self, player):
return self.raft_can_access_balboa_island(player) and self.raft_can_craft_machete(player)
def raft_can_access_caravan_island(self, player):
return self.raft_can_complete_balboa_island(player) and self.raft_can_drive(player) and self.has("Caravan Island Frequency", player)
return self.raft_can_drive(player) and self.has("Caravan Island Frequency", player)
def raft_can_complete_caravan_island(self, player):
return self.raft_can_access_caravan_island(player) and self.raft_can_craft_ziplineTool(player)
def raft_can_access_tangaroa(self, player):
return self.raft_can_complete_caravan_island(player) and self.raft_can_drive(player) and self.has("Tangaroa Frequency", player)
return self.raft_can_drive(player) and self.has("Tangaroa Frequency", player)
def raft_can_complete_tangaroa(self, player):
return self.raft_can_access_tangaroa(player)
return self.raft_can_access_tangaroa(player) and self.raft_can_craft_ziplineTool(player)
def raft_can_access_varuna_point(self, player):
return self.raft_can_complete_tangaroa(player) and self.raft_can_drive(player) and self.has("Varuna Point Frequency", player)
return self.raft_can_drive(player) and self.has("Varuna Point Frequency", player)
def raft_can_complete_varuna_point(self, player):
return self.raft_can_access_varuna_point(player)
return self.raft_can_access_varuna_point(player) and self.raft_can_craft_ziplineTool(player)
def raft_can_access_temperance(self, player):
return self.raft_can_complete_varuna_point(player) and self.raft_can_drive(player) and self.has("Temperance Frequency", player)
return self.raft_can_drive(player) and self.has("Temperance Frequency", player)
def raft_can_complete_temperance(self, player):
return self.raft_can_access_temperance(player)
return self.raft_can_access_temperance(player) # No zipline required on Temperance
def raft_can_access_utopia(self, player):
return self.raft_can_complete_temperance(player) and self.raft_can_drive(player) and self.has("Utopia Frequency", player)
return (self.raft_can_drive(player)
# Access checks are to prevent frequencies for other
# islands from appearing in Utopia
and self.raft_can_access_radio_tower(player)
and self.raft_can_access_vasagatan(player)
and self.raft_can_access_balboa_island(player)
and self.raft_can_access_caravan_island(player)
and self.raft_can_access_tangaroa(player)
and self.raft_can_access_varuna_point(player)
and self.raft_can_access_temperance(player)
and self.has("Utopia Frequency", player)
and self.raft_can_craft_shovel(player)) # Shovels are available but we don't want to softlock players
def raft_can_complete_utopia(self, player):
return self.raft_can_access_utopia(player)
return self.raft_can_access_utopia(player) and self.raft_can_craft_ziplineTool(player)
def set_rules(world, player):
regionChecks = {
"Raft": lambda state: True,
"ResearchTable": lambda state: True,
"RadioTower": lambda state: state.raft_can_access_radio_tower(player), # All can_access functions have state as implicit parameter for function
"Vasagatan": lambda state: state.raft_can_complete_radio_tower(player) and state.raft_can_access_vasagatan(player),
"BalboaIsland": lambda state: state.raft_can_complete_vasagatan(player) and state.raft_can_access_balboa_island(player),
"CaravanIsland": lambda state: state.raft_can_complete_balboa_island(player) and state.raft_can_access_caravan_island(player),
"Tangaroa": lambda state: state.raft_can_complete_caravan_island(player) and state.raft_can_access_tangaroa(player),
"Varuna Point": lambda state: state.raft_can_complete_tangaroa(player) and state.raft_can_access_varuna_point(player),
"Temperance": lambda state: state.raft_can_complete_varuna_point(player) and state.raft_can_access_temperance(player),
"Vasagatan": lambda state: state.raft_can_access_vasagatan(player),
"BalboaIsland": lambda state: state.raft_can_access_balboa_island(player),
"CaravanIsland": lambda state: state.raft_can_access_caravan_island(player),
"Tangaroa": lambda state: state.raft_can_access_tangaroa(player),
"Varuna Point": lambda state: state.raft_can_access_varuna_point(player),
"Temperance": lambda state: state.raft_can_access_temperance(player),
"Utopia": lambda state: state.raft_can_complete_temperance(player) and state.raft_can_access_utopia(player)
}
itemChecks = {
@@ -183,7 +196,7 @@ def set_rules(world, player):
if region != "Menu":
for exitRegion in world.get_region(region, player).exits:
set_rule(world.get_entrance(exitRegion.name, player), regionChecks[region])
# Location access rules
for location in location_table:
locFromWorld = world.get_location(location["name"], player)

View File

@@ -56,21 +56,21 @@ class RaftWorld(World):
extraItemNamePool = []
extras = len(location_table) - len(item_table) - 1 # Victory takes up 1 unaccounted-for slot
if extras > 0:
if (self.world.use_resource_packs[self.player].value):
if (self.world.filler_item_types[self.player].value != 1): # Use resource packs
for packItem in resourcePackItems:
for i in range(minimumResourcePackAmount, maximumResourcePackAmount + 1):
extraItemNamePool.append(createResourcePackName(i, packItem))
if self.world.duplicate_items[self.player].value != 0:
if self.world.filler_item_types[self.player].value != 0: # Use duplicate items
dupeItemPool = item_table.copy()
# Remove frequencies if necessary
if self.world.island_frequency_locations[self.player].value != 3: # Not completely random locations
if self.world.island_frequency_locations[self.player].value != 5: # Not completely random locations
dupeItemPool = (itm for itm in dupeItemPool if "Frequency" not in itm["name"])
# Remove progression or non-progression items if necessary
if (self.world.duplicate_items[self.player].value == 1): # Progression only
if (self.world.duplicate_items[self.player].value == 0): # Progression only
dupeItemPool = (itm for itm in dupeItemPool if itm["progression"] == True)
elif (self.world.duplicate_items[self.player].value == 2): # Non-progression only
elif (self.world.duplicate_items[self.player].value == 1): # Non-progression only
dupeItemPool = (itm for itm in dupeItemPool if itm["progression"] == False)
dupeItemPool = list(dupeItemPool)
@@ -91,19 +91,15 @@ class RaftWorld(World):
def create_regions(self):
create_regions(self.world, self.player)
def fill_slot_data(self):
slot_data = {}
return slot_data
def get_pre_fill_items(self):
if self.world.island_frequency_locations[self.player] in [0, 1]:
if self.world.island_frequency_locations[self.player] in [0, 1, 2, 3]:
return [loc.item for loc in self.world.get_filled_locations()]
return []
def create_item_replaceAsNecessary(self, name: str) -> Item:
isFrequency = "Frequency" in name
shouldUseProgressive = ((isFrequency and self.world.island_frequency_locations[self.player].value == 2)
shouldUseProgressive = ((isFrequency and self.world.island_frequency_locations[self.player].value == 4)
or (not isFrequency and self.world.progressive_items[self.player].value))
if shouldUseProgressive and name in progressive_table:
name = progressive_table[name]
@@ -148,6 +144,40 @@ class RaftWorld(World):
self.setLocationItemFromRegion("Tangaroa", "Varuna Point Frequency")
self.setLocationItemFromRegion("Varuna Point", "Temperance Frequency")
self.setLocationItemFromRegion("Temperance", "Utopia Frequency")
elif self.world.island_frequency_locations[self.player] in [2, 3]:
locationToFrequencyItemMap = {
"Vasagatan": "Vasagatan Frequency",
"BalboaIsland": "Balboa Island Frequency",
"CaravanIsland": "Caravan Island Frequency",
"Tangaroa": "Tangaroa Frequency",
"Varuna Point": "Varuna Point Frequency",
"Temperance": "Temperance Frequency",
"Utopia": "Utopia Frequency"
}
locationToVanillaFrequencyLocationMap = {
"RadioTower": "Radio Tower Frequency to Vasagatan",
"Vasagatan": "Vasagatan Frequency to Balboa",
"BalboaIsland": "Relay Station quest",
"CaravanIsland": "Caravan Island Frequency to Tangaroa",
"Tangaroa": "Tangaroa Frequency to Varuna Point",
"Varuna Point": "Varuna Point Frequency to Temperance",
"Temperance": "Temperance Frequency to Utopia"
}
# Utopia is never chosen until the end, otherwise these are chosen randomly
availableLocationList = ["Vasagatan", "BalboaIsland", "CaravanIsland", "Tangaroa", "Varuna Point", "Temperance", "Utopia"]
previousLocation = "RadioTower"
while (len(availableLocationList) > 0):
if (len(availableLocationList) > 1):
currentLocation = availableLocationList[random.randint(0, len(availableLocationList) - 2)]
else:
currentLocation = availableLocationList[0] # Utopia (only one left in list)
availableLocationList.remove(currentLocation)
if self.world.island_frequency_locations[self.player] == 2:
self.setLocationItem(locationToVanillaFrequencyLocationMap[previousLocation], locationToFrequencyItemMap[currentLocation])
elif self.world.island_frequency_locations[self.player] == 3:
self.setLocationItemFromRegion(previousLocation, locationToFrequencyItemMap[currentLocation])
previousLocation = currentLocation
# Victory item
self.world.get_location("Utopia Complete", self.player).place_locked_item(
RaftItem("Victory", ItemClassification.progression, None, player=self.player))
@@ -166,7 +196,8 @@ class RaftWorld(World):
def fill_slot_data(self):
return {
"IslandGenerationDistance": self.world.island_generation_distance[self.player].value,
"ExpensiveResearch": self.world.expensive_research[self.player].value
"ExpensiveResearch": bool(self.world.expensive_research[self.player].value),
"DeathLink": bool(self.world.death_link[self.player].value)
}
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):

View File

@@ -22,7 +22,7 @@ Decoration Packages are unchanged.
Researches and pickups remain visually unchanged, regardless of what the unlock is.
## When the player receives an item, what happens?
A Raft notification will appear with the item information. The unlock will also appear in the chat. Unlocks that would normally give you the item (eg Machete) will NOT give it to you, but must instead be crafted.
A Raft notification will appear with the item information. The unlock will also appear in the chat. Unlocks that would normally give you the item (eg Zipline) will NOT give it to you, but must instead be crafted.
## Are there any limitations compared to vanilla Raft?
- Mods that add new researchable technologies, modify story islands, or give items like blueprints are likely incompatible with Raftipelago.

View File

@@ -4,23 +4,50 @@
- [Raft](https://store.steampowered.com/app/648800/Raft/)
- [Raft Mod Loader](https://www.raftmodding.com/loader) ("*RML*")
- [ModUtils mod](https://www.raftmodding.com/mods/modutils)
- [Raftipelago mod](https://www.raftmodding.com/mods/raftipelago)
## Installation Procedures
1. Install Raft. The currently-supported Raft version is Version 1.0: The Final Chapter. If you plan on playing Raft mainly with Archipelago, it's recommended to disable Raft auto-updating through Steam, as there is no beta channel to get old builds.
1. Install Raft. The currently-supported Raft version is Version 1.0: The Final Chapter. Any minor version (such as 1.08) should be compatible.
2. Install RML.
3. Install the Raftipelago mod from the Raft Modding website. You should open the auto-installation link on the webpage through RML. Alternatively, you can download the .rmod file and place it in the Mods folder manually.
3. Install the Raftipelago and ModUtils mods from the Raft Modding website. You should open the auto-installation link on the webpage through RML. Alternatively, you can download the .rmod file and place it in the Mods folder manually.
4. Open RML and click Play. If you've already installed it, the shortcut in the Start Menu is called "RMLLauncher.exe". Raft should start.
4. Open RML and click Play. If you've already installed it, the executable that was used to install RML ("RMLLauncher.exe" unless renamed) should be used to run RML. Raft should start after clicking Play.
5. Open the RML menu. This should open automatically when Raft first loads. If it does not, and you see RML information in the top center of the Raft main menu, press F9 to open it.
6. Navigate to the "Mod manager" tab in the left-hand menu.
7. Click on the plug icon for Raftipelago to load the mod.
7. Click on the plug icon for ModUtils to load the mod. You can also click on the (i) next to the plug icon, then check the "Load this mod at startup" button. This will make the mod always load at startup.
8. Click on the plug icon for Raftipelago to load the mod. While it's possible to also make this mod load at startup, it's recommended *not* to do so; if this mod loads before ModUtils, the mod will fail to load properly.
## Joining a MultiWorld Game
1. Ensure you're on the Main Menu with Raftipelago loaded.
2. Open the Debug Console by pressing F10.
3. Type */connect {serverAddress} {username} {password}* into the console and hit Enter.
- Example: */connect archipelago.gg:12345 SunnyBat*
- If there is no password, the password argument may be omitted (as is the case in the above example).
- serverAddress must not contain spaces.
- If your username or password contains spaces, surround that value with quotation marks ("). Adding quotation marks even when not necessary (eg "SunnyBat") is fine.
- If your username or password starts with a quotation mark, surround the value with an additional set of quotation marks (eg the value *"myP@s$w0rD* would be entered as *""myP@s$w0rD"*).
4. Start a new game or load an existing one. It's recommended to avoid using an existing game that was not created with your current run of Raftipelago (either vanilla or a different Raftipelago run). It will work, but if anything is unlocked, it will be automatically registered with Archipelago once the world is loaded. This is irreversible.
5. You can disconnect from an Archipelago server by typing */disconnect confirmDisconnect* into the console and hitting Enter.
## Multiplayer Raft
You're able to have multiple Raft players on a single Raftipelago world. This will work, with a few notes:
- Only the player that creates/loads the world can connect to Archipelago (this is the "host" of the Raft world). Other players do not need to connect; everything will be routed through the the host.
- Players other than the host will be labeled as a "Raft Player (Steam name)" when using ingame chat, which will be routed through Archipelago chat.
- Ingame chat will only work when the host is connected to the Archipelago server.
## Installation Troubleshooting
@@ -45,38 +72,12 @@ If this happens, then RML is configured to only start a new instance of Raft, th
You can either:
- Close the existing instance of Raft then click Play
- Check the box next to the "Disable Automatic Game Start" setting in the Settings menu then click Play.
## Joining a MultiWorld Game
1. Ensure you're on the Main Menu with Raftipelago loaded.
2. Open the Debug Console by pressing F10.
3. Type */connect {serverAddress} {username} {password}* into the console and hit Enter.
- Example: */connect archipelago.gg:12345 SunnyBat*
- serverAddress must not contain spaces.
- If your username or password contains spaces, surround that value with quotation marks ("). Adding quotation marks even when not necessary (eg "SunnyBat") is fine.
- If your username or password starts with a quotation mark, surround the value with an additional set of quotation marks (eg the value *"myP@s$w0rD* would be entered as *""myP@s$w0rD"*).
4. Start a new game or load an existing one.
- Raftipelago save games are marked as *incompatible* with vanilla Raft. This means when Raftipelago is not loaded, saves made with Raftipelago will show as corrupt/unselectable.
- Avoid using an existing game that was not created with your current run of Raftipelago (either vanilla or a different Raftipelago run). It will work, but if anything is unlocked, it will be automatically registered with Archipelago once the world is loaded. This is irreversible.
5. You can disconnect from an Archipelago server by typing */disconnect confirmDisconnect* into the console and hitting Enter.
## Multiplayer Raft
You're able to have multiple Raft players on a single Raftipelago world. This will work, with a few notes:
- Only the player that creates/loads the world can connect to Archipelago (this is the "host" of the Raft world). Other players do not need to connect; everything will be routed through the the host.
- Resource Packs are only received by the host and any other players connected to the Raft world when the resource pack is received.
- Players other than the host will be labeled as a "Raft Player (Steam name)" when using ingame chat, which will be routed through Archipelago chat.
- Ingame chat will only work when the host is connected to the Archipelago server.
## Game Troubleshooting
### The "Load game" button is disabled for my world / my world is corrupt
Be sure that you click the "Load game" button **after** you load Raftipelago. You can click the Load Game button again to reload all of the saves in your folder (there is no need to restart Raft if the mod loaded successfully).
Be sure that you click the "Load game" button **after** you load Raftipelago. You can click the Load Game button again to refresh all of the saves in your folder (there is no need to restart Raft if the mod loaded successfully).
### I'm certain I'm doing things correctly, but the world is still not loadable

View File

@@ -1,12 +1,12 @@
{
"Raft": ["RadioTower", "ResearchTable"],
"Raft": ["ResearchTable", "RadioTower", "Vasagatan", "BalboaIsland", "CaravanIsland", "Tangaroa", "Varuna Point", "Temperance", "Utopia"],
"ResearchTable": [],
"RadioTower": ["Vasagatan"],
"Vasagatan": ["BalboaIsland"],
"BalboaIsland": ["CaravanIsland"],
"CaravanIsland": ["Tangaroa"],
"Tangaroa": ["Varuna Point"],
"Varuna Point": ["Temperance"],
"Temperance": ["Utopia"],
"RadioTower": [],
"Vasagatan": [],
"BalboaIsland": [],
"CaravanIsland": [],
"Tangaroa": [],
"Varuna Point": [],
"Temperance": [],
"Utopia": []
}

View File

@@ -1,143 +1,154 @@
from typing import Dict
from BaseClasses import Item
import typing
from .Options import ItemWeights
class RiskOfRainItem(Item):
game: str = "Risk of Rain 2"
# 37000 - 38000
item_table = {
"Dio's Best Friend": 37001,
"Common Item": 37002,
"Uncommon Item": 37003,
"Legendary Item": 37004,
"Boss Item": 37005,
"Lunar Item": 37006,
"Equipment": 37007,
"Item Scrap, White": 37008,
"Item Scrap, Green": 37009,
"Item Scrap, Red": 37010,
"Item Scrap, Yellow": 37011,
"Victory": None,
"Beat Level One": None,
"Beat Level Two": None,
"Beat Level Three": None,
"Beat Level Four": None,
"Beat Level Five": None,
item_table: Dict[str, int] = {
"Dio's Best Friend": 37001,
"Common Item": 37002,
"Uncommon Item": 37003,
"Legendary Item": 37004,
"Boss Item": 37005,
"Lunar Item": 37006,
"Equipment": 37007,
"Item Scrap, White": 37008,
"Item Scrap, Green": 37009,
"Item Scrap, Red": 37010,
"Item Scrap, Yellow": 37011
}
default_weights = {
"Item Scrap, Green": 16,
"Item Scrap, Red": 4,
"Item Scrap, Yellow": 1,
"Item Scrap, White": 32,
"Common Item": 64,
"Uncommon Item": 32,
"Legendary Item": 8,
"Boss Item": 4,
"Lunar Item": 16,
"Equipment": 32
default_weights: Dict[str, int] = {
"Item Scrap, Green": 16,
"Item Scrap, Red": 4,
"Item Scrap, Yellow": 1,
"Item Scrap, White": 32,
"Common Item": 64,
"Uncommon Item": 32,
"Legendary Item": 8,
"Boss Item": 4,
"Lunar Item": 16,
"Equipment": 32
}
new_weights = {
"Item Scrap, Green": 15,
"Item Scrap, Red": 5,
"Item Scrap, Yellow": 1,
"Item Scrap, White": 30,
"Common Item": 75,
"Uncommon Item": 40,
"Legendary Item": 10,
"Boss Item": 5,
"Lunar Item": 10,
"Equipment": 20
new_weights: Dict[str, int] = {
"Item Scrap, Green": 15,
"Item Scrap, Red": 5,
"Item Scrap, Yellow": 1,
"Item Scrap, White": 30,
"Common Item": 75,
"Uncommon Item": 40,
"Legendary Item": 10,
"Boss Item": 5,
"Lunar Item": 10,
"Equipment": 20
}
uncommon_weights = {
"Item Scrap, Green": 45,
"Item Scrap, Red": 5,
"Item Scrap, Yellow": 1,
"Item Scrap, White": 30,
"Common Item": 45,
"Uncommon Item": 100,
"Legendary Item": 10,
"Boss Item": 5,
"Lunar Item": 15,
"Equipment": 20
uncommon_weights: Dict[str, int] = {
"Item Scrap, Green": 45,
"Item Scrap, Red": 5,
"Item Scrap, Yellow": 1,
"Item Scrap, White": 30,
"Common Item": 45,
"Uncommon Item": 100,
"Legendary Item": 10,
"Boss Item": 5,
"Lunar Item": 15,
"Equipment": 20
}
legendary_weights = {
"Item Scrap, Green": 15,
"Item Scrap, Red": 5,
"Item Scrap, Yellow": 1,
"Item Scrap, White": 30,
"Common Item": 50,
"Uncommon Item": 25,
"Legendary Item": 100,
"Boss Item": 5,
"Lunar Item": 15,
"Equipment": 20
legendary_weights: Dict[str, int] = {
"Item Scrap, Green": 15,
"Item Scrap, Red": 5,
"Item Scrap, Yellow": 1,
"Item Scrap, White": 30,
"Common Item": 50,
"Uncommon Item": 25,
"Legendary Item": 100,
"Boss Item": 5,
"Lunar Item": 15,
"Equipment": 20
}
lunartic_weights = {
"Item Scrap, Green": 0,
"Item Scrap, Red": 0,
"Item Scrap, Yellow": 0,
"Item Scrap, White": 0,
"Common Item": 0,
"Uncommon Item": 0,
"Legendary Item": 0,
"Boss Item": 0,
"Lunar Item": 100,
"Equipment": 0
lunartic_weights: Dict[str, int] = {
"Item Scrap, Green": 0,
"Item Scrap, Red": 0,
"Item Scrap, Yellow": 0,
"Item Scrap, White": 0,
"Common Item": 0,
"Uncommon Item": 0,
"Legendary Item": 0,
"Boss Item": 0,
"Lunar Item": 100,
"Equipment": 0
}
no_scraps_weights = {
"Item Scrap, Green": 0,
"Item Scrap, Red": 0,
"Item Scrap, Yellow": 0,
"Item Scrap, White": 0,
"Common Item": 100,
"Uncommon Item": 40,
"Legendary Item": 15,
"Boss Item": 5,
"Lunar Item": 10,
"Equipment": 25
chaos_weights: Dict[str, int] = {
"Item Scrap, Green": 80,
"Item Scrap, Red": 45,
"Item Scrap, Yellow": 30,
"Item Scrap, White": 100,
"Common Item": 100,
"Uncommon Item": 70,
"Legendary Item": 30,
"Boss Item": 20,
"Lunar Item": 60,
"Equipment": 40
}
even_weights = {
"Item Scrap, Green": 1,
"Item Scrap, Red": 1,
"Item Scrap, Yellow": 1,
"Item Scrap, White": 1,
"Common Item": 1,
"Uncommon Item": 1,
"Legendary Item": 1,
"Boss Item": 1,
"Lunar Item": 1,
"Equipment": 1
no_scraps_weights: Dict[str, int] = {
"Item Scrap, Green": 0,
"Item Scrap, Red": 0,
"Item Scrap, Yellow": 0,
"Item Scrap, White": 0,
"Common Item": 100,
"Uncommon Item": 40,
"Legendary Item": 15,
"Boss Item": 5,
"Lunar Item": 10,
"Equipment": 25
}
scraps_only = {
"Item Scrap, Green": 70,
"Item Scrap, White": 100,
"Item Scrap, Red": 30,
"Item Scrap, Yellow": 5,
"Common Item": 0,
"Uncommon Item": 0,
"Legendary Item": 0,
"Boss Item": 0,
"Lunar Item": 0,
"Equipment": 0
even_weights: Dict[str, int] = {
"Item Scrap, Green": 1,
"Item Scrap, Red": 1,
"Item Scrap, Yellow": 1,
"Item Scrap, White": 1,
"Common Item": 1,
"Uncommon Item": 1,
"Legendary Item": 1,
"Boss Item": 1,
"Lunar Item": 1,
"Equipment": 1
}
item_pool_weights: typing.Dict[int, typing.Dict[str, int]] = {
0: default_weights,
1: new_weights,
2: uncommon_weights,
3: legendary_weights,
4: lunartic_weights,
6: no_scraps_weights,
7: even_weights,
8: scraps_only
scraps_only: Dict[str, int] = {
"Item Scrap, Green": 70,
"Item Scrap, White": 100,
"Item Scrap, Red": 30,
"Item Scrap, Yellow": 5,
"Common Item": 0,
"Uncommon Item": 0,
"Legendary Item": 0,
"Boss Item": 0,
"Lunar Item": 0,
"Equipment": 0
}
lookup_id_to_name: typing.Dict[int, str] = {id: name for name, id in item_table.items() if id}
item_pool_weights: Dict[int, Dict[str, int]] = {
ItemWeights.option_default: default_weights,
ItemWeights.option_new: new_weights,
ItemWeights.option_uncommon: uncommon_weights,
ItemWeights.option_legendary: legendary_weights,
ItemWeights.option_lunartic: lunartic_weights,
ItemWeights.option_chaos: chaos_weights,
ItemWeights.option_no_scraps: no_scraps_weights,
ItemWeights.option_even: even_weights,
ItemWeights.option_scraps_only: scraps_only
}
lookup_id_to_name: Dict[int, str] = {id: name for name, id in item_table.items()}

View File

@@ -1,19 +1,13 @@
from typing import Dict
from BaseClasses import Location
import typing
from .Options import TotalLocations
class RiskOfRainLocation(Location):
game: str = "Risk of Rain 2"
# 37000 - 38000
base_location_table = {
"Victory": None,
}
# 37006 - 37506
item_pickups = {
f"ItemPickup{i}": 37005+i for i in range(1, 501)
item_pickups: Dict[str, int] = {
f"ItemPickup{i+1}": 37000+i for i in range(TotalLocations.range_end)
}
location_table = {**base_location_table, **item_pickups}
lookup_id_to_name: typing.Dict[int, str] = {id: name for name, id in location_table.items()}

View File

@@ -1,4 +1,4 @@
import typing
from typing import Dict
from Options import Option, DefaultOnToggle, Range, Choice
@@ -36,7 +36,8 @@ class AllowLunarItems(DefaultOnToggle):
class StartWithRevive(DefaultOnToggle):
"""Start the game with a `Dio's Best Friend` item."""
display_name = "Start with a Revive"
class FinalStageDeath(DefaultOnToggle):
"""Death on the final boss stage counts as a win."""
display_name = "Final Stage Death is Win"
@@ -124,7 +125,7 @@ class Equipment(Range):
class ItemPoolPresetToggle(DefaultOnToggle):
"""Will use the item weight presets when set to true, otherwise will use the custom set item pool weights."""
display_name = "Item Weight Presets"
display_name = "Use Item Weight Presets"
class ItemWeights(Choice):
@@ -150,7 +151,7 @@ class ItemWeights(Choice):
# define a dictionary for the weights of the generated item pool.
ror2_weights: typing.Dict[str, type(Option)] = {
ror2_weights: Dict[str, type(Option)] = {
"green_scrap": GreenScrap,
"red_scrap": RedScrap,
"yellow_scrap": YellowScrap,
@@ -163,7 +164,7 @@ ror2_weights: typing.Dict[str, type(Option)] = {
"equipment": Equipment
}
ror2_options: typing.Dict[str, type(Option)] = {
ror2_options: Dict[str, type(Option)] = {
"total_locations": TotalLocations,
"total_revivals": TotalRevivals,
"start_with_revive": StartWithRevive,

View File

@@ -2,29 +2,32 @@ from BaseClasses import MultiWorld
from worlds.generic.Rules import set_rule, add_rule
def set_rules(world: MultiWorld, player: int):
total_locations = world.total_locations[player] # total locations for current player
def set_rules(world: MultiWorld, player: int) -> None:
total_locations = world.total_locations[player].value # total locations for current player
event_location_step = 25 # set an event location at these locations for "spheres"
divisions = total_locations // event_location_step
total_revivals = world.worlds[player].total_revivals # pulling this info we calculated in generate_basic
if divisions:
for i in range(1, divisions): # since divisions is the floor of total_locations / 25
event_loc = world.get_location(f"Pickup{i * event_location_step}", player)
set_rule(event_loc, lambda state, i=i: state.can_reach(f"ItemPickup{i * event_location_step - 1}", "Location", player))
set_rule(event_loc,
lambda state, i=i: state.can_reach(f"ItemPickup{i * event_location_step - 1}", "Location", player))
for n in range(i * event_location_step, (i + 1) * event_location_step): # we want to create a rule for each of the 25 locations per division
if n == i * event_location_step:
set_rule(world.get_location(f"ItemPickup{n}", player), lambda state, event_item=event_loc.item.name: state.has(event_item, player))
set_rule(world.get_location(f"ItemPickup{n}", player),
lambda state, event_item=event_loc.item.name: state.has(event_item, player))
else:
set_rule(world.get_location(f"ItemPickup{n}", player),
lambda state, n = n: state.can_reach(f"ItemPickup{n - 1}", 'Location', player))
lambda state, n=n: state.can_reach(f"ItemPickup{n - 1}", "Location", player))
for i in range(divisions * event_location_step, total_locations+1):
set_rule(world.get_location(f"ItemPickup{i}", player), lambda state, i=i: state.can_reach(f"ItemPickup{i - 1}", "Location", player))
set_rule(world.get_location(f"ItemPickup{i}", player),
lambda state, i=i: state.can_reach(f"ItemPickup{i - 1}", "Location", player))
set_rule(world.get_location("Victory", player),
lambda state: state.can_reach(f"ItemPickup{total_locations}", "Location", player))
if world.total_revivals[player] or world.start_with_revive[player]:
total_revivals = world.total_revivals[player] * world.total_locations[player] // 100
if total_revivals or world.start_with_revive[player].value:
add_rule(world.get_location("Victory", player),
lambda state: state.has("Dio's Best Friend", player, total_revivals + world.start_with_revive[player].value))
lambda state: state.has("Dio's Best Friend", player,
total_revivals + world.start_with_revive[player]))
world.completion_condition[player] = lambda state: state.has("Victory", player)

View File

@@ -1,10 +1,11 @@
import string
from typing import Dict, List
from .Items import RiskOfRainItem, item_table, item_pool_weights
from .Locations import location_table, RiskOfRainLocation, base_location_table
from .Locations import RiskOfRainLocation, item_pickups
from .Rules import set_rules
from BaseClasses import Region, RegionType, Entrance, Item, ItemClassification, MultiWorld, Tutorial
from .Options import ror2_options
from .Options import ror2_options, ItemWeights
from worlds.AutoWorld import World, WebWorld
client_version = 1
@@ -32,34 +33,31 @@ class RiskOfRainWorld(World):
topology_present = False
item_name_to_id = item_table
location_name_to_id = location_table
location_name_to_id = item_pickups
data_version = 3
data_version = 4
forced_auto_forfeit = True
web = RiskOfWeb()
total_revivals: int
def generate_basic(self):
def generate_early(self) -> None:
# figure out how many revivals should exist in the pool
self.total_revivals = int(self.world.total_revivals[self.player].value / 100 *
self.world.total_locations[self.player].value)
def generate_basic(self) -> None:
# shortcut for starting_inventory... The start_with_revive option lets you start with a Dio's Best Friend
if self.world.start_with_revive[self.player].value:
self.world.push_precollected(self.world.create_item("Dio's Best Friend", self.player))
# if presets are enabled generate junk_pool from the selected preset
pool_option = self.world.item_weights[self.player].value
if self.world.item_pool_presets[self.player].value:
junk_pool: Dict[str, int] = {}
if self.world.item_pool_presets[self.player]:
# generate chaos weights if the preset is chosen
if pool_option == 5:
junk_pool = {
"Item Scrap, Green": self.world.random.randint(0, 80),
"Item Scrap, Red": self.world.random.randint(0, 45),
"Item Scrap, Yellow": self.world.random.randint(0, 30),
"Item Scrap, White": self.world.random.randint(0, 100),
"Common Item": self.world.random.randint(0, 100),
"Uncommon Item": self.world.random.randint(0, 70),
"Legendary Item": self.world.random.randint(0, 30),
"Boss Item": self.world.random.randint(0, 20),
"Lunar Item": self.world.random.randint(0, 60),
"Equipment": self.world.random.randint(0, 40)
}
if pool_option == ItemWeights.option_chaos:
for name, max_value in item_pool_weights[pool_option].items():
junk_pool[name] = self.world.random.randint(0, max_value)
else:
junk_pool = item_pool_weights[pool_option].copy()
else: # generate junk pool from user created presets
@@ -77,37 +75,43 @@ class RiskOfRainWorld(World):
}
# remove lunar items from the pool if they're disabled in the yaml unless lunartic is rolled
if not self.world.enable_lunar[self.player]:
if not pool_option == 4:
junk_pool.pop("Lunar Item")
if not (self.world.enable_lunar[self.player] or pool_option == ItemWeights.option_lunartic):
junk_pool.pop("Lunar Item")
# Generate item pool
itempool = []
itempool: List = []
# Add revive items for the player
itempool += ["Dio's Best Friend"] * int(self.world.total_revivals[self.player] / 100 * self.world.total_locations[self.player])
itempool += ["Dio's Best Friend"] * self.total_revivals
# Fill remaining items with randomly generated junk
itempool += self.world.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()),
k=self.world.total_locations[self.player] -
int(self.world.total_revivals[self.player] / 100 * self.world.total_locations[self.player]))
k=self.world.total_locations[self.player].value - self.total_revivals)
# Convert itempool into real items
itempool = list(map(lambda name: self.create_item(name), itempool))
self.world.itempool += itempool
def set_rules(self):
def set_rules(self) -> None:
set_rules(self.world, self.player)
def create_regions(self):
create_regions(self.world, self.player)
create_events(self.world, self.player, int(self.world.total_locations[self.player]))
def create_regions(self) -> None:
menu = create_region(self.world, self.player, "Menu")
petrichor = create_region(self.world, self.player, "Petrichor V",
[f"ItemPickup{i + 1}" for i in range(self.world.total_locations[self.player].value)])
connection = Entrance(self.player, "Lobby", menu)
menu.exits.append(connection)
connection.connect(petrichor)
self.world.regions += [menu, petrichor]
create_events(self.world, self.player)
def fill_slot_data(self):
return {
"itemPickupStep": self.world.item_pickup_step[self.player].value,
"seed": "".join(self.world.slot_seeds[self.player].choice(string.digits) for i in range(16)),
"seed": "".join(self.world.slot_seeds[self.player].choice(string.digits) for _ in range(16)),
"totalLocations": self.world.total_locations[self.player].value,
"totalRevivals": self.world.total_revivals[self.player].value,
"startWithDio": self.world.start_with_revive[self.player].value,
@@ -116,49 +120,39 @@ class RiskOfRainWorld(World):
def create_item(self, name: str) -> Item:
item_id = item_table[name]
item = RiskOfRainItem(name, ItemClassification.filler, item_id, self.player)
if name == "Dio's Best Friend":
item.classification = ItemClassification.progression
classification = ItemClassification.progression
elif name in {"Equipment", "Legendary Item"}:
item.classification = ItemClassification.useful
classification = ItemClassification.useful
else:
classification = ItemClassification.filler
item = RiskOfRainItem(name, classification, item_id, self.player)
return item
def create_events(world: MultiWorld, player: int, total_locations: int):
def create_events(world: MultiWorld, player: int) -> None:
total_locations = world.total_locations[player].value
num_of_events = total_locations // 25
if total_locations / 25 == num_of_events:
num_of_events -= 1
world_region = world.get_region("Petrichor V", player)
for i in range(num_of_events):
event_loc = RiskOfRainLocation(player, f"Pickup{(i + 1) * 25}", None, world.get_region('Petrichor V', player))
event_loc = RiskOfRainLocation(player, f"Pickup{(i + 1) * 25}", None, world_region)
event_loc.place_locked_item(RiskOfRainItem(f"Pickup{(i + 1) * 25}", ItemClassification.progression, None, player))
event_loc.access_rule(lambda state, i=i: state.can_reach(f"ItemPickup{((i + 1) * 25) - 1}", player))
world.get_region('Petrichor V', player).locations.append(event_loc)
world_region.locations.append(event_loc)
victory_event = RiskOfRainLocation(player, "Victory", None, world_region)
victory_event.place_locked_item(RiskOfRainItem("Victory", ItemClassification.progression, None, player))
world_region.locations.append(victory_event)
# generate locations based on player setting
def create_regions(world, player: int):
world.regions += [
create_region(world, player, 'Menu', None, ['Lobby']),
create_region(world, player, 'Petrichor V',
[location for location in base_location_table] +
[f"ItemPickup{i}" for i in range(1, 1 + world.total_locations[player])])
]
world.get_entrance("Lobby", player).connect(world.get_region("Petrichor V", player))
world.get_location("Victory", player).place_locked_item(RiskOfRainItem("Victory", ItemClassification.progression,
None, player))
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
ret = Region(name, RegionType.Generic, name, player)
ret.world = world
def create_region(world: MultiWorld, player: int, name: str, locations: List[str] = None) -> Region:
ret = Region(name, RegionType.Generic, name, player, world)
if locations:
for location in locations:
loc_id = location_table[location]
loc_id = item_pickups[location]
location = RiskOfRainLocation(player, location, loc_id, ret)
ret.locations.append(location)
if exits:
for exit in exits:
ret.exits.append(Entrance(player, exit, ret))
return ret

View File

@@ -29,7 +29,7 @@ You can see the [basic multiworld setup guide](/tutorial/Archipelago/setup/en) h
about why Archipelago uses YAML files and what they're for.
### Where do I get a YAML?
You can use the [game settings page for Hollow Knight](/games/Hollow%20Knight/player-settings) here on the Archipelago
You can use the [game settings page](/games/Risk%20of%20Rain%202/player-settings) here on the Archipelago
website to generate a YAML using a graphical interface.

View File

@@ -46,7 +46,7 @@ def check_for_impossible_shuffle(shuffled_levels: typing.List[int], gate_0_range
class SA2BWorld(World):
"""
Sonic Adventure 2 Battle is an action platforming game. Play as Sonic, Tails, Knuckles, Shadow, Rogue, and Eggman across 31 stages and prevent the destruction of the earth.
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
@@ -282,8 +282,7 @@ class SA2BWorld(World):
spoiler_handle.writelines(text)
@classmethod
def stage_fill_hook(cls, world, progitempool, nonexcludeditempool, localrestitempool, nonlocalrestitempool,
restitempool, fill_locations):
def stage_fill_hook(cls, world, progitempool, usefulitempool, filleritempool, fill_locations):
if world.get_game_players("Sonic Adventure 2 Battle"):
progitempool.sort(
key=lambda item: 0 if (item.name != 'Emblem') else 1)

View File

@@ -1,5 +1,6 @@
from BaseClasses import Item, ItemClassification
import typing
from .MissionTables import vanilla_mission_req_table
class ItemData(typing.NamedTuple):
@@ -49,27 +50,27 @@ item_table = {
"Projectile Accelerator (Bunker)": ItemData(200 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 0),
"Neosteel Bunker (Bunker)": ItemData(201 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 1),
"Titanium Housing (Missile Turret)": ItemData(202 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 2),
"Titanium Housing (Missile Turret)": ItemData(202 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 2, classification=ItemClassification.filler),
"Hellstorm Batteries (Missile Turret)": ItemData(203 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 3),
"Advanced Construction (SCV)": ItemData(204 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 4),
"Dual-Fusion Welders (SCV)": ItemData(205 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 5),
"Fire-Suppression System (Building)": ItemData(206 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 6),
"Fire-Suppression System (Building)": ItemData(206 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 6, classification=ItemClassification.filler),
"Orbital Command (Building)": ItemData(207 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 7),
"Stimpack (Marine)": ItemData(208 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 8),
"Combat Shield (Marine)": ItemData(209 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 9),
"Advanced Medic Facilities (Medic)": ItemData(210 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 10),
"Stabilizer Medpacks (Medic)": ItemData(211 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 11),
"Combat Shield (Marine)": ItemData(209 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 9, classification=ItemClassification.progression),
"Advanced Medic Facilities (Medic)": ItemData(210 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 10, classification=ItemClassification.progression),
"Stabilizer Medpacks (Medic)": ItemData(211 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 11, classification=ItemClassification.progression),
"Incinerator Gauntlets (Firebat)": ItemData(212 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 12, classification=ItemClassification.filler),
"Juggernaut Plating (Firebat)": ItemData(213 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 13),
"Concussive Shells (Marauder)": ItemData(214 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 14),
"Kinetic Foam (Marauder)": ItemData(215 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 15),
"U-238 Rounds (Reaper)": ItemData(216 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 16),
"G-4 Clusterbomb (Reaper)": ItemData(217 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17, classification=ItemClassification.filler),
"G-4 Clusterbomb (Reaper)": ItemData(217 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17, classification=ItemClassification.progression),
"Twin-Linked Flamethrower (Hellion)": ItemData(300 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 0, classification=ItemClassification.filler),
"Thermite Filaments (Hellion)": ItemData(301 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 1),
"Cerberus Mine (Vulture)": ItemData(302 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 2),
"Replenishable Magazine (Vulture)": ItemData(303 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 3),
"Cerberus Mine (Vulture)": ItemData(302 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 2, classification=ItemClassification.filler),
"Replenishable Magazine (Vulture)": ItemData(303 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 3, classification=ItemClassification.filler),
"Multi-Lock Weapons System (Goliath)": ItemData(304 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 4),
"Ares-Class Targeting System (Goliath)": ItemData(305 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 5),
"Tri-Lithium Power Cell (Diamondback)": ItemData(306 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 6, classification=ItemClassification.filler),
@@ -77,9 +78,9 @@ item_table = {
"Maelstrom Rounds (Siege Tank)": ItemData(308 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 8),
"Shaped Blast (Siege Tank)": ItemData(309 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 9),
"Rapid Deployment Tube (Medivac)": ItemData(310 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 10, classification=ItemClassification.filler),
"Advanced Healing AI (Medivac)": ItemData(311 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 11),
"Advanced Healing AI (Medivac)": ItemData(311 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 11, classification=ItemClassification.filler),
"Tomahawk Power Cells (Wraith)": ItemData(312 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 12, classification=ItemClassification.filler),
"Displacement Field (Wraith)": ItemData(313 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 13),
"Displacement Field (Wraith)": ItemData(313 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 13, classification=ItemClassification.filler),
"Ripwave Missiles (Viking)": ItemData(314 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 14),
"Phobos-Class Weapons System (Viking)": ItemData(315 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 15),
"Cross-Spectrum Dampeners (Banshee)": ItemData(316 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 16, classification=ItemClassification.filler),
@@ -88,7 +89,7 @@ item_table = {
"Defensive Matrix (Battlecruiser)": ItemData(319 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 19, classification=ItemClassification.filler),
"Ocular Implants (Ghost)": ItemData(320 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 20),
"Crius Suit (Ghost)": ItemData(321 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 21),
"Psionic Lash (Spectre)": ItemData(322 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 22),
"Psionic Lash (Spectre)": ItemData(322 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 22, classification=ItemClassification.progression),
"Nyx-Class Cloaking Module (Spectre)": ItemData(323 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 23),
"330mm Barrage Cannon (Thor)": ItemData(324 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 24, classification=ItemClassification.filler),
"Immortality Protocol (Thor)": ItemData(325 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 25, classification=ItemClassification.filler),
@@ -97,12 +98,12 @@ item_table = {
"Missile Turret": ItemData(401 + SC2WOL_ITEM_ID_OFFSET, "Building", 1, classification=ItemClassification.progression),
"Sensor Tower": ItemData(402 + SC2WOL_ITEM_ID_OFFSET, "Building", 2),
"War Pigs": ItemData(500 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 0),
"War Pigs": ItemData(500 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 0, classification=ItemClassification.progression),
"Devil Dogs": ItemData(501 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 1, classification=ItemClassification.filler),
"Hammer Securities": ItemData(502 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 2),
"Spartan Company": ItemData(503 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 3),
"Spartan Company": ItemData(503 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 3, classification=ItemClassification.progression),
"Siege Breakers": ItemData(504 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 4),
"Hel's Angel": ItemData(505 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 5),
"Hel's Angel": ItemData(505 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 5, classification=ItemClassification.progression),
"Dusk Wings": ItemData(506 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 6),
"Jackson's Revenge": ItemData(507 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 7),
@@ -153,12 +154,7 @@ basic_unit: typing.Tuple[str, ...] = (
item_name_groups = {}
for item, data in item_table.items():
item_name_groups.setdefault(data.type, []).append(item)
item_name_groups["Missions"] = ["Beat Liberation Day", "Beat The Outlaws", "Beat Zero Hour", "Beat Evacuation",
"None Outbreak", "Beat Safe Haven", "Beat Haven's Fall", "Beat Smash and Grab", "Beat The Dig",
"Beat The Moebius Factor", "Beat Supernova", "Beat Maw of the Void", "Beat Devil's Playground",
"Beat Welcome to the Jungle", "Beat Breakout", "Beat Ghost of a Chance",
"Beat The Great Train Robbery", "Beat Cutthroat", "Beat Engine of Destruction",
"Beat Media Blitz", "Beat Piercing the Shroud"]
item_name_groups["Missions"] = ["Beat " + mission_name for mission_name in vanilla_mission_req_table]
filler_items: typing.Tuple[str, ...] = (
'+15 Starting Minerals',
@@ -167,3 +163,17 @@ filler_items: typing.Tuple[str, ...] = (
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in get_full_item_list().items() if
data.code}
# Map type to expected int
type_flaggroups: typing.Dict[str, int] = {
"Unit": 0,
"Upgrade": 1,
"Armory 1": 2,
"Armory 2": 3,
"Building": 4,
"Mercenary": 5,
"Laboratory": 6,
"Protoss": 7,
"Minerals": 8,
"Vespene": 9,
"Supply": 10,
}

View File

@@ -44,7 +44,7 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData("Zero Hour", "Beat Zero Hour", None,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
lambda state: state._sc2wol_has_anti_air(world, player)),
LocationData("Evacuation", "Evacuation: First Chysalis", SC2WOL_LOC_ID_OFFSET + 401),
LocationData("Evacuation", "Evacuation: Second Chysalis", SC2WOL_LOC_ID_OFFSET + 402,
lambda state: state._sc2wol_has_common_unit(world, player)),
@@ -52,7 +52,7 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Evacuation", "Beat Evacuation", None,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500,
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501,
@@ -63,19 +63,37 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Safe Haven", "Safe Haven: North Nexus", SC2WOL_LOC_ID_OFFSET + 601,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Safe Haven", "Safe Haven: East Nexus", SC2WOL_LOC_ID_OFFSET + 602,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Safe Haven", "Safe Haven: South Nexus", SC2WOL_LOC_ID_OFFSET + 603,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Safe Haven", "Beat Safe Haven", None,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Haven's Fall", "Haven's Fall: North Hive", SC2WOL_LOC_ID_OFFSET + 701,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Haven's Fall", "Haven's Fall: East Hive", SC2WOL_LOC_ID_OFFSET + 702,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Haven's Fall", "Haven's Fall: South Hive", SC2WOL_LOC_ID_OFFSET + 703,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Haven's Fall", "Beat Haven's Fall", None,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Smash and Grab", "Smash and Grab: First Relic", SC2WOL_LOC_ID_OFFSET + 801),
LocationData("Smash and Grab", "Smash and Grab: Second Relic", SC2WOL_LOC_ID_OFFSET + 802),
LocationData("Smash and Grab", "Smash and Grab: Third Relic", SC2WOL_LOC_ID_OFFSET + 803,
@@ -101,11 +119,7 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
state._sc2wol_has_anti_air(world, player) and
state._sc2wol_has_heavy_defense(world, player)),
LocationData("The Moebius Factor", "The Moebius Factor: Victory", SC2WOL_LOC_ID_OFFSET + 1000,
lambda state: state._sc2wol_has_air(world, player)),
LocationData("The Moebius Factor", "The Moebius Factor: 1st Data Core ", SC2WOL_LOC_ID_OFFSET + 1001,
lambda state: True),
LocationData("The Moebius Factor", "The Moebius Factor: 2nd Data Core", SC2WOL_LOC_ID_OFFSET + 1002,
lambda state: state._sc2wol_has_air(world, player)),
lambda state: state._sc2wol_has_air(world, player) and state._sc2wol_has_anti_air(world, player)),
LocationData("The Moebius Factor", "The Moebius Factor: South Rescue", SC2WOL_LOC_ID_OFFSET + 1003,
lambda state: state._sc2wol_able_to_rescue(world, player)),
LocationData("The Moebius Factor", "The Moebius Factor: Wall Rescue", SC2WOL_LOC_ID_OFFSET + 1004,
@@ -121,35 +135,49 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData("The Moebius Factor", "Beat The Moebius Factor", None,
lambda state: state._sc2wol_has_air(world, player)),
LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_beats_protoss_deathball(world, player)),
LocationData("Supernova", "Supernova: West Relic", SC2WOL_LOC_ID_OFFSET + 1101),
LocationData("Supernova", "Supernova: North Relic", SC2WOL_LOC_ID_OFFSET + 1102,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Supernova", "Supernova: North Relic", SC2WOL_LOC_ID_OFFSET + 1102),
LocationData("Supernova", "Supernova: South Relic", SC2WOL_LOC_ID_OFFSET + 1103,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_beats_protoss_deathball(world, player)),
LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_beats_protoss_deathball(world, player)),
LocationData("Supernova", "Beat Supernova", None,
lambda state: state._sc2wol_has_common_unit(world, player)),
lambda state: state._sc2wol_beats_protoss_deathball(world, player)),
LocationData("Maw of the Void", "Maw of the Void: Victory", SC2WOL_LOC_ID_OFFSET + 1200,
lambda state: state.has('Battlecruiser', player) or state.has('Science Vessel', player) and
state._sc2wol_has_air(world, player)),
lambda state: state.has('Battlecruiser', player) or
state._sc2wol_has_air(world, player) and
state._sc2wol_has_competent_anti_air(world, player) and
state.has('Science Vessel', player)),
LocationData("Maw of the Void", "Maw of the Void: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1201),
LocationData("Maw of the Void", "Maw of the Void: Expansion Prisoners", SC2WOL_LOC_ID_OFFSET + 1202),
LocationData("Maw of the Void", "Maw of the Void: Expansion Prisoners", SC2WOL_LOC_ID_OFFSET + 1202,
lambda state: state.has('Battlecruiser', player) or
state._sc2wol_has_air(world, player) and
state._sc2wol_has_competent_anti_air(world, player) and
state.has('Science Vessel', player)),
LocationData("Maw of the Void", "Maw of the Void: South Close Prisoners", SC2WOL_LOC_ID_OFFSET + 1203,
lambda state: state.has('Battlecruiser', player) or state.has('Science Vessel', player) and
state._sc2wol_has_air(world, player)),
lambda state: state.has('Battlecruiser', player) or
state._sc2wol_has_air(world, player) and
state._sc2wol_has_competent_anti_air(world, player) and
state.has('Science Vessel', player)),
LocationData("Maw of the Void", "Maw of the Void: South Far Prisoners", SC2WOL_LOC_ID_OFFSET + 1204,
lambda state: state.has('Battlecruiser', player) or state.has('Science Vessel', player) and
state._sc2wol_has_air(world, player)),
lambda state: state.has('Battlecruiser', player) or
state._sc2wol_has_air(world, player) and
state._sc2wol_has_competent_anti_air(world, player) and
state.has('Science Vessel', player)),
LocationData("Maw of the Void", "Maw of the Void: North Prisoners", SC2WOL_LOC_ID_OFFSET + 1205,
lambda state: state.has('Battlecruiser', player) or state.has('Science Vessel', player) and
state._sc2wol_has_air(world, player)),
lambda state: state.has('Battlecruiser', player) or
state._sc2wol_has_air(world, player) and
state._sc2wol_has_competent_anti_air(world, player) and
state.has('Science Vessel', player)),
LocationData("Maw of the Void", "Beat Maw of the Void", None,
lambda state: state.has('Battlecruiser', player) or state.has('Science Vessel', player) and
state._sc2wol_has_air(world, player)),
lambda state: state.has('Battlecruiser', player) or
state._sc2wol_has_air(world, player) and
state._sc2wol_has_competent_anti_air(world, player) and
state.has('Science Vessel', player)),
LocationData("Devil's Playground", "Devil's Playground: Victory", SC2WOL_LOC_ID_OFFSET + 1300,
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
lambda state: state._sc2wol_has_anti_air(world, player) and (
state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player))),
LocationData("Devil's Playground", "Devil's Playground: Tosh's Miners", SC2WOL_LOC_ID_OFFSET + 1301),
LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302,
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
@@ -157,17 +185,17 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: Victory", SC2WOL_LOC_ID_OFFSET + 1400,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: Close Relic", SC2WOL_LOC_ID_OFFSET + 1401),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: West Relic", SC2WOL_LOC_ID_OFFSET + 1402,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Welcome to the Jungle", "Welcome to the Jungle: North-East Relic", SC2WOL_LOC_ID_OFFSET + 1403,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Welcome to the Jungle", "Beat Welcome to the Jungle", None,
lambda state: state._sc2wol_has_common_unit(world, player) and
state._sc2wol_has_mobile_anti_air(world, player)),
state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Breakout", "Breakout: Victory", SC2WOL_LOC_ID_OFFSET + 1500),
LocationData("Breakout", "Breakout: Diamondback Prison", SC2WOL_LOC_ID_OFFSET + 1501),
LocationData("Breakout", "Breakout: Siegetank Prison", SC2WOL_LOC_ID_OFFSET + 1502),
@@ -180,7 +208,8 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData("Ghost of a Chance", "Ghost of a Chance: Third Island Spectres", SC2WOL_LOC_ID_OFFSET + 1605),
LocationData("Ghost of a Chance", "Beat Ghost of a Chance", None),
LocationData("The Great Train Robbery", "The Great Train Robbery: Victory", SC2WOL_LOC_ID_OFFSET + 1700,
lambda state: state._sc2wol_has_train_killers(world, player)),
lambda state: state._sc2wol_has_train_killers(world, player) and
state._sc2wol_has_anti_air(world, player)),
LocationData("The Great Train Robbery", "The Great Train Robbery: North Defiler", SC2WOL_LOC_ID_OFFSET + 1701),
LocationData("The Great Train Robbery", "The Great Train Robbery: Mid Defiler", SC2WOL_LOC_ID_OFFSET + 1702),
LocationData("The Great Train Robbery", "The Great Train Robbery: South Defiler", SC2WOL_LOC_ID_OFFSET + 1703),
@@ -198,20 +227,20 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData("Cutthroat", "Beat Cutthroat", None,
lambda state: state._sc2wol_has_common_unit(world, player)),
LocationData("Engine of Destruction", "Engine of Destruction: Victory", SC2WOL_LOC_ID_OFFSET + 1900,
lambda state: state._sc2wol_has_mobile_anti_air(world, player)),
lambda state: state._sc2wol_has_competent_anti_air(world, player)),
LocationData("Engine of Destruction", "Engine of Destruction: Odin", SC2WOL_LOC_ID_OFFSET + 1901),
LocationData("Engine of Destruction", "Engine of Destruction: Loki", SC2WOL_LOC_ID_OFFSET + 1902,
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
lambda state: state._sc2wol_has_competent_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
LocationData("Engine of Destruction", "Engine of Destruction: Lab Devourer", SC2WOL_LOC_ID_OFFSET + 1903),
LocationData("Engine of Destruction", "Engine of Destruction: North Devourer", SC2WOL_LOC_ID_OFFSET + 1904,
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
lambda state: state._sc2wol_has_competent_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
LocationData("Engine of Destruction", "Engine of Destruction: Southeast Devourer", SC2WOL_LOC_ID_OFFSET + 1905,
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
lambda state: state._sc2wol_has_competent_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
LocationData("Engine of Destruction", "Beat Engine of Destruction", None,
lambda state: state._sc2wol_has_mobile_anti_air(world, player) and
lambda state: state._sc2wol_has_competent_anti_air(world, player) and
state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)),
LocationData("Media Blitz", "Media Blitz: Victory", SC2WOL_LOC_ID_OFFSET + 2000,
lambda state: state._sc2wol_has_competent_comp(world, player)),
@@ -224,13 +253,19 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004),
LocationData("Media Blitz", "Beat Media Blitz", None,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Piercing the Shroud", "Piercing the Shroud: Victory", SC2WOL_LOC_ID_OFFSET + 2100),
LocationData("Piercing the Shroud", "Piercing the Shroud: Victory", SC2WOL_LOC_ID_OFFSET + 2100,
lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)),
LocationData("Piercing the Shroud", "Piercing the Shroud: Holding Cell Relic", SC2WOL_LOC_ID_OFFSET + 2101),
LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk Relic", SC2WOL_LOC_ID_OFFSET + 2102),
LocationData("Piercing the Shroud", "Piercing the Shroud: First Escape Relic", SC2WOL_LOC_ID_OFFSET + 2103),
LocationData("Piercing the Shroud", "Piercing the Shroud: Second Escape Relic", SC2WOL_LOC_ID_OFFSET + 2104),
LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk ", SC2WOL_LOC_ID_OFFSET + 2105),
LocationData("Piercing the Shroud", "Beat Piercing the Shroud", None),
LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk Relic", SC2WOL_LOC_ID_OFFSET + 2102,
lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)),
LocationData("Piercing the Shroud", "Piercing the Shroud: First Escape Relic", SC2WOL_LOC_ID_OFFSET + 2103,
lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)),
LocationData("Piercing the Shroud", "Piercing the Shroud: Second Escape Relic", SC2WOL_LOC_ID_OFFSET + 2104,
lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)),
LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk ", SC2WOL_LOC_ID_OFFSET + 2105,
lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)),
LocationData("Piercing the Shroud", "Beat Piercing the Shroud", None,
lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)),
LocationData("Whispers of Doom", "Whispers of Doom: Victory", SC2WOL_LOC_ID_OFFSET + 2200),
LocationData("Whispers of Doom", "Whispers of Doom: First Hatchery", SC2WOL_LOC_ID_OFFSET + 2201),
LocationData("Whispers of Doom", "Whispers of Doom: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 2202),
@@ -251,13 +286,12 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L
lambda state: state._sc2wol_has_protoss_common_units(world, player)),
LocationData("Echoes of the Future", "Beat Echoes of the Future", None,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2500,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2500),
LocationData("In Utter Darkness", "In Utter Darkness: Protoss Archive", SC2WOL_LOC_ID_OFFSET + 2501,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2502),
LocationData("In Utter Darkness", "Beat In Utter Darkness", None,
lambda state: state._sc2wol_has_protoss_medium_units(world, player)),
LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2502,
lambda state: state._sc2wol_has_protoss_common_units(world, player)),
LocationData("In Utter Darkness", "Beat In Utter Darkness", None),
LocationData("Gates of Hell", "Gates of Hell: Victory", SC2WOL_LOC_ID_OFFSET + 2600,
lambda state: state._sc2wol_has_competent_comp(world, player)),
LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601,

View File

@@ -10,16 +10,17 @@ class SC2WoLLogic(LogicMixin):
return self.has_any({'Marine', 'Marauder'}, player)
def _sc2wol_has_air(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Viking', 'Wraith', 'Medivac', 'Banshee', 'Hercules'}, player)
return self.has_any({'Viking', 'Wraith', 'Banshee'}, player) or \
self.has_any({'Hercules', 'Medivac'}, player) and self._sc2wol_has_common_unit(world, player)
def _sc2wol_has_air_anti_air(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Viking', 'Wraith'}, player)
def _sc2wol_has_mobile_anti_air(self, world: MultiWorld, player: int) -> bool:
def _sc2wol_has_competent_anti_air(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Marine', 'Goliath'}, player) or self._sc2wol_has_air_anti_air(world, player)
def _sc2wol_has_anti_air(self, world: MultiWorld, player: int) -> bool:
return self.has('Missile Turret', player) or self._sc2wol_has_mobile_anti_air(world, player)
return self.has_any({'Missile Turret', 'Thor', 'War Pigs', 'Spartan Company', "Hel's Angel", 'Battlecruiser'}, player) or self._sc2wol_has_competent_anti_air(world, player)
def _sc2wol_has_heavy_defense(self, world: MultiWorld, player: int) -> bool:
return (self.has_any({'Siege Tank', 'Vulture'}, player) or
@@ -28,13 +29,15 @@ class SC2WoLLogic(LogicMixin):
def _sc2wol_has_competent_comp(self, world: MultiWorld, player: int) -> bool:
return (self.has('Marine', player) or self.has('Marauder', player) and
self._sc2wol_has_mobile_anti_air(world, player)) and self.has_any({'Medivac', 'Medic'}, player) or \
self.has('Thor', player) or self.has("Banshee", player) and self._sc2wol_has_mobile_anti_air(world, player) or \
self.has('Battlecruiser', player) and self._sc2wol_has_common_unit(world, player)
self._sc2wol_has_competent_anti_air(world, player)) and self.has_any({'Medivac', 'Medic'}, player) or \
self.has('Thor', player) or self.has("Banshee", player) and self._sc2wol_has_competent_anti_air(world, player) or \
self.has('Battlecruiser', player) and self._sc2wol_has_common_unit(world, player) or \
self.has('Siege Tank', player) and self._sc2wol_has_competent_anti_air(world, player)
def _sc2wol_has_train_killers(self, world: MultiWorld, player: int) -> bool:
return (self.has_any({'Siege Tank', 'Diamondback'}, player) or
self.has_all({'Reaper', "G-4 Clusterbomb"}, player) or self.has_all({'Spectre', 'Psionic Lash'}, player))
self.has_all({'Reaper', "G-4 Clusterbomb"}, player) or self.has_all({'Spectre', 'Psionic Lash'}, player)
or self.has('Marauders', player))
def _sc2wol_able_to_rescue(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Medivac', 'Hercules', 'Raven', 'Viking'}, player)
@@ -46,6 +49,10 @@ class SC2WoLLogic(LogicMixin):
return self._sc2wol_has_protoss_common_units(world, player) and \
self.has_any({'Stalker', 'Void Ray', 'Phoenix', 'Carrier'}, player)
def _sc2wol_beats_protoss_deathball(self, world: MultiWorld, player: int) -> bool:
return self.has_any({'Banshee', 'Battlecruiser'}, player) and self._sc2wol_has_competent_anti_air or \
self._sc2wol_has_competent_comp(world, player) and self._sc2wol_has_air_anti_air(world, player)
def _sc2wol_cleared_missions(self, world: MultiWorld, player: int, mission_count: int) -> bool:
return self.has_group("Missions", player, mission_count)

View File

@@ -69,8 +69,8 @@ vanilla_mission_req_table = {
"Zero Hour": MissionInfo(3, 4, [2], "Mar Sara", completion_critical=True),
"Evacuation": MissionInfo(4, 4, [3], "Colonist"),
"Outbreak": MissionInfo(5, 3, [4], "Colonist"),
"Safe Haven": MissionInfo(6, 1, [5], "Colonist", number=7),
"Haven's Fall": MissionInfo(7, 1, [5], "Colonist", number=7),
"Safe Haven": MissionInfo(6, 4, [5], "Colonist", number=7),
"Haven's Fall": MissionInfo(7, 4, [5], "Colonist", number=7),
"Smash and Grab": MissionInfo(8, 5, [3], "Artifact", completion_critical=True),
"The Dig": MissionInfo(9, 4, [8], "Artifact", number=8, completion_critical=True),
"The Moebius Factor": MissionInfo(10, 9, [9], "Artifact", number=11, completion_critical=True),

View File

@@ -200,7 +200,10 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
connect(world, player, names, "Menu", missions[i])
else:
connect(world, player, names, missions[connection], missions[i],
(lambda name: (lambda state: state.has(f"Beat {name}", player)))(missions[connection]))
(lambda name, missions_req: (lambda state: state.has(f"Beat {name}", player) and
state._sc2wol_cleared_missions(world, player,
missions_req)))
(missions[connection], vanilla_shuffle_order[i].number))
connections.append(connection + 1)
mission_req_table.update({missions[i]: MissionInfo(

View File

@@ -33,7 +33,7 @@ class SC2WoLWorld(World):
game = "Starcraft 2 Wings of Liberty"
web = Starcraft2WoLWebWorld()
data_version = 2
data_version = 3
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {location.name: location.code for location in get_locations(None, None)}
@@ -43,6 +43,7 @@ class SC2WoLWorld(World):
locked_locations: typing.List[str]
location_cache: typing.List[Location]
mission_req_table = {}
required_client_version = 0, 3, 5
def __init__(self, world: MultiWorld, player: int):
super(SC2WoLWorld, self).__init__(world, player)

View File

@@ -1,14 +0,0 @@
from worlds.sm.variaRandomizer.rando.Items import ItemManager
items_start_id = 83000
def gen_special_id():
special_id_value_start = 32
while True:
yield special_id_value_start
special_id_value_start += 1
gen_run = gen_special_id()
lookup_id_to_name = dict((items_start_id + (value.Id if value.Id != None else next(gen_run)), value.Name) for key, value in ItemManager.Items.items())
lookup_name_to_id = {item_name: item_id for item_id, item_name in lookup_id_to_name.items()}

View File

@@ -1,14 +0,0 @@
from worlds.sm.variaRandomizer.graph.location import locationsDict
locations_start_id = 82000
def gen_boss_id():
boss_id_value_start = 256
while True:
yield boss_id_value_start
boss_id_value_start += 1
gen_run = gen_boss_id()
lookup_id_to_name = dict((locations_start_id + (value.Id if value.Id != None else next(gen_run)), key) for key, value in locationsDict.items())
lookup_name_to_id = {location_name: location_id for location_id, location_name in lookup_id_to_name.items()}

View File

@@ -122,8 +122,6 @@ class AreaRandomization(Choice):
option_off = 0
option_light = 1
option_on = 2
alias_false = 0
alias_true = 2
default = 0
class AreaLayout(Toggle):

View File

@@ -11,8 +11,6 @@ from worlds.sm.variaRandomizer.graph.graph_utils import GraphUtils
logger = logging.getLogger("Super Metroid")
from .Locations import lookup_name_to_id as locations_lookup_name_to_id
from .Items import lookup_name_to_id as items_lookup_name_to_id
from .Regions import create_regions
from .Rules import set_rules, add_entrance_rule
from .Options import sm_options
@@ -68,6 +66,8 @@ class SMWeb(WebWorld):
["Farrak Kilhn"]
)]
locations_start_id = 82000
items_start_id = 83000
class SMWorld(World):
"""
@@ -78,12 +78,11 @@ class SMWorld(World):
game: str = "Super Metroid"
topology_present = True
data_version = 1
data_version = 2
option_definitions = sm_options
item_names: Set[str] = frozenset(items_lookup_name_to_id)
location_names: Set[str] = frozenset(locations_lookup_name_to_id)
item_name_to_id = items_lookup_name_to_id
location_name_to_id = locations_lookup_name_to_id
item_name_to_id = {value.Name: items_start_id + value.Id for key, value in ItemManager.Items.items() if value.Id != None}
location_name_to_id = {key: locations_start_id + value.Id for key, value in locationsDict.items() if value.Id != None}
web = SMWeb()
remote_items: bool = False
@@ -661,8 +660,7 @@ class SMWorld(World):
loc.address = loc.item.code = None
@classmethod
def stage_fill_hook(cls, world, progitempool, nonexcludeditempool, localrestitempool, nonlocalrestitempool,
restitempool, fill_locations):
def stage_fill_hook(cls, world, progitempool, usefulitempool, filleritempool, fill_locations):
if world.get_game_players("Super Metroid"):
progitempool.sort(
key=lambda item: 1 if (item.name == 'Morph Ball') else 0)
@@ -701,8 +699,8 @@ class SMWorld(World):
dest.Name) for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions if src.Boss]))
def create_locations(self, player: int):
for name, id in locations_lookup_name_to_id.items():
self.locations[name] = SMLocation(player, name, id)
for name in locationsDict:
self.locations[name] = SMLocation(player, name, self.location_name_to_id.get(name, None))
def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None):

View File

@@ -2,14 +2,14 @@
; generated by asar
[labels]
B8:8026 :neg_1_1
B8:83C1 :neg_1_1
85:B9B4 :neg_1_2
85:B9E6 :neg_1_3
B8:C81F :neg_1_4
B8:C831 :neg_1_5
B8:C843 :neg_1_6
B8:800C :pos_1_0
B8:81DE :pos_1_1
B8:830C :pos_1_0
B8:85D7 :pos_1_1
84:FA6B :pos_1_2
84:FA75 :pos_1_3
B8:C862 :pos_1_4
@@ -20,7 +20,7 @@ B8:C87C :pos_1_6
85:990F CLIPLEN_end
85:990C CLIPLEN_no_multi
85:FF1D CLIPSET
B8:80EF COLLECTTANK
B8:84E8 COLLECTTANK
85:FF45 MISCFX
84:8BF2 NORMAL
85:FF4E SETFX
@@ -38,6 +38,11 @@ CE:FF00 config_multiworld
CE:FF08 config_player_id
CE:FF06 config_remote_items
CE:FF02 config_sprite
B8:8419 copy_config_to_sram
B8:83FD copy_memory
B8:8417 copy_memory_done
B8:8409 copy_memory_even
B8:840F copy_memory_loop
84:F894 h_item
84:F8AD i_chozo_item
84:F8B4 i_hidden_item
@@ -46,11 +51,11 @@ B8:885C i_item_setup_shared
B8:8878 i_item_setup_shared_all_items
B8:8883 i_item_setup_shared_alwaysloaded
84:FA79 i_live_pickup
B8:817F i_live_pickup_multiworld
B8:81C4 i_live_pickup_multiworld_end
B8:819B i_live_pickup_multiworld_local_item_or_offworld
B8:81B0 i_live_pickup_multiworld_own_item
B8:81BC i_live_pickup_multiworld_own_item1
B8:8578 i_live_pickup_multiworld
B8:85BD i_live_pickup_multiworld_end
B8:8594 i_live_pickup_multiworld_local_item_or_offworld
B8:85A9 i_live_pickup_multiworld_own_item
B8:85B5 i_live_pickup_multiworld_own_item1
84:FA1E i_load_custom_graphics
84:FA39 i_load_custom_graphics_all_items
84:FA49 i_load_custom_graphics_alwaysloaded
@@ -85,22 +90,27 @@ B8:81BC i_live_pickup_multiworld_own_item1
85:B9CA message_write_placeholders_loop
85:B9DC message_write_placeholders_notfound
85:B9DF message_write_placeholders_value_ok
B8:8092 mw_display_item_sent
B8:80FF mw_handle_queue
B8:8178 mw_handle_queue_end
B8:8101 mw_handle_queue_loop
B8:8151 mw_handle_queue_new_remote_item
B8:816D mw_handle_queue_next
B8:8163 mw_handle_queue_perform_receive
B8:81C8 mw_hook_main_game
B8:8011 mw_init
B8:8044 mw_init_end
B8:8000 mw_init_memory
B8:8083 mw_load_sram
B8:80B0 mw_receive_item
B8:80E8 mw_receive_item_end
B8:8070 mw_save_sram
B8:8049 mw_write_message
B8:848B mw_display_item_sent
B8:84F8 mw_handle_queue
B8:8571 mw_handle_queue_end
B8:84FA mw_handle_queue_loop
B8:854A mw_handle_queue_new_remote_item
B8:8566 mw_handle_queue_next
B8:855C mw_handle_queue_perform_receive
B8:85C1 mw_hook_main_game
B8:8311 mw_init
B8:8366 mw_init_continuereset
B8:83EA mw_init_end
B8:8300 mw_init_memory
B8:833B mw_init_reset_sram
B8:8351 mw_init_smstringdata
B8:8474 mw_load_sram
B8:8482 mw_load_sram_done
B8:8485 mw_load_sram_setnewgame
B8:84A9 mw_receive_item
B8:84E1 mw_receive_item_end
B8:8469 mw_save_sram
B8:8442 mw_write_message
84:F888 nonprog_item_eight_palette_indices
89:9200 offworld_graphics_data_item
89:9100 offworld_graphics_data_progression_item
@@ -125,7 +135,7 @@ B8:8049 mw_write_message
84:F96E p_visible_item_end
84:F95B p_visible_item_loop
84:F967 p_visible_item_trigger
B8:81DF patch_load_multiworld
B8:85D8 patch_load_multiworld
84:FA7E perform_item_pickup
84:F886 plm_graphics_entry_offworld_item
84:F87C plm_graphics_entry_offworld_progression_item
@@ -144,17 +154,19 @@ B8:C808 start_item_data_minor
B8:C818 start_item_data_reserve
B8:C856 update_graphic
84:F890 v_item
B8:83EF write_repeated_memory
B8:83F4 write_repeated_memory_loop
[source files]
0000 e25029c5 main.asm
0000 f30c3fdc main.asm
0001 06780555 ../common/nofanfare.asm
0002 e76d1f83 ../common/multiworld.asm
0003 613d24e1 ../common/itemextras.asm
0002 4f9a780e ../common/multiworld.asm
0003 f7e9db95 ../common/itemextras.asm
0004 d6616c0c ../common/items.asm
0005 440b54fe ../common/startitem.asm
0005 dbfcb38d ../common/startitem.asm
[rom checksum]
09b134c5
b526d5c7
[addr-to-line mapping]
ff:ffff 0000:00000001
@@ -204,330 +216,423 @@ ff:ffff 0000:00000001
84:8bf2 0001:00000152
84:8bf6 0001:00000153
84:8bf7 0001:00000153
b8:8000 0002:00000019
b8:8002 0002:0000001a
b8:8006 0002:0000001b
b8:8008 0002:0000001c
b8:800c 0002:00000020
b8:800e 0002:00000021
b8:8010 0002:00000022
b8:8011 0002:00000025
b8:8012 0002:00000025
b8:8013 0002:00000025
b8:8014 0002:00000025
b8:8015 0000:00000013
b8:8017 0002:00000029
b8:801b 0002:0000002a
b8:801e 0002:0000002b
b8:8020 0002:0000002d
b8:8023 0002:0000002e
b8:8026 0002:00000031
b8:802a 0002:00000032
b8:802e 0002:00000033
b8:8032 0002:00000034
b8:8036 0002:00000035
b8:8037 0002:00000035
b8:8038 0002:00000036
b8:803b 0002:00000037
b8:803d 0002:00000039
b8:8040 0002:0000003a
b8:8044 0002:0000003d
b8:8045 0002:0000003d
b8:8046 0002:0000003d
b8:8047 0002:0000003d
b8:8048 0002:0000003e
b8:8049 0002:00000043
b8:804a 0002:00000043
b8:804b 0002:00000044
b8:804c 0002:00000044
b8:804d 0002:00000045
b8:8051 0002:00000046
b8:8054 0002:00000046
b8:8055 0002:00000047
b8:8056 0002:00000048
b8:805a 0002:00000049
b8:805b 0002:0000004a
b8:805f 0002:0000004b
b8:8060 0002:0000004c
b8:8064 0002:0000004e
b8:8068 0002:0000004f
b8:8069 0002:00000050
b8:806d 0002:00000051
b8:806e 0002:00000051
b8:806f 0002:00000052
b8:8070 0002:00000055
b8:8071 0002:00000055
b8:8072 0000:00000013
b8:8074 0002:00000057
b8:8078 0002:00000058
b8:807c 0002:00000059
b8:807d 0002:00000059
b8:807e 0002:0000005b
b8:807f 0002:0000005c
b8:8082 0002:0000005d
b8:8083 0002:00000060
b8:8084 0002:00000060
b8:8085 0000:00000013
b8:8087 0002:00000062
b8:808b 0002:00000063
b8:808f 0002:00000064
b8:8090 0002:00000064
b8:8091 0002:00000065
b8:8092 0002:0000006a
b8:8094 0002:0000006b
b8:8096 0002:0000006e
b8:8099 0002:0000006f
b8:809b 0002:00000070
b8:809e 0002:00000071
b8:80a0 0002:00000072
b8:80a3 0002:00000073
b8:80a7 0002:00000074
b8:80a9 0002:00000075
b8:80ab 0002:00000076
b8:80ad 0002:00000077
b8:80af 0002:00000078
b8:80b0 0002:0000007c
b8:80b1 0002:0000007c
b8:80b2 0002:0000007d
b8:80b5 0002:0000007e
b8:80b7 0002:0000007f
b8:80ba 0002:00000080
b8:80bc 0002:00000081
b8:80bd 0002:00000082
b8:80be 0002:00000084
b8:80c1 0002:00000085
b8:80c3 0002:00000086
b8:80c6 0002:00000087
b8:80c7 0002:00000088
b8:80ca 0002:00000089
b8:80cb 0002:00000089
b8:80cc 0002:0000008a
b8:80d0 0002:0000008b
b8:80d1 0002:0000008c
b8:80d4 0002:0000008d
b8:80d8 0002:0000008e
b8:80da 0002:00000090
b8:80dd 0002:00000091
b8:80df 0002:00000092
b8:80e2 0002:00000093
b8:80e4 0002:00000095
b8:80e8 0002:00000097
b8:80ea 0002:00000098
b8:80ec 0002:00000099
b8:80ed 0002:00000099
b8:80ee 0002:0000009a
b8:80ef 0002:000000a5
b8:80f0 0002:000000a6
b8:80f4 0002:000000a7
b8:80f5 0002:000000a8
b8:80f9 0002:000000a9
b8:80fa 0002:000000ab
b8:80fe 0002:000000ac
b8:80ff 0002:000000de
b8:8100 0002:000000de
b8:8101 0002:000000e1
b8:8105 0002:000000e2
b8:8109 0002:000000e3
b8:810b 0002:000000e5
b8:810d 0002:000000e5
b8:810e 0002:000000e8
b8:8112 0002:000000e9
b8:8114 0002:000000ea
b8:8118 0002:000000eb
b8:811a 0002:000000ec
b8:811e 0002:000000ed
b8:8121 0002:000000ee
b8:8123 0002:000000ef
b8:8125 0002:000000f0
b8:8129 0002:000000f1
b8:812b 0002:000000f2
b8:812d 0002:000000f3
b8:8130 0002:000000f4
b8:8133 0002:000000f5
b8:8135 0002:000000f6
b8:813d 0002:000000fa
b8:813e 0002:000000fb
b8:813f 0002:000000fc
b8:8143 0002:000000ff
b8:8147 0002:00000100
b8:814b 0002:00000101
b8:814d 0002:00000103
b8:814e 0002:00000104
b8:814f 0002:00000105
b8:8151 0002:0000010a
b8:8152 0002:0000010b
b8:8156 0002:0000010e
b8:815a 0002:0000010f
b8:815e 0002:00000110
b8:8162 0002:00000111
b8:8163 0002:00000115
b8:8165 0002:00000116
b8:8168 0002:00000117
b8:816a 0002:00000118
b8:816d 0002:0000011b
b8:8171 0002:0000011c
b8:8172 0002:0000011d
b8:8176 0002:0000011f
b8:8178 0002:00000122
b8:817a 0002:00000123
b8:817c 0002:00000124
b8:817d 0002:00000124
b8:817e 0002:00000125
b8:817f 0002:00000129
b8:8180 0002:00000129
b8:8181 0002:00000129
b8:8182 0002:0000012a
b8:8186 0002:0000012b
b8:8189 0002:0000012b
b8:818a 0002:0000012d
b8:818e 0002:0000012e
b8:818f 0002:0000012f
b8:8193 0002:00000130
b8:8196 0002:00000131
b8:8198 0002:00000133
b8:819b 0002:00000136
b8:819f 0002:00000137
b8:81a3 0002:00000138
b8:81a5 0002:0000013a
b8:81a9 0002:0000013b
b8:81aa 0002:0000013d
b8:81ae 0002:0000013e
b8:81b0 0002:00000141
b8:81b4 0002:00000142
b8:81b7 0002:00000143
b8:81b9 0002:00000144
b8:81bc 0002:00000147
b8:81bd 0002:00000148
b8:81be 0002:00000149
b8:81c2 0002:0000014a
b8:81c4 0002:0000014d
b8:81c5 0002:0000014d
b8:81c6 0002:0000014d
b8:81c7 0002:0000014e
b8:81c8 0002:00000152
b8:81cc 0002:00000153
b8:81d0 0002:00000154
b8:81d2 0002:00000155
b8:81d6 0002:00000156
b8:81d9 0002:00000157
b8:81db 0002:00000158
b8:81de 0002:0000015a
b8:81df 0002:0000015d
b8:81e3 0002:0000015e
b8:81e4 0002:0000015f
b8:81e7 0002:00000160
b8:81eb 0002:00000162
b8:81ec 0002:00000163
b8:81ed 0002:00000164
b8:81ee 0002:00000165
b8:81ef 0002:00000166
8b:914a 0002:0000016b
81:80f7 0002:0000016e
81:8027 0002:00000171
82:8bb3 0002:00000174
85:b9a3 0002:0000020e
85:b9a4 0002:0000020e
85:b9a5 0002:00000211
85:b9a7 0002:00000212
85:b9ad 0002:00000212
85:b9ae 0002:00000213
85:b9b1 0002:00000214
85:b9b2 0002:00000215
85:b9b3 0002:00000215
85:b9b4 0002:00000219
85:b9b7 0002:0000021a
85:b9bb 0002:0000021b
85:b9bd 0002:0000021b
85:b9bf 0002:0000021c
85:b9c2 0002:0000021d
85:b9c4 0002:0000021f
85:b9c5 0002:00000220
85:b9c7 0002:00000224
85:b9ca 0002:00000226
85:b9cd 0002:00000227
85:b9cf 0002:00000228
85:b9d1 0002:00000229
85:b9d5 0002:0000022a
85:b9d7 0002:0000022b
85:b9d9 0002:0000022c
85:b9da 0002:0000022d
85:b9dc 0002:0000022f
85:b9df 0002:00000231
85:b9e2 0002:00000231
85:b9e3 0002:00000232
85:b9e6 0002:00000234
85:b9ea 0002:00000235
85:b9ed 0002:00000236
85:b9ee 0002:00000237
85:b9ef 0002:00000237
85:b9f0 0002:00000238
85:b9f4 0002:00000239
85:b9f5 0002:0000023a
85:b9f9 0002:0000023b
85:b9fb 0002:0000023c
85:b9fc 0002:0000023d
85:b9fd 0002:0000023e
85:ba00 0002:0000023f
85:ba02 0002:00000240
85:ba04 0002:00000243
85:ba05 0002:00000243
85:ba06 0002:00000244
85:ba09 0002:00000245
85:ba8a 0002:00000253
85:ba8c 0002:00000254
85:ba8f 0002:00000255
85:ba92 0002:00000256
85:ba95 0002:0000025e
85:ba96 0002:0000025f
85:ba98 0002:00000260
85:ba9b 0002:00000261
85:ba9d 0002:00000262
85:ba9f 0002:00000263
85:baa2 0002:00000264
85:baa4 0002:00000265
85:baa7 0002:00000266
85:baa9 0002:00000269
85:baaa 0002:0000026a
85:baab 0002:0000026b
85:baac 0002:0000026c
85:baae 0002:0000026d
85:baaf 0002:0000026e
85:bab0 0002:0000026f
85:bab1 0002:00000274
85:bab4 0002:00000275
85:bab5 0002:00000276
85:bab8 0002:00000277
85:bab9 0002:00000278
85:baba 0002:00000279
85:babb 0002:0000027a
85:babc 0002:00000285
85:babd 0002:00000286
85:babf 0002:00000287
85:bac2 0002:00000288
85:bac4 0002:00000289
85:bac7 0002:0000028a
85:bac9 0002:0000028d
85:baca 0002:0000028e
85:bacb 0002:0000028f
85:bacd 0002:00000290
85:bace 0002:00000292
85:bacf 0002:00000293
85:bad1 0002:00000294
85:bad4 0002:00000295
85:bad6 0002:00000296
85:bad9 0002:00000297
85:badb 0002:00000298
85:badc 0002:0000029a
85:badd 0002:0000029b
85:badf 0002:0000029c
85:bae2 0002:0000029d
85:bae4 0002:0000029e
85:bae7 0002:0000029f
85:bae9 0002:000002a0
85:8246 0002:000002a5
85:8249 0002:000002a6
85:824b 0002:000002a7
85:82f9 0002:000002ab
b8:8300 0002:0000005a
b8:8302 0002:0000005b
b8:8306 0002:0000005c
b8:8308 0002:0000005d
b8:830c 0002:00000061
b8:830e 0002:00000062
b8:8310 0002:00000063
b8:8311 0002:00000066
b8:8312 0002:00000066
b8:8313 0002:00000066
b8:8314 0002:00000066
b8:8315 0000:00000013
b8:8317 0002:0000006a
b8:831b 0002:0000006b
b8:831e 0002:0000006c
b8:8320 0002:0000006d
b8:8324 0002:0000006e
b8:8328 0002:0000006f
b8:832a 0002:00000070
b8:832e 0002:00000071
b8:8332 0002:00000072
b8:8334 0002:00000074
b8:8338 0002:00000075
b8:833b 0002:00000078
b8:833c 0002:00000079
b8:833f 0002:0000007a
b8:8342 0002:0000007b
b8:8345 0002:0000007c
b8:8348 0002:0000007d
b8:8349 0002:0000007e
b8:834a 0002:0000007f
b8:834e 0002:00000080
b8:834f 0002:00000082
b8:8366 0002:00000086
b8:8368 0002:00000087
b8:8369 0002:00000088
b8:836a 0002:00000089
b8:836c 0002:0000008a
b8:836e 0002:0000008b
b8:8370 0002:0000008c
b8:8372 0002:0000008d
b8:8375 0002:0000008e
b8:8377 0002:0000008f
b8:837a 0002:00000090
b8:837d 0002:00000091
b8:837f 0002:00000092
b8:8383 0002:00000094
b8:8385 0002:00000095
b8:8387 0002:00000096
b8:8389 0002:00000097
b8:838b 0002:00000098
b8:838d 0002:00000099
b8:838f 0002:0000009a
b8:8392 0002:0000009b
b8:8394 0002:0000009c
b8:8397 0002:0000009d
b8:839a 0002:0000009e
b8:839c 0002:0000009f
b8:83a0 0002:000000a1
b8:83a3 0002:000000a2
b8:83a7 0002:000000a3
b8:83ab 0002:000000a4
b8:83af 0002:000000a5
b8:83b3 0002:000000a6
b8:83b7 0002:000000a8
b8:83bb 0002:000000b0
b8:83be 0002:000000b1
b8:83c1 0002:000000b3
b8:83c2 0002:000000b4
b8:83c3 0002:000000b5
b8:83c7 0002:000000b6
b8:83cb 0002:000000b7
b8:83cd 0002:000000c4
b8:83d1 0002:000000c5
b8:83d4 0002:000000c6
b8:83d6 0002:000000c7
b8:83da 0002:000000c8
b8:83dd 0002:000000c9
b8:83df 0002:000000ce
b8:83e2 0002:000000cf
b8:83e6 0002:000000d0
b8:83ea 0002:000000d3
b8:83eb 0002:000000d3
b8:83ec 0002:000000d3
b8:83ed 0002:000000d3
b8:83ee 0002:000000d4
b8:83ef 0002:000000db
b8:83f0 0002:000000dc
b8:83f1 0002:000000dd
b8:83f2 0002:000000de
b8:83f3 0002:000000df
b8:83f4 0002:000000e1
b8:83f7 0002:000000e2
b8:83f8 0002:000000e3
b8:83f9 0002:000000e4
b8:83fa 0002:000000e5
b8:83fc 0002:000000e7
b8:83fd 0002:000000ee
b8:83fe 0002:000000ef
b8:83ff 0002:000000f0
b8:8400 0002:000000f1
b8:8402 0002:000000f3
b8:8404 0002:000000f4
b8:8405 0002:000000f5
b8:8407 0002:000000f6
b8:8409 0002:000000f8
b8:840b 0002:000000f9
b8:840c 0002:000000fa
b8:840d 0002:000000fb
b8:840f 0002:000000fd
b8:8411 0002:000000fe
b8:8413 0002:000000ff
b8:8414 0002:00000100
b8:8415 0002:00000101
b8:8417 0002:00000103
b8:8418 0002:00000104
b8:8419 0002:00000108
b8:841d 0002:00000109
b8:8421 0002:0000010a
b8:8425 0002:0000010b
b8:8429 0002:0000010c
b8:842d 0002:0000010d
b8:8431 0002:0000010e
b8:8435 0002:0000010f
b8:8439 0002:00000110
b8:843d 0002:00000111
b8:8441 0002:00000112
b8:8442 0002:00000118
b8:8443 0002:00000118
b8:8444 0002:00000119
b8:8445 0002:00000119
b8:8446 0002:0000011a
b8:844a 0002:0000011b
b8:844d 0002:0000011b
b8:844e 0002:0000011c
b8:844f 0002:0000011d
b8:8453 0002:0000011e
b8:8454 0002:0000011f
b8:8458 0002:00000120
b8:8459 0002:00000121
b8:845d 0002:00000123
b8:8461 0002:00000124
b8:8462 0002:00000125
b8:8466 0002:00000126
b8:8467 0002:00000126
b8:8468 0002:00000127
b8:8469 0002:0000012c
b8:846a 0002:0000012c
b8:846b 0000:00000013
b8:846d 0002:0000012f
b8:846e 0002:0000012f
b8:846f 0002:00000131
b8:8470 0002:00000132
b8:8473 0002:00000133
b8:8474 0002:00000138
b8:8475 0002:00000138
b8:8476 0000:00000013
b8:8478 0002:0000013a
b8:847c 0002:0000013b
b8:8480 0002:0000013c
b8:8482 0002:0000013e
b8:8483 0002:0000013e
b8:8484 0002:0000013f
b8:8485 0002:00000147
b8:8489 0002:00000148
b8:848b 0002:0000014e
b8:848d 0002:0000014f
b8:848f 0002:00000152
b8:8492 0002:00000153
b8:8494 0002:00000154
b8:8497 0002:00000155
b8:8499 0002:00000156
b8:849c 0002:00000157
b8:84a0 0002:00000158
b8:84a2 0002:00000159
b8:84a4 0002:0000015a
b8:84a6 0002:0000015b
b8:84a8 0002:0000015c
b8:84a9 0002:00000160
b8:84aa 0002:00000160
b8:84ab 0002:00000161
b8:84ae 0002:00000162
b8:84b0 0002:00000163
b8:84b3 0002:00000164
b8:84b5 0002:00000165
b8:84b6 0002:00000166
b8:84b7 0002:00000168
b8:84ba 0002:00000169
b8:84bc 0002:0000016a
b8:84bf 0002:0000016b
b8:84c0 0002:0000016c
b8:84c3 0002:0000016d
b8:84c4 0002:0000016d
b8:84c5 0002:0000016e
b8:84c9 0002:0000016f
b8:84ca 0002:00000170
b8:84cd 0002:00000171
b8:84d1 0002:00000172
b8:84d3 0002:00000174
b8:84d6 0002:00000175
b8:84d8 0002:00000176
b8:84db 0002:00000177
b8:84dd 0002:00000179
b8:84e1 0002:0000017b
b8:84e3 0002:0000017c
b8:84e5 0002:0000017d
b8:84e6 0002:0000017d
b8:84e7 0002:0000017e
b8:84e8 0002:00000189
b8:84e9 0002:0000018a
b8:84ed 0002:0000018b
b8:84ee 0002:0000018c
b8:84f2 0002:0000018d
b8:84f3 0002:0000018f
b8:84f7 0002:00000190
b8:84f8 0002:000001c2
b8:84f9 0002:000001c2
b8:84fa 0002:000001c5
b8:84fe 0002:000001c6
b8:8502 0002:000001c7
b8:8504 0002:000001c9
b8:8506 0002:000001c9
b8:8507 0002:000001cc
b8:850b 0002:000001cd
b8:850d 0002:000001ce
b8:8511 0002:000001cf
b8:8513 0002:000001d0
b8:8517 0002:000001d1
b8:851a 0002:000001d2
b8:851c 0002:000001d3
b8:851e 0002:000001d4
b8:8522 0002:000001d5
b8:8524 0002:000001d6
b8:8526 0002:000001d7
b8:8529 0002:000001d8
b8:852c 0002:000001d9
b8:852e 0002:000001da
b8:8536 0002:000001de
b8:8537 0002:000001df
b8:8538 0002:000001e0
b8:853c 0002:000001e3
b8:8540 0002:000001e4
b8:8544 0002:000001e5
b8:8546 0002:000001e7
b8:8547 0002:000001e8
b8:8548 0002:000001e9
b8:854a 0002:000001ee
b8:854b 0002:000001ef
b8:854f 0002:000001f2
b8:8553 0002:000001f3
b8:8557 0002:000001f4
b8:855b 0002:000001f5
b8:855c 0002:000001f9
b8:855e 0002:000001fa
b8:8561 0002:000001fb
b8:8563 0002:000001fc
b8:8566 0002:000001ff
b8:856a 0002:00000200
b8:856b 0002:00000201
b8:856f 0002:00000203
b8:8571 0002:00000206
b8:8573 0002:00000207
b8:8575 0002:00000208
b8:8576 0002:00000208
b8:8577 0002:00000209
b8:8578 0002:0000020d
b8:8579 0002:0000020d
b8:857a 0002:0000020d
b8:857b 0002:0000020e
b8:857f 0002:0000020f
b8:8582 0002:0000020f
b8:8583 0002:00000211
b8:8587 0002:00000212
b8:8588 0002:00000213
b8:858c 0002:00000214
b8:858f 0002:00000215
b8:8591 0002:00000217
b8:8594 0002:0000021a
b8:8598 0002:0000021b
b8:859c 0002:0000021c
b8:859e 0002:0000021e
b8:85a2 0002:0000021f
b8:85a3 0002:00000221
b8:85a7 0002:00000222
b8:85a9 0002:00000225
b8:85ad 0002:00000226
b8:85b0 0002:00000227
b8:85b2 0002:00000228
b8:85b5 0002:0000022b
b8:85b6 0002:0000022c
b8:85b7 0002:0000022d
b8:85bb 0002:0000022e
b8:85bd 0002:00000231
b8:85be 0002:00000231
b8:85bf 0002:00000231
b8:85c0 0002:00000232
b8:85c1 0002:00000236
b8:85c5 0002:00000237
b8:85c9 0002:00000238
b8:85cb 0002:00000239
b8:85cf 0002:0000023a
b8:85d2 0002:0000023b
b8:85d4 0002:0000023c
b8:85d7 0002:0000023e
b8:85d8 0002:00000241
b8:85dc 0002:00000243
b8:85dd 0002:00000244
b8:85de 0002:00000245
b8:85df 0002:00000246
b8:85e0 0002:00000247
8b:914a 0002:0000024c
81:80f7 0002:0000024f
81:8027 0002:00000252
82:8bb3 0002:00000255
85:b9a3 0002:000002ef
85:b9a4 0002:000002ef
85:b9a5 0002:000002f2
85:b9a7 0002:000002f3
85:b9ad 0002:000002f3
85:b9ae 0002:000002f4
85:b9b1 0002:000002f5
85:b9b2 0002:000002f6
85:b9b3 0002:000002f6
85:b9b4 0002:000002fa
85:b9b7 0002:000002fb
85:b9bb 0002:000002fc
85:b9bd 0002:000002fc
85:b9bf 0002:000002fd
85:b9c2 0002:000002fe
85:b9c4 0002:00000300
85:b9c5 0002:00000301
85:b9c7 0002:00000305
85:b9ca 0002:00000307
85:b9cd 0002:00000308
85:b9cf 0002:00000309
85:b9d1 0002:0000030a
85:b9d5 0002:0000030b
85:b9d7 0002:0000030c
85:b9d9 0002:0000030d
85:b9da 0002:0000030e
85:b9dc 0002:00000310
85:b9df 0002:00000312
85:b9e2 0002:00000312
85:b9e3 0002:00000313
85:b9e6 0002:00000315
85:b9ea 0002:00000316
85:b9ed 0002:00000317
85:b9ee 0002:00000318
85:b9ef 0002:00000318
85:b9f0 0002:00000319
85:b9f4 0002:0000031a
85:b9f5 0002:0000031b
85:b9f9 0002:0000031c
85:b9fb 0002:0000031d
85:b9fc 0002:0000031e
85:b9fd 0002:0000031f
85:ba00 0002:00000320
85:ba02 0002:00000321
85:ba04 0002:00000324
85:ba05 0002:00000324
85:ba06 0002:00000325
85:ba09 0002:00000326
85:ba8a 0002:00000334
85:ba8c 0002:00000335
85:ba8f 0002:00000336
85:ba92 0002:00000337
85:ba95 0002:0000033f
85:ba96 0002:00000340
85:ba98 0002:00000341
85:ba9b 0002:00000342
85:ba9d 0002:00000343
85:ba9f 0002:00000344
85:baa2 0002:00000345
85:baa4 0002:00000346
85:baa7 0002:00000347
85:baa9 0002:0000034a
85:baaa 0002:0000034b
85:baab 0002:0000034c
85:baac 0002:0000034d
85:baae 0002:0000034e
85:baaf 0002:0000034f
85:bab0 0002:00000350
85:bab1 0002:00000355
85:bab4 0002:00000356
85:bab5 0002:00000357
85:bab8 0002:00000358
85:bab9 0002:00000359
85:baba 0002:0000035a
85:babb 0002:0000035b
85:babc 0002:00000366
85:babd 0002:00000367
85:babf 0002:00000368
85:bac2 0002:00000369
85:bac4 0002:0000036a
85:bac7 0002:0000036b
85:bac9 0002:0000036e
85:baca 0002:0000036f
85:bacb 0002:00000370
85:bacd 0002:00000371
85:bace 0002:00000373
85:bacf 0002:00000374
85:bad1 0002:00000375
85:bad4 0002:00000376
85:bad6 0002:00000377
85:bad9 0002:00000378
85:badb 0002:00000379
85:badc 0002:0000037b
85:badd 0002:0000037c
85:badf 0002:0000037d
85:bae2 0002:0000037e
85:bae4 0002:0000037f
85:bae7 0002:00000380
85:bae9 0002:00000381
85:8246 0002:00000386
85:8249 0002:00000387
85:824b 0002:00000388
85:82f9 0002:0000038c
b8:885c 0003:00000045
b8:885d 0003:00000045
b8:885e 0003:00000046

View File

@@ -4,7 +4,7 @@
"CLIPLEN_end": "85:990F",
"CLIPLEN_no_multi": "85:990C",
"CLIPSET": "85:FF1D",
"COLLECTTANK": "B8:80EF",
"COLLECTTANK": "B8:84E8",
"MISCFX": "85:FF45",
"NORMAL": "84:8BF2",
"SETFX": "85:FF4E",
@@ -22,6 +22,11 @@
"config_player_id": "CE:FF08",
"config_remote_items": "CE:FF06",
"config_sprite": "CE:FF02",
"copy_config_to_sram": "B8:8419",
"copy_memory": "B8:83FD",
"copy_memory_done": "B8:8417",
"copy_memory_even": "B8:8409",
"copy_memory_loop": "B8:840F",
"h_item": "84:F894",
"i_chozo_item": "84:F8AD",
"i_hidden_item": "84:F8B4",
@@ -30,11 +35,11 @@
"i_item_setup_shared_all_items": "B8:8878",
"i_item_setup_shared_alwaysloaded": "B8:8883",
"i_live_pickup": "84:FA79",
"i_live_pickup_multiworld": "B8:817F",
"i_live_pickup_multiworld_end": "B8:81C4",
"i_live_pickup_multiworld_local_item_or_offworld": "B8:819B",
"i_live_pickup_multiworld_own_item": "B8:81B0",
"i_live_pickup_multiworld_own_item1": "B8:81BC",
"i_live_pickup_multiworld": "B8:8578",
"i_live_pickup_multiworld_end": "B8:85BD",
"i_live_pickup_multiworld_local_item_or_offworld": "B8:8594",
"i_live_pickup_multiworld_own_item": "B8:85A9",
"i_live_pickup_multiworld_own_item1": "B8:85B5",
"i_load_custom_graphics": "84:FA1E",
"i_load_custom_graphics_all_items": "84:FA39",
"i_load_custom_graphics_alwaysloaded": "84:FA49",
@@ -69,22 +74,27 @@
"message_write_placeholders_loop": "85:B9CA",
"message_write_placeholders_notfound": "85:B9DC",
"message_write_placeholders_value_ok": "85:B9DF",
"mw_display_item_sent": "B8:8092",
"mw_handle_queue": "B8:80FF",
"mw_handle_queue_end": "B8:8178",
"mw_handle_queue_loop": "B8:8101",
"mw_handle_queue_new_remote_item": "B8:8151",
"mw_handle_queue_next": "B8:816D",
"mw_handle_queue_perform_receive": "B8:8163",
"mw_hook_main_game": "B8:81C8",
"mw_init": "B8:8011",
"mw_init_end": "B8:8044",
"mw_init_memory": "B8:8000",
"mw_load_sram": "B8:8083",
"mw_receive_item": "B8:80B0",
"mw_receive_item_end": "B8:80E8",
"mw_save_sram": "B8:8070",
"mw_write_message": "B8:8049",
"mw_display_item_sent": "B8:848B",
"mw_handle_queue": "B8:84F8",
"mw_handle_queue_end": "B8:8571",
"mw_handle_queue_loop": "B8:84FA",
"mw_handle_queue_new_remote_item": "B8:854A",
"mw_handle_queue_next": "B8:8566",
"mw_handle_queue_perform_receive": "B8:855C",
"mw_hook_main_game": "B8:85C1",
"mw_init": "B8:8311",
"mw_init_continuereset": "B8:8366",
"mw_init_end": "B8:83EA",
"mw_init_memory": "B8:8300",
"mw_init_reset_sram": "B8:833B",
"mw_init_smstringdata": "B8:8351",
"mw_load_sram": "B8:8474",
"mw_load_sram_done": "B8:8482",
"mw_load_sram_setnewgame": "B8:8485",
"mw_receive_item": "B8:84A9",
"mw_receive_item_end": "B8:84E1",
"mw_save_sram": "B8:8469",
"mw_write_message": "B8:8442",
"nonprog_item_eight_palette_indices": "84:F888",
"offworld_graphics_data_item": "89:9200",
"offworld_graphics_data_progression_item": "89:9100",
@@ -109,7 +119,7 @@
"p_visible_item_end": "84:F96E",
"p_visible_item_loop": "84:F95B",
"p_visible_item_trigger": "84:F967",
"patch_load_multiworld": "B8:81DF",
"patch_load_multiworld": "B8:85D8",
"perform_item_pickup": "84:FA7E",
"plm_graphics_entry_offworld_item": "84:F886",
"plm_graphics_entry_offworld_progression_item": "84:F87C",
@@ -128,14 +138,24 @@
"start_item_data_reserve": "B8:C818",
"update_graphic": "B8:C856",
"v_item": "84:F890",
"write_repeated_memory": "B8:83EF",
"write_repeated_memory_loop": "B8:83F4",
"ITEM_RAM": "7E:09A2",
"SRAM_MW_ITEMS_RECV": "70:2000",
"SRAM_MW_ITEMS_RECV_RPTR": "70:2600",
"SRAM_MW_ITEMS_RECV_WPTR": "70:2602",
"SRAM_MW_ITEMS_RECV_SPTR": "70:2604",
"SRAM_MW_ITEMS_SENT_RPTR": "70:2680",
"SRAM_MW_ITEMS_SENT_WPTR": "70:2682",
"SRAM_MW_ITEMS_RECV_WCOUNT": "70:2602",
"ReceiveQueueCompletedCount_InRamThatGetsSavedToSaveSlot": "7e:d8ae",
"SRAM_MW_ITEMS_SENT_RCOUNT": "70:2680",
"SRAM_MW_ITEMS_SENT_WCOUNT": "70:2682",
"SRAM_MW_ITEMS_SENT": "70:2700",
"SRAM_MW_INITIALIZED": "70:26fe",
"SRAM_MW_SM": "70:3000",
"SRAM_MW_ROMTITLE": "70:3015",
"SRAM_MW_SEEDINT": "70:3060",
"SRAM_MW_INITIALIZED": "70:3064",
"SRAM_MW_CONFIG_ENABLED": "70:3070",
"SRAM_MW_CONFIG_CUSTOM_SPRITE": "70:3072",
"SRAM_MW_CONFIG_DEATHLINK": "70:3074",
"SRAM_MW_CONFIG_REMOTE_ITEMS": "70:3076",
"SRAM_MW_CONFIG_PLAYER_ID": "70:3078",
"varia_seedint_location": "df:ff00",
"CollectedItems": "7E:D86E"
}

View File

@@ -294,7 +294,18 @@ accessPoints = [
sm.canGetBackFromRidleyZone(),
sm.canPassWastelandDessgeegas(),
sm.canPassRedKiHunters())),
'RidleyRoomOut': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']))
'RidleyRoomOut': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])),
'Wasteland': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
sm.canGetBackFromRidleyZone(),
sm.canPassWastelandDessgeegas()))
}, internal=True),
AccessPoint('Wasteland', 'LowerNorfair', {
# no transition to firefleas to exlude pb of shame location when starting at firefleas top
'Ridley Zone': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
sm.traverse('WastelandLeft'),
sm.canGetBackFromRidleyZone(),
sm.canPassWastelandDessgeegas(),
sm.canPassNinjaPirates()))
}, internal=True),
AccessPoint('Three Muskateers Room Left', 'LowerNorfair', {
'Firefleas': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),

View File

@@ -797,10 +797,10 @@ locationsDict["Power Bomb (lower Norfair above fire flea room)"].Available = (
lambda sm: SMBool(True)
)
locationsDict["Power Bomb (Power Bombs of shame)"].AccessFrom = {
'Ridley Zone': lambda sm: sm.canUsePowerBombs()
'Wasteland': lambda sm: sm.canUsePowerBombs()
}
locationsDict["Power Bomb (Power Bombs of shame)"].Available = (
lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])
lambda sm: SMBool(True)
)
locationsDict["Missile (lower Norfair near Wave Beam)"].AccessFrom = {
'Firefleas': lambda sm: SMBool(True)

View File

@@ -46,6 +46,9 @@ locCCM_table = {
"CCM: Snowman's Lost His Head": 3626025,
"CCM: Wall Kicks Will Work": 3626026,
"CCM: Bob-omb Buddy": 3626203,
"CCM: 1Up Block Near Snowman": 3626215,
"CCM: 1Up Block Ice Pillar": 3626216,
"CCM: 1Up Block Secret Slide": 3626217
}
#Big Boo's Haunt
@@ -55,7 +58,8 @@ locBBH_table = {
"BBH: Secret of the Haunted Books": 3626030,
"BBH: Seek the 8 Red Coins": 3626031,
"BBH: Big Boo's Balcony": 3626032,
"BBH: Eye to Eye in the Secret Room": 3626033
"BBH: Eye to Eye in the Secret Room": 3626033,
"BBH: 1Up Block Top of Mansion": 3626218
}
#Hazy Maze Cave
@@ -65,7 +69,9 @@ locHMC_table = {
"HMC: Metal-Head Mario Can Move!": 3626037,
"HMC: Navigating the Toxic Maze": 3626038,
"HMC: A-Maze-Ing Emergency Exit": 3626039,
"HMC: Watch for Rolling Rocks": 3626040
"HMC: Watch for Rolling Rocks": 3626040,
"HMC: 1Up Block above Pit": 3626219,
"HMC: 1Up Block Past Rolling Rocks": 3626220,
}
#Lethal Lava Land
@@ -87,6 +93,9 @@ locSSL_table = {
"SSL: Free Flying for 8 Red Coins": 3626053,
"SSL: Pyramid Puzzle": 3626054,
"SSL: Bob-omb Buddy": 3626207,
"SSL: 1Up Block Outside Pyramid": 3626221,
"SSL: 1Up Block Pyramid Left Path": 3626222,
"SSL: 1Up Block Pyramid Back": 3626223
}
#Dire, Dire Docks
@@ -108,6 +117,8 @@ locSL_table = {
"SL: Shell Shreddin' for Red Coins": 3626067,
"SL: Into the Igloo": 3626068,
"SL: Bob-omb Buddy": 3626209,
"SL: 1Up Block Near Moneybags": 3626224,
"SL: 1Up Block inside Igloo": 3626225
}
#Wet-Dry World
@@ -119,6 +130,7 @@ locWDW_table = {
"WDW: Go to Town for Red Coins": 3626074,
"WDW: Quick Race Through Downtown!": 3626075,
"WDW: Bob-omb Buddy": 3626210,
"WDW: 1Up Block in Downtown": 3626226
}
#Tall, Tall Mountain
@@ -130,6 +142,7 @@ locTTM_table = {
"TTM: Breathtaking View from Bridge": 3626081,
"TTM: Blast to the Lonely Mushroom": 3626082,
"TTM: Bob-omb Buddy": 3626211,
"TTM: 1Up Block on Red Mushroom": 3626227
}
#Tiny-Huge Island
@@ -141,6 +154,9 @@ locTHI_table = {
"THI: Wiggler's Red Coins": 3626088,
"THI: Make Wiggler Squirm": 3626089,
"THI: Bob-omb Buddy": 3626212,
"THI: 1Up Block THI Small near Start": 3626228,
"THI: 1Up Block THI Large near Start": 3626229,
"THI: 1Up Block Windy Area": 3626230
}
#Tick Tock Clock
@@ -150,7 +166,9 @@ locTTC_table = {
"TTC: Get a Hand": 3626093,
"TTC: Stomp on the Thwomp": 3626094,
"TTC: Timed Jumps on Moving Bars": 3626095,
"TTC: Stop Time for Red Coins": 3626096
"TTC: Stop Time for Red Coins": 3626096,
"TTC: 1Up Block Midway Up": 3626231,
"TTC: 1Up Block at the Top": 3626232
}
#Rainbow Ride
@@ -162,6 +180,9 @@ locRR_table = {
"RR: Tricky Triangles!": 3626102,
"RR: Somewhere Over the Rainbow": 3626103,
"RR: Bob-omb Buddy": 3626214,
"RR: 1Up Block Top of Red Coin Maze": 3626233,
"RR: 1Up Block Under Fly Guy": 3626234,
"RR: 1Up Block On House in the Sky": 3626235
}
loc100Coin_table = {
@@ -193,7 +214,9 @@ locSA_table = {
locBitDW_table = {
"Bowser in the Dark World Red Coins": 3626105,
"Bowser in the Dark World Key": 3626178
"Bowser in the Dark World Key": 3626178,
"Bowser in the Dark World 1Up Block on Tower": 3626236,
"Bowser in the Dark World 1Up Block near Goombas": 3626237
}
locTotWC_table = {
@@ -203,25 +226,31 @@ locTotWC_table = {
locCotMC_table = {
"Cavern of the Metal Cap Switch": 3626182,
"Cavern of the Metal Cap Red Coins": 3626133
"Cavern of the Metal Cap Red Coins": 3626133,
"Cavern of the Metal Cap 1Up Block": 3626241
}
locVCutM_table = {
"Vanish Cap Under the Moat Switch": 3626183,
"Vanish Cap Under the Moat Red Coins": 3626147
"Vanish Cap Under the Moat Red Coins": 3626147,
"Vanish Cap Under the Moat 1Up Block": 3626242
}
locBitFS_table = {
"Bowser in the Fire Sea Red Coins": 3626112,
"Bowser in the Fire Sea Key": 3626179
"Bowser in the Fire Sea Key": 3626179,
"Bowser in the Fire Sea 1Up Block Swaying Stairs": 3626238,
"Bowser in the Fire Sea 1Up Block Near Poles": 3626239
}
locWMotR_table = {
"Wing Mario Over the Rainbow": 3626154
"Wing Mario Over the Rainbow Red Coins": 3626154,
"Wing Mario Over the Rainbow 1Up Block": 3626243
}
locBitS_table = {
"Bowser in the Sky Red Coins": 3626119
"Bowser in the Sky Red Coins": 3626119,
"Bowser in the Sky 1Up Block": 3626240
}
#Secret Stars found inside the Castle
@@ -239,4 +268,4 @@ location_table = {**locBoB_table,**locWhomp_table,**locJRB_table,**locCCM_table,
**locWDW_table,**locTTM_table,**locTHI_table,**locTTC_table,**locRR_table, \
**loc100Coin_table,**locPSS_table,**locSA_table,**locBitDW_table,**locTotWC_table, \
**locCotMC_table, **locVCutM_table, **locBitFS_table, **locWMotR_table, **locBitS_table, \
**locSS_table}
**locSS_table}

View File

@@ -1,48 +1,57 @@
import typing
from Options import Option, DefaultOnToggle, Range, Toggle, DeathLink, Choice
class EnableCoinStars(DefaultOnToggle):
"""Disable to Ignore 100 Coin Stars. You can still collect them, but they don't do anything"""
display_name = "Enable 100 Coin Stars"
class StrictCapRequirements(DefaultOnToggle):
"""If disabled, Stars that expect special caps may have to be acquired without the caps"""
display_name = "Strict Cap Requirements"
class StrictCannonRequirements(DefaultOnToggle):
"""If disabled, Stars that expect cannons may have to be acquired without them. Only makes a difference if Buddy Checks are enabled"""
display_name = "Strict Cannon Requirements"
class FirstBowserStarDoorCost(Range):
"""How many stars are required at the Star Door to Bowser in the Dark World"""
range_start = 0
range_end = 50
default = 8
class BasementStarDoorCost(Range):
"""How many stars are required at the Star Door in the Basement"""
range_start = 0
range_end = 70
default = 30
class SecondFloorStarDoorCost(Range):
"""How many stars are required to access the third floor"""
range_start = 0
range_end = 90
default = 50
class MIPS1Cost(Range):
"""How many stars are required to spawn MIPS the first time"""
range_start = 0
range_end = 40
default = 15
class MIPS2Cost(Range):
"""How many stars are required to spawn MIPS the secound time. Must be bigger or equal MIPS1Cost"""
range_start = 0
range_end = 80
default = 50
class StarsToFinish(Range):
"""How many stars are required at the infinite stairs"""
display_name = "Endless Stairs Stars"
@@ -50,29 +59,40 @@ class StarsToFinish(Range):
range_end = 100
default = 70
class AmountOfStars(Range):
"""How many stars exist. Disabling 100 Coin Stars removes 15 from the Pool. At least max of any Cost set"""
range_start = 35
range_end = 120
default = 120
class AreaRandomizer(Choice):
"""Randomize Entrances"""
display_name = "Entrance Randomizer"
alias_false = 0
option_Off = 0
option_Courses_Only = 1
option_Courses_and_Secrets = 2
class BuddyChecks(Toggle):
"""Bob-omb Buddies are checks, Cannon Unlocks are items"""
display_name = "Bob-omb Buddy Checks"
class ExclamationBoxes(Choice):
"""Include 1Up Exclamation Boxes during randomization"""
display_name = "Randomize 1Up !-Blocks"
option_Off = 0
option_1Ups_Only = 1
class ProgressiveKeys(DefaultOnToggle):
"""Keys will first grant you access to the Basement, then to the Secound Floor"""
display_name = "Progressive Keys"
sm64_options: typing.Dict[str,type(Option)] = {
sm64_options: typing.Dict[str, type(Option)] = {
"AreaRandomizer": AreaRandomizer,
"ProgressiveKeys": ProgressiveKeys,
"EnableCoinStars": EnableCoinStars,
@@ -87,4 +107,5 @@ sm64_options: typing.Dict[str,type(Option)] = {
"StarsToFinish": StarsToFinish,
"death_link": DeathLink,
"BuddyChecks": BuddyChecks,
}
"ExclamationBoxes": ExclamationBoxes,
}

View File

@@ -86,6 +86,7 @@ def set_rules(world, player: int, area_connections):
# which would make it impossible to reach downtown area without the cannon.
add_rule(world.get_location("WDW: Quick Race Through Downtown!", player), lambda state: state.has("Cannon Unlock WDW", player))
add_rule(world.get_location("WDW: Go to Town for Red Coins", player), lambda state: state.has("Cannon Unlock WDW", player))
add_rule(world.get_location("WDW: 1Up Block in Downtown", player), lambda state: state.has("Cannon Unlock WDW", player))
if world.StrictCapRequirements[player]:
add_rule(world.get_location("BoB: Mario Wings to the Sky", player), lambda state: state.has("Wing Cap", player))
@@ -109,7 +110,8 @@ def set_rules(world, player: int, area_connections):
add_rule(world.get_location("BoB: 100 Coins", player), lambda state: state.has("Cannon Unlock BoB", player) or state.has("Wing Cap", player))
#Rules for Secret Stars
add_rule(world.get_location("Wing Mario Over the Rainbow", player), lambda state: state.has("Wing Cap", player))
add_rule(world.get_location("Wing Mario Over the Rainbow Red Coins", player), lambda state: state.has("Wing Cap", player))
add_rule(world.get_location("Wing Mario Over the Rainbow 1Up Block", player), lambda state: state.has("Wing Cap", player))
add_rule(world.get_location("Toad (Basement)", player), lambda state: state.can_reach("Basement", 'Region', player) and state.has("Power Star", player, 12))
add_rule(world.get_location("Toad (Second Floor)", player), lambda state: state.can_reach("Second Floor", 'Region', player) and state.has("Power Star", player, 25))
add_rule(world.get_location("Toad (Third Floor)", player), lambda state: state.can_reach("Third Floor", 'Region', player) and state.has("Power Star", player, 35))

View File

@@ -9,6 +9,7 @@ from .Regions import create_regions, sm64courses, sm64entrances_s, sm64_internal
from BaseClasses import Item, Tutorial, ItemClassification
from ..AutoWorld import World, WebWorld
class SM64Web(WebWorld):
tutorials = [Tutorial(
"Multiworld Setup Guide",
@@ -34,7 +35,7 @@ class SM64World(World):
item_name_to_id = item_table
location_name_to_id = location_table
data_version = 6
data_version = 8
required_client_version = (0, 3, 0)
area_connections: typing.Dict[int, int]
@@ -71,7 +72,6 @@ class SM64World(World):
return item
def generate_basic(self):
staritem = self.create_item("Power Star")
starcount = self.world.AmountOfStars[self.player].value
if (not self.world.EnableCoinStars[self.player].value):
starcount = max(35,self.world.AmountOfStars[self.player].value-15)
@@ -79,17 +79,15 @@ class SM64World(World):
self.world.BasementStarDoorCost[self.player].value, self.world.SecondFloorStarDoorCost[self.player].value,
self.world.MIPS1Cost[self.player].value, self.world.MIPS2Cost[self.player].value,
self.world.StarsToFinish[self.player].value)
self.world.itempool += [staritem for i in range(0,starcount)]
mushroomitem = self.create_item("1Up Mushroom")
self.world.itempool += [mushroomitem for i in range(starcount,120 - (15 if not self.world.EnableCoinStars[self.player].value else 0))]
self.world.itempool += [self.create_item("Power Star") for i in range(0,starcount)]
self.world.itempool += [self.create_item("1Up Mushroom") for i in range(starcount,120 - (15 if not self.world.EnableCoinStars[self.player].value else 0))]
if (not self.world.ProgressiveKeys[self.player].value):
key1 = self.create_item("Basement Key")
key2 = self.create_item("Second Floor Key")
self.world.itempool += [key1,key2]
else:
key = self.create_item("Progressive Key")
self.world.itempool += [key,key]
self.world.itempool += [self.create_item("Progressive Key") for i in range(0,2)]
wingcap = self.create_item("Wing Cap")
metalcap = self.create_item("Metal Cap")
@@ -110,6 +108,39 @@ class SM64World(World):
self.world.get_location("THI: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock THI"))
self.world.get_location("RR: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock RR"))
if (self.world.ExclamationBoxes[self.player].value > 0):
self.world.itempool += [self.create_item("1Up Mushroom") for i in range(0,29)]
else:
self.world.get_location("CCM: 1Up Block Near Snowman", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("CCM: 1Up Block Ice Pillar", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("CCM: 1Up Block Secret Slide", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("BBH: 1Up Block Top of Mansion", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("HMC: 1Up Block above Pit", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("HMC: 1Up Block Past Rolling Rocks", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("SSL: 1Up Block Outside Pyramid", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("SSL: 1Up Block Pyramid Left Path", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("SSL: 1Up Block Pyramid Back", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("SL: 1Up Block Near Moneybags", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("SL: 1Up Block inside Igloo", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("WDW: 1Up Block in Downtown", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("TTM: 1Up Block on Red Mushroom", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("THI: 1Up Block THI Small near Start", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("THI: 1Up Block THI Large near Start", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("THI: 1Up Block Windy Area", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("TTC: 1Up Block Midway Up", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("TTC: 1Up Block at the Top", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("RR: 1Up Block Top of Red Coin Maze", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("RR: 1Up Block Under Fly Guy", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("RR: 1Up Block On House in the Sky", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("Bowser in the Dark World 1Up Block on Tower", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("Bowser in the Dark World 1Up Block near Goombas", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("Cavern of the Metal Cap 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("Vanish Cap Under the Moat 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("Bowser in the Fire Sea 1Up Block Swaying Stairs", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("Bowser in the Fire Sea 1Up Block Near Poles", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("Wing Mario Over the Rainbow 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.world.get_location("Bowser in the Sky 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
def get_filler_item_name(self) -> str:
return "1Up Mushroom"

View File

@@ -107,7 +107,6 @@ class HeartBeepSpeed(Choice):
option_Half = 2
option_Normal = 3
option_Double = 4
alias_false = 0
default = 3
class HeartColor(Choice):

View File

@@ -25,6 +25,10 @@ from Options import Accessibility
world_folder = os.path.dirname(__file__)
logger = logging.getLogger("SMZ3")
# Location IDs in the range 256+196 to 256+202 shifted +34 between 11.2 and 11.3
# this is required to keep backward compatibility
def convertLocSMZ3IDToAPID(value):
return (value - 34) if value >= 256+230 and value <= 256+236 else value
class SMZ3CollectionState(metaclass=AutoLogicRegister):
def init_mixin(self, parent: MultiWorld):
@@ -61,12 +65,13 @@ class SMZ3World(World):
"""
game: str = "SMZ3"
topology_present = False
data_version = 2
data_version = 3
option_definitions = smz3_options
item_names: Set[str] = frozenset(TotalSMZ3Item.lookup_name_to_id)
location_names: Set[str]
item_name_to_id = TotalSMZ3Item.lookup_name_to_id
location_name_to_id: Dict[str, int] = {key : locations_start_id + value.Id for key, value in TotalSMZ3World(Config(), "", 0, "").locationLookup.items()}
location_name_to_id: Dict[str, int] = {key : locations_start_id + convertLocSMZ3IDToAPID(value.Id)
for key, value in TotalSMZ3World(Config(), "", 0, "").locationLookup.items()}
web = SMZ3Web()
remote_items: bool = False
@@ -526,9 +531,11 @@ class SMZ3World(World):
def JunkFillGT(self, factor):
poolLength = len(self.world.itempool)
playerGroups = self.world.get_player_groups(self.player)
playerGroups.add(self.player)
junkPoolIdx = [i for i in range(0, poolLength)
if self.world.itempool[i].classification in (ItemClassification.filler, ItemClassification.trap) and
self.world.itempool[i].player == self.player]
self.world.itempool[i].player in playerGroups]
toRemove = []
for loc in self.locations.values():
# commenting this for now since doing a partial GT pre fill would allow for non SMZ3 progression in GT
@@ -590,8 +597,6 @@ class SMZ3World(World):
# /* Check Swords option and place as needed */
if self.smz3World.Config.SwordLocation == SwordLocation.Uncle:
self.FillItemAtLocation(self.progression, TotalSMZ3Item.ItemType.ProgressiveSword, self.smz3World.GetLocation("Link's Uncle"))
elif self.smz3World.Config.SwordLocation == SwordLocation.Early:
self.FrontFillItemInOwnWorld(self.progression, TotalSMZ3Item.ItemType.ProgressiveSword)
# /* Check Morph option and place as needed */
if self.smz3World.Config.MorphLocation == MorphLocation.Original:
@@ -599,6 +604,10 @@ class SMZ3World(World):
elif self.smz3World.Config.MorphLocation == MorphLocation.Early:
self.FrontFillItemInOwnWorld(self.progression, TotalSMZ3Item.ItemType.Morph)
# We do early Sword placement after Morph in case its Original location
if self.smz3World.Config.SwordLocation == SwordLocation.Early:
self.FrontFillItemInOwnWorld(self.progression, TotalSMZ3Item.ItemType.ProgressiveSword)
# /* We place a PB and Super in Sphere 1 to make sure the filler
# * doesn't start locking items behind this when there are a
# * high chance of the trash fill actually making them available */

View File

@@ -21,8 +21,6 @@ class OffOnFullChoice(Choice):
option_on = 1
option_full = 2
alias_chaos = 2
alias_false = 0
alias_true = 1
class Difficulty(EvermizerFlags, Choice):

View File

@@ -1,3 +1,4 @@
import functools
from typing import Dict, Set, List
# EN Locale Creature Name to rough depth in meters found at
@@ -50,33 +51,75 @@ all_creatures: Dict[str, int] = {
"Lava Larva": 1300,
"Lava Lizard": 1300,
"Sea Dragon Leviathan": 1300,
"Sea Emperor Leviathan": 1700,
"Cuddlefish": 300,
"Sea Emperor Juvenile": 1700,
# "Cuddlefish": 300, # maybe at some point, needs hatching in containment chamber (20 real-life minutes)
}
# be nice and make these require Stasis Rifle
aggressive: Set[str] = {
"Cave Crawler", # is very easy without Stasis Rifle, but included for consistency
"Crashfish",
"Biter",
"Bleeder",
"Blighter",
"Blood Crawler",
"Mesmer",
"Reaper Leviathan",
"Crabsquid",
"Warper",
"Crabsnake",
"Ampeel",
"Stalker",
"Sand Shark",
"Boneshark",
"Lava Lizard",
"Sea Dragon Leviathan",
"River Prowler",
"Ghost Leviathan Juvenile",
"Ghost Leviathan"
}
containment: Set[str] = { # creatures that have to be raised from eggs
"Cuddlefish",
}
hatchable: Set[str] = { # aggressive creatures that can be grown from eggs as alternative to stasis
"Ampeel", # warning: electric shocks
"Crabsquid", # warning: electric shocks
"Crabsnake",
"Boneshark",
"Crashfish",
"Gasopod",
"Lava Lizard",
"Mesmer",
"Sand Shark",
"Stalker",
}
suffix: str = " Scan"
creature_locations: Dict[str, int] = {
creature+suffix: creature_id for creature_id, creature in enumerate(all_creatures, start=34000)
creature + suffix: creature_id for creature_id, creature in enumerate(all_creatures, start=34000)
}
all_creatures_presorted: List[str] = sorted(all_creatures)
class Definitions:
"""Only compute lists if needed and then cache them."""
@functools.cached_property
def all_creatures_presorted(self) -> List[str]:
return sorted(all_creatures)
@functools.cached_property
def all_creatures_presorted_without_containment(self) -> List[str]:
return [name for name in self.all_creatures_presorted if name not in containment]
@functools.cached_property
def all_creatures_presorted_without_stasis(self) -> List[str]:
return [name for name in self.all_creatures_presorted if name not in aggressive or name in hatchable]
@functools.cached_property
def all_creatures_presorted_without_aggressive(self) -> List[str]:
return [name for name in self.all_creatures_presorted if name not in aggressive]
# only singleton needed
Definitions: Definitions = Definitions()

View File

@@ -42,7 +42,7 @@ item_table: Dict[int, ItemDict] = {
'count': 1,
'name': 'Stillsuit',
'tech_type': 'Stillsuit'},
35008: {'classification': ItemClassification.filler,
35008: {'classification': ItemClassification.progression,
'count': 2,
'name': 'Alien Containment Fragment',
'tech_type': 'BaseWaterParkFragment'},
@@ -139,7 +139,7 @@ item_table: Dict[int, ItemDict] = {
'name': 'Power Transmitter Fragment',
'tech_type': 'PowerTransmitterFragment'},
35032: {'classification': ItemClassification.progression,
'count': 4,
'count': 5,
'name': 'Prawn Suit Fragment',
'tech_type': 'ExosuitFragment'},
35033: {'classification': ItemClassification.useful,
@@ -163,7 +163,7 @@ item_table: Dict[int, ItemDict] = {
'name': 'Scanner Room Fragment',
'tech_type': 'BaseMapRoomFragment'},
35038: {'classification': ItemClassification.progression,
'count': 5,
'count': 4,
'name': 'Seamoth Fragment',
'tech_type': 'SeamothFragment'},
35039: {'classification': ItemClassification.progression,
@@ -203,9 +203,9 @@ item_table: Dict[int, ItemDict] = {
'name': 'Picture Frame',
'tech_type': 'PictureFrameFragment'},
35048: {'classification': ItemClassification.filler,
'count': 2,
'name': 'Bench Fragment',
'tech_type': 'BenchFragment'},
'count': 1,
'name': 'Bench',
'tech_type': 'Bench'},
35049: {'classification': ItemClassification.filler,
'count': 1,
'name': 'Basic Plant Pot',
@@ -222,7 +222,7 @@ item_table: Dict[int, ItemDict] = {
'count': 2,
'name': 'Observatory Fragment',
'tech_type': 'BaseObservatoryFragment'},
35053: {'classification': ItemClassification.useful,
35053: {'classification': ItemClassification.progression,
'count': 2,
'name': 'Multipurpose Room',
'tech_type': 'BaseRoom'},
@@ -333,7 +333,12 @@ item_table: Dict[int, ItemDict] = {
35080: {'classification': ItemClassification.filler,
'count': 1,
'name': 'Water Filtration Machine',
'tech_type': 'BaseFiltrationMachine'}}
'tech_type': 'BaseFiltrationMachine'},
35081: {'classification': ItemClassification.progression,
'count': 1,
'name': 'Ultra High Capacity Tank',
'tech_type': 'HighCapacityTank'},
}
advancement_item_names: Set[str] = set()
non_advancement_item_names: Set[str] = set()

View File

@@ -15,7 +15,12 @@ class LocationDict(TypedDict, total=False):
need_propulsion_cannon: bool
events: List[str] = ["Neptune Launch", "Disable Quarantine", "Full Infection", "Repair Aurora Drive"]
events: List[str] = [
"Neptune Launch",
"Disable Quarantine",
"Full Infection",
"Repair Aurora Drive",
]
location_table: Dict[int, LocationDict] = {
33000: {'can_slip_through': False,

View File

@@ -1,5 +1,7 @@
import typing
from Options import Choice, Range, DeathLink
from .Creatures import all_creatures
from .Creatures import all_creatures, Definitions
class ItemPool(Choice):
@@ -33,12 +35,40 @@ class Goal(Choice):
class CreatureScans(Range):
"""Place items on specific creature scans.
"""Place items on specific, randomly chosen, creature scans.
Warning: Includes aggressive Leviathans."""
display_name = "Creature Scans"
range_end = len(all_creatures)
class AggressiveScanLogic(Choice):
"""By default (Stasis), aggressive Creature Scans are logically expected only with a Stasis Rifle.
Containment: Removes Stasis Rifle as expected solution and expects Alien Containment instead.
Either: Creatures may be expected to be scanned via Stasis Rifle or Containment, whichever is found first.
None: Aggressive Creatures are assumed to not need any tools to scan.
Removed: No Creatures needing Stasis or Containment will be in the pool at all.
Note: Containment, Either and None adds Cuddlefish as an option for scans.
Note: Stasis, Either and None adds unhatchable aggressive species, such as Warper.
Note: This is purely a logic expectation, and does not affect gameplay, only placement."""
display_name = "Aggressive Creature Scan Logic"
option_stasis = 0
option_containment = 1
option_either = 2
option_none = 3
option_removed = 4
def get_pool(self) -> typing.List[str]:
if self == self.option_removed:
return Definitions.all_creatures_presorted_without_aggressive
elif self == self.option_stasis:
return Definitions.all_creatures_presorted_without_containment
elif self == self.option_containment:
return Definitions.all_creatures_presorted_without_stasis
else:
return Definitions.all_creatures_presorted
class SubnauticaDeathLink(DeathLink):
"""When you die, everyone dies. Of course the reverse is true too.
Note: can be toggled via in-game console command "deathlink"."""
@@ -48,5 +78,6 @@ options = {
"item_pool": ItemPool,
"goal": Goal,
"creature_scans": CreatureScans,
"creature_scan_logic": AggressiveScanLogic,
"death_link": SubnauticaDeathLink,
}

View File

@@ -1,122 +1,128 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Dict, Callable, Optional
from worlds.generic.Rules import set_rule
from worlds.generic.Rules import set_rule, add_rule
from .Locations import location_table, LocationDict
from .Creatures import all_creatures, aggressive, suffix
from .Creatures import all_creatures, aggressive, suffix, hatchable, containment
from .Options import AggressiveScanLogic
import math
if TYPE_CHECKING:
from . import SubnauticaWorld
from BaseClasses import CollectionState, Location
def has_seaglide(state, player: int):
def has_seaglide(state: "CollectionState", player: int):
return state.has("Seaglide Fragment", player, 2)
def has_modification_station(state, player: int):
def has_modification_station(state: "CollectionState", player: int):
return state.has("Modification Station Fragment", player, 3)
def has_mobile_vehicle_bay(state, player: int):
def has_mobile_vehicle_bay(state: "CollectionState", player: int):
return state.has("Mobile Vehicle Bay Fragment", player, 3)
def has_moonpool(state, player: int):
def has_moonpool(state: "CollectionState", player: int):
return state.has("Moonpool Fragment", player, 2)
def has_vehicle_upgrade_console(state, player: int):
def has_vehicle_upgrade_console(state: "CollectionState", player: int):
return state.has("Vehicle Upgrade Console", player) and \
has_moonpool(state, player)
def has_seamoth(state, player: int):
def has_seamoth(state: "CollectionState", player: int):
return state.has("Seamoth Fragment", player, 3) and \
has_mobile_vehicle_bay(state, player)
def has_seamoth_depth_module_mk1(state, player: int):
def has_seamoth_depth_module_mk1(state: "CollectionState", player: int):
return has_vehicle_upgrade_console(state, player)
def has_seamoth_depth_module_mk2(state, player: int):
def has_seamoth_depth_module_mk2(state: "CollectionState", player: int):
return has_seamoth_depth_module_mk1(state, player) and \
has_modification_station(state, player)
def has_seamoth_depth_module_mk3(state, player: int):
def has_seamoth_depth_module_mk3(state: "CollectionState", player: int):
return has_seamoth_depth_module_mk2(state, player) and \
has_modification_station(state, player)
def has_cyclops_bridge(state, player: int):
def has_cyclops_bridge(state: "CollectionState", player: int):
return state.has("Cyclops Bridge Fragment", player, 3)
def has_cyclops_engine(state, player: int):
def has_cyclops_engine(state: "CollectionState", player: int):
return state.has("Cyclops Engine Fragment", player, 3)
def has_cyclops_hull(state, player: int):
def has_cyclops_hull(state: "CollectionState", player: int):
return state.has("Cyclops Hull Fragment", player, 3)
def has_cyclops(state, player: int):
def has_cyclops(state: "CollectionState", player: int):
return has_cyclops_bridge(state, player) and \
has_cyclops_engine(state, player) and \
has_cyclops_hull(state, player) and \
has_mobile_vehicle_bay(state, player)
def has_cyclops_depth_module_mk1(state, player: int):
def has_cyclops_depth_module_mk1(state: "CollectionState", player: int):
return state.has("Cyclops Depth Module MK1", player) and \
has_modification_station(state, player)
def has_cyclops_depth_module_mk2(state, player: int):
def has_cyclops_depth_module_mk2(state: "CollectionState", player: int):
return has_cyclops_depth_module_mk1(state, player) and \
has_modification_station(state, player)
def has_cyclops_depth_module_mk3(state, player: int):
def has_cyclops_depth_module_mk3(state: "CollectionState", player: int):
return has_cyclops_depth_module_mk2(state, player) and \
has_modification_station(state, player)
def has_prawn(state, player: int):
def has_prawn(state: "CollectionState", player: int):
return state.has("Prawn Suit Fragment", player, 4) and \
has_mobile_vehicle_bay(state, player)
def has_praw_propulsion_arm(state, player: int):
def has_prawn_propulsion_arm(state: "CollectionState", player: int):
return state.has("Prawn Suit Propulsion Cannon Fragment", player, 2) and \
has_vehicle_upgrade_console(state, player)
def has_prawn_depth_module_mk1(state, player: int):
def has_prawn_depth_module_mk1(state: "CollectionState", player: int):
return has_vehicle_upgrade_console(state, player)
def has_prawn_depth_module_mk2(state, player: int):
def has_prawn_depth_module_mk2(state: "CollectionState", player: int):
return has_prawn_depth_module_mk1(state, player) and \
has_modification_station(state, player)
def has_laser_cutter(state, player: int):
def has_laser_cutter(state: "CollectionState", player: int):
return state.has("Laser Cutter Fragment", player, 3)
def has_stasis_rile(state, player: int):
def has_stasis_rifle(state: "CollectionState", player: int):
return state.has("Stasis Rifle Fragment", player, 2)
def has_containment(state: "CollectionState", player: int):
return state.has("Alien Containment Fragment", player, 2) and state.has("Multipurpose Room", player)
# Either we have propulsion cannon, or prawn + propulsion cannon arm
def has_propulsion_cannon(state, player: int):
def has_propulsion_cannon(state: "CollectionState", player: int):
return state.has("Propulsion Cannon Fragment", player, 2) or \
(has_prawn(state, player) and has_praw_propulsion_arm(state, player))
(has_prawn(state, player) and has_prawn_propulsion_arm(state, player))
def has_cyclops_shield(state, player: int):
def has_cyclops_shield(state: "CollectionState", player: int):
return has_cyclops(state, player) and \
state.has("Cyclops Shield Generator", player)
@@ -129,7 +135,7 @@ def has_cyclops_shield(state, player: int):
# negligeable with from high capacity tank. 430m -> 460m
# Fins are not used when using seaglide
#
def get_max_swim_depth(state, player: int):
def get_max_swim_depth(state: "CollectionState", player: int):
# TODO, Make this a difficulty setting.
# Only go up to 200m without any submarines for now.
return 200
@@ -140,7 +146,7 @@ def get_max_swim_depth(state, player: int):
# has_ultra_glide_fins = state.has("Ultra Glide Fins", player)
# max_depth = 400 # More like 430m. Give some room
# if has_seaglide(state, player: int):
# if has_seaglide(state: "CollectionState", player: int):
# if has_ultra_high_capacity_tank:
# max_depth = 750 # It's about 50m more. Give some room
# else:
@@ -156,7 +162,7 @@ def get_max_swim_depth(state, player: int):
# return max_depth
def get_seamoth_max_depth(state, player: int):
def get_seamoth_max_depth(state: "CollectionState", player: int):
if has_seamoth(state, player):
if has_seamoth_depth_module_mk3(state, player):
return 900
@@ -170,7 +176,7 @@ def get_seamoth_max_depth(state, player: int):
return 0
def get_cyclops_max_depth(state, player):
def get_cyclops_max_depth(state: "CollectionState", player):
if has_cyclops(state, player):
if has_cyclops_depth_module_mk3(state, player):
return 1700
@@ -184,7 +190,7 @@ def get_cyclops_max_depth(state, player):
return 0
def get_prawn_max_depth(state, player):
def get_prawn_max_depth(state: "CollectionState", player):
if has_prawn(state, player):
if has_prawn_depth_module_mk2(state, player):
return 1700
@@ -196,7 +202,7 @@ def get_prawn_max_depth(state, player):
return 0
def get_max_depth(state, player: int):
def get_max_depth(state: "CollectionState", player: int):
# TODO, Difficulty option, we can add vehicle depth + swim depth
# But at this point, we have to consider traver distance in caves, not
# just depth
@@ -206,7 +212,7 @@ def get_max_depth(state, player: int):
get_prawn_max_depth(state, player))
def can_access_location(state, player: int, loc: LocationDict) -> bool:
def can_access_location(state: "CollectionState", player: int, loc: LocationDict) -> bool:
need_laser_cutter = loc.get("need_laser_cutter", False)
if need_laser_cutter and not has_laser_cutter(state, player):
return False
@@ -239,17 +245,34 @@ def set_location_rule(world, player: int, loc: LocationDict):
set_rule(world.get_location(loc["name"], player), lambda state: can_access_location(state, player, loc))
def can_scan_creature(state, player: int, creature: str) -> bool:
def can_scan_creature(state: "CollectionState", player: int, creature: str) -> bool:
if not has_seaglide(state, player):
return False
if creature in aggressive and not has_stasis_rile(state, player):
return False
return get_max_depth(state, player) >= all_creatures[creature]
def set_creature_rule(world, player, creature_name: str):
set_rule(world.get_location(creature_name + suffix, player),
def set_creature_rule(world, player: int, creature_name: str) -> "Location":
location = world.get_location(creature_name + suffix, player)
set_rule(location,
lambda state: can_scan_creature(state, player, creature_name))
return location
def get_aggression_rule(option: AggressiveScanLogic, creature_name: str) -> \
Optional[Callable[["CollectionState", int], bool]]:
"""Get logic rule for a creature scan location."""
if creature_name not in hatchable and option != option.option_none: # can only be done via stasis
return has_stasis_rifle
# otherwise allow option preference
return aggression_rules.get(option.value, None)
aggression_rules: Dict[int, Callable[["CollectionState", int], bool]] = {
AggressiveScanLogic.option_stasis: has_stasis_rifle,
AggressiveScanLogic.option_containment: has_containment,
AggressiveScanLogic.option_either: lambda state, player:
has_stasis_rifle(state, player) or has_containment(state, player)
}
def set_rules(subnautica_world: "SubnauticaWorld"):
@@ -259,11 +282,22 @@ def set_rules(subnautica_world: "SubnauticaWorld"):
for loc in location_table.values():
set_location_rule(world, player, loc)
for creature_name in subnautica_world.creatures_to_scan:
set_creature_rule(world, player, creature_name)
if subnautica_world.creatures_to_scan:
option = world.creature_scan_logic[player]
for creature_name in subnautica_world.creatures_to_scan:
location = set_creature_rule(world, player, creature_name)
if creature_name in containment: # there is no other way, hard-required containment
add_rule(location, lambda state: has_containment(state, player))
elif creature_name in aggressive:
rule = get_aggression_rule(option, creature_name)
if rule:
add_rule(location,
lambda state, loc_rule=get_aggression_rule(option, creature_name): loc_rule(state, player))
# Victory locations
set_rule(world.get_location("Neptune Launch", player), lambda state:
set_rule(world.get_location("Neptune Launch", player),
lambda state:
get_max_depth(state, player) >= 1444 and
has_mobile_vehicle_bay(state, player) and
state.has("Neptune Launch Platform", player) and

View File

@@ -41,8 +41,8 @@ class SubnauticaWorld(World):
location_name_to_id = all_locations
option_definitions = Options.options
data_version = 5
required_client_version = (0, 3, 4)
data_version = 7
required_client_version = (0, 3, 5)
prefill_items: List[Item]
creatures_to_scan: List[str]
@@ -52,7 +52,15 @@ class SubnauticaWorld(World):
self.create_item("Seaglide Fragment"),
self.create_item("Seaglide Fragment")
]
self.creatures_to_scan = self.world.random.sample(Creatures.all_creatures_presorted,
scan_option: Options.AggressiveScanLogic = self.world.creature_scan_logic[self.player]
creature_pool = scan_option.get_pool()
self.world.creature_scans[self.player].value = min(
len(creature_pool),
self.world.creature_scans[self.player].value
)
self.creatures_to_scan = self.world.random.sample(creature_pool,
self.world.creature_scans[self.player].value)
def create_regions(self):

View File

@@ -3,66 +3,82 @@ from BaseClasses import MultiWorld
from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, Option, OptionDict
from schema import Schema, And, Optional
class StartWithJewelryBox(Toggle):
"Start with Jewelry Box unlocked"
display_name = "Start with Jewelry Box"
#class ProgressiveVerticalMovement(Toggle):
# "Always find vertical movement in the following order Succubus Hairpin -> Light Wall -> Celestial Sash"
# display_name = "Progressive vertical movement"
#class ProgressiveKeycards(Toggle):
# "Always find Security Keycard's in the following order D -> C -> B -> A"
# display_name = "Progressive keycards"
class DownloadableItems(DefaultOnToggle):
"With the tablet you will be able to download items at terminals"
display_name = "Downloadable items"
class EyeSpy(Toggle):
"Requires Oculus Ring in inventory to be able to break hidden walls."
display_name = "Eye Spy"
class StartWithMeyef(Toggle):
"Start with Meyef, ideal for when you want to play multiplayer."
display_name = "Start with Meyef"
class QuickSeed(Toggle):
"Start with Talaria Attachment, Nyoom!"
display_name = "Quick seed"
class SpecificKeycards(Toggle):
"Keycards can only open corresponding doors"
display_name = "Specific Keycards"
class Inverted(Toggle):
"Start in the past"
display_name = "Inverted"
#class StinkyMaw(Toggle):
# "Require gasmask for Maw"
# display_name = "Stinky Maw"
class GyreArchives(Toggle):
"Gyre locations are in logic. New warps are gated by Merchant Crow and Kobo"
display_name = "Gyre Archives"
class Cantoran(Toggle):
"Cantoran's fight and check are available upon revisiting his room"
display_name = "Cantoran"
class LoreChecks(Toggle):
"Memories and journal entries contain items."
display_name = "Lore Checks"
class BossRando(Toggle):
"Shuffles the positions of all bosses."
display_name = "Boss Randomization"
class BossScaling(DefaultOnToggle):
"When Boss Rando is enabled, scales the bosses' HP, XP, and ATK to the stats of the location they replace (Reccomended)"
display_name = "Scale Random Boss Stats"
class DamageRando(Choice):
"Randomly nerfs and buffs some orbs and their associated spells as well as some associated rings."
display_name = "Damage Rando"
@@ -73,9 +89,9 @@ class DamageRando(Choice):
option_mostlybuffs = 4
option_allbuffs = 5
option_manual = 6
alias_false = 0
alias_true = 2
class DamageRandoOverrides(OptionDict):
"Manual +/-/normal odds for an orb. Put 0 if you don't want a certain nerf or buff to be a possibility. Orbs that you don't specify will roll with 1/1/1 as odds"
schema = Schema({
@@ -180,6 +196,7 @@ class DamageRandoOverrides(OptionDict):
"Radiant": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
}
class HpCap(Range):
"Sets the number that Lunais's HP maxes out at."
display_name = "HP Cap"
@@ -187,10 +204,12 @@ class HpCap(Range):
range_end = 999
default = 999
class BossHealing(DefaultOnToggle):
"Enables/disables healing after boss fights. NOTE: Currently only applicable when Boss Rando is enabled."
display_name = "Heal After Bosses"
class ShopFill(Choice):
"""Sets the items for sale in Merchant Crow's shops.
Default: No sunglasses or trendy jacket, but sand vials for sale.
@@ -203,10 +222,12 @@ class ShopFill(Choice):
option_vanilla = 2
option_empty = 3
class ShopWarpShards(DefaultOnToggle):
"Shops always sell warp shards (when keys possessed), ignoring inventory setting."
display_name = "Always Sell Warp Shards"
class ShopMultiplier(Range):
"Multiplier for the cost of items in the shop. Set to 0 for free shops."
display_name = "Shop Price Multiplier"
@@ -214,6 +235,7 @@ class ShopMultiplier(Range):
range_end = 10
default = 1
class LootPool(Choice):
"""Sets the items that drop from enemies (does not apply to boss reward checks)
Vanilla: Drops are the same as the base game
@@ -224,6 +246,7 @@ class LootPool(Choice):
option_randomized = 1
option_empty = 2
class DropRateCategory(Choice):
"""Sets the drop rate when 'Loot Pool' is set to 'Random'
Tiered: Based on item rarity/value
@@ -237,6 +260,7 @@ class DropRateCategory(Choice):
option_randomized = 2
option_fixed = 3
class FixedDropRate(Range):
"Base drop rate percentage when 'Drop Rate Category' is set to 'Fixed'"
display_name = "Fixed Drop Rate"
@@ -244,6 +268,7 @@ class FixedDropRate(Range):
range_end = 100
default = 5
class LootTierDistro(Choice):
"""Sets how often items of each rarity tier are placed when 'Loot Pool' is set to 'Random'
Default Weight: Rarer items will be assigned to enemy drop slots less frequently than common items
@@ -255,14 +280,17 @@ class LootTierDistro(Choice):
option_full_random = 1
option_inverted_weight = 2
class ShowBestiary(Toggle):
"All entries in the bestiary are visible, without needing to kill one of a given enemy first"
display_name = "Show Bestiary Entries"
class ShowDrops(Toggle):
"All item drops in the bestiary are visible, without needing an enemy to drop one of a given item first"
display_name = "Show Bestiary Item Drops"
# Some options that are available in the timespinner randomizer arent currently implemented
timespinner_options: Dict[str, Option] = {
"StartWithJewelryBox": StartWithJewelryBox,
@@ -296,9 +324,11 @@ timespinner_options: Dict[str, Option] = {
"DeathLink": DeathLink,
}
def is_option_enabled(world: MultiWorld, player: int, name: str) -> bool:
return get_option_value(world, player, name) > 0
def get_option_value(world: MultiWorld, player: int, name: str) -> Union[int, dict]:
option = getattr(world, name, None)
if option == None:

View File

@@ -15,9 +15,9 @@ class DisableNonRandomizedPuzzles(DefaultOnToggle):
class EarlySecretArea(Toggle):
"""Opens the Mountainside shortcut to the Mountain Secret Area from the start.
"""Opens the Mountainside shortcut to the Caves from the start.
(Otherwise known as "UTM", "Caves" or the "Challenge Area")"""
display_name = "Early Secret Area"
display_name = "Early Caves"
class ShuffleSymbols(DefaultOnToggle):
@@ -58,15 +58,9 @@ class ShuffleVaultBoxes(Toggle):
display_name = "Shuffle Vault Boxes"
class ShuffleUncommonLocations(Toggle):
"""Adds some optional puzzles that are somewhat difficult or out of the way.
Examples: Mountaintop River Shape, Tutorial Patio Floor, Theater Videos"""
display_name = "Shuffle Uncommon Locations"
class ShufflePostgame(Toggle):
"""Adds locations into the pool that are guaranteed to be locked behind your goal. Use this if you don't play with
forfeit on victory."""
"""Adds locations into the pool that are guaranteed to become accessible before or at the same time as your goal.
Use this if you don't play with forfeit on victory."""
display_name = "Shuffle Postgame"
@@ -90,7 +84,7 @@ class MountainLasers(Range):
class ChallengeLasers(Range):
"""Sets the amount of beams required to enter the secret area through the Mountain Bottom Layer Discard."""
"""Sets the amount of beams required to enter the Caves through the Mountain Bottom Floor Discard."""
display_name = "Required Lasers for Challenge"
range_start = 1
range_end = 11
@@ -122,7 +116,6 @@ the_witness_options: Dict[str, type] = {
"disable_non_randomized_puzzles": DisableNonRandomizedPuzzles,
"shuffle_discarded_panels": ShuffleDiscardedPanels,
"shuffle_vault_boxes": ShuffleVaultBoxes,
"shuffle_uncommon": ShuffleUncommonLocations,
"shuffle_postgame": ShufflePostgame,
"victory_condition": VictoryCondition,
"mountain_lasers": MountainLasers,

View File

@@ -28,38 +28,38 @@ Traps:
610 - Power Surge
Doors:
1100 - Glass Factory Entry Door (Panel) - 0x01A54
1105 - Door to Symmetry Island Lower (Panel) - 0x000B0
1107 - Door to Symmetry Island Upper (Panel) - 0x1C349
1110 - Door to Desert Flood Light Room (Panel) - 0x0C339
1111 - Desert Flood Room Flood Controls (Panel) - 0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B
1119 - Quarry Door to Mill (Panel) - 0x01E5A,0x01E59
1100 - Glass Factory Entry (Panel) - 0x01A54
1105 - Symmetry Island Lower (Panel) - 0x000B0
1107 - Symmetry Island Upper (Panel) - 0x1C349
1110 - Desert Light Room Entry (Panel) - 0x0C339
1111 - Desert Flood Controls (Panel) - 0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B
1119 - Quarry Mill Entry (Panel) - 0x01E5A,0x01E59
1120 - Quarry Mill Ramp Controls (Panel) - 0x03678,0x03676
1122 - Quarry Mill Elevator Controls (Panel) - 0x03679,0x03675
1122 - Quarry Mill Lift Controls (Panel) - 0x03679,0x03675
1125 - Quarry Boathouse Ramp Height Control (Panel) - 0x03852
1127 - Quarry Boathouse Ramp Horizontal Control (Panel) - 0x03858
1131 - Shadows Door Timer (Panel) - 0x334DB,0x334DC
1150 - Monastery Entry Door Left (Panel) - 0x00B10
1151 - Monastery Entry Door Right (Panel) - 0x00C92
1162 - Town Door to RGB House (Panel) - 0x28998
1163 - Town Door to Church (Panel) - 0x28A0D
1150 - Monastery Entry Left (Panel) - 0x00B10
1151 - Monastery Entry Right (Panel) - 0x00C92
1162 - Town Tinted Glass Door (Panel) - 0x28998
1163 - Town Church Entry (Panel) - 0x28A0D
1166 - Town Maze Panel (Drop-Down Staircase) (Panel) - 0x28A79
1169 - Windmill Door (Panel) - 0x17F5F
1169 - Windmill Entry (Panel) - 0x17F5F
1200 - Treehouse First & Second Doors (Panel) - 0x0288C,0x02886
1202 - Treehouse Third Door (Panel) - 0x0A182
1205 - Treehouse Laser House Door Timer (Panel) - 0x2700B,0x334DC
1208 - Treehouse Shortcut Drop-Down Bridge (Panel) - 0x17CBC
1208 - Treehouse Drawbridge (Panel) - 0x17CBC
1175 - Jungle Popup Wall (Panel) - 0x17CAB
1180 - Bunker Entry Door (Panel) - 0x17C2E
1183 - Inside Bunker Door to Bunker Proper (Panel) - 0x0A099
1180 - Bunker Entry (Panel) - 0x17C2E
1183 - Bunker Tinted Glass Door (Panel) - 0x0A099
1186 - Bunker Elevator Control (Panel) - 0x0A079
1190 - Swamp Entry Door (Panel) - 0x0056E
1190 - Swamp Entry (Panel) - 0x0056E
1192 - Swamp Sliding Bridge (Panel) - 0x00609,0x18488
1195 - Swamp Rotating Bridge (Panel) - 0x181F5
1197 - Swamp Maze Control (Panel) - 0x17C0A
1310 - Boat - 0x17CDF,0x17CC8,0x17CA6,0x09DB8,0x17C95,0x0A054
1400 - Caves Mountain Shortcut - 0x2D73F
1400 - Caves Mountain Shortcut (Door) - 0x2D73F
1500 - Symmetry Laser - 0x00509
1501 - Desert Laser - 0x012FB,0x01317
@@ -73,101 +73,101 @@ Doors:
1509 - Swamp Laser - 0x00BF6
1510 - Treehouse Laser - 0x028A4
1600 - Outside Tutorial Optional Door - 0x03BA2
1603 - Outside Tutorial Outpost Entry Door - 0x0A170
1606 - Outside Tutorial Outpost Exit Door - 0x04CA3
1609 - Glass Factory Entry Door - 0x01A29
1612 - Glass Factory Back Wall - 0x0D7ED
1615 - Symmetry Island Lower Door - 0x17F3E
1618 - Symmetry Island Upper Door - 0x18269
1619 - Orchard Middle Gate - 0x03307
1620 - Orchard Final Gate - 0x03313
1621 - Desert Door to Flood Light Room - 0x09FEE
1624 - Desert Door to Pond Room - 0x0C2C3
1627 - Desert Door to Water Levels Room - 0x0A24B
1630 - Desert Door to Elevator Room - 0x0C316
1633 - Quarry Main Entry 1 - 0x09D6F
1636 - Quarry Main Entry 2 - 0x17C07
1639 - Quarry Door to Mill - 0x02010
1642 - Quarry Mill Side Door - 0x275FF
1645 - Quarry Mill Rooftop Shortcut - 0x17CE8
1648 - Quarry Mill Stairs - 0x0368A
1651 - Quarry Boathouse Boat Staircase - 0x2769B,0x27163
1653 - Quarry Boathouse First Barrier - 0x17C50
1654 - Quarry Boathouse Shortcut - 0x3865F
1656 - Shadows Timed Door - 0x19B24
1657 - Shadows Laser Room Right Door - 0x194B2
1660 - Shadows Laser Room Left Door - 0x19665
1663 - Shadows Barrier to Quarry - 0x19865,0x0A2DF
1666 - Shadows Barrier to Ledge - 0x1855B,0x19ADE
1669 - Keep Hedge Maze 1 Exit Door - 0x01954
1672 - Keep Pressure Plates 1 Exit Door - 0x01BEC
1675 - Keep Hedge Maze 2 Shortcut - 0x018CE
1678 - Keep Hedge Maze 2 Exit Door - 0x019D8
1681 - Keep Hedge Maze 3 Shortcut - 0x019B5
1684 - Keep Hedge Maze 3 Exit Door - 0x019E6
1687 - Keep Hedge Maze 4 Shortcut - 0x0199A
1690 - Keep Hedge Maze 4 Exit Door - 0x01A0E
1693 - Keep Pressure Plates 2 Exit Door - 0x01BEA
1696 - Keep Pressure Plates 3 Exit Door - 0x01CD5
1699 - Keep Pressure Plates 4 Exit Door - 0x01D40
1702 - Keep Shortcut to Shadows - 0x09E3D
1705 - Keep Tower Shortcut - 0x04F8F
1708 - Monastery Shortcut - 0x0364E
1711 - Monastery Inner Door - 0x0C128
1714 - Monastery Outer Door - 0x0C153
1717 - Monastery Door to Garden - 0x03750
1718 - Town Cargo Box Door - 0x0A0C9
1720 - Town Wooden Roof Staircase - 0x034F5
1723 - Town Tinted Door to RGB House - 0x28A61
1726 - Town Door to Church - 0x03BB0
1729 - Town Maze Staircase - 0x28AA2
1732 - Town Windmill Door - 0x1845B
1735 - Town RGB House Staircase - 0x2897B
1738 - Town Tower Blue Panels Door - 0x27798
1741 - Town Tower Lattice Door - 0x27799
1744 - Town Tower Environmental Set Door - 0x2779A
1747 - Town Tower Wooden Roof Set Door - 0x2779C
1750 - Theater Entry Door - 0x17F88
1753 - Theater Exit Door Left - 0x0A16D
1756 - Theater Exit Door Right - 0x3CCDF
1759 - Jungle Bamboo Shortcut to River - 0x3873B
1760 - Jungle Popup Wall - 0x1475B
1762 - River Shortcut to Monastery Garden - 0x0CF2A
1765 - Bunker Bunker Entry Door - 0x0C2A4
1768 - Bunker Tinted Glass Door - 0x17C79
1771 - Bunker Door to Ultraviolet Room - 0x0C2A3
1774 - Bunker Door to Elevator - 0x0A08D
1777 - Swamp Entry Door - 0x00C1C
1780 - Swamp Door to Broken Shapers - 0x184B7
1600 - Outside Tutorial Outpost Path (Door) - 0x03BA2
1603 - Outside Tutorial Outpost Entry (Door) - 0x0A170
1606 - Outside Tutorial Outpost Exit (Door) - 0x04CA3
1609 - Glass Factory Entry (Door) - 0x01A29
1612 - Glass Factory Back Wall (Door) - 0x0D7ED
1615 - Symmetry Island Lower (Door) - 0x17F3E
1618 - Symmetry Island Upper (Door) - 0x18269
1619 - Orchard First Gate (Door) - 0x03307
1620 - Orchard Second Gate (Door) - 0x03313
1621 - Desert Light Room Entry (Door) - 0x09FEE
1624 - Desert Pond Room Entry (Door) - 0x0C2C3
1627 - Desert Flood Room Entry (Door) - 0x0A24B
1630 - Desert Elevator Room Entry (Door) - 0x0C316
1633 - Quarry Entry 1 (Door) - 0x09D6F
1636 - Quarry Entry 2 (Door) - 0x17C07
1639 - Quarry Mill Entry (Door) - 0x02010
1642 - Quarry Mill Side Exit (Door) - 0x275FF
1645 - Quarry Mill Roof Exit (Door) - 0x17CE8
1648 - Quarry Mill Stairs (Door) - 0x0368A
1651 - Quarry Boathouse Dock (Door) - 0x2769B,0x27163
1653 - Quarry Boathouse First Barrier (Door) - 0x17C50
1654 - Quarry Boathouse Second Barrier (Door) - 0x3865F
1656 - Shadows Timed Door (Door) - 0x19B24
1657 - Shadows Laser Entry Right (Door) - 0x194B2
1660 - Shadows Laser Entry Left (Door) - 0x19665
1663 - Shadows Quarry Barrier (Door) - 0x19865,0x0A2DF
1666 - Shadows Ledge Barrier (Door) - 0x1855B,0x19ADE
1669 - Keep Hedge Maze 1 Exit (Door) - 0x01954
1672 - Keep Pressure Plates 1 Exit (Door) - 0x01BEC
1675 - Keep Hedge Maze 2 Shortcut (Door) - 0x018CE
1678 - Keep Hedge Maze 2 Exit (Door) - 0x019D8
1681 - Keep Hedge Maze 3 Shortcut (Door) - 0x019B5
1684 - Keep Hedge Maze 3 Exit (Door) - 0x019E6
1687 - Keep Hedge Maze 4 Shortcut (Door) - 0x0199A
1690 - Keep Hedge Maze 4 Exit (Door) - 0x01A0E
1693 - Keep Pressure Plates 2 Exit (Door) - 0x01BEA
1696 - Keep Pressure Plates 3 Exit (Door) - 0x01CD5
1699 - Keep Pressure Plates 4 Exit (Door) - 0x01D40
1702 - Keep Shadows Shortcut (Door) - 0x09E3D
1705 - Keep Tower Shortcut (Door) - 0x04F8F
1708 - Monastery Shortcut (Door) - 0x0364E
1711 - Monastery Entry Inner (Door) - 0x0C128
1714 - Monastery Entry Outer (Door) - 0x0C153
1717 - Monastery Garden Entry (Door) - 0x03750
1718 - Town Cargo Box Entry (Door) - 0x0A0C9
1720 - Town Wooden Roof Stairs (Door) - 0x034F5
1723 - Town Tinted Glass Door (Door) - 0x28A61
1726 - Town Church Entry (Door) - 0x03BB0
1729 - Town Maze Stairs (Door) - 0x28AA2
1732 - Town Windmill Entry (Door) - 0x1845B
1735 - Town RGB House Stairs (Door) - 0x2897B
1738 - Town Tower First Door (Door) - 0x27798
1741 - Town Tower Third Door (Door) - 0x27799
1744 - Town Tower Fourth Door (Door) - 0x2779A
1747 - Town Tower Second Door (Door) - 0x2779C
1750 - Theater Entry (Door) - 0x17F88
1753 - Theater Exit Left (Door) - 0x0A16D
1756 - Theater Exit Right (Door) - 0x3CCDF
1759 - Jungle Bamboo Laser Shortcut (Door) - 0x3873B
1760 - Jungle Popup Wall (Door) - 0x1475B
1762 - River Monastery Shortcut (Door) - 0x0CF2A
1765 - Bunker Entry (Door) - 0x0C2A4
1768 - Bunker Tinted Glass Door (Door) - 0x17C79
1771 - Bunker UV Room Entry (Door) - 0x0C2A3
1774 - Bunker Elevator Room Entry (Door) - 0x0A08D
1777 - Swamp Entry (Door) - 0x00C1C
1780 - Swamp Between Bridges First Door - 0x184B7
1783 - Swamp Platform Shortcut Door - 0x38AE6
1786 - Swamp Cyan Water Pump - 0x04B7F
1789 - Swamp Door to Rotated Shapers - 0x18507
1792 - Swamp Red Water Pump - 0x183F2
1795 - Swamp Red Underwater Exit - 0x305D5
1798 - Swamp Blue Water Pump - 0x18482
1801 - Swamp Purple Water Pump - 0x0A1D6
1804 - Swamp Near Laser Shortcut - 0x2D880
1807 - Treehouse First Door - 0x0C309
1810 - Treehouse Second Door - 0x0C310
1813 - Treehouse Beyond Yellow Bridge Door - 0x0A181
1816 - Treehouse Drawbridge - 0x0C32D
1819 - Treehouse Timed Door to Laser House - 0x0C323
1822 - Inside Mountain First Layer Exit Door - 0x09E54
1825 - Inside Mountain Second Layer Staircase Near - 0x09FFB
1828 - Inside Mountain Second Layer Exit Door - 0x09EDD
1831 - Inside Mountain Second Layer Staircase Far - 0x09E07
1834 - Inside Mountain Giant Puzzle Exit Door - 0x09F89
1840 - Inside Mountain Door to Final Room - 0x0C141
1843 - Inside Mountain Bottom Layer Rock - 0x17F33
1846 - Inside Mountain Door to Secret Area - 0x2D77D
1849 - Caves Pillar Door - 0x019A5
1855 - Caves Swamp Shortcut - 0x2D859
1858 - Challenge Entry Door - 0x0A19A
1861 - Challenge Door to Theater Walkway - 0x0348A
1864 - Theater Walkway Door to Windmill Interior - 0x27739
1867 - Theater Walkway Door to Desert Elevator Room - 0x27263
1870 - Theater Walkway Door to Town - 0x09E87
1786 - Swamp Cyan Water Pump (Door) - 0x04B7F
1789 - Swamp Between Bridges Second Door - 0x18507
1792 - Swamp Red Water Pump (Door) - 0x183F2
1795 - Swamp Red Underwater Exit (Door) - 0x305D5
1798 - Swamp Blue Water Pump (Door) - 0x18482
1801 - Swamp Purple Water Pump (Door) - 0x0A1D6
1804 - Swamp Laser Shortcut (Door) - 0x2D880
1807 - Treehouse First Door (Door) - 0x0C309
1810 - Treehouse Second Door (Door) - 0x0C310
1813 - Treehouse Third Door (Door) - 0x0A181
1816 - Treehouse Drawbridge (Door) - 0x0C32D
1819 - Treehouse Laser House Entry (Door) - 0x0C323
1822 - Mountain Floor 1 Exit (Door) - 0x09E54
1825 - Mountain Floor 2 Staircase Near (Door) - 0x09FFB
1828 - Mountain Floor 2 Exit (Door) - 0x09EDD
1831 - Mountain Floor 2 Staircase Far (Door) - 0x09E07
1834 - Mountain Bottom Floor Giant Puzzle Exit (Door) - 0x09F89
1840 - Mountain Bottom Floor Final Room Entry (Door) - 0x0C141
1843 - Mountain Bottom Floor Rock (Door) - 0x17F33
1846 - Caves Entry (Door) - 0x2D77D
1849 - Caves Pillar Door (Door) - 0x019A5
1855 - Caves Swamp Shortcut (Door) - 0x2D859
1858 - Challenge Entry (Door) - 0x0A19A
1861 - Challenge Tunnels Entry (Door) - 0x0348A
1864 - Tunnels Theater Shortcut (Door) - 0x27739
1867 - Tunnels Desert Shortcut (Door) - 0x27263
1870 - Tunnels Town Shortcut (Door) - 0x09E87
1903 - Outside Tutorial Outpost Doors - 0x03BA2,0x0A170,0x04CA3
1906 - Symmetry Island Doors - 0x17F3E,0x18269
@@ -181,18 +181,18 @@ Doors:
1930 - Keep Hedge Maze Doors - 0x01954,0x018CE,0x019D8,0x019B5,0x019E6,0x0199A,0x01A0E
1933 - Keep Pressure Plates Doors - 0x01BEC,0x01BEA,0x01CD5,0x01D40
1936 - Keep Shortcuts - 0x09E3D,0x04F8F
1939 - Monastery Entry Door - 0x0C128,0x0C153
1939 - Monastery Entry - 0x0C128,0x0C153
1942 - Monastery Shortcuts - 0x0364E,0x03750
1945 - Town Doors - 0x0A0C9,0x034F5,0x28A61,0x03BB0,0x28AA2,0x1845B,0x2897B
1948 - Town Tower Doors - 0x27798,0x27799,0x2779A,0x2779C
1951 - Theater Exit Door - 0x0A16D,0x3CCDF
1951 - Theater Exit - 0x0A16D,0x3CCDF
1954 - Jungle & River Shortcuts - 0x3873B,0x0CF2A
1957 - Bunker Doors - 0x0C2A4,0x17C79,0x0C2A3,0x0A08D
1960 - Swamp Doors - 0x00C1C,0x184B7,0x38AE6,0x18507
1963 - Swamp Water Pumps - 0x04B7F,0x183F2,0x305D5,0x18482,0x0A1D6
1966 - Treehouse Entry Doors - 0x0C309,0x0C310,0x0A181
1975 - Inside Mountain Second Layer Stairs & Doors - 0x09FFB,0x09EDD,0x09E07
1978 - Inside Mountain Bottom Layer Doors to Caves - 0x17F33,0x2D77D
1975 - Mountain Floor 2 Stairs & Doors - 0x09FFB,0x09EDD,0x09E07
1978 - Mountain Bottom Floor Doors to Caves - 0x17F33,0x2D77D
1981 - Caves Doors to Challenge - 0x019A5,0x0A19A
1984 - Caves Exits to Main Island - 0x2D859,0x2D73F
1987 - Theater Walkway Doors - 0x27739,0x27263,0x09E87
1987 - Tunnels Doors - 0x27739,0x27263,0x09E87

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ from .rules import set_rules
from .regions import WitnessRegions
from .Options import is_option_enabled, the_witness_options, get_option_value
from .utils import best_junk_to_add_based_on_weights
from logging import warning
class WitnessWebWorld(WebWorld):
@@ -35,7 +36,7 @@ class WitnessWorld(World):
"""
game = "The Witness"
topology_present = False
data_version = 5
data_version = 7
static_logic = StaticWitnessLogic()
static_locat = StaticWitnessLocations()
@@ -56,15 +57,20 @@ class WitnessWorld(World):
'panelhex_to_id': self.locat.CHECK_PANELHEX_TO_ID,
'item_id_to_door_hexes': self.items.ITEM_ID_TO_DOOR_HEX,
'door_hexes': self.items.DOORS,
'symbols_not_in_the_game': self.items.SYMBOLS_NOT_IN_THE_GAME
'symbols_not_in_the_game': self.items.SYMBOLS_NOT_IN_THE_GAME,
'disabled_panels': self.player_logic.COMPLETELY_DISABLED_CHECKS,
}
def generate_early(self):
if not (is_option_enabled(self.world, self.player, "shuffle_symbols")
or get_option_value(self.world, self.player, "shuffle_doors")
or is_option_enabled(self.world, self.player, "shuffle_lasers")):
raise Exception("This Witness world doesn't have any progression items. Please turn on Symbol Shuffle, Door"
" Shuffle or Laser Shuffle")
if self.world.players == 1:
warning("This Witness world doesn't have any progression items. Please turn on Symbol Shuffle, Door"
" Shuffle or Laser Shuffle if that doesn't seem right.")
else:
raise Exception("This Witness world doesn't have any progression items. Please turn on Symbol Shuffle,"
" Door Shuffle or Laser Shuffle.")
self.player_logic = WitnessPlayerLogic(self.world, self.player)
self.locat = WitnessPlayerLocations(self.world, self.player, self.player_logic)

View File

@@ -30,17 +30,17 @@ class StaticWitnessLocations:
"Outside Tutorial Vault Box",
"Outside Tutorial Discard",
"Outside Tutorial Dots Introduction 5",
"Outside Tutorial Squares Introduction 9",
"Outside Tutorial Shed Row 5",
"Outside Tutorial Tree Row 9",
"Glass Factory Discard",
"Glass Factory Vertical Symmetry 5",
"Glass Factory Rotational Symmetry 3",
"Glass Factory Back Wall 5",
"Glass Factory Front 3",
"Glass Factory Melting 3",
"Symmetry Island Black Dots 5",
"Symmetry Island Colored Dots 6",
"Symmetry Island Fading Lines 7",
"Symmetry Island Right 5",
"Symmetry Island Back 6",
"Symmetry Island Left 7",
"Symmetry Island Scenery Outlines 5",
"Symmetry Island Laser Panel",
@@ -48,26 +48,28 @@ class StaticWitnessLocations:
"Desert Vault Box",
"Desert Discard",
"Desert Sun Reflection 8",
"Desert Artificial Light Reflection 3",
"Desert Pond Reflection 5",
"Desert Flood Reflection 6",
"Desert Surface 8",
"Desert Light Room 3",
"Desert Pond Room 5",
"Desert Flood Room 6",
"Desert Final Bent 3",
"Desert Final Hexagonal",
"Desert Laser Panel",
"Quarry Mill Eraser and Dots 6",
"Quarry Mill Eraser and Squares 8",
"Quarry Mill Small Squares & Dots & Eraser",
"Quarry Boathouse Intro Shapers",
"Quarry Boathouse Intro Stars",
"Quarry Boathouse Eraser and Shapers 5",
"Quarry Boathouse Stars & Eraser & Shapers 2",
"Quarry Boathouse Stars & Eraser & Shapers 5",
"Quarry Mill Lower Row 6",
"Quarry Mill Upper Row 8",
"Quarry Mill Control Room Right",
"Quarry Boathouse Intro Right",
"Quarry Boathouse Intro Left",
"Quarry Boathouse Front Row 5",
"Quarry Boathouse Back First Row 9",
"Quarry Boathouse Back Second Row 3",
"Quarry Discard",
"Quarry Laser Panel",
"Shadows Lower Avoid 8",
"Shadows Environmental Avoid 8",
"Shadows Follow 5",
"Shadows Intro 8",
"Shadows Far 8",
"Shadows Near 5",
"Shadows Laser Panel",
"Keep Hedge Maze 4",
@@ -79,44 +81,44 @@ class StaticWitnessLocations:
"Shipwreck Vault Box",
"Shipwreck Discard",
"Monastery Rhombic Avoid 3",
"Monastery Branch Follow 2",
"Monastery Outside 3",
"Monastery Inside 4",
"Monastery Laser Panel",
"Town Cargo Box Discard",
"Town Hexagonal Reflection",
"Town Tall Hexagonal",
"Town Church Lattice",
"Town Rooftop Discard",
"Town Symmetry Squares 5 + Dots",
"Town Full Dot Grid Shapers 5",
"Town Shapers & Dots & Eraser",
"Town Red Rooftop 5",
"Town Wooden Roof Lower Row 5",
"Town Wooden Rooftop",
"Town Laser Panel",
"Theater Discard",
"Jungle Discard",
"Jungle Waves 3",
"Jungle Waves 7",
"Jungle First Row 3",
"Jungle Second Row 4",
"Jungle Popup Wall 6",
"Jungle Laser Panel",
"River Vault Box",
"Bunker Drawn Squares 5",
"Bunker Drawn Squares 9",
"Bunker Drawn Squares through Tinted Glass 3",
"Bunker Drop-Down Door Squares 2",
"Bunker Intro Left 5",
"Bunker Intro Back 4",
"Bunker Glass Room 3",
"Bunker UV Room 2",
"Bunker Laser Panel",
"Swamp Seperatable Shapers 6",
"Swamp Combinable Shapers 8",
"Swamp Broken Shapers 4",
"Swamp Cyan Underwater Negative Shapers 5",
"Swamp Platform Shapers 4",
"Swamp Rotated Shapers 4",
"Swamp Red Underwater Negative Shapers 4",
"Swamp More Rotated Shapers 4",
"Swamp Blue Underwater Negative Shapers 5",
"Swamp Intro Front 6",
"Swamp Intro Back 8",
"Swamp Between Bridges Near Row 4",
"Swamp Cyan Underwater 5",
"Swamp Platform Row 4",
"Swamp Between Bridges Far Row 4",
"Swamp Red Underwater 4",
"Swamp Beyond Rotating Bridge 4",
"Swamp Blue Underwater 5",
"Swamp Laser Panel",
"Treehouse Yellow Bridge 9",
@@ -125,73 +127,77 @@ class StaticWitnessLocations:
"Treehouse Green Bridge 7",
"Treehouse Green Bridge Discard",
"Treehouse Left Orange Bridge 15",
"Treehouse Burnt House Discard",
"Treehouse Laser Discard",
"Treehouse Right Orange Bridge 12",
"Treehouse Laser Panel",
"Mountaintop Discard",
"Mountaintop Vault Box",
}
"Mountainside Discard",
"Mountainside Vault Box",
UNCOMMON_LOCATIONS = {
"Mountaintop River Shape",
"Tutorial Patio Floor",
"Quarry Mill Big Squares & Dots & Eraser",
"Quarry Mill Control Room Left",
"Theater Tutorial Video",
"Theater Desert Video",
"Theater Jungle Video",
"Theater Shipwreck Video",
"Theater Mountain Video",
"Town RGB Squares",
"Town RGB Stars",
"Swamp Underwater Back Optional",
"Town RGB Room Left",
"Town RGB Room Right",
"Swamp Purple Underwater",
}
CAVES_LOCATIONS = {
"Inside Mountain Caves Dot Grid Triangles 4",
"Inside Mountain Caves Symmetry Triangles",
"Inside Mountain Caves Stars & Squares and Triangles 2",
"Inside Mountain Caves Shapers and Triangles 2",
"Inside Mountain Caves Symmetry Shapers",
"Inside Mountain Caves Broken and Negative Shapers",
"Inside Mountain Caves Broken Shapers",
"Caves Blue Tunnel Right First 4",
"Caves Blue Tunnel Left First 1",
"Caves Blue Tunnel Left Second 5",
"Caves Blue Tunnel Right Second 5",
"Caves Blue Tunnel Right Third 1",
"Caves Blue Tunnel Left Fourth 1",
"Caves Blue Tunnel Left Third 1",
"Inside Mountain Caves Rainbow Squares",
"Inside Mountain Caves Squares & Stars and Colored Eraser",
"Inside Mountain Caves Rotated Broken Shapers",
"Inside Mountain Caves Stars and Squares",
"Inside Mountain Caves Lone Pillar",
"Inside Mountain Caves Wooden Beam Shapers",
"Inside Mountain Caves Wooden Beam Squares and Shapers",
"Inside Mountain Caves Wooden Beam Stars and Squares",
"Inside Mountain Caves Wooden Beam Shapers and Stars",
"Inside Mountain Caves Upstairs Invisible Dots 8",
"Inside Mountain Caves Upstairs Invisible Dot Symmetry 3",
"Inside Mountain Caves Upstairs Dot Grid Negative Shapers",
"Inside Mountain Caves Upstairs Dot Grid Rotated Shapers",
"Caves First Floor Middle",
"Caves First Floor Right",
"Caves First Floor Left",
"Caves First Floor Grounded",
"Caves Lone Pillar",
"Caves First Wooden Beam",
"Caves Second Wooden Beam",
"Caves Third Wooden Beam",
"Caves Fourth Wooden Beam",
"Caves Right Upstairs Left Row 8",
"Caves Right Upstairs Right Row 3",
"Caves Left Upstairs Single",
"Caves Left Upstairs Left Row 5",
"Theater Walkway Vault Box",
"Inside Mountain Bottom Layer Discard",
"Tunnels Vault Box",
"Mountain Bottom Floor Discard",
"Theater Challenge Video",
}
MOUNTAIN_UNREACHABLE_FROM_BEHIND = {
"Mountaintop Trap Door Triple Exit",
"Inside Mountain Obscured Vision 5",
"Inside Mountain Moving Background 7",
"Inside Mountain Physically Obstructed 3",
"Inside Mountain Angled Inside Trash 2",
"Inside Mountain Color Cycle 5",
"Inside Mountain Same Solution 6",
"Mountain Floor 1 Right Row 5",
"Mountain Floor 1 Left Row 7",
"Mountain Floor 1 Back Row 3",
"Mountain Floor 1 Trash Pillar 2",
"Mountain Floor 2 Near Row 5",
"Mountain Floor 2 Far Row 6",
}
MOUNTAIN_REACHABLE_FROM_BEHIND = {
"Inside Mountain Elevator Discard",
"Inside Mountain Giant Puzzle",
"Mountain Floor 2 Elevator Discard",
"Mountain Bottom Floor Giant Puzzle",
"Inside Mountain Final Room Left Pillar 4",
"Inside Mountain Final Room Right Pillar 4",
"Mountain Final Room Left Pillar 4",
"Mountain Final Room Right Pillar 4",
}
MOUNTAIN_EXTRAS = {
"Challenge Vault Box",
"Theater Challenge Video",
"Mountain Bottom Floor Discard"
}
ALL_LOCATIONS_TO_ID = dict()
@@ -241,42 +247,51 @@ class WitnessPlayerLocations:
StaticWitnessLocations.GENERAL_LOCATIONS
)
doors = get_option_value(world, player, "shuffle_doors")
doors = get_option_value(world, player, "shuffle_doors") >= 2
earlyutm = is_option_enabled(world, player, "early_secret_area")
victory = get_option_value(world, player, "victory_condition")
lasers = get_option_value(world, player, "challenge_lasers")
mount_lasers = get_option_value(world, player, "mountain_lasers")
chal_lasers = get_option_value(world, player, "challenge_lasers")
laser_shuffle = get_option_value(world, player, "shuffle_lasers")
postgame = set()
postgame = postgame | StaticWitnessLocations.CAVES_LOCATIONS
postgame = postgame | StaticWitnessLocations.MOUNTAIN_REACHABLE_FROM_BEHIND
postgame = postgame | StaticWitnessLocations.MOUNTAIN_UNREACHABLE_FROM_BEHIND
postgame = postgame | StaticWitnessLocations.MOUNTAIN_EXTRAS
self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | postgame
if earlyutm or doors >= 2 or (victory == 1 and (lasers <= 11 or laser_shuffle)):
mountain_enterable_from_top = victory == 0 or victory == 1 or (victory == 3 and chal_lasers > mount_lasers)
if earlyutm or doors: # in non-doors, there is no way to get symbol-locked by the final pillars (currently)
postgame -= StaticWitnessLocations.CAVES_LOCATIONS
if doors >= 2:
if (doors or earlyutm) and (victory == 0 or (victory == 2 and mount_lasers > chal_lasers)):
postgame -= {"Challenge Vault Box", "Theater Challenge Video"}
if doors or mountain_enterable_from_top:
postgame -= StaticWitnessLocations.MOUNTAIN_REACHABLE_FROM_BEHIND
if victory != 2:
if mountain_enterable_from_top:
postgame -= StaticWitnessLocations.MOUNTAIN_UNREACHABLE_FROM_BEHIND
if (victory == 0 and doors) or victory == 1 or (victory == 2 and mount_lasers > chal_lasers and doors):
postgame -= {"Mountain Bottom Floor Discard"}
if is_option_enabled(world, player, "shuffle_discarded_panels"):
self.PANEL_TYPES_TO_SHUFFLE.add("Discard")
if is_option_enabled(world, player, "shuffle_vault_boxes"):
self.PANEL_TYPES_TO_SHUFFLE.add("Vault")
if is_option_enabled(world, player, "shuffle_uncommon"):
self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | StaticWitnessLocations.UNCOMMON_LOCATIONS
self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | player_logic.ADDED_CHECKS
if not is_option_enabled(world, player, "shuffle_postgame"):
self.CHECK_LOCATIONS -= postgame
self.CHECK_LOCATIONS.discard(StaticWitnessLogic.CHECKS_BY_HEX[player_logic.VICTORY_LOCATION]["checkName"])
self.CHECK_LOCATIONS = self.CHECK_LOCATIONS - {
StaticWitnessLogic.CHECKS_BY_HEX[check_hex]["checkName"]
for check_hex in player_logic.COMPLETELY_DISABLED_CHECKS

View File

@@ -36,6 +36,9 @@ class WitnessPlayerLogic:
Panels outside of the same region will still be checked manually.
"""
if panel_hex in self.COMPLETELY_DISABLED_CHECKS:
return frozenset()
check_obj = StaticWitnessLogic.CHECKS_BY_HEX[panel_hex]
these_items = frozenset({frozenset()})
@@ -57,7 +60,10 @@ class WitnessPlayerLogic:
for dependentItem in door_items:
all_options.add(items_option.union(dependentItem))
return frozenset(all_options)
if panel_hex != "0x28A0D":
return frozenset(all_options)
else: # 0x28A0D depends on another entity for *non-power* reasons -> This dependency needs to be preserved
these_items = all_options
these_panels = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["panels"]
@@ -72,7 +78,9 @@ class WitnessPlayerLogic:
for option_panel in option:
dep_obj = StaticWitnessLogic.CHECKS_BY_HEX.get(option_panel)
if option_panel in {"7 Lasers", "11 Lasers"}:
if option_panel in self.COMPLETELY_DISABLED_CHECKS:
new_items = frozenset()
elif option_panel in {"7 Lasers", "11 Lasers"}:
new_items = frozenset({frozenset([option_panel])})
# If a panel turns on when a panel in a different region turns on,
# the latter panel will be an "event panel", unless it ends up being
@@ -204,9 +212,11 @@ class WitnessPlayerLogic:
elif get_option_value(world, player, "victory_condition") == 3:
self.VICTORY_LOCATION = "0xFFF00"
self.COMPLETELY_DISABLED_CHECKS.add(
self.VICTORY_LOCATION
)
if get_option_value(world, player, "challenge_lasers") <= 7:
adjustment_linesets_in_order.append([
"Requirement Changes:",
"0xFFF00 - 11 Lasers - True",
])
if is_option_enabled(world, player, "disable_non_randomized_puzzles"):
adjustment_linesets_in_order.append(get_disable_unrandomized_list())
@@ -314,7 +324,7 @@ class WitnessPlayerLogic:
self.VICTORY_LOCATION = "0x0356B"
self.EVENT_ITEM_NAMES = {
"0x01A0F": "Keep Laser Panel (Hedge Mazes) Activates",
"0x09D9B": "Monastery Overhead Doors Open",
"0x09D9B": "Monastery Shutters Open",
"0x193A6": "Monastery Laser Panel Activates",
"0x00037": "Monastery Branch Panels Activate",
"0x0A079": "Access to Bunker Laser",
@@ -325,24 +335,24 @@ class WitnessPlayerLogic:
"0x01D3F": "Keep Laser Panel (Pressure Plates) Activates",
"0x09F7F": "Mountain Access",
"0x0367C": "Quarry Laser Mill Requirement Met",
"0x009A1": "Swamp Rotated Shapers 1 Activates",
"0x009A1": "Swamp Between Bridges Far 1 Activates",
"0x00006": "Swamp Cyan Water Drains",
"0x00990": "Swamp Broken Shapers 1 Activates",
"0x0A8DC": "Lower Avoid 6 Activates",
"0x0000A": "Swamp More Rotated Shapers 1 Access",
"0x09E86": "Inside Mountain Second Layer Blue Bridge Access",
"0x09ED8": "Inside Mountain Second Layer Yellow Bridge Access",
"0x00990": "Swamp Between Bridges Near Row 1 Activates",
"0x0A8DC": "Intro 6 Activates",
"0x0000A": "Swamp Beyond Rotating Bridge 1 Access",
"0x09E86": "Mountain Floor 2 Blue Bridge Access",
"0x09ED8": "Mountain Floor 2 Yellow Bridge Access",
"0x0A3D0": "Quarry Laser Boathouse Requirement Met",
"0x00596": "Swamp Red Water Drains",
"0x00E3A": "Swamp Purple Water Drains",
"0x0343A": "Door to Symmetry Island Powers On",
"0xFFF00": "Inside Mountain Bottom Layer Discard Turns On",
"0xFFF00": "Mountain Bottom Floor Discard Turns On",
"0x17CA6": "All Boat Panels Turn On",
"0x17CDF": "All Boat Panels Turn On",
"0x09DB8": "All Boat Panels Turn On",
"0x17C95": "All Boat Panels Turn On",
"0x03BB0": "Town Church Lattice Vision From Outside",
"0x28AC1": "Town Shapers & Dots & Eraser Turns On",
"0x28AC1": "Town Wooden Rooftop Turns On",
"0x28A69": "Town Tower 1st Door Opens",
"0x28ACC": "Town Tower 2nd Door Opens",
"0x28AD9": "Town Tower 3rd Door Opens",
@@ -350,12 +360,20 @@ class WitnessPlayerLogic:
"0x03675": "Quarry Mill Ramp Activation From Above",
"0x03679": "Quarry Mill Lift Lowering While Standing On It",
"0x2FAF6": "Tutorial Gate Secret Solution Knowledge",
"0x079DF": "Town Hexagonal Reflection Turns On",
"0x079DF": "Town Tall Hexagonal Turns On",
"0x17DA2": "Right Orange Bridge Fully Extended",
"0x19B24": "Shadows Lower Avoid Patterns Visible",
"0x19B24": "Shadows Intro Patterns Visible",
"0x2700B": "Open Door to Treehouse Laser House",
"0x00055": "Orchard Apple Trees 4 Turns On",
"0x17DDB": "Left Orange Bridge Fully Extended",
"0x03535": "Shipwreck Video Pattern Knowledge",
"0x03542": "Mountain Video Pattern Knowledge",
"0x0339E": "Desert Video Pattern Knowledge",
"0x03481": "Tutorial Video Pattern Knowledge",
"0x03702": "Jungle Video Pattern Knowledge",
"0x0356B": "Challenge Video Pattern Knowledge",
"0x0A15F": "Desert Laser Panel Shutters Open (1)",
"0x012D7": "Desert Laser Panel Shutters Open (2)",
}
self.ALWAYS_EVENT_NAMES_BY_HEX = {
@@ -371,12 +389,6 @@ class WitnessPlayerLogic:
"0x0C2B2": "Bunker Laser Activation",
"0x00BF6": "Swamp Laser Activation",
"0x028A4": "Treehouse Laser Activation",
"0x03535": "Shipwreck Video Pattern Knowledge",
"0x03542": "Mountain Video Pattern Knowledge",
"0x0339E": "Desert Video Pattern Knowledge",
"0x03481": "Tutorial Video Pattern Knowledge",
"0x03702": "Jungle Video Pattern Knowledge",
"0x0356B": "Challenge Video Pattern Knowledge",
"0x09F7F": "Mountaintop Trap Door Turns On",
"0x17C34": "Mountain Access",
}

View File

@@ -73,7 +73,7 @@ class WitnessRegions:
all_locations = all_locations | set(locations_for_this_region)
world.regions += [
create_region(world, player, region_name, self.locat,locations_for_this_region)
create_region(world, player, region_name, self.locat, locations_for_this_region)
]
for region_name, region in StaticWitnessLogic.ALL_REGIONS_BY_NAME.items():

View File

@@ -1,15 +1,15 @@
Event Items:
Town Tower 4th Door Opens - 0x17CFB,0x3C12B,0x00B8D,0x17CF7
Monastery Laser Activation - 0x00A5B,0x17CE7,0x17FA9,0x17CA4
Monastery Laser Activation - 0x00A5B,0x17CE7,0x17FA9
Bunker Laser Activation - 0x00061,0x17D01,0x17C42
Shadows Laser Activation - 0x00021,0x17D28,0x17C71
Requirement Changes:
0x17C65 - 0x00A5B | 0x17CE7 | 0x17FA9 | 0x17CA4
0x17C65 - 0x00A5B | 0x17CE7 | 0x17FA9
0x0C2B2 - 0x00061 | 0x17D01 | 0x17C42
0x181B3 - 0x00021 | 0x17D28 | 0x17C71
0x28B39 - True - Reflection
0x17CAB - True - True
0x2779A - True - 0x17CFB | 0x3C12B | 0x17CF7
Disabled Locations:
0x03505 (Tutorial Gate Close)
@@ -25,82 +25,84 @@ Disabled Locations:
0x00055 (Orchard Apple Tree 3)
0x032F7 (Orchard Apple Tree 4)
0x032FF (Orchard Apple Tree 5)
0x198B5 (Shadows Lower Avoid 1)
0x198BD (Shadows Lower Avoid 2)
0x198BF (Shadows Lower Avoid 3)
0x19771 (Shadows Lower Avoid 4)
0x0A8DC (Shadows Lower Avoid 5)
0x0AC74 (Shadows Lower Avoid 6)
0x0AC7A (Shadows Lower Avoid 7)
0x0A8E0 (Shadows Lower Avoid 8)
0x386FA (Shadows Environmental Avoid 1)
0x1C33F (Shadows Environmental Avoid 2)
0x196E2 (Shadows Environmental Avoid 3)
0x1972A (Shadows Environmental Avoid 4)
0x19809 (Shadows Environmental Avoid 5)
0x19806 (Shadows Environmental Avoid 6)
0x196F8 (Shadows Environmental Avoid 7)
0x1972F (Shadows Environmental Avoid 8)
0x19797 (Shadows Follow 1)
0x1979A (Shadows Follow 2)
0x197E0 (Shadows Follow 3)
0x197E8 (Shadows Follow 4)
0x197E5 (Shadows Follow 5)
0x198B5 (Shadows Intro 1)
0x198BD (Shadows Intro 2)
0x198BF (Shadows Intro 3)
0x19771 (Shadows Intro 4)
0x0A8DC (Shadows Intro 5)
0x0AC74 (Shadows Intro 6)
0x0AC7A (Shadows Intro 7)
0x0A8E0 (Shadows Intro 8)
0x386FA (Shadows Far 1)
0x1C33F (Shadows Far 2)
0x196E2 (Shadows Far 3)
0x1972A (Shadows Far 4)
0x19809 (Shadows Far 5)
0x19806 (Shadows Far 6)
0x196F8 (Shadows Far 7)
0x1972F (Shadows Far 8)
0x19797 (Shadows Near 1)
0x1979A (Shadows Near 2)
0x197E0 (Shadows Near 3)
0x197E8 (Shadows Near 4)
0x197E5 (Shadows Near 5)
0x19650 (Shadows Laser)
0x00139 (Keep Hedge Maze 1)
0x019DC (Keep Hedge Maze 2)
0x019E7 (Keep Hedge Maze 3)
0x01A0F (Keep Hedge Maze 4)
0x0360E (Laser Hedges)
0x00B10 (Monastery Door Open Left)
0x00C92 (Monastery Door Open Right)
0x00290 (Monastery Rhombic Avoid 1)
0x00038 (Monastery Rhombic Avoid 2)
0x00037 (Monastery Rhombic Avoid 3)
0x193A7 (Monastery Branch Avoid 1)
0x193AA (Monastery Branch Avoid 2)
0x193AB (Monastery Branch Follow 1)
0x193A6 (Monastery Branch Follow 2)
0x17CA4 (Monastery Laser) - 0x193A6 - True
0x18590 (Tree Outlines) - True - Symmetry & Environment
0x28AE3 (Vines Shadows Follow) - 0x18590 - Shadows Follow & Environment
0x28938 (Four-way Apple Tree) - 0x28AE3 - Environment
0x079DF (Triple Environmental Puzzle) - 0x28938 - Shadows Avoid & Environment & Reflection
0x28B39 (Hexagonal Reflection) - 0x079DF & 0x2896A - Reflection
0x00B10 (Monastery Entry Left)
0x00C92 (Monastery Entry Right)
0x00290 (Monastery Outside 1)
0x00038 (Monastery Outside 2)
0x00037 (Monastery Outside 3)
0x193A7 (Monastery Inside 1)
0x193AA (Monastery Inside 2)
0x193AB (Monastery Inside 3)
0x193A6 (Monastery Inside 4)
0x17CA4 (Monastery Laser)
0x18590 (Transparent) - True - Symmetry & Environment
0x28AE3 (Vines) - 0x18590 - Shadows Follow & Environment
0x28938 (Apple Tree) - 0x28AE3 - Environment
0x079DF (Triple Exit) - 0x28938 - Shadows Avoid & Environment & Reflection
0x28B39 (Tall Hexagonal) - 0x079DF & 0x2896A - Reflection
0x03553 (Theater Tutorial Video)
0x03552 (Theater Desert Video)
0x0354E (Theater Jungle Video)
0x03549 (Theater Challenge Video)
0x0354F (Theater Shipwreck Video)
0x03545 (Theater Mountain Video)
0x002C4 (Waves 1)
0x00767 (Waves 2)
0x002C6 (Waves 3)
0x0070E (Waves 4)
0x0070F (Waves 5)
0x0087D (Waves 6)
0x002C7 (Waves 7)
0x15ADD (River Rhombic Avoid Vault)
0x002C4 (First Row 1)
0x00767 (First Row 2)
0x002C6 (First Row 3)
0x0070E (Second Row 1)
0x0070F (Second Row 2)
0x0087D (Second Row 3)
0x002C7 (Second Row 4)
0x15ADD (River Outside Vault)
0x03702 (River Vault Box)
0x17CAA (Rhombic Avoid to Monastery Garden)
0x17CAA (Monastery Shortcut Panel)
0x17C2E (Door to Bunker)
0x09F7D (Bunker Drawn Squares 1)
0x09FDC (Bunker Drawn Squares 2)
0x09FF7 (Bunker Drawn Squares 3)
0x09F82 (Bunker Drawn Squares 4)
0x09FF8 (Bunker Drawn Squares 5)
0x09D9F (Bunker Drawn Squares 6)
0x09DA1 (Bunker Drawn Squares 7)
0x09DA2 (Bunker Drawn Squares 8)
0x09DAF (Bunker Drawn Squares 9)
0x0A010 (Bunker Drawn Squares through Tinted Glass 1)
0x0A01B (Bunker Drawn Squares through Tinted Glass 2)
0x0A01F (Bunker Drawn Squares through Tinted Glass 3)
0x0A099 (Door to Bunker Proper)
0x09F7D (Bunker Intro Left 1)
0x09FDC (Bunker Intro Left 2)
0x09FF7 (Bunker Intro Left 3)
0x09F82 (Bunker Intro Left 4)
0x09FF8 (Bunker Intro Left 5)
0x09D9F (Bunker Intro Back 1)
0x09DA1 (Bunker Intro Back 2)
0x09DA2 (Bunker Intro Back 3)
0x09DAF (Bunker Intro Back 4)
0x0A010 (Bunker Glass Room 1)
0x0A01B (Bunker Glass Room 2)
0x0A01F (Bunker Glass Room 3)
0x0A099 (Tinted Glass Door)
0x34BC5 (Bunker Drop-Down Door Open)
0x34BC6 (Bunker Drop-Down Door Close)
0x17E63 (Bunker Drop-Down Door Squares 1)
0x17E67 (Bunker Drop-Down Door Squares 2)
0x17E63 (Bunker UV Room 1)
0x17E67 (Bunker UV Room 2)
0x09DE0 (Bunker Laser)
0x0A079 (Bunker Elevator Control)
0x0042D (Mountaintop River Shape)
0x0042D (Mountaintop River Shape)
0x17CAA (River Garden Entry Panel)

View File

@@ -1,31 +1,31 @@
Items:
Glass Factory Entry Door (Panel)
Door to Symmetry Island Lower (Panel)
Door to Symmetry Island Upper (Panel)
Door to Desert Flood Light Room (Panel)
Desert Flood Room Flood Controls (Panel)
Quarry Door to Mill (Panel)
Glass Factory Entry (Panel)
Symmetry Island Lower (Panel)
Symmetry Island Upper (Panel)
Desert Light Room Entry (Panel)
Desert Flood Controls (Panel)
Quarry Mill Entry (Panel)
Quarry Mill Ramp Controls (Panel)
Quarry Mill Elevator Controls (Panel)
Quarry Mill Lift Controls (Panel)
Quarry Boathouse Ramp Height Control (Panel)
Quarry Boathouse Ramp Horizontal Control (Panel)
Shadows Door Timer (Panel)
Monastery Entry Door Left (Panel)
Monastery Entry Door Right (Panel)
Town Door to RGB House (Panel)
Town Door to Church (Panel)
Monastery Entry Left (Panel)
Monastery Entry Right (Panel)
Town Tinted Glass Door (Panel)
Town Church Entry (Panel)
Town Maze Panel (Drop-Down Staircase) (Panel)
Windmill Door (Panel)
Windmill Entry (Panel)
Treehouse First & Second Doors (Panel)
Treehouse Third Door (Panel)
Treehouse Laser House Door Timer (Panel)
Treehouse Shortcut Drop-Down Bridge (Panel)
Treehouse Drawbridge (Panel)
Jungle Popup Wall (Panel)
Bunker Entry Door (Panel)
Inside Bunker Door to Bunker Proper (Panel)
Bunker Entry (Panel)
Bunker Tinted Glass Door (Panel)
Bunker Elevator Control (Panel)
Swamp Entry Door (Panel)
Swamp Entry (Panel)
Swamp Sliding Bridge (Panel)
Swamp Rotating Bridge (Panel)
Swamp Maze Control (Panel)
Boat
Boat

View File

@@ -1,128 +1,128 @@
Items:
Outside Tutorial Optional Door
Outside Tutorial Outpost Entry Door
Outside Tutorial Outpost Exit Door
Glass Factory Entry Door
Glass Factory Back Wall
Symmetry Island Lower Door
Symmetry Island Upper Door
Orchard Middle Gate
Orchard Final Gate
Desert Door to Flood Light Room
Desert Door to Pond Room
Desert Door to Water Levels Room
Desert Door to Elevator Room
Quarry Main Entry 1
Quarry Main Entry 2
Quarry Door to Mill
Quarry Mill Side Door
Quarry Mill Rooftop Shortcut
Quarry Mill Stairs
Quarry Boathouse Boat Staircase
Quarry Boathouse First Barrier
Quarry Boathouse Shortcut
Shadows Timed Door
Shadows Laser Room Right Door
Shadows Laser Room Left Door
Shadows Barrier to Quarry
Shadows Barrier to Ledge
Keep Hedge Maze 1 Exit Door
Keep Pressure Plates 1 Exit Door
Keep Hedge Maze 2 Shortcut
Keep Hedge Maze 2 Exit Door
Keep Hedge Maze 3 Shortcut
Keep Hedge Maze 3 Exit Door
Keep Hedge Maze 4 Shortcut
Keep Hedge Maze 4 Exit Door
Keep Pressure Plates 2 Exit Door
Keep Pressure Plates 3 Exit Door
Keep Pressure Plates 4 Exit Door
Keep Shortcut to Shadows
Keep Tower Shortcut
Monastery Shortcut
Monastery Inner Door
Monastery Outer Door
Monastery Door to Garden
Town Cargo Box Door
Town Wooden Roof Staircase
Town Tinted Door to RGB House
Town Door to Church
Town Maze Staircase
Town Windmill Door
Town RGB House Staircase
Town Tower Blue Panels Door
Town Tower Lattice Door
Town Tower Environmental Set Door
Town Tower Wooden Roof Set Door
Theater Entry Door
Theater Exit Door Left
Theater Exit Door Right
Jungle Bamboo Shortcut to River
Jungle Popup Wall
River Shortcut to Monastery Garden
Bunker Bunker Entry Door
Bunker Tinted Glass Door
Bunker Door to Ultraviolet Room
Bunker Door to Elevator
Swamp Entry Door
Swamp Door to Broken Shapers
Outside Tutorial Outpost Path (Door)
Outside Tutorial Outpost Entry (Door)
Outside Tutorial Outpost Exit (Door)
Glass Factory Entry (Door)
Glass Factory Back Wall (Door)
Symmetry Island Lower (Door)
Symmetry Island Upper (Door)
Orchard First Gate (Door)
Orchard Second Gate (Door)
Desert Light Room Entry (Door)
Desert Pond Room Entry (Door)
Desert Flood Room Entry (Door)
Desert Elevator Room Entry (Door)
Quarry Entry 1 (Door)
Quarry Entry 2 (Door)
Quarry Mill Entry (Door)
Quarry Mill Side Exit (Door)
Quarry Mill Roof Exit (Door)
Quarry Mill Stairs (Door)
Quarry Boathouse Dock (Door)
Quarry Boathouse First Barrier (Door)
Quarry Boathouse Second Barrier (Door)
Shadows Timed Door (Door)
Shadows Laser Entry Right (Door)
Shadows Laser Entry Left (Door)
Shadows Quarry Barrier (Door)
Shadows Ledge Barrier (Door)
Keep Hedge Maze 1 Exit (Door)
Keep Pressure Plates 1 Exit (Door)
Keep Hedge Maze 2 Shortcut (Door)
Keep Hedge Maze 2 Exit (Door)
Keep Hedge Maze 3 Shortcut (Door)
Keep Hedge Maze 3 Exit (Door)
Keep Hedge Maze 4 Shortcut (Door)
Keep Hedge Maze 4 Exit (Door)
Keep Pressure Plates 2 Exit (Door)
Keep Pressure Plates 3 Exit (Door)
Keep Pressure Plates 4 Exit (Door)
Keep Shadows Shortcut (Door)
Keep Tower Shortcut (Door)
Monastery Shortcut (Door)
Monastery Entry Inner (Door)
Monastery Entry Outer (Door)
Monastery Garden Entry (Door)
Town Cargo Box Entry (Door)
Town Wooden Roof Stairs (Door)
Town Tinted Glass Door (Door)
Town Church Entry (Door)
Town Maze Stairs (Door)
Town Windmill Entry (Door)
Town RGB House Stairs (Door)
Town Tower First Door (Door)
Town Tower Third Door (Door)
Town Tower Fourth Door (Door)
Town Tower Second Door (Door)
Theater Entry (Door)
Theater Exit Left (Door)
Theater Exit Right (Door)
Jungle Bamboo Laser Shortcut (Door)
Jungle Popup Wall (Door)
River Monastery Shortcut (Door)
Bunker Entry (Door)
Bunker Tinted Glass Door (Door)
Bunker UV Room Entry (Door)
Bunker Elevator Room Entry (Door)
Swamp Entry (Door)
Swamp Between Bridges First Door
Swamp Platform Shortcut Door
Swamp Cyan Water Pump
Swamp Door to Rotated Shapers
Swamp Red Water Pump
Swamp Red Underwater Exit
Swamp Blue Water Pump
Swamp Purple Water Pump
Swamp Near Laser Shortcut
Treehouse First Door
Treehouse Second Door
Treehouse Beyond Yellow Bridge Door
Treehouse Drawbridge
Treehouse Timed Door to Laser House
Inside Mountain First Layer Exit Door
Inside Mountain Second Layer Staircase Near
Inside Mountain Second Layer Exit Door
Inside Mountain Second Layer Staircase Far
Inside Mountain Giant Puzzle Exit Door
Inside Mountain Door to Final Room
Inside Mountain Bottom Layer Rock
Inside Mountain Door to Secret Area
Caves Pillar Door
Caves Mountain Shortcut
Caves Swamp Shortcut
Challenge Entry Door
Challenge Door to Theater Walkway
Theater Walkway Door to Windmill Interior
Theater Walkway Door to Desert Elevator Room
Theater Walkway Door to Town
Swamp Cyan Water Pump (Door)
Swamp Between Bridges Second Door
Swamp Red Water Pump (Door)
Swamp Red Underwater Exit (Door)
Swamp Blue Water Pump (Door)
Swamp Purple Water Pump (Door)
Swamp Laser Shortcut (Door)
Treehouse First Door (Door)
Treehouse Second Door (Door)
Treehouse Third Door (Door)
Treehouse Drawbridge (Door)
Treehouse Laser House Entry (Door)
Mountain Floor 1 Exit (Door)
Mountain Floor 2 Staircase Near (Door)
Mountain Floor 2 Exit (Door)
Mountain Floor 2 Staircase Far (Door)
Mountain Bottom Floor Giant Puzzle Exit (Door)
Mountain Bottom Floor Final Room Entry (Door)
Mountain Bottom Floor Rock (Door)
Caves Entry (Door)
Caves Pillar Door (Door)
Caves Mountain Shortcut (Door)
Caves Swamp Shortcut (Door)
Challenge Entry (Door)
Challenge Tunnels Entry (Door)
Tunnels Theater Shortcut (Door)
Tunnels Desert Shortcut (Door)
Tunnels Town Shortcut (Door)
Added Locations:
Outside Tutorial Door to Outpost Panel
Outside Tutorial Exit Door from Outpost Panel
Glass Factory Entry Door Panel
Glass Factory Vertical Symmetry 5
Symmetry Island Door to Symmetry Island Lower Panel
Symmetry Island Door to Symmetry Island Upper Panel
Outside Tutorial Outpost Entry Panel
Outside Tutorial Outpost Exit Panel
Glass Factory Entry Panel
Glass Factory Back Wall 5
Symmetry Island Lower Panel
Symmetry Island Upper Panel
Orchard Apple Tree 3
Orchard Apple Tree 5
Desert Door to Desert Flood Light Room Panel
Desert Artificial Light Reflection 3
Desert Door to Water Levels Room Panel
Desert Flood Reflection 6
Quarry Door to Quarry 1 Panel
Quarry Door to Quarry 2 Panel
Quarry Door to Mill Right
Quarry Door to Mill Left
Quarry Mill Ground Floor Shortcut Door Panel
Quarry Mill Door to Outside Quarry Stairs Panel
Desert Light Room Entry Panel
Desert Light Room 3
Desert Flood Room Entry Panel
Desert Flood Room 6
Quarry Entry 1 Panel
Quarry Entry 2 Panel
Quarry Mill Entry Right Panel
Quarry Mill Entry Left Panel
Quarry Mill Side Exit Panel
Quarry Mill Roof Exit Panel
Quarry Mill Stair Control
Quarry Boathouse Shortcut Door Panel
Quarry Boathouse Second Barrier Panel
Shadows Door Timer Inside
Shadows Door Timer Outside
Shadows Environmental Avoid 8
Shadows Follow 5
Shadows Lower Avoid 3
Shadows Lower Avoid 5
Shadows Far 8
Shadows Near 5
Shadows Intro 3
Shadows Intro 5
Keep Hedge Maze 1
Keep Pressure Plates 1
Keep Hedge Maze 2
@@ -131,71 +131,70 @@ Keep Hedge Maze 4
Keep Pressure Plates 2
Keep Pressure Plates 3
Keep Pressure Plates 4
Keep Shortcut to Shadows Panel
Keep Tower Shortcut to Keep Panel
Monastery Shortcut Door Panel
Monastery Door Open Left
Monastery Door Open Right
Monastery Rhombic Avoid 3
Town Cargo Box Panel
Town Full Dot Grid Shapers 5
Town Tinted Door Panel
Town Door to Church Stars Panel
Keep Shadows Shortcut Panel
Keep Tower Shortcut Panel
Monastery Shortcut Panel
Monastery Entry Left
Monastery Entry Right
Monastery Outside 3
Town Cargo Box Entry Panel
Town Wooden Roof Lower Row 5
Town Tinted Glass Door Panel
Town Church Entry Panel
Town Maze Stair Control
Town Windmill Door Panel
Town Sound Room Left
Town Windmill Entry Panel
Town Sound Room Right
Town Symmetry Squares 5 + Dots
Town Red Rooftop 5
Town Church Lattice
Town Hexagonal Reflection
Town Shapers & Dots & Eraser
Windmill Door to Front of Theater Panel
Theater Door to Cargo Box Left Panel
Theater Door to Cargo Box Right Panel
Jungle Shortcut to River Panel
Town Tall Hexagonal
Town Wooden Rooftop
Windmill Theater Entry Panel
Theater Exit Left Panel
Theater Exit Right Panel
Jungle Laser Shortcut Panel
Jungle Popup Wall Control
River Rhombic Avoid to Monastery Garden
Bunker Bunker Entry Panel
Bunker Door to Bunker Proper Panel
Bunker Drawn Squares through Tinted Glass 3
Bunker Drop-Down Door Squares 2
River Monastery Shortcut Panel
Bunker Entry Panel
Bunker Tinted Glass Door Panel
Bunker Glass Room 3
Bunker UV Room 2
Swamp Entry Panel
Swamp Platform Shapers 4
Swamp Platform Row 4
Swamp Platform Shortcut Right Panel
Swamp Blue Underwater Negative Shapers 5
Swamp Broken Shapers 4
Swamp Cyan Underwater Negative Shapers 5
Swamp Red Underwater Negative Shapers 4
Swamp More Rotated Shapers 4
Swamp More Rotated Shapers 4
Swamp Near Laser Shortcut Right Panel
Swamp Blue Underwater 5
Swamp Between Bridges Near Row 4
Swamp Cyan Underwater 5
Swamp Red Underwater 4
Swamp Beyond Rotating Bridge 4
Swamp Beyond Rotating Bridge 4
Swamp Laser Shortcut Right Panel
Treehouse First Door Panel
Treehouse Second Door Panel
Treehouse Beyond Yellow Bridge Door Panel
Treehouse Third Door Panel
Treehouse Bridge Control
Treehouse Left Orange Bridge 15
Treehouse Right Orange Bridge 12
Treehouse Laser House Door Timer Outside Control
Treehouse Laser House Door Timer Inside Control
Inside Mountain Moving Background 7
Inside Mountain Obscured Vision 5
Inside Mountain Physically Obstructed 3
Inside Mountain Angled Inside Trash 2
Inside Mountain Color Cycle 5
Inside Mountain Light Bridge Controller 2
Inside Mountain Light Bridge Controller 3
Inside Mountain Same Solution 6
Inside Mountain Giant Puzzle
Inside Mountain Door to Final Room Left
Inside Mountain Door to Final Room Right
Inside Mountain Bottom Layer Discard
Inside Mountain Rock Control
Inside Mountain Secret Area Entry Panel
Inside Mountain Caves Lone Pillar
Inside Mountain Caves Shortcut to Mountain Panel
Inside Mountain Caves Shortcut to Swamp Panel
Inside Mountain Caves Challenge Entry Panel
Challenge Door to Theater Walkway Panel
Theater Walkway Theater Shortcut Panel
Theater Walkway Desert Shortcut Panel
Theater Walkway Town Shortcut Panel
Treehouse Laser House Door Timer Inside
Mountain Floor 1 Left Row 7
Mountain Floor 1 Right Row 5
Mountain Floor 1 Back Row 3
Mountain Floor 1 Trash Pillar 2
Mountain Floor 2 Near Row 5
Mountain Floor 2 Light Bridge Controller Near
Mountain Floor 2 Light Bridge Controller Far
Mountain Floor 2 Far Row 6
Mountain Bottom Floor Giant Puzzle
Mountain Bottom Floor Final Room Entry Left
Mountain Bottom Floor Final Room Entry Right
Mountain Bottom Floor Discard
Mountain Bottom Floor Rock Control
Mountain Bottom Floor Caves Entry Panel
Caves Lone Pillar
Caves Mountain Shortcut Panel
Caves Swamp Shortcut Panel
Caves Challenge Entry Panel
Challenge Tunnels Entry Panel
Tunnels Theater Shortcut Panel
Tunnels Desert Shortcut Panel
Tunnels Town Shortcut Panel

View File

@@ -1,104 +1,104 @@
Items:
Outside Tutorial Optional Door
Outside Tutorial Outpost Entry Door
Outside Tutorial Outpost Exit Door
Glass Factory Entry Door
Glass Factory Back Wall
Symmetry Island Lower Door
Symmetry Island Upper Door
Orchard Middle Gate
Orchard Final Gate
Desert Door to Flood Light Room
Desert Door to Pond Room
Desert Door to Water Levels Room
Desert Door to Elevator Room
Quarry Main Entry 1
Quarry Main Entry 2
Quarry Door to Mill
Quarry Mill Side Door
Quarry Mill Rooftop Shortcut
Quarry Mill Stairs
Quarry Boathouse Boat Staircase
Quarry Boathouse First Barrier
Quarry Boathouse Shortcut
Shadows Timed Door
Shadows Laser Room Right Door
Shadows Laser Room Left Door
Shadows Barrier to Quarry
Shadows Barrier to Ledge
Keep Hedge Maze 1 Exit Door
Keep Pressure Plates 1 Exit Door
Keep Hedge Maze 2 Shortcut
Keep Hedge Maze 2 Exit Door
Keep Hedge Maze 3 Shortcut
Keep Hedge Maze 3 Exit Door
Keep Hedge Maze 4 Shortcut
Keep Hedge Maze 4 Exit Door
Keep Pressure Plates 2 Exit Door
Keep Pressure Plates 3 Exit Door
Keep Pressure Plates 4 Exit Door
Keep Shortcut to Shadows
Keep Tower Shortcut
Monastery Shortcut
Monastery Inner Door
Monastery Outer Door
Monastery Door to Garden
Town Cargo Box Door
Town Wooden Roof Staircase
Town Tinted Door to RGB House
Town Door to Church
Town Maze Staircase
Town Windmill Door
Town RGB House Staircase
Town Tower Blue Panels Door
Town Tower Lattice Door
Town Tower Environmental Set Door
Town Tower Wooden Roof Set Door
Theater Entry Door
Theater Exit Door Left
Theater Exit Door Right
Jungle Bamboo Shortcut to River
Jungle Popup Wall
River Shortcut to Monastery Garden
Bunker Bunker Entry Door
Bunker Tinted Glass Door
Bunker Door to Ultraviolet Room
Bunker Door to Elevator
Swamp Entry Door
Swamp Door to Broken Shapers
Outside Tutorial Outpost Path (Door)
Outside Tutorial Outpost Entry (Door)
Outside Tutorial Outpost Exit (Door)
Glass Factory Entry (Door)
Glass Factory Back Wall (Door)
Symmetry Island Lower (Door)
Symmetry Island Upper (Door)
Orchard First Gate (Door)
Orchard Second Gate (Door)
Desert Light Room Entry (Door)
Desert Pond Room Entry (Door)
Desert Flood Room Entry (Door)
Desert Elevator Room Entry (Door)
Quarry Entry 1 (Door)
Quarry Entry 2 (Door)
Quarry Mill Entry (Door)
Quarry Mill Side Exit (Door)
Quarry Mill Roof Exit (Door)
Quarry Mill Stairs (Door)
Quarry Boathouse Dock (Door)
Quarry Boathouse First Barrier (Door)
Quarry Boathouse Second Barrier (Door)
Shadows Timed Door (Door)
Shadows Laser Entry Right (Door)
Shadows Laser Entry Left (Door)
Shadows Quarry Barrier (Door)
Shadows Ledge Barrier (Door)
Keep Hedge Maze 1 Exit (Door)
Keep Pressure Plates 1 Exit (Door)
Keep Hedge Maze 2 Shortcut (Door)
Keep Hedge Maze 2 Exit (Door)
Keep Hedge Maze 3 Shortcut (Door)
Keep Hedge Maze 3 Exit (Door)
Keep Hedge Maze 4 Shortcut (Door)
Keep Hedge Maze 4 Exit (Door)
Keep Pressure Plates 2 Exit (Door)
Keep Pressure Plates 3 Exit (Door)
Keep Pressure Plates 4 Exit (Door)
Keep Shadows Shortcut (Door)
Keep Tower Shortcut (Door)
Monastery Shortcut (Door)
Monastery Entry Inner (Door)
Monastery Entry Outer (Door)
Monastery Garden Entry (Door)
Town Cargo Box Entry (Door)
Town Wooden Roof Stairs (Door)
Town Tinted Glass Door (Door)
Town Church Entry (Door)
Town Maze Stairs (Door)
Town Windmill Entry (Door)
Town RGB House Stairs (Door)
Town Tower First Door (Door)
Town Tower Third Door (Door)
Town Tower Fourth Door (Door)
Town Tower Second Door (Door)
Theater Entry (Door)
Theater Exit Left (Door)
Theater Exit Right (Door)
Jungle Bamboo Laser Shortcut (Door)
Jungle Popup Wall (Door)
River Monastery Shortcut (Door)
Bunker Entry (Door)
Bunker Tinted Glass Door (Door)
Bunker UV Room Entry (Door)
Bunker Elevator Room Entry (Door)
Swamp Entry (Door)
Swamp Between Bridges First Door
Swamp Platform Shortcut Door
Swamp Cyan Water Pump
Swamp Door to Rotated Shapers
Swamp Red Water Pump
Swamp Red Underwater Exit
Swamp Blue Water Pump
Swamp Purple Water Pump
Swamp Near Laser Shortcut
Treehouse First Door
Treehouse Second Door
Treehouse Beyond Yellow Bridge Door
Treehouse Drawbridge
Treehouse Timed Door to Laser House
Inside Mountain First Layer Exit Door
Inside Mountain Second Layer Staircase Near
Inside Mountain Second Layer Exit Door
Inside Mountain Second Layer Staircase Far
Inside Mountain Giant Puzzle Exit Door
Inside Mountain Door to Final Room
Inside Mountain Bottom Layer Rock
Inside Mountain Door to Secret Area
Caves Pillar Door
Caves Mountain Shortcut
Caves Swamp Shortcut
Challenge Entry Door
Challenge Door to Theater Walkway
Theater Walkway Door to Windmill Interior
Theater Walkway Door to Desert Elevator Room
Theater Walkway Door to Town
Swamp Cyan Water Pump (Door)
Swamp Between Bridges Second Door
Swamp Red Water Pump (Door)
Swamp Red Underwater Exit (Door)
Swamp Blue Water Pump (Door)
Swamp Purple Water Pump (Door)
Swamp Laser Shortcut (Door)
Treehouse First Door (Door)
Treehouse Second Door (Door)
Treehouse Third Door (Door)
Treehouse Drawbridge (Door)
Treehouse Laser House Entry (Door)
Mountain Floor 1 Exit (Door)
Mountain Floor 2 Staircase Near (Door)
Mountain Floor 2 Exit (Door)
Mountain Floor 2 Staircase Far (Door)
Mountain Bottom Floor Giant Puzzle Exit (Door)
Mountain Bottom Floor Final Room Entry (Door)
Mountain Bottom Floor Rock (Door)
Caves Entry (Door)
Caves Pillar Door (Door)
Caves Mountain Shortcut (Door)
Caves Swamp Shortcut (Door)
Challenge Entry (Door)
Challenge Tunnels Entry (Door)
Tunnels Theater Shortcut (Door)
Tunnels Desert Shortcut (Door)
Tunnels Town Shortcut (Door)
Desert Flood Room Flood Controls (Panel)
Desert Flood Controls (Panel)
Quarry Mill Ramp Controls (Panel)
Quarry Mill Elevator Controls (Panel)
Quarry Mill Lift Controls (Panel)
Quarry Boathouse Ramp Height Control (Panel)
Quarry Boathouse Ramp Horizontal Control (Panel)
Bunker Elevator Control (Panel)
@@ -108,32 +108,32 @@ Swamp Maze Control (Panel)
Boat
Added Locations:
Outside Tutorial Door to Outpost Panel
Outside Tutorial Exit Door from Outpost Panel
Glass Factory Entry Door Panel
Glass Factory Vertical Symmetry 5
Symmetry Island Door to Symmetry Island Lower Panel
Symmetry Island Door to Symmetry Island Upper Panel
Outside Tutorial Outpost Entry Panel
Outside Tutorial Outpost Exit Panel
Glass Factory Entry Panel
Glass Factory Back Wall 5
Symmetry Island Lower Panel
Symmetry Island Upper Panel
Orchard Apple Tree 3
Orchard Apple Tree 5
Desert Door to Desert Flood Light Room Panel
Desert Artificial Light Reflection 3
Desert Door to Water Levels Room Panel
Desert Flood Reflection 6
Quarry Door to Quarry 1 Panel
Quarry Door to Quarry 2 Panel
Quarry Door to Mill Right
Quarry Door to Mill Left
Quarry Mill Ground Floor Shortcut Door Panel
Quarry Mill Door to Outside Quarry Stairs Panel
Desert Light Room Entry Panel
Desert Light Room 3
Desert Flood Room Entry Panel
Desert Flood Room 6
Quarry Entry 1 Panel
Quarry Entry 2 Panel
Quarry Mill Entry Right Panel
Quarry Mill Entry Left Panel
Quarry Mill Side Exit Panel
Quarry Mill Roof Exit Panel
Quarry Mill Stair Control
Quarry Boathouse Shortcut Door Panel
Quarry Boathouse Second Barrier Panel
Shadows Door Timer Inside
Shadows Door Timer Outside
Shadows Environmental Avoid 8
Shadows Follow 5
Shadows Lower Avoid 3
Shadows Lower Avoid 5
Shadows Far 8
Shadows Near 5
Shadows Intro 3
Shadows Intro 5
Keep Hedge Maze 1
Keep Pressure Plates 1
Keep Hedge Maze 2
@@ -142,71 +142,70 @@ Keep Hedge Maze 4
Keep Pressure Plates 2
Keep Pressure Plates 3
Keep Pressure Plates 4
Keep Shortcut to Shadows Panel
Keep Tower Shortcut to Keep Panel
Monastery Shortcut Door Panel
Monastery Door Open Left
Monastery Door Open Right
Monastery Rhombic Avoid 3
Town Cargo Box Panel
Town Full Dot Grid Shapers 5
Town Tinted Door Panel
Town Door to Church Stars Panel
Keep Shadows Shortcut Panel
Keep Tower Shortcut Panel
Monastery Shortcut Panel
Monastery Entry Left
Monastery Entry Right
Monastery Outside 3
Town Cargo Box Entry Panel
Town Wooden Roof Lower Row 5
Town Tinted Glass Door Panel
Town Church Entry Panel
Town Maze Stair Control
Town Windmill Door Panel
Town Sound Room Left
Town Windmill Entry Panel
Town Sound Room Right
Town Symmetry Squares 5 + Dots
Town Red Rooftop 5
Town Church Lattice
Town Hexagonal Reflection
Town Shapers & Dots & Eraser
Windmill Door to Front of Theater Panel
Theater Door to Cargo Box Left Panel
Theater Door to Cargo Box Right Panel
Jungle Shortcut to River Panel
Town Tall Hexagonal
Town Wooden Rooftop
Windmill Theater Entry Panel
Theater Exit Left Panel
Theater Exit Right Panel
Jungle Laser Shortcut Panel
Jungle Popup Wall Control
River Rhombic Avoid to Monastery Garden
Bunker Bunker Entry Panel
Bunker Door to Bunker Proper Panel
Bunker Drawn Squares through Tinted Glass 3
Bunker Drop-Down Door Squares 2
River Monastery Shortcut Panel
Bunker Entry Panel
Bunker Tinted Glass Door Panel
Bunker Glass Room 3
Bunker UV Room 2
Swamp Entry Panel
Swamp Platform Shapers 4
Swamp Platform Row 4
Swamp Platform Shortcut Right Panel
Swamp Blue Underwater Negative Shapers 5
Swamp Broken Shapers 4
Swamp Cyan Underwater Negative Shapers 5
Swamp Red Underwater Negative Shapers 4
Swamp More Rotated Shapers 4
Swamp More Rotated Shapers 4
Swamp Near Laser Shortcut Right Panel
Swamp Blue Underwater 5
Swamp Between Bridges Near Row 4
Swamp Cyan Underwater 5
Swamp Red Underwater 4
Swamp Beyond Rotating Bridge 4
Swamp Beyond Rotating Bridge 4
Swamp Laser Shortcut Right Panel
Treehouse First Door Panel
Treehouse Second Door Panel
Treehouse Beyond Yellow Bridge Door Panel
Treehouse Third Door Panel
Treehouse Bridge Control
Treehouse Left Orange Bridge 15
Treehouse Right Orange Bridge 12
Treehouse Laser House Door Timer Outside Control
Treehouse Laser House Door Timer Inside Control
Inside Mountain Moving Background 7
Inside Mountain Obscured Vision 5
Inside Mountain Physically Obstructed 3
Inside Mountain Angled Inside Trash 2
Inside Mountain Color Cycle 5
Inside Mountain Light Bridge Controller 2
Inside Mountain Light Bridge Controller 3
Inside Mountain Same Solution 6
Inside Mountain Giant Puzzle
Inside Mountain Door to Final Room Left
Inside Mountain Door to Final Room Right
Inside Mountain Bottom Layer Discard
Inside Mountain Rock Control
Inside Mountain Secret Area Entry Panel
Inside Mountain Caves Lone Pillar
Inside Mountain Caves Shortcut to Mountain Panel
Inside Mountain Caves Shortcut to Swamp Panel
Inside Mountain Caves Challenge Entry Panel
Challenge Door to Theater Walkway Panel
Theater Walkway Theater Shortcut Panel
Theater Walkway Desert Shortcut Panel
Theater Walkway Town Shortcut Panel
Treehouse Laser House Door Timer Inside
Mountain Floor 1 Left Row 7
Mountain Floor 1 Right Row 5
Mountain Floor 1 Back Row 3
Mountain Floor 1 Trash Pillar 2
Mountain Floor 2 Near Row 5
Mountain Floor 2 Light Bridge Controller Near
Mountain Floor 2 Light Bridge Controller Far
Mountain Floor 2 Far Row 6
Mountain Bottom Floor Giant Puzzle
Mountain Bottom Floor Final Room Entry Left
Mountain Bottom Floor Final Room Entry Right
Mountain Bottom Floor Discard
Mountain Bottom Floor Rock Control
Mountain Bottom Floor Caves Entry Panel
Caves Lone Pillar
Caves Mountain Shortcut Panel
Caves Swamp Shortcut Panel
Caves Challenge Entry Panel
Challenge Tunnels Entry Panel
Tunnels Theater Shortcut Panel
Tunnels Desert Shortcut Panel
Tunnels Town Shortcut Panel

View File

@@ -1,73 +1,73 @@
Items:
Glass Factory Back Wall
Quarry Boathouse Boat Staircase
Glass Factory Back Wall (Door)
Quarry Boathouse Dock (Door)
Outside Tutorial Outpost Doors
Glass Factory Entry Door
Glass Factory Entry (Door)
Symmetry Island Doors
Orchard Gates
Desert Doors
Quarry Main Entry
Quarry Door to Mill
Quarry Mill Entry (Door)
Quarry Mill Shortcuts
Quarry Boathouse Barriers
Shadows Timed Door
Shadows Timed Door (Door)
Shadows Laser Room Door
Shadows Barriers
Keep Hedge Maze Doors
Keep Pressure Plates Doors
Keep Shortcuts
Monastery Entry Door
Monastery Entry
Monastery Shortcuts
Town Doors
Town Tower Doors
Theater Entry Door
Theater Exit Door
Theater Entry (Door)
Theater Exit
Jungle & River Shortcuts
Jungle Popup Wall
Jungle Popup Wall (Door)
Bunker Doors
Swamp Doors
Swamp Near Laser Shortcut
Swamp Laser Shortcut (Door)
Swamp Water Pumps
Treehouse Entry Doors
Treehouse Drawbridge
Treehouse Timed Door to Laser House
Inside Mountain First Layer Exit Door
Inside Mountain Second Layer Stairs & Doors
Inside Mountain Giant Puzzle Exit Door
Inside Mountain Door to Final Room
Inside Mountain Bottom Layer Doors to Caves
Treehouse Drawbridge (Door)
Treehouse Laser House Entry (Door)
Mountain Floor 1 Exit (Door)
Mountain Floor 2 Stairs & Doors
Mountain Bottom Floor Giant Puzzle Exit (Door)
Mountain Bottom Floor Final Room Entry (Door)
Mountain Bottom Floor Doors to Caves
Caves Doors to Challenge
Caves Exits to Main Island
Challenge Door to Theater Walkway
Theater Walkway Doors
Challenge Tunnels Entry (Door)
Tunnels Doors
Added Locations:
Outside Tutorial Door to Outpost Panel
Outside Tutorial Exit Door from Outpost Panel
Glass Factory Entry Door Panel
Glass Factory Vertical Symmetry 5
Symmetry Island Door to Symmetry Island Lower Panel
Symmetry Island Door to Symmetry Island Upper Panel
Outside Tutorial Outpost Entry Panel
Outside Tutorial Outpost Exit Panel
Glass Factory Entry Panel
Glass Factory Back Wall 5
Symmetry Island Lower Panel
Symmetry Island Upper Panel
Orchard Apple Tree 3
Orchard Apple Tree 5
Desert Door to Desert Flood Light Room Panel
Desert Artificial Light Reflection 3
Desert Door to Water Levels Room Panel
Desert Flood Reflection 6
Quarry Door to Quarry 1 Panel
Quarry Door to Quarry 2 Panel
Quarry Door to Mill Right
Quarry Door to Mill Left
Quarry Mill Ground Floor Shortcut Door Panel
Quarry Mill Door to Outside Quarry Stairs Panel
Desert Light Room Entry Panel
Desert Light Room 3
Desert Flood Room Entry Panel
Desert Flood Room 6
Quarry Entry 1 Panel
Quarry Entry 2 Panel
Quarry Mill Entry Right Panel
Quarry Mill Entry Left Panel
Quarry Mill Side Exit Panel
Quarry Mill Roof Exit Panel
Quarry Mill Stair Control
Quarry Boathouse Shortcut Door Panel
Quarry Boathouse Second Barrier Panel
Shadows Door Timer Inside
Shadows Door Timer Outside
Shadows Environmental Avoid 8
Shadows Follow 5
Shadows Lower Avoid 3
Shadows Lower Avoid 5
Shadows Far 8
Shadows Near 5
Shadows Intro 3
Shadows Intro 5
Keep Hedge Maze 1
Keep Pressure Plates 1
Keep Hedge Maze 2
@@ -76,71 +76,70 @@ Keep Hedge Maze 4
Keep Pressure Plates 2
Keep Pressure Plates 3
Keep Pressure Plates 4
Keep Shortcut to Shadows Panel
Keep Tower Shortcut to Keep Panel
Monastery Shortcut Door Panel
Monastery Door Open Left
Monastery Door Open Right
Monastery Rhombic Avoid 3
Town Cargo Box Panel
Town Full Dot Grid Shapers 5
Town Tinted Door Panel
Town Door to Church Stars Panel
Keep Shadows Shortcut Panel
Keep Tower Shortcut Panel
Monastery Shortcut Panel
Monastery Entry Left
Monastery Entry Right
Monastery Outside 3
Town Cargo Box Entry Panel
Town Wooden Roof Lower Row 5
Town Tinted Glass Door Panel
Town Church Entry Panel
Town Maze Stair Control
Town Windmill Door Panel
Town Sound Room Left
Town Windmill Entry Panel
Town Sound Room Right
Town Symmetry Squares 5 + Dots
Town Red Rooftop 5
Town Church Lattice
Town Hexagonal Reflection
Town Shapers & Dots & Eraser
Windmill Door to Front of Theater Panel
Theater Door to Cargo Box Left Panel
Theater Door to Cargo Box Right Panel
Jungle Shortcut to River Panel
Town Tall Hexagonal
Town Wooden Rooftop
Windmill Theater Entry Panel
Theater Exit Left Panel
Theater Exit Right Panel
Jungle Laser Shortcut Panel
Jungle Popup Wall Control
River Rhombic Avoid to Monastery Garden
Bunker Bunker Entry Panel
Bunker Door to Bunker Proper Panel
Bunker Drawn Squares through Tinted Glass 3
Bunker Drop-Down Door Squares 2
River Monastery Shortcut Panel
Bunker Entry Panel
Bunker Tinted Glass Door Panel
Bunker Glass Room 3
Bunker UV Room 2
Swamp Entry Panel
Swamp Platform Shapers 4
Swamp Platform Row 4
Swamp Platform Shortcut Right Panel
Swamp Blue Underwater Negative Shapers 5
Swamp Broken Shapers 4
Swamp Cyan Underwater Negative Shapers 5
Swamp Red Underwater Negative Shapers 4
Swamp More Rotated Shapers 4
Swamp More Rotated Shapers 4
Swamp Near Laser Shortcut Right Panel
Swamp Blue Underwater 5
Swamp Between Bridges Near Row 4
Swamp Cyan Underwater 5
Swamp Red Underwater 4
Swamp Beyond Rotating Bridge 4
Swamp Beyond Rotating Bridge 4
Swamp Laser Shortcut Right Panel
Treehouse First Door Panel
Treehouse Second Door Panel
Treehouse Beyond Yellow Bridge Door Panel
Treehouse Third Door Panel
Treehouse Bridge Control
Treehouse Left Orange Bridge 15
Treehouse Right Orange Bridge 12
Treehouse Laser House Door Timer Outside Control
Treehouse Laser House Door Timer Inside Control
Inside Mountain Moving Background 7
Inside Mountain Obscured Vision 5
Inside Mountain Physically Obstructed 3
Inside Mountain Angled Inside Trash 2
Inside Mountain Color Cycle 5
Inside Mountain Light Bridge Controller 2
Inside Mountain Light Bridge Controller 3
Inside Mountain Same Solution 6
Inside Mountain Giant Puzzle
Inside Mountain Door to Final Room Left
Inside Mountain Door to Final Room Right
Inside Mountain Bottom Layer Discard
Inside Mountain Rock Control
Inside Mountain Secret Area Entry Panel
Inside Mountain Caves Lone Pillar
Inside Mountain Caves Shortcut to Mountain Panel
Inside Mountain Caves Shortcut to Swamp Panel
Inside Mountain Caves Challenge Entry Panel
Challenge Door to Theater Walkway Panel
Theater Walkway Theater Shortcut Panel
Theater Walkway Desert Shortcut Panel
Theater Walkway Town Shortcut Panel
Treehouse Laser House Door Timer Inside
Mountain Floor 1 Left Row 7
Mountain Floor 1 Right Row 5
Mountain Floor 1 Back Row 3
Mountain Floor 1 Trash Pillar 2
Mountain Floor 2 Near Row 5
Mountain Floor 2 Light Bridge Controller Near
Mountain Floor 2 Light Bridge Controller Far
Mountain Floor 2 Far Row 6
Mountain Bottom Floor Giant Puzzle
Mountain Bottom Floor Final Room Entry Left
Mountain Bottom Floor Final Room Entry Right
Mountain Bottom Floor Discard
Mountain Bottom Floor Rock Control
Mountain Bottom Floor Caves Entry Panel
Caves Lone Pillar
Caves Mountain Shortcut Panel
Caves Swamp Shortcut Panel
Caves Challenge Entry Panel
Challenge Tunnels Entry Panel
Tunnels Theater Shortcut Panel
Tunnels Desert Shortcut Panel
Tunnels Town Shortcut Panel

View File

@@ -5,5 +5,5 @@ Starting Inventory:
Caves Exits to Main Island
Remove Items:
Caves Mountain Shortcut
Caves Swamp Shortcut
Caves Mountain Shortcut (Door)
Caves Swamp Shortcut (Door)