Merge branch 'main' into soe
This commit is contained in:
@@ -4,7 +4,7 @@ import Utils
|
||||
from Patch import read_rom
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = 'e397fef0e947d1bd760c68c4fe99a600'
|
||||
RANDOMIZERBASEHASH = '9952c2a3ec1b421e408df0d20c8f0c7f'
|
||||
ROM_PLAYER_LIMIT = 255
|
||||
|
||||
import io
|
||||
@@ -1645,7 +1645,6 @@ def patch_rom(world, rom, player, enemized):
|
||||
# remote items flag, does not currently work
|
||||
rom.write_byte(0x18637C, int(world.worlds[player].remote_items))
|
||||
|
||||
rom.write_byte(0x18008D, int(world.death_link[player]))
|
||||
# set rom name
|
||||
# 21 bytes
|
||||
from Main import __version__
|
||||
@@ -1768,7 +1767,7 @@ def hud_format_text(text):
|
||||
|
||||
def apply_rom_settings(rom, beep, color, quickswap, menuspeed, music: bool, sprite: str, palettes_options,
|
||||
world=None, player=1, allow_random_on_event=False, reduceflashing=False,
|
||||
triforcehud: str = None):
|
||||
triforcehud: str = None, deathlink: bool = False):
|
||||
local_random = random if not world else world.slot_seeds[player]
|
||||
disable_music: bool = not music
|
||||
# enable instant item menu
|
||||
@@ -1902,6 +1901,8 @@ def apply_rom_settings(rom, beep, color, quickswap, menuspeed, music: bool, spri
|
||||
elif palettes_options['dungeon'] == 'random':
|
||||
randomize_uw_palettes(rom, local_random)
|
||||
|
||||
rom.write_byte(0x18008D, int(deathlink))
|
||||
|
||||
apply_random_sprite_on_event(rom, sprite, local_random, allow_random_on_event,
|
||||
world.sprite_pool[player] if world else [])
|
||||
if isinstance(rom, LocalRom):
|
||||
|
||||
@@ -294,7 +294,8 @@ class ALTTPWorld(World):
|
||||
world.sprite[player],
|
||||
palettes_options, world, player, True,
|
||||
reduceflashing=world.reduceflashing[player] or world.is_race,
|
||||
triforcehud=world.triforcehud[player].current_key)
|
||||
triforcehud=world.triforcehud[player].current_key,
|
||||
deathlink=world.death_link[player])
|
||||
|
||||
outfilepname = f'_P{player}'
|
||||
outfilepname += f"_{world.player_name[player].replace(' ', '_')}" \
|
||||
@@ -323,7 +324,7 @@ class ALTTPWorld(World):
|
||||
del (multidata["connect_names"][self.world.player_name[self.player]])
|
||||
|
||||
def get_required_client_version(self) -> tuple:
|
||||
return max((0, 1, 4), super(ALTTPWorld, self).get_required_client_version())
|
||||
return max((0, 2, 0), super(ALTTPWorld, self).get_required_client_version())
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return ALttPItem(name, self.player, **as_dict_item_table[name])
|
||||
|
||||
@@ -29,7 +29,11 @@ base_info = {
|
||||
"author": "Berserker",
|
||||
"homepage": "https://archipelago.gg",
|
||||
"description": "Integration client for the Archipelago Randomizer",
|
||||
"factorio_version": "1.1"
|
||||
"factorio_version": "1.1",
|
||||
"dependencies": [
|
||||
"base >= 1.1.0",
|
||||
"? science-not-invited"
|
||||
]
|
||||
}
|
||||
|
||||
recipe_time_scales = {
|
||||
@@ -95,7 +99,8 @@ def generate_mod(world, output_directory: str):
|
||||
"free_sample_blacklist": {item : 1 for item in free_sample_blacklist},
|
||||
"progressive_technology_table": {tech.name : tech.progressive for tech in
|
||||
progressive_technology_table.values()},
|
||||
"custom_recipes": world.custom_recipes}
|
||||
"custom_recipes": world.custom_recipes,
|
||||
"max_science_pack": multiworld.max_science_pack[player].value}
|
||||
|
||||
for factorio_option in Options.factorio_options:
|
||||
template_data[factorio_option] = getattr(multiworld, factorio_option)[player].value
|
||||
|
||||
@@ -85,7 +85,8 @@ class CustomTechnology(Technology):
|
||||
def __init__(self, origin: Technology, world, allowed_packs: Set[str], player: int):
|
||||
ingredients = origin.ingredients & allowed_packs
|
||||
military_allowed = "military-science-pack" in allowed_packs \
|
||||
and (ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"})
|
||||
and ((ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"})
|
||||
or origin.name == "rocket-silo")
|
||||
self.player = player
|
||||
if origin.name not in world.worlds[player].static_nodes:
|
||||
if military_allowed:
|
||||
|
||||
@@ -5,5 +5,9 @@
|
||||
"author": "Berserker and Dewiniaid",
|
||||
"homepage": "https://archipelago.gg",
|
||||
"description": "Integration client for the Archipelago Randomizer",
|
||||
"factorio_version": "1.1"
|
||||
"factorio_version": "1.1",
|
||||
"dependencies": [
|
||||
"base >= 1.1.0",
|
||||
"? science-not-invited"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -26,11 +26,31 @@ template_tech.prerequisites = {}
|
||||
|
||||
function prep_copy(new_copy, old_tech)
|
||||
old_tech.hidden = true
|
||||
new_copy.unit = table.deepcopy(old_tech.unit)
|
||||
local ingredient_filter = allowed_ingredients[old_tech.name]
|
||||
if ingredient_filter ~= nil then
|
||||
new_copy.unit.ingredients = filter_ingredients(new_copy.unit.ingredients, ingredient_filter)
|
||||
new_copy.unit.ingredients = add_ingredients(new_copy.unit.ingredients, ingredient_filter)
|
||||
if mods["science-not-invited"] then
|
||||
local weights = {
|
||||
["automation-science-pack"] = 0, -- Red science
|
||||
["logistic-science-pack"] = 0, -- Green science
|
||||
["military-science-pack"] = 0, -- Black science
|
||||
["chemical-science-pack"] = 0, -- Blue science
|
||||
["production-science-pack"] = 0, -- Purple science
|
||||
["utility-science-pack"] = 0, -- Yellow science
|
||||
["space-science-pack"] = 0 -- Space science
|
||||
}
|
||||
for key, value in pairs(ingredient_filter) do
|
||||
weights[key] = value
|
||||
end
|
||||
SNI.setWeights(weights)
|
||||
SNI.sendInvite(old_tech)
|
||||
-- SCIENCE-not-invited could potentially make tech cost 9.223e+18.
|
||||
old_tech.unit.count = math.min(10000, old_tech.unit.count)
|
||||
end
|
||||
new_copy.unit = table.deepcopy(old_tech.unit)
|
||||
new_copy.unit.ingredients = filter_ingredients(new_copy.unit.ingredients, ingredient_filter)
|
||||
new_copy.unit.ingredients = add_ingredients(new_copy.unit.ingredients, ingredient_filter)
|
||||
else
|
||||
new_copy.unit = table.deepcopy(old_tech.unit)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,2 +1,20 @@
|
||||
{% from "macros.lua" import dict_to_lua %}
|
||||
data.raw["map-gen-presets"].default["archipelago"] = {{ dict_to_lua({"default": False, "order": "a", "basic_settings": world_gen["basic"], "advanced_settings": world_gen["advanced"]}) }}
|
||||
if mods["science-not-invited"] then
|
||||
local weights = {
|
||||
["automation-science-pack"] = 0, -- Red science
|
||||
["logistic-science-pack"] = 0, -- Green science
|
||||
["military-science-pack"] = 0, -- Black science
|
||||
["chemical-science-pack"] = 0, -- Blue science
|
||||
["production-science-pack"] = 0, -- Purple science
|
||||
["utility-science-pack"] = 0, -- Yellow science
|
||||
["space-science-pack"] = 0 -- Space science
|
||||
}
|
||||
{% if max_science_pack == 6 -%}
|
||||
weights["space-science-pack"] = 1
|
||||
{%- endif %}
|
||||
{% for key in allowed_science_packs -%}
|
||||
weights["{{key}}"] = 1
|
||||
{% endfor %}
|
||||
SNI.setWeights(weights)
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from typing import NamedTuple, Union
|
||||
import logging
|
||||
|
||||
from BaseClasses import Item
|
||||
|
||||
from ..AutoWorld import World
|
||||
|
||||
|
||||
@@ -11,11 +13,17 @@ class GenericWorld(World):
|
||||
"Nothing": -1
|
||||
}
|
||||
location_name_to_id = {
|
||||
"Cheat Console" : -1,
|
||||
"Cheat Console": -1,
|
||||
"Server": -2
|
||||
}
|
||||
hidden = True
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
if name == "Nothing":
|
||||
return Item(name, False, -1, self.player)
|
||||
raise KeyError(name)
|
||||
|
||||
|
||||
class PlandoItem(NamedTuple):
|
||||
item: str
|
||||
location: str
|
||||
|
||||
@@ -5,8 +5,9 @@ from .Regions import TimeOfDay
|
||||
class OOTEntrance(Entrance):
|
||||
game: str = 'Ocarina of Time'
|
||||
|
||||
def __init__(self, player, name='', parent=None):
|
||||
def __init__(self, player, world, name='', parent=None):
|
||||
super(OOTEntrance, self).__init__(player, name, parent)
|
||||
self.world = world
|
||||
self.access_rules = []
|
||||
self.reverse = None
|
||||
self.replaces = None
|
||||
@@ -17,3 +18,27 @@ class OOTEntrance(Entrance):
|
||||
self.primary = False
|
||||
self.always = False
|
||||
self.never = False
|
||||
|
||||
def bind_two_way(self, other_entrance):
|
||||
self.reverse = other_entrance
|
||||
other_entrance.reverse = self
|
||||
|
||||
def disconnect(self):
|
||||
self.connected_region.entrances.remove(self)
|
||||
previously_connected = self.connected_region
|
||||
self.connected_region = None
|
||||
return previously_connected
|
||||
|
||||
def get_new_target(self):
|
||||
root = self.world.get_region('Root Exits', self.player)
|
||||
target_entrance = OOTEntrance(self.player, self.world, 'Root -> ' + self.connected_region.name, root)
|
||||
target_entrance.connect(self.connected_region)
|
||||
target_entrance.replaces = self
|
||||
root.exits.append(target_entrance)
|
||||
return target_entrance
|
||||
|
||||
def assume_reachable(self):
|
||||
if self.assumed == None:
|
||||
self.assumed = self.get_new_target()
|
||||
self.disconnect()
|
||||
return self.assumed
|
||||
|
||||
@@ -1,25 +1,771 @@
|
||||
from itertools import chain
|
||||
import logging
|
||||
|
||||
from worlds.generic.Rules import set_rule
|
||||
|
||||
from .Hints import get_hint_area, HintAreaNotFound
|
||||
from .Regions import TimeOfDay
|
||||
|
||||
|
||||
def set_all_entrances_data(world, player):
|
||||
for type, forward_entry, *return_entry in entrance_shuffle_table:
|
||||
forward_entrance = world.get_entrance(forward_entry[0], player)
|
||||
forward_entrance.data = forward_entry[1]
|
||||
forward_entrance.type = type
|
||||
forward_entrance.primary = True
|
||||
if type == 'Grotto':
|
||||
forward_entrance.data['index'] = 0x1000 + forward_entrance.data['grotto_id']
|
||||
if return_entry:
|
||||
return_entry = return_entry[0]
|
||||
return_entrance = world.get_entrance(return_entry[0], player)
|
||||
return_entrance.data = return_entry[1]
|
||||
return_entrance.type = type
|
||||
forward_entrance.bind_two_way(return_entrance)
|
||||
if type == 'Grotto':
|
||||
return_entrance.data['index'] = 0x7FFF
|
||||
|
||||
|
||||
def assume_entrance_pool(entrance_pool, ootworld):
|
||||
assumed_pool = []
|
||||
for entrance in entrance_pool:
|
||||
assumed_forward = entrance.assume_reachable()
|
||||
if entrance.reverse != None:
|
||||
assumed_return = entrance.reverse.assume_reachable()
|
||||
if (entrance.type in ('Dungeon', 'Grotto', 'Grave') and entrance.reverse.name != 'Spirit Temple Lobby -> Desert Colossus From Spirit Lobby') or \
|
||||
(entrance.type == 'Interior' and ootworld.shuffle_special_interior_entrances):
|
||||
# In most cases, Dungeon, Grotto/Grave and Simple Interior exits shouldn't be assumed able to give access to their parent region
|
||||
set_rule(assumed_return, lambda state, **kwargs: False)
|
||||
assumed_forward.bind_two_way(assumed_return)
|
||||
assumed_pool.append(assumed_forward)
|
||||
return assumed_pool
|
||||
|
||||
|
||||
def build_one_way_targets(world, types_to_include, exclude=(), target_region_names=()):
|
||||
one_way_entrances = []
|
||||
for pool_type in types_to_include:
|
||||
one_way_entrances += world.get_shufflable_entrances(type=pool_type)
|
||||
valid_one_way_entrances = list(filter(lambda entrance: entrance.name not in exclude, one_way_entrances))
|
||||
if target_region_names:
|
||||
return [entrance.get_new_target() for entrance in valid_one_way_entrances
|
||||
if entrance.connected_region.name in target_region_names]
|
||||
return [entrance.get_new_target() for entrance in valid_one_way_entrances]
|
||||
|
||||
|
||||
# Abbreviations
|
||||
# DMC Death Mountain Crater
|
||||
# DMT Death Mountain Trail
|
||||
# GC Goron City
|
||||
# GF Gerudo Fortress
|
||||
# GS Gold Skulltula
|
||||
# GV Gerudo Valley
|
||||
# HC Hyrule Castle
|
||||
# HF Hyrule Field
|
||||
# KF Kokiri Forest
|
||||
# LH Lake Hylia
|
||||
# LLR Lon Lon Ranch
|
||||
# LW Lost Woods
|
||||
# OGC Outside Ganon's Castle
|
||||
# SFM Sacred Forest Meadow
|
||||
# ToT Temple of Time
|
||||
# ZD Zora's Domain
|
||||
# ZF Zora's Fountain
|
||||
# ZR Zora's River
|
||||
|
||||
entrance_shuffle_table = [
|
||||
('Dungeon', ('KF Outside Deku Tree -> Deku Tree Lobby', { 'index': 0x0000 }),
|
||||
('Deku Tree Lobby -> KF Outside Deku Tree', { 'index': 0x0209, 'blue_warp': 0x0457 })),
|
||||
('Dungeon', ('Death Mountain -> Dodongos Cavern Beginning', { 'index': 0x0004 }),
|
||||
('Dodongos Cavern Beginning -> Death Mountain', { 'index': 0x0242, 'blue_warp': 0x047A })),
|
||||
('Dungeon', ('Zoras Fountain -> Jabu Jabus Belly Beginning', { 'index': 0x0028 }),
|
||||
('Jabu Jabus Belly Beginning -> Zoras Fountain', { 'index': 0x0221, 'blue_warp': 0x010E })),
|
||||
('Dungeon', ('SFM Forest Temple Entrance Ledge -> Forest Temple Lobby', { 'index': 0x0169 }),
|
||||
('Forest Temple Lobby -> SFM Forest Temple Entrance Ledge', { 'index': 0x0215, 'blue_warp': 0x0608 })),
|
||||
('Dungeon', ('DMC Fire Temple Entrance -> Fire Temple Lower', { 'index': 0x0165 }),
|
||||
('Fire Temple Lower -> DMC Fire Temple Entrance', { 'index': 0x024A, 'blue_warp': 0x0564 })),
|
||||
('Dungeon', ('Lake Hylia -> Water Temple Lobby', { 'index': 0x0010 }),
|
||||
('Water Temple Lobby -> Lake Hylia', { 'index': 0x021D, 'blue_warp': 0x060C })),
|
||||
('Dungeon', ('Desert Colossus -> Spirit Temple Lobby', { 'index': 0x0082 }),
|
||||
('Spirit Temple Lobby -> Desert Colossus From Spirit Lobby', { 'index': 0x01E1, 'blue_warp': 0x0610 })),
|
||||
('Dungeon', ('Graveyard Warp Pad Region -> Shadow Temple Entryway', { 'index': 0x0037 }),
|
||||
('Shadow Temple Entryway -> Graveyard Warp Pad Region', { 'index': 0x0205, 'blue_warp': 0x0580 })),
|
||||
('Dungeon', ('Kakariko Village -> Bottom of the Well', { 'index': 0x0098 }),
|
||||
('Bottom of the Well -> Kakariko Village', { 'index': 0x02A6 })),
|
||||
('Dungeon', ('ZF Ice Ledge -> Ice Cavern Beginning', { 'index': 0x0088 }),
|
||||
('Ice Cavern Beginning -> ZF Ice Ledge', { 'index': 0x03D4 })),
|
||||
('Dungeon', ('Gerudo Fortress -> Gerudo Training Grounds Lobby', { 'index': 0x0008 }),
|
||||
('Gerudo Training Grounds Lobby -> Gerudo Fortress', { 'index': 0x03A8 })),
|
||||
|
||||
('Interior', ('Kokiri Forest -> KF Midos House', { 'index': 0x0433 }),
|
||||
('KF Midos House -> Kokiri Forest', { 'index': 0x0443 })),
|
||||
('Interior', ('Kokiri Forest -> KF Sarias House', { 'index': 0x0437 }),
|
||||
('KF Sarias House -> Kokiri Forest', { 'index': 0x0447 })),
|
||||
('Interior', ('Kokiri Forest -> KF House of Twins', { 'index': 0x009C }),
|
||||
('KF House of Twins -> Kokiri Forest', { 'index': 0x033C })),
|
||||
('Interior', ('Kokiri Forest -> KF Know It All House', { 'index': 0x00C9 }),
|
||||
('KF Know It All House -> Kokiri Forest', { 'index': 0x026A })),
|
||||
('Interior', ('Kokiri Forest -> KF Kokiri Shop', { 'index': 0x00C1 }),
|
||||
('KF Kokiri Shop -> Kokiri Forest', { 'index': 0x0266 })),
|
||||
('Interior', ('Lake Hylia -> LH Lab', { 'index': 0x0043 }),
|
||||
('LH Lab -> Lake Hylia', { 'index': 0x03CC })),
|
||||
('Interior', ('LH Fishing Island -> LH Fishing Hole', { 'index': 0x045F }),
|
||||
('LH Fishing Hole -> LH Fishing Island', { 'index': 0x0309 })),
|
||||
('Interior', ('GV Fortress Side -> GV Carpenter Tent', { 'index': 0x03A0 }),
|
||||
('GV Carpenter Tent -> GV Fortress Side', { 'index': 0x03D0 })),
|
||||
('Interior', ('Market Entrance -> Market Guard House', { 'index': 0x007E }),
|
||||
('Market Guard House -> Market Entrance', { 'index': 0x026E })),
|
||||
('Interior', ('Market -> Market Mask Shop', { 'index': 0x0530 }),
|
||||
('Market Mask Shop -> Market', { 'index': 0x01D1, 'addresses': [0xC6DA5E] })),
|
||||
('Interior', ('Market -> Market Bombchu Bowling', { 'index': 0x0507 }),
|
||||
('Market Bombchu Bowling -> Market', { 'index': 0x03BC })),
|
||||
('Interior', ('Market -> Market Potion Shop', { 'index': 0x0388 }),
|
||||
('Market Potion Shop -> Market', { 'index': 0x02A2 })),
|
||||
('Interior', ('Market -> Market Treasure Chest Game', { 'index': 0x0063 }),
|
||||
('Market Treasure Chest Game -> Market', { 'index': 0x01D5 })),
|
||||
('Interior', ('Market Back Alley -> Market Bombchu Shop', { 'index': 0x0528 }),
|
||||
('Market Bombchu Shop -> Market Back Alley', { 'index': 0x03C0 })),
|
||||
('Interior', ('Market Back Alley -> Market Man in Green House', { 'index': 0x043B }),
|
||||
('Market Man in Green House -> Market Back Alley', { 'index': 0x0067 })),
|
||||
('Interior', ('Kakariko Village -> Kak Carpenter Boss House', { 'index': 0x02FD }),
|
||||
('Kak Carpenter Boss House -> Kakariko Village', { 'index': 0x0349 })),
|
||||
('Interior', ('Kakariko Village -> Kak House of Skulltula', { 'index': 0x0550 }),
|
||||
('Kak House of Skulltula -> Kakariko Village', { 'index': 0x04EE })),
|
||||
('Interior', ('Kakariko Village -> Kak Impas House', { 'index': 0x039C }),
|
||||
('Kak Impas House -> Kakariko Village', { 'index': 0x0345 })),
|
||||
('Interior', ('Kak Impas Ledge -> Kak Impas House Back', { 'index': 0x05C8 }),
|
||||
('Kak Impas House Back -> Kak Impas Ledge', { 'index': 0x05DC })),
|
||||
('Interior', ('Kak Backyard -> Kak Odd Medicine Building', { 'index': 0x0072 }),
|
||||
('Kak Odd Medicine Building -> Kak Backyard', { 'index': 0x034D })),
|
||||
('Interior', ('Graveyard -> Graveyard Dampes House', { 'index': 0x030D }),
|
||||
('Graveyard Dampes House -> Graveyard', { 'index': 0x0355 })),
|
||||
('Interior', ('Goron City -> GC Shop', { 'index': 0x037C }),
|
||||
('GC Shop -> Goron City', { 'index': 0x03FC })),
|
||||
('Interior', ('Zoras Domain -> ZD Shop', { 'index': 0x0380 }),
|
||||
('ZD Shop -> Zoras Domain', { 'index': 0x03C4 })),
|
||||
('Interior', ('Lon Lon Ranch -> LLR Talons House', { 'index': 0x004F }),
|
||||
('LLR Talons House -> Lon Lon Ranch', { 'index': 0x0378 })),
|
||||
('Interior', ('Lon Lon Ranch -> LLR Stables', { 'index': 0x02F9 }),
|
||||
('LLR Stables -> Lon Lon Ranch', { 'index': 0x042F })),
|
||||
('Interior', ('Lon Lon Ranch -> LLR Tower', { 'index': 0x05D0 }),
|
||||
('LLR Tower -> Lon Lon Ranch', { 'index': 0x05D4 })),
|
||||
('Interior', ('Market -> Market Bazaar', { 'index': 0x052C }),
|
||||
('Market Bazaar -> Market', { 'index': 0x03B8, 'addresses': [0xBEFD74] })),
|
||||
('Interior', ('Market -> Market Shooting Gallery', { 'index': 0x016D }),
|
||||
('Market Shooting Gallery -> Market', { 'index': 0x01CD, 'addresses': [0xBEFD7C] })),
|
||||
('Interior', ('Kakariko Village -> Kak Bazaar', { 'index': 0x00B7 }),
|
||||
('Kak Bazaar -> Kakariko Village', { 'index': 0x0201, 'addresses': [0xBEFD72] })),
|
||||
('Interior', ('Kakariko Village -> Kak Shooting Gallery', { 'index': 0x003B }),
|
||||
('Kak Shooting Gallery -> Kakariko Village', { 'index': 0x0463, 'addresses': [0xBEFD7A] })),
|
||||
('Interior', ('Desert Colossus -> Colossus Great Fairy Fountain', { 'index': 0x0588 }),
|
||||
('Colossus Great Fairy Fountain -> Desert Colossus', { 'index': 0x057C, 'addresses': [0xBEFD82] })),
|
||||
('Interior', ('Hyrule Castle Grounds -> HC Great Fairy Fountain', { 'index': 0x0578 }),
|
||||
('HC Great Fairy Fountain -> Castle Grounds', { 'index': 0x0340, 'addresses': [0xBEFD80] })),
|
||||
('Interior', ('Ganons Castle Grounds -> OGC Great Fairy Fountain', { 'index': 0x04C2 }),
|
||||
('OGC Great Fairy Fountain -> Castle Grounds', { 'index': 0x0340, 'addresses': [0xBEFD6C] })),
|
||||
('Interior', ('DMC Lower Nearby -> DMC Great Fairy Fountain', { 'index': 0x04BE }),
|
||||
('DMC Great Fairy Fountain -> DMC Lower Local', { 'index': 0x0482, 'addresses': [0xBEFD6A] })),
|
||||
('Interior', ('Death Mountain Summit -> DMT Great Fairy Fountain', { 'index': 0x0315 }),
|
||||
('DMT Great Fairy Fountain -> Death Mountain Summit', { 'index': 0x045B, 'addresses': [0xBEFD68] })),
|
||||
('Interior', ('Zoras Fountain -> ZF Great Fairy Fountain', { 'index': 0x0371 }),
|
||||
('ZF Great Fairy Fountain -> Zoras Fountain', { 'index': 0x0394, 'addresses': [0xBEFD7E] })),
|
||||
|
||||
('SpecialInterior', ('Kokiri Forest -> KF Links House', { 'index': 0x0272 }),
|
||||
('KF Links House -> Kokiri Forest', { 'index': 0x0211 })),
|
||||
('SpecialInterior', ('ToT Entrance -> Temple of Time', { 'index': 0x0053 }),
|
||||
('Temple of Time -> ToT Entrance', { 'index': 0x0472 })),
|
||||
('SpecialInterior', ('Kakariko Village -> Kak Windmill', { 'index': 0x0453 }),
|
||||
('Kak Windmill -> Kakariko Village', { 'index': 0x0351 })),
|
||||
('SpecialInterior', ('Kakariko Village -> Kak Potion Shop Front', { 'index': 0x0384 }),
|
||||
('Kak Potion Shop Front -> Kakariko Village', { 'index': 0x044B })),
|
||||
('SpecialInterior', ('Kak Backyard -> Kak Potion Shop Back', { 'index': 0x03EC }),
|
||||
('Kak Potion Shop Back -> Kak Backyard', { 'index': 0x04FF })),
|
||||
|
||||
('Grotto', ('Desert Colossus -> Colossus Grotto', { 'grotto_id': 0x00, 'entrance': 0x05BC, 'content': 0xFD, 'scene': 0x5C }),
|
||||
('Colossus Grotto -> Desert Colossus', { 'grotto_id': 0x00 })),
|
||||
('Grotto', ('Lake Hylia -> LH Grotto', { 'grotto_id': 0x01, 'entrance': 0x05A4, 'content': 0xEF, 'scene': 0x57 }),
|
||||
('LH Grotto -> Lake Hylia', { 'grotto_id': 0x01 })),
|
||||
('Grotto', ('Zora River -> ZR Storms Grotto', { 'grotto_id': 0x02, 'entrance': 0x05BC, 'content': 0xEB, 'scene': 0x54 }),
|
||||
('ZR Storms Grotto -> Zora River', { 'grotto_id': 0x02 })),
|
||||
('Grotto', ('Zora River -> ZR Fairy Grotto', { 'grotto_id': 0x03, 'entrance': 0x036D, 'content': 0xE6, 'scene': 0x54 }),
|
||||
('ZR Fairy Grotto -> Zora River', { 'grotto_id': 0x03 })),
|
||||
('Grotto', ('Zora River -> ZR Open Grotto', { 'grotto_id': 0x04, 'entrance': 0x003F, 'content': 0x29, 'scene': 0x54 }),
|
||||
('ZR Open Grotto -> Zora River', { 'grotto_id': 0x04 })),
|
||||
('Grotto', ('DMC Lower Nearby -> DMC Hammer Grotto', { 'grotto_id': 0x05, 'entrance': 0x05A4, 'content': 0xF9, 'scene': 0x61 }),
|
||||
('DMC Hammer Grotto -> DMC Lower Local', { 'grotto_id': 0x05 })),
|
||||
('Grotto', ('DMC Upper Nearby -> DMC Upper Grotto', { 'grotto_id': 0x06, 'entrance': 0x003F, 'content': 0x7A, 'scene': 0x61 }),
|
||||
('DMC Upper Grotto -> DMC Upper Local', { 'grotto_id': 0x06 })),
|
||||
('Grotto', ('GC Grotto Platform -> GC Grotto', { 'grotto_id': 0x07, 'entrance': 0x05A4, 'content': 0xFB, 'scene': 0x62 }),
|
||||
('GC Grotto -> GC Grotto Platform', { 'grotto_id': 0x07 })),
|
||||
('Grotto', ('Death Mountain -> DMT Storms Grotto', { 'grotto_id': 0x08, 'entrance': 0x003F, 'content': 0x57, 'scene': 0x60 }),
|
||||
('DMT Storms Grotto -> Death Mountain', { 'grotto_id': 0x08 })),
|
||||
('Grotto', ('Death Mountain Summit -> DMT Cow Grotto', { 'grotto_id': 0x09, 'entrance': 0x05FC, 'content': 0xF8, 'scene': 0x60 }),
|
||||
('DMT Cow Grotto -> Death Mountain Summit', { 'grotto_id': 0x09 })),
|
||||
('Grotto', ('Kak Backyard -> Kak Open Grotto', { 'grotto_id': 0x0A, 'entrance': 0x003F, 'content': 0x28, 'scene': 0x52 }),
|
||||
('Kak Open Grotto -> Kak Backyard', { 'grotto_id': 0x0A })),
|
||||
('Grotto', ('Kakariko Village -> Kak Redead Grotto', { 'grotto_id': 0x0B, 'entrance': 0x05A0, 'content': 0xE7, 'scene': 0x52 }),
|
||||
('Kak Redead Grotto -> Kakariko Village', { 'grotto_id': 0x0B })),
|
||||
('Grotto', ('Hyrule Castle Grounds -> HC Storms Grotto', { 'grotto_id': 0x0C, 'entrance': 0x05B8, 'content': 0xF6, 'scene': 0x5F }),
|
||||
('HC Storms Grotto -> Castle Grounds', { 'grotto_id': 0x0C })),
|
||||
('Grotto', ('Hyrule Field -> HF Tektite Grotto', { 'grotto_id': 0x0D, 'entrance': 0x05C0, 'content': 0xE1, 'scene': 0x51 }),
|
||||
('HF Tektite Grotto -> Hyrule Field', { 'grotto_id': 0x0D })),
|
||||
('Grotto', ('Hyrule Field -> HF Near Kak Grotto', { 'grotto_id': 0x0E, 'entrance': 0x0598, 'content': 0xE5, 'scene': 0x51 }),
|
||||
('HF Near Kak Grotto -> Hyrule Field', { 'grotto_id': 0x0E })),
|
||||
('Grotto', ('Hyrule Field -> HF Fairy Grotto', { 'grotto_id': 0x0F, 'entrance': 0x036D, 'content': 0xFF, 'scene': 0x51 }),
|
||||
('HF Fairy Grotto -> Hyrule Field', { 'grotto_id': 0x0F })),
|
||||
('Grotto', ('Hyrule Field -> HF Near Market Grotto', { 'grotto_id': 0x10, 'entrance': 0x003F, 'content': 0x00, 'scene': 0x51 }),
|
||||
('HF Near Market Grotto -> Hyrule Field', { 'grotto_id': 0x10 })),
|
||||
('Grotto', ('Hyrule Field -> HF Cow Grotto', { 'grotto_id': 0x11, 'entrance': 0x05A8, 'content': 0xE4, 'scene': 0x51 }),
|
||||
('HF Cow Grotto -> Hyrule Field', { 'grotto_id': 0x11 })),
|
||||
('Grotto', ('Hyrule Field -> HF Inside Fence Grotto', { 'grotto_id': 0x12, 'entrance': 0x059C, 'content': 0xE6, 'scene': 0x51 }),
|
||||
('HF Inside Fence Grotto -> Hyrule Field', { 'grotto_id': 0x12 })),
|
||||
('Grotto', ('Hyrule Field -> HF Open Grotto', { 'grotto_id': 0x13, 'entrance': 0x003F, 'content': 0x03, 'scene': 0x51 }),
|
||||
('HF Open Grotto -> Hyrule Field', { 'grotto_id': 0x13 })),
|
||||
('Grotto', ('Hyrule Field -> HF Southeast Grotto', { 'grotto_id': 0x14, 'entrance': 0x003F, 'content': 0x22, 'scene': 0x51 }),
|
||||
('HF Southeast Grotto -> Hyrule Field', { 'grotto_id': 0x14 })),
|
||||
('Grotto', ('Lon Lon Ranch -> LLR Grotto', { 'grotto_id': 0x15, 'entrance': 0x05A4, 'content': 0xFC, 'scene': 0x63 }),
|
||||
('LLR Grotto -> Lon Lon Ranch', { 'grotto_id': 0x15 })),
|
||||
('Grotto', ('SFM Entryway -> SFM Wolfos Grotto', { 'grotto_id': 0x16, 'entrance': 0x05B4, 'content': 0xED, 'scene': 0x56 }),
|
||||
('SFM Wolfos Grotto -> SFM Entryway', { 'grotto_id': 0x16 })),
|
||||
('Grotto', ('Sacred Forest Meadow -> SFM Storms Grotto', { 'grotto_id': 0x17, 'entrance': 0x05BC, 'content': 0xEE, 'scene': 0x56 }),
|
||||
('SFM Storms Grotto -> Sacred Forest Meadow', { 'grotto_id': 0x17 })),
|
||||
('Grotto', ('Sacred Forest Meadow -> SFM Fairy Grotto', { 'grotto_id': 0x18, 'entrance': 0x036D, 'content': 0xFF, 'scene': 0x56 }),
|
||||
('SFM Fairy Grotto -> Sacred Forest Meadow', { 'grotto_id': 0x18 })),
|
||||
('Grotto', ('LW Beyond Mido -> LW Scrubs Grotto', { 'grotto_id': 0x19, 'entrance': 0x05B0, 'content': 0xF5, 'scene': 0x5B }),
|
||||
('LW Scrubs Grotto -> LW Beyond Mido', { 'grotto_id': 0x19 })),
|
||||
('Grotto', ('Lost Woods -> LW Near Shortcuts Grotto', { 'grotto_id': 0x1A, 'entrance': 0x003F, 'content': 0x14, 'scene': 0x5B }),
|
||||
('LW Near Shortcuts Grotto -> Lost Woods', { 'grotto_id': 0x1A })),
|
||||
('Grotto', ('Kokiri Forest -> KF Storms Grotto', { 'grotto_id': 0x1B, 'entrance': 0x003F, 'content': 0x2C, 'scene': 0x55 }),
|
||||
('KF Storms Grotto -> Kokiri Forest', { 'grotto_id': 0x1B })),
|
||||
('Grotto', ('Zoras Domain -> ZD Storms Grotto', { 'grotto_id': 0x1C, 'entrance': 0x036D, 'content': 0xFF, 'scene': 0x58 }),
|
||||
('ZD Storms Grotto -> Zoras Domain', { 'grotto_id': 0x1C })),
|
||||
('Grotto', ('Gerudo Fortress -> GF Storms Grotto', { 'grotto_id': 0x1D, 'entrance': 0x036D, 'content': 0xFF, 'scene': 0x5D }),
|
||||
('GF Storms Grotto -> Gerudo Fortress', { 'grotto_id': 0x1D })),
|
||||
('Grotto', ('GV Fortress Side -> GV Storms Grotto', { 'grotto_id': 0x1E, 'entrance': 0x05BC, 'content': 0xF0, 'scene': 0x5A }),
|
||||
('GV Storms Grotto -> GV Fortress Side', { 'grotto_id': 0x1E })),
|
||||
('Grotto', ('GV Grotto Ledge -> GV Octorok Grotto', { 'grotto_id': 0x1F, 'entrance': 0x05AC, 'content': 0xF2, 'scene': 0x5A }),
|
||||
('GV Octorok Grotto -> GV Grotto Ledge', { 'grotto_id': 0x1F })),
|
||||
('Grotto', ('LW Beyond Mido -> Deku Theater', { 'grotto_id': 0x20, 'entrance': 0x05C4, 'content': 0xF3, 'scene': 0x5B }),
|
||||
('Deku Theater -> LW Beyond Mido', { 'grotto_id': 0x20 })),
|
||||
|
||||
('Grave', ('Graveyard -> Graveyard Shield Grave', { 'index': 0x004B }),
|
||||
('Graveyard Shield Grave -> Graveyard', { 'index': 0x035D })),
|
||||
('Grave', ('Graveyard -> Graveyard Heart Piece Grave', { 'index': 0x031C }),
|
||||
('Graveyard Heart Piece Grave -> Graveyard', { 'index': 0x0361 })),
|
||||
('Grave', ('Graveyard -> Graveyard Composers Grave', { 'index': 0x002D }),
|
||||
('Graveyard Composers Grave -> Graveyard', { 'index': 0x050B })),
|
||||
('Grave', ('Graveyard -> Graveyard Dampes Grave', { 'index': 0x044F }),
|
||||
('Graveyard Dampes Grave -> Graveyard', { 'index': 0x0359 })),
|
||||
|
||||
('Overworld', ('Kokiri Forest -> LW Bridge From Forest', { 'index': 0x05E0 }),
|
||||
('LW Bridge -> Kokiri Forest', { 'index': 0x020D })),
|
||||
('Overworld', ('Kokiri Forest -> Lost Woods', { 'index': 0x011E }),
|
||||
('LW Forest Exit -> Kokiri Forest', { 'index': 0x0286 })),
|
||||
('Overworld', ('Lost Woods -> GC Woods Warp', { 'index': 0x04E2 }),
|
||||
('GC Woods Warp -> Lost Woods', { 'index': 0x04D6 })),
|
||||
('Overworld', ('Lost Woods -> Zora River', { 'index': 0x01DD }),
|
||||
('Zora River -> Lost Woods', { 'index': 0x04DA })),
|
||||
('Overworld', ('LW Beyond Mido -> SFM Entryway', { 'index': 0x00FC }),
|
||||
('SFM Entryway -> LW Beyond Mido', { 'index': 0x01A9 })),
|
||||
('Overworld', ('LW Bridge -> Hyrule Field', { 'index': 0x0185 }),
|
||||
('Hyrule Field -> LW Bridge', { 'index': 0x04DE })),
|
||||
('Overworld', ('Hyrule Field -> Lake Hylia', { 'index': 0x0102 }),
|
||||
('Lake Hylia -> Hyrule Field', { 'index': 0x0189 })),
|
||||
('Overworld', ('Hyrule Field -> Gerudo Valley', { 'index': 0x0117 }),
|
||||
('Gerudo Valley -> Hyrule Field', { 'index': 0x018D })),
|
||||
('Overworld', ('Hyrule Field -> Market Entrance', { 'index': 0x0276 }),
|
||||
('Market Entrance -> Hyrule Field', { 'index': 0x01FD })),
|
||||
('Overworld', ('Hyrule Field -> Kakariko Village', { 'index': 0x00DB }),
|
||||
('Kakariko Village -> Hyrule Field', { 'index': 0x017D })),
|
||||
('Overworld', ('Hyrule Field -> ZR Front', { 'index': 0x00EA }),
|
||||
('ZR Front -> Hyrule Field', { 'index': 0x0181 })),
|
||||
('Overworld', ('Hyrule Field -> Lon Lon Ranch', { 'index': 0x0157 }),
|
||||
('Lon Lon Ranch -> Hyrule Field', { 'index': 0x01F9 })),
|
||||
('Overworld', ('Lake Hylia -> Zoras Domain', { 'index': 0x0328 }),
|
||||
('Zoras Domain -> Lake Hylia', { 'index': 0x0560 })),
|
||||
('Overworld', ('GV Fortress Side -> Gerudo Fortress', { 'index': 0x0129 }),
|
||||
('Gerudo Fortress -> GV Fortress Side', { 'index': 0x022D })),
|
||||
('Overworld', ('GF Outside Gate -> Wasteland Near Fortress', { 'index': 0x0130 }),
|
||||
('Wasteland Near Fortress -> GF Outside Gate', { 'index': 0x03AC })),
|
||||
('Overworld', ('Wasteland Near Colossus -> Desert Colossus', { 'index': 0x0123 }),
|
||||
('Desert Colossus -> Wasteland Near Colossus', { 'index': 0x0365 })),
|
||||
('Overworld', ('Market Entrance -> Market', { 'index': 0x00B1 }),
|
||||
('Market -> Market Entrance', { 'index': 0x0033 })),
|
||||
('Overworld', ('Market -> Castle Grounds', { 'index': 0x0138 }),
|
||||
('Castle Grounds -> Market', { 'index': 0x025A })),
|
||||
('Overworld', ('Market -> ToT Entrance', { 'index': 0x0171 }),
|
||||
('ToT Entrance -> Market', { 'index': 0x025E })),
|
||||
('Overworld', ('Kakariko Village -> Graveyard', { 'index': 0x00E4 }),
|
||||
('Graveyard -> Kakariko Village', { 'index': 0x0195 })),
|
||||
('Overworld', ('Kak Behind Gate -> Death Mountain', { 'index': 0x013D }),
|
||||
('Death Mountain -> Kak Behind Gate', { 'index': 0x0191 })),
|
||||
('Overworld', ('Death Mountain -> Goron City', { 'index': 0x014D }),
|
||||
('Goron City -> Death Mountain', { 'index': 0x01B9 })),
|
||||
('Overworld', ('GC Darunias Chamber -> DMC Lower Local', { 'index': 0x0246 }),
|
||||
('DMC Lower Nearby -> GC Darunias Chamber', { 'index': 0x01C1 })),
|
||||
('Overworld', ('Death Mountain Summit -> DMC Upper Local', { 'index': 0x0147 }),
|
||||
('DMC Upper Nearby -> Death Mountain Summit', { 'index': 0x01BD })),
|
||||
('Overworld', ('ZR Behind Waterfall -> Zoras Domain', { 'index': 0x0108 }),
|
||||
('Zoras Domain -> ZR Behind Waterfall', { 'index': 0x019D })),
|
||||
('Overworld', ('ZD Behind King Zora -> Zoras Fountain', { 'index': 0x0225 }),
|
||||
('Zoras Fountain -> ZD Behind King Zora', { 'index': 0x01A1 })),
|
||||
|
||||
('OwlDrop', ('LH Owl Flight -> Hyrule Field', { 'index': 0x027E, 'addresses': [0xAC9F26] })),
|
||||
('OwlDrop', ('DMT Owl Flight -> Kak Impas Rooftop', { 'index': 0x0554, 'addresses': [0xAC9EF2] })),
|
||||
|
||||
('Spawn', ('Child Spawn -> KF Links House', { 'index': 0x00BB, 'addresses': [0xB06342] })),
|
||||
('Spawn', ('Adult Spawn -> Temple of Time', { 'index': 0x05F4, 'addresses': [0xB06332] })),
|
||||
|
||||
('WarpSong', ('Minuet of Forest Warp -> Sacred Forest Meadow', { 'index': 0x0600, 'addresses': [0xBF023C] })),
|
||||
('WarpSong', ('Bolero of Fire Warp -> DMC Central Local', { 'index': 0x04F6, 'addresses': [0xBF023E] })),
|
||||
('WarpSong', ('Serenade of Water Warp -> Lake Hylia', { 'index': 0x0604, 'addresses': [0xBF0240] })),
|
||||
('WarpSong', ('Requiem of Spirit Warp -> Desert Colossus', { 'index': 0x01F1, 'addresses': [0xBF0242] })),
|
||||
('WarpSong', ('Nocturne of Shadow Warp -> Graveyard Warp Pad Region', { 'index': 0x0568, 'addresses': [0xBF0244] })),
|
||||
('WarpSong', ('Prelude of Light Warp -> Temple of Time', { 'index': 0x05F4, 'addresses': [0xBF0246] })),
|
||||
|
||||
('Extra', ('ZD Eyeball Frog Timeout -> Zoras Domain', { 'index': 0x0153 })),
|
||||
('Extra', ('ZR Top of Waterfall -> Zora River', { 'index': 0x0199 })),
|
||||
]
|
||||
|
||||
|
||||
# Basically, the entrances in the list above that go to:
|
||||
# - DMC Central Local (child access for the bean and skull)
|
||||
# - Desert Colossus (child access to colossus and spirit)
|
||||
# - Graveyard Warp Pad Region (access to shadow, plus the gossip stone)
|
||||
# We will always need to pick one from each list to receive a one-way entrance
|
||||
# if shuffling warp songs (depending on other settings).
|
||||
# Table maps: short key -> ([target regions], [allowed types])
|
||||
priority_entrance_table = {
|
||||
'Bolero': (['DMC Central Local'], ['OwlDrop', 'WarpSong']),
|
||||
'Nocturne': (['Graveyard Warp Pad Region'], ['OwlDrop', 'Spawn', 'WarpSong']),
|
||||
'Requiem': (['Desert Colossus', 'Desert Colossus From Spirit Lobby'], ['OwlDrop', 'Spawn', 'WarpSong']),
|
||||
}
|
||||
|
||||
|
||||
class EntranceShuffleError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def shuffle_random_entrances(ootworld):
|
||||
world = ootworld.world
|
||||
player = ootworld.player
|
||||
|
||||
# Gather locations to keep reachable for validation
|
||||
all_state = world.get_all_state(use_cache=True)
|
||||
locations_to_ensure_reachable = {loc for loc in world.get_reachable_locations(all_state, player) if not (loc.type == 'Drop' or (loc.type == 'Event' and 'Subrule' in loc.name))}
|
||||
|
||||
# Set entrance data for all entrances
|
||||
set_all_entrances_data(world, player)
|
||||
|
||||
# Determine entrance pools based on settings
|
||||
one_way_entrance_pools = {}
|
||||
entrance_pools = {}
|
||||
one_way_priorities = {}
|
||||
|
||||
if ootworld.owl_drops:
|
||||
one_way_entrance_pools['OwlDrop'] = ootworld.get_shufflable_entrances(type='OwlDrop')
|
||||
if ootworld.spawn_positions:
|
||||
one_way_entrance_pools['Spawn'] = ootworld.get_shufflable_entrances(type='Spawn')
|
||||
if ootworld.warp_songs:
|
||||
one_way_entrance_pools['WarpSong'] = ootworld.get_shufflable_entrances(type='WarpSong')
|
||||
if world.accessibility[player].current_key != 'minimal' and ootworld.logic_rules == 'glitchless':
|
||||
one_way_priorities['Bolero'] = priority_entrance_table['Bolero']
|
||||
one_way_priorities['Nocturne'] = priority_entrance_table['Nocturne']
|
||||
if not ootworld.shuffle_dungeon_entrances and not ootworld.shuffle_overworld_entrances:
|
||||
one_way_priorities['Requiem'] = priority_entrance_table['Requiem']
|
||||
|
||||
if ootworld.shuffle_dungeon_entrances:
|
||||
entrance_pools['Dungeon'] = ootworld.get_shufflable_entrances(type='Dungeon', only_primary=True)
|
||||
if ootworld.open_forest == 'closed':
|
||||
entrance_pools['Dungeon'].remove(world.get_entrance('KF Outside Deku Tree -> Deku Tree Lobby', player))
|
||||
if ootworld.shuffle_interior_entrances != 'off':
|
||||
entrance_pools['Interior'] = ootworld.get_shufflable_entrances(type='Interior', only_primary=True)
|
||||
if ootworld.shuffle_special_interior_entrances:
|
||||
entrance_pools['Interior'] += ootworld.get_shufflable_entrances(type='SpecialInterior', only_primary=True)
|
||||
if ootworld.shuffle_grotto_entrances:
|
||||
entrance_pools['GrottoGrave'] = ootworld.get_shufflable_entrances(type='Grotto', only_primary=True)
|
||||
entrance_pools['GrottoGrave'] += ootworld.get_shufflable_entrances(type='Grave', only_primary=True)
|
||||
if ootworld.shuffle_overworld_entrances:
|
||||
entrance_pools['Overworld'] = ootworld.get_shufflable_entrances(type='Overworld')
|
||||
|
||||
# Mark shuffled entrances
|
||||
for entrance in chain(chain.from_iterable(one_way_entrance_pools.values()), chain.from_iterable(entrance_pools.values())):
|
||||
entrance.shuffled = True
|
||||
if entrance.reverse:
|
||||
entrance.reverse.shuffled = True
|
||||
|
||||
# Build target entrance pools
|
||||
one_way_target_entrance_pools = {}
|
||||
for pool_type, entrance_pool in one_way_entrance_pools.items():
|
||||
if pool_type == 'OwlDrop':
|
||||
valid_target_types = ('WarpSong', 'OwlDrop', 'Overworld', 'Extra')
|
||||
one_way_target_entrance_pools[pool_type] = build_one_way_targets(ootworld, valid_target_types, exclude=['Prelude of Light Warp -> Temple of Time'])
|
||||
for target in one_way_target_entrance_pools[pool_type]:
|
||||
set_rule(target, lambda state: state._oot_reach_as_age(target.parent_region, 'child', player))
|
||||
elif pool_type in {'Spawn', 'WarpSong'}:
|
||||
valid_target_types = ('Spawn', 'WarpSong', 'OwlDrop', 'Overworld', 'Interior', 'SpecialInterior', 'Extra')
|
||||
one_way_target_entrance_pools[pool_type] = build_one_way_targets(ootworld, valid_target_types)
|
||||
# Ensure that the last entrance doesn't assume the rest of the targets are reachable?
|
||||
# Disconnect one-way entrances for priority placement
|
||||
for entrance in chain.from_iterable(one_way_entrance_pools.values()):
|
||||
entrance.disconnect()
|
||||
|
||||
target_entrance_pools = {}
|
||||
for pool_type, entrance_pool in entrance_pools.items():
|
||||
target_entrance_pools[pool_type] = assume_entrance_pool(entrance_pool, ootworld)
|
||||
|
||||
# Build all_state and none_state
|
||||
all_state = ootworld.get_state_with_complete_itempool()
|
||||
none_state = all_state.copy()
|
||||
for item_tuple in none_state.prog_items:
|
||||
if item_tuple[1] == player:
|
||||
none_state.prog_items[item_tuple] = 0
|
||||
|
||||
# Plando entrances?
|
||||
|
||||
# Place priority entrances
|
||||
shuffle_one_way_priority_entrances(ootworld, one_way_priorities, one_way_entrance_pools, one_way_target_entrance_pools, locations_to_ensure_reachable, all_state, none_state, retry_count=2)
|
||||
|
||||
# Delete priority targets from one-way pools
|
||||
replaced_entrances = [entrance.replaces for entrance in chain.from_iterable(one_way_entrance_pools.values())]
|
||||
for remaining_target in chain.from_iterable(one_way_target_entrance_pools.values()):
|
||||
if remaining_target.replaces in replaced_entrances:
|
||||
delete_target_entrance(remaining_target)
|
||||
|
||||
for pool_type, entrance_pool in one_way_entrance_pools.items():
|
||||
shuffle_entrance_pool(ootworld, entrance_pool, one_way_target_entrance_pools[pool_type], locations_to_ensure_reachable, all_state, none_state, check_all=True, retry_count=5)
|
||||
replaced_entrances = [entrance.replaces for entrance in entrance_pool]
|
||||
for remaining_target in chain.from_iterable(one_way_target_entrance_pools.values()):
|
||||
if remaining_target.replaces in replaced_entrances:
|
||||
delete_target_entrance(remaining_target)
|
||||
for unused_target in one_way_target_entrance_pools[pool_type]:
|
||||
delete_target_entrance(unused_target)
|
||||
|
||||
# Shuffle all entrance pools, in order
|
||||
for pool_type, entrance_pool in entrance_pools.items():
|
||||
shuffle_entrance_pool(ootworld, entrance_pool, target_entrance_pools[pool_type], locations_to_ensure_reachable, all_state, none_state)
|
||||
|
||||
# Verification steps:
|
||||
# All entrances are properly connected to a region
|
||||
# Multiple checks after shuffling to ensure everything is OK
|
||||
# Check that all entrances hook up correctly
|
||||
for entrance in ootworld.get_shuffled_entrances():
|
||||
if entrance.connected_region == None:
|
||||
logging.getLogger('').error(f'{entrance} was shuffled but is not connected to any region')
|
||||
if entrance.replaces == None:
|
||||
logging.getLogger('').error(f'{entrance} was shuffled but does not replace any entrance')
|
||||
if len(ootworld.get_region('Root Exits').exits) > 8:
|
||||
for exit in ootworld.get_region('Root Exits').exits:
|
||||
logging.getLogger('').error(f'Root Exit: {exit} -> {exit.connected_region}')
|
||||
logging.getLogger('').error(f'Root has too many entrances left after shuffling entrances')
|
||||
# Game is beatable
|
||||
new_all_state = world.get_all_state(use_cache=False)
|
||||
if not world.has_beaten_game(new_all_state, player):
|
||||
raise EntranceShuffleError('Cannot beat game')
|
||||
# Validate world
|
||||
validate_world(ootworld, None, locations_to_ensure_reachable, all_state, none_state)
|
||||
|
||||
|
||||
def replace_entrance(ootworld, entrance, target, rollbacks, locations_to_ensure_reachable, all_state, none_state):
|
||||
try:
|
||||
check_entrances_compatibility(entrance, target, rollbacks)
|
||||
change_connections(entrance, target)
|
||||
validate_world(ootworld, entrance, locations_to_ensure_reachable, all_state, none_state)
|
||||
rollbacks.append((entrance, target))
|
||||
return True
|
||||
except EntranceShuffleError as e:
|
||||
logging.getLogger('').debug(f'Failed to connect {entrance} to {target}, reason: {e}')
|
||||
if entrance.connected_region:
|
||||
restore_connections(entrance, target)
|
||||
return False
|
||||
|
||||
|
||||
def shuffle_one_way_priority_entrances(ootworld, one_way_priorities, one_way_entrance_pools, one_way_target_entrance_pools,
|
||||
locations_to_ensure_reachable, all_state, none_state, retry_count=2):
|
||||
|
||||
ootworld.priority_entrances = []
|
||||
|
||||
while retry_count:
|
||||
retry_count -= 1
|
||||
rollbacks = []
|
||||
|
||||
try:
|
||||
for key, (regions, types) in one_way_priorities.items():
|
||||
place_one_way_priority_entrance(ootworld, key, regions, types, rollbacks, locations_to_ensure_reachable,
|
||||
all_state, none_state, one_way_entrance_pools, one_way_target_entrance_pools)
|
||||
for entrance, target in rollbacks:
|
||||
confirm_replacement(entrance, target)
|
||||
return
|
||||
except EntranceShuffleError as error:
|
||||
for entrance, target in rollbacks:
|
||||
restore_connections(entrance, target)
|
||||
logging.getLogger('').debug(f'Failed to place all priority one-way entrances, retrying {retry_count} more times')
|
||||
|
||||
raise EntranceShuffleError(f'Priority one-way entrance placement attempt count exceeded for world {ootworld.player}')
|
||||
|
||||
def place_one_way_priority_entrance(ootworld, priority_name, allowed_regions, allowed_types, rollbacks, locations_to_ensure_reachable,
|
||||
all_state, none_state, one_way_entrance_pools, one_way_target_entrance_pools):
|
||||
|
||||
avail_pool = list(chain.from_iterable(one_way_entrance_pools[t] for t in allowed_types if t in one_way_entrance_pools))
|
||||
ootworld.world.random.shuffle(avail_pool)
|
||||
|
||||
for entrance in avail_pool:
|
||||
if entrance.replaces:
|
||||
continue
|
||||
if entrance.parent_region.name == 'Adult Spawn' and (priority_name != 'Nocturne' or ootworld.hints == 'mask'):
|
||||
continue
|
||||
if not ootworld.shuffle_dungeon_entrances and priority_name == 'Nocturne':
|
||||
if entrance.type != 'WarpSong' and entrance.parent_region.name != 'Adult Spawn':
|
||||
continue
|
||||
for target in one_way_target_entrance_pools[entrance.type]:
|
||||
if target.connected_region and target.connected_region.name in allowed_regions:
|
||||
if replace_entrance(ootworld, entrance, target, rollbacks, locations_to_ensure_reachable, all_state, none_state):
|
||||
logging.getLogger('').debug(f'Priority placing {entrance} as {target} for {priority_name}')
|
||||
ootworld.priority_entrances.append(entrance)
|
||||
return
|
||||
raise EntranceShuffleError(f'Unable to place priority one-way entrance for {priority_name} in world {ootworld.player}')
|
||||
|
||||
|
||||
def shuffle_entrance_pool(ootworld, entrance_pool, target_entrances, locations_to_ensure_reachable, all_state, none_state, check_all=False, retry_count=20):
|
||||
|
||||
restrictive_entrances, soft_entrances = split_entrances_by_requirements(ootworld, entrance_pool, target_entrances)
|
||||
|
||||
while retry_count:
|
||||
retry_count -= 1
|
||||
rollbacks = []
|
||||
try:
|
||||
shuffle_entrances(ootworld, restrictive_entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state)
|
||||
if check_all:
|
||||
shuffle_entrances(ootworld, soft_entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state)
|
||||
else:
|
||||
shuffle_entrances(ootworld, soft_entrances, target_entrances, rollbacks, set(), all_state, none_state)
|
||||
|
||||
validate_world(ootworld, None, locations_to_ensure_reachable, all_state, none_state)
|
||||
for entrance, target in rollbacks:
|
||||
confirm_replacement(entrance, target)
|
||||
return
|
||||
except EntranceShuffleError as e:
|
||||
for entrance, target in rollbacks:
|
||||
restore_connections(entrance, target)
|
||||
logging.getLogger('').debug(f'Failed to place all entrances in pool, retrying {retry_count} more times')
|
||||
|
||||
raise EntranceShuffleError(f'Entrance placement attempt count exceeded for world {ootworld.player}')
|
||||
|
||||
def shuffle_entrances(ootworld, entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state):
|
||||
ootworld.world.random.shuffle(entrances)
|
||||
for entrance in entrances:
|
||||
if entrance.connected_region != None:
|
||||
continue
|
||||
ootworld.world.random.shuffle(target_entrances)
|
||||
for target in target_entrances:
|
||||
if target.connected_region == None:
|
||||
continue
|
||||
if replace_entrance(ootworld, entrance, target, rollbacks, locations_to_ensure_reachable, all_state, none_state):
|
||||
break
|
||||
if entrance.connected_region == None:
|
||||
raise EntranceShuffleError('No more valid entrances')
|
||||
|
||||
|
||||
def split_entrances_by_requirements(ootworld, entrances_to_split, assumed_entrances):
|
||||
world = ootworld.world
|
||||
player = ootworld.player
|
||||
|
||||
# Disconnect all root assumed entrances and save original connections
|
||||
original_connected_regions = {}
|
||||
entrances_to_disconnect = set(assumed_entrances).union(entrance.reverse for entrance in assumed_entrances if entrance.reverse)
|
||||
for entrance in entrances_to_disconnect:
|
||||
if entrance.connected_region:
|
||||
original_connected_regions[entrance] = entrance.disconnect()
|
||||
|
||||
all_state = world.get_all_state(use_cache=False)
|
||||
|
||||
restrictive_entrances = []
|
||||
soft_entrances = []
|
||||
|
||||
for entrance in entrances_to_split:
|
||||
all_state.age[player] = 'child'
|
||||
if not all_state.can_reach(entrance, 'Entrance', player):
|
||||
restrictive_entrances.append(entrance)
|
||||
continue
|
||||
all_state.age[player] = 'adult'
|
||||
if not all_state.can_reach(entrance, 'Entrance', player):
|
||||
restrictive_entrances.append(entrance)
|
||||
continue
|
||||
all_state.age[player] = None
|
||||
if not all_state._oot_reach_at_time(entrance.parent_region.name, TimeOfDay.ALL, [], player):
|
||||
restrictive_entrances.append(entrance)
|
||||
continue
|
||||
soft_entrances.append(entrance)
|
||||
|
||||
# Reconnect assumed entrances
|
||||
for entrance in entrances_to_disconnect:
|
||||
if entrance in original_connected_regions:
|
||||
entrance.connect(original_connected_regions[entrance])
|
||||
|
||||
return restrictive_entrances, soft_entrances
|
||||
|
||||
|
||||
# Check to ensure the world is valid.
|
||||
# TODO: improve this function
|
||||
def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all_state_orig, none_state_orig):
|
||||
|
||||
world = ootworld.world
|
||||
player = ootworld.player
|
||||
|
||||
all_state = all_state_orig.copy()
|
||||
none_state = none_state_orig.copy()
|
||||
|
||||
all_state.sweep_for_events()
|
||||
none_state.sweep_for_events()
|
||||
|
||||
if ootworld.shuffle_interior_entrances or ootworld.shuffle_overworld_entrances or ootworld.spawn_positions:
|
||||
time_travel_state = none_state.copy()
|
||||
time_travel_state.collect(ootworld.create_item('Time Travel'), event=True)
|
||||
time_travel_state._oot_update_age_reachable_regions(player)
|
||||
|
||||
# For various reasons, we don't want the player to end up through certain entrances as the wrong age
|
||||
# This means we need to hard check that none of the relevant entrances are ever reachable as that age
|
||||
# This is mostly relevant when shuffling special interiors (such as windmill or kak potion shop)
|
||||
# Warp Songs and Overworld Spawns can also end up inside certain indoors so those need to be handled as well
|
||||
CHILD_FORBIDDEN = ['OGC Great Fairy Fountain -> Castle Grounds', 'GV Carpenter Tent -> GV Fortress Side']
|
||||
ADULT_FORBIDDEN = ['HC Great Fairy Fountain -> Castle Grounds', 'HC Storms Grotto -> Castle Grounds']
|
||||
|
||||
for entrance in ootworld.get_shufflable_entrances():
|
||||
if entrance.shuffled and entrance.replaces:
|
||||
if entrance.replaces.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.replaces.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential child access')
|
||||
if entrance.replaces.name in ADULT_FORBIDDEN and not entrance_unreachable_as(entrance, 'adult', already_checked=[entrance.replaces.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential adult access')
|
||||
else:
|
||||
if entrance.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.name} potentially accessible as child')
|
||||
if entrance.name in ADULT_FORBIDDEN and not entrance_unreachable_as(entrance, 'adult', already_checked=[entrance.reverse]):
|
||||
raise EntranceShuffleError(f'{entrance.name} potentially accessible as adult')
|
||||
|
||||
# Check if all locations are reachable if not beatable-only or game is not yet complete
|
||||
if locations_to_ensure_reachable:
|
||||
if world.accessibility[player].current_key != 'minimal' or not world.can_beat_game(all_state):
|
||||
for loc in locations_to_ensure_reachable:
|
||||
if not all_state.can_reach(loc, 'Location', player):
|
||||
raise EntranceShuffleError(f'{loc} is unreachable')
|
||||
|
||||
if ootworld.shuffle_interior_entrances and (entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior']):
|
||||
# Ensure Kak Potion Shop entrances are in the same hint area so there is no ambiguity as to which entrance is used for hints
|
||||
potion_front_entrance = get_entrance_replacing(world.get_region('Kak Potion Shop Front', player), 'Kakariko Village -> Kak Potion Shop Front', player)
|
||||
potion_back_entrance = get_entrance_replacing(world.get_region('Kak Potion Shop Back', player), 'Kak Backyard -> Kak Potion Shop Back', player)
|
||||
if potion_front_entrance is not None and potion_back_entrance is not None and not same_hint_area(potion_front_entrance, potion_back_entrance):
|
||||
raise EntranceShuffleError('Kak Potion Shop entrances are not in the same hint area')
|
||||
|
||||
# When cows are shuffled, ensure the same thing for Impa's House, since the cow is reachable from both sides
|
||||
if ootworld.shuffle_cows:
|
||||
impas_front_entrance = get_entrance_replacing(world.get_region('Kak Impas House', player), 'Kakariko Village -> Kak Impas House', player)
|
||||
impas_back_entrance = get_entrance_replacing(world.get_region('Kak Impas House Back', player), 'Kak Impas Ledge -> Kak Impas House Back', player)
|
||||
if impas_front_entrance is not None and impas_back_entrance is not None and not same_hint_area(impas_front_entrance, impas_back_entrance):
|
||||
raise EntranceShuffleError('Kak Impas House entrances are not in the same hint area')
|
||||
|
||||
# Check basic refills, time passing, return to ToT
|
||||
if (ootworld.shuffle_special_interior_entrances or ootworld.shuffle_overworld_entrances or ootworld.spawn_positions) and \
|
||||
(entrance_placed == None or entrance_placed.type in ['SpecialInterior', 'Overworld', 'Spawn', 'WarpSong', 'OwlDrop']):
|
||||
|
||||
valid_starting_regions = {'Kokiri Forest', 'Kakariko Village'}
|
||||
if not any(region for region in valid_starting_regions if none_state.can_reach(region, 'Region', player)):
|
||||
raise EntranceShuffleError('Invalid starting area')
|
||||
|
||||
if not (any(region for region in time_travel_state.child_reachable_regions[player] if region.time_passes) and
|
||||
any(region for region in time_travel_state.adult_reachable_regions[player] if region.time_passes)):
|
||||
raise EntranceShuffleError('Time passing is not guaranteed as both ages')
|
||||
|
||||
if ootworld.starting_age == 'child' and (world.get_region('Temple of Time', player) not in time_travel_state.adult_reachable_regions[player]):
|
||||
raise EntranceShuffleError('Path to ToT as adult not guaranteed')
|
||||
if ootworld.starting_age == 'adult' and (world.get_region('Temple of Time', player) not in time_travel_state.child_reachable_regions[player]):
|
||||
raise EntranceShuffleError('Path to ToT as child not guaranteed')
|
||||
|
||||
if (ootworld.shuffle_interior_entrances or ootworld.shuffle_overworld_entrances) and \
|
||||
(entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior', 'Overworld', 'Spawn', 'WarpSong', 'OwlDrop']):
|
||||
# Ensure big poe shop is always reachable as adult
|
||||
if world.get_region('Market Guard House', player) not in time_travel_state.adult_reachable_regions[player]:
|
||||
raise EntranceShuffleError('Big Poe Shop access not guaranteed as adult')
|
||||
if ootworld.shopsanity == 'off':
|
||||
# Ensure that Goron and Zora shops are accessible as adult
|
||||
if world.get_region('GC Shop', player) not in all_state.adult_reachable_regions[player]:
|
||||
raise EntranceShuffleError('Goron City Shop not accessible as adult')
|
||||
if world.get_region('ZD Shop', player) not in all_state.adult_reachable_regions[player]:
|
||||
raise EntranceShuffleError('Zora\'s Domain Shop not accessible as adult')
|
||||
|
||||
|
||||
|
||||
# Recursively check if a given entrance is unreachable as a given age
|
||||
def entrance_unreachable_as(entrance, age, already_checked=[]):
|
||||
already_checked.append(entrance)
|
||||
|
||||
if entrance.type in {'WarpSong', 'Overworld'}:
|
||||
return False
|
||||
elif entrance.type == 'OwlDrop':
|
||||
return age == 'adult'
|
||||
elif entrance.name == 'Child Spawn -> KF Links House':
|
||||
return age == 'adult'
|
||||
elif entrance.name == 'Adult Spawn -> Temple of Time':
|
||||
return age == 'child'
|
||||
|
||||
for parent_entrance in entrance.parent_region.entrances:
|
||||
if parent_entrance in already_checked:
|
||||
continue
|
||||
unreachable = entrance_unreachable_as(parent_entrance, age, already_checked)
|
||||
if not unreachable:
|
||||
return False
|
||||
return True
|
||||
|
||||
def same_hint_area(first, second):
|
||||
try:
|
||||
return get_hint_area(first) == get_hint_area(second)
|
||||
except HintAreaNotFound:
|
||||
return False
|
||||
|
||||
def get_entrance_replacing(region, entrance_name, player):
|
||||
original_entrance = region.world.get_entrance(entrance_name, player)
|
||||
if not original_entrance.shuffled:
|
||||
return original_entrance
|
||||
|
||||
try:
|
||||
return next(filter(lambda entrance: entrance.replaces and entrance.replaces.name == entrance_name and \
|
||||
entrance.parent_region and entrance.parent_region.name != 'Root Exits' and \
|
||||
entrance.type not in ('OwlDrop', 'Spawn', 'WarpSong') and entrance.player == player,
|
||||
region.entrances))
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def change_connections(entrance, target):
|
||||
entrance.connect(target.disconnect())
|
||||
entrance.replaces = target.replaces
|
||||
if entrance.reverse:
|
||||
target.replaces.reverse.connect(entrance.reverse.assumed.disconnect())
|
||||
target.replaces.reverse.replaces = entrance.reverse
|
||||
|
||||
def restore_connections(entrance, target):
|
||||
target.connect(entrance.disconnect())
|
||||
entrance.replaces = None
|
||||
if entrance.reverse:
|
||||
entrance.reverse.assumed.connect(target.replaces.reverse.disconnect())
|
||||
target.replaces.reverse.replaces = None
|
||||
|
||||
def check_entrances_compatibility(entrance, target, rollbacks):
|
||||
# An entrance shouldn't be connected to its own scene
|
||||
if entrance.parent_region.get_scene() and entrance.parent_region.get_scene() == target.connected_region.get_scene():
|
||||
raise EntranceShuffleError('Self-scene connections are forbidden')
|
||||
|
||||
# One-way entrances shouldn't lead to the same scene as other one-ways
|
||||
if entrance.type in {'OwlDrop', 'Spawn', 'WarpSong'} and \
|
||||
any([rollback[0].connected_region.get_scene() == target.connected_region.get_scene() for rollback in rollbacks]):
|
||||
raise EntranceShuffleError('Another one-way entrance leads to the same scene')
|
||||
|
||||
def confirm_replacement(entrance, target):
|
||||
delete_target_entrance(target)
|
||||
logging.getLogger('').debug(f'Connected {entrance} to {entrance.connected_region}')
|
||||
if entrance.reverse:
|
||||
replaced_reverse = target.replaces.reverse
|
||||
delete_target_entrance(entrance.reverse.assumed)
|
||||
logging.getLogger('').debug(f'Connected {replaced_reverse} to {replaced_reverse.connected_region}')
|
||||
|
||||
|
||||
def delete_target_entrance(target):
|
||||
if target.connected_region != None:
|
||||
target.disconnect()
|
||||
if target.parent_region != None:
|
||||
target.parent_region.exits.remove(target)
|
||||
target.parent_region = None
|
||||
|
||||
@@ -397,6 +397,8 @@ def get_barren_hint(world, checked):
|
||||
return None
|
||||
|
||||
area_weights = [world.empty_areas[area]['weight'] for area in areas]
|
||||
if not any(area_weights):
|
||||
return None
|
||||
|
||||
area = world.hint_rng.choices(areas, weights=area_weights)[0]
|
||||
if world.empty_areas[area]['dungeon']:
|
||||
@@ -637,8 +639,6 @@ hint_dist_keys = {
|
||||
|
||||
# builds out general hints based on location and whether an item is required or not
|
||||
def buildWorldGossipHints(world, checkedLocations=None):
|
||||
# Seed the RNG
|
||||
world.hint_rng = world.world.slot_seeds[world.player]
|
||||
|
||||
# rebuild hint exclusion list
|
||||
hintExclusions(world, clear_cache=True)
|
||||
|
||||
@@ -727,6 +727,14 @@ known_logic_tricks = {
|
||||
To kill it, the logic normally guarantees one of
|
||||
Hookshot, Bow, or Magic.
|
||||
'''},
|
||||
'Skip King Zora as Adult with Nothing': {
|
||||
'name' : 'logic_king_zora_skip',
|
||||
'tags' : ("Zora's Domain",),
|
||||
'tooltip' : '''\
|
||||
With a precise jump as adult, it is possible to
|
||||
get on the fence next to King Zora from the front
|
||||
to access Zora's Fountain.
|
||||
'''},
|
||||
'Shadow Temple River Statue with Bombchu': {
|
||||
'name' : 'logic_shadow_statue',
|
||||
'tags' : ("Shadow Temple",),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import typing
|
||||
from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionList
|
||||
from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionList, DeathLink
|
||||
from .ColorSFXOptions import *
|
||||
|
||||
|
||||
@@ -94,12 +94,37 @@ class StartingAge(Choice):
|
||||
option_adult = 1
|
||||
|
||||
|
||||
# TODO: document and name ER options
|
||||
class InteriorEntrances(Choice):
|
||||
"""Shuffles interior entrances. "Simple" shuffles houses and Great Fairies; "All" includes Windmill, Link's House, Temple of Time, and Kak potion shop."""
|
||||
option_off = 0
|
||||
option_simple = 1
|
||||
option_all = 2
|
||||
alias_false = 0
|
||||
alias_true = 2
|
||||
|
||||
|
||||
class GrottoEntrances(Toggle):
|
||||
"""Shuffles grotto and grave entrances."""
|
||||
|
||||
|
||||
class DungeonEntrances(Toggle):
|
||||
"""Shuffles dungeon entrances, excluding Ganon's Castle. Opens Deku, Fire and BotW to both ages."""
|
||||
|
||||
|
||||
class OverworldEntrances(Toggle):
|
||||
"""Shuffles overworld loading zones."""
|
||||
|
||||
|
||||
class OwlDrops(Toggle):
|
||||
"""Randomizes owl drops from Lake Hylia or Death Mountain Trail as child."""
|
||||
|
||||
|
||||
class WarpSongs(Toggle):
|
||||
"""Randomizes warp song destinations."""
|
||||
|
||||
|
||||
class SpawnPositions(Toggle):
|
||||
"""Randomizes the starting position on loading a save. Consistent between savewarps."""
|
||||
|
||||
|
||||
class TriforceHunt(Toggle):
|
||||
@@ -138,13 +163,13 @@ class MQDungeons(Range):
|
||||
|
||||
world_options: typing.Dict[str, type(Option)] = {
|
||||
"starting_age": StartingAge,
|
||||
# "shuffle_interior_entrances": InteriorEntrances,
|
||||
# "shuffle_grotto_entrances": Toggle,
|
||||
# "shuffle_dungeon_entrances": Toggle,
|
||||
# "shuffle_overworld_entrances": Toggle,
|
||||
# "owl_drops": Toggle,
|
||||
# "warp_songs": Toggle,
|
||||
# "spawn_positions": Toggle,
|
||||
"shuffle_interior_entrances": InteriorEntrances,
|
||||
"shuffle_grotto_entrances": GrottoEntrances,
|
||||
"shuffle_dungeon_entrances": DungeonEntrances,
|
||||
"shuffle_overworld_entrances": OverworldEntrances,
|
||||
"owl_drops": OwlDrops,
|
||||
"warp_songs": WarpSongs,
|
||||
"spawn_positions": SpawnPositions,
|
||||
"triforce_hunt": TriforceHunt,
|
||||
"triforce_goal": TriforceGoal,
|
||||
"extra_triforce_percentage": ExtraTriforces,
|
||||
@@ -765,6 +790,13 @@ sfx_options: typing.Dict[str, type(Option)] = {
|
||||
}
|
||||
|
||||
|
||||
class LogicTricks(OptionList):
|
||||
"""Set various tricks for logic in Ocarina of Time.
|
||||
Format as a comma-separated list of "nice" names: ["Fewer Tunic Requirements", "Hidden Grottos without Stone of Agony"].
|
||||
A full list of supported tricks can be found at https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/oot/LogicTricks.py"""
|
||||
displayname = "Logic Tricks"
|
||||
|
||||
|
||||
# All options assembled into a single dict
|
||||
oot_options: typing.Dict[str, type(Option)] = {
|
||||
"logic_rules": Logic,
|
||||
@@ -780,5 +812,6 @@ oot_options: typing.Dict[str, type(Option)] = {
|
||||
**itempool_options,
|
||||
**cosmetic_options,
|
||||
**sfx_options,
|
||||
"logic_tricks": OptionList,
|
||||
"logic_tricks": LogicTricks,
|
||||
"death_link": DeathLink,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import itertools
|
||||
import re
|
||||
import zlib
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
|
||||
from .LocationList import business_scrubs
|
||||
from .Hints import writeGossipStoneHints, buildAltarHints, \
|
||||
@@ -1321,9 +1322,12 @@ def patch_rom(world, rom):
|
||||
# Write item overrides
|
||||
override_table = get_override_table(world)
|
||||
rom.write_bytes(rom.sym('cfg_item_overrides'), get_override_table_bytes(override_table))
|
||||
rom.write_byte(rom.sym('PLAYER_ID'), world.player) # Write player ID
|
||||
rom.write_byte(rom.sym('PLAYER_ID'), min(world.player, 255)) # Write player ID
|
||||
rom.write_bytes(rom.sym('AP_PLAYER_NAME'), bytearray(world.world.get_player_name(world.player), 'ascii'))
|
||||
|
||||
if world.death_link:
|
||||
rom.write_byte(rom.sym('DEATH_LINK'), 0x01)
|
||||
|
||||
# Revert Song Get Override Injection
|
||||
if not songs_as_items:
|
||||
# general get song
|
||||
@@ -1804,7 +1808,7 @@ def write_rom_item(rom, item_id, item):
|
||||
|
||||
|
||||
def get_override_table(world):
|
||||
return list(filter(lambda val: val != None, map(get_override_entry, world.world.get_filled_locations(world.player))))
|
||||
return list(filter(lambda val: val != None, map(partial(get_override_entry, world.player), world.world.get_filled_locations(world.player))))
|
||||
|
||||
|
||||
override_struct = struct.Struct('>xBBBHBB') # match override_t in get_items.c
|
||||
@@ -1812,10 +1816,10 @@ def get_override_table_bytes(override_table):
|
||||
return b''.join(sorted(itertools.starmap(override_struct.pack, override_table)))
|
||||
|
||||
|
||||
def get_override_entry(location):
|
||||
def get_override_entry(player_id, location):
|
||||
scene = location.scene
|
||||
default = location.default
|
||||
player_id = location.item.player
|
||||
player_id = 0 if player_id == 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.
|
||||
looks_like_item_id = 0
|
||||
|
||||
@@ -451,14 +451,16 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
if self.world.ensure_tod_access:
|
||||
# tod has DAY or (tod == NONE and (ss or find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
return ast.parse("(tod & TimeOfDay.DAY) if tod else (state.has_all(('Ocarina', 'Suns Song')) or state.search.can_reach(spot.parent_region, age=age, tod=TimeOfDay.DAY))", mode='eval').body
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
return ast.parse(f"(state.has('Ocarina', player) and state.has('Suns Song', player)) or state._oot_reach_at_time('{r.name}', TimeOfDay.DAY, [], player)", mode='eval').body
|
||||
return ast.NameConstant(True)
|
||||
|
||||
def at_dampe_time(self, node):
|
||||
if self.world.ensure_tod_access:
|
||||
# tod has DAMPE or (tod == NONE and (find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
return ast.parse("(tod & TimeOfDay.DAMPE) if tod else state.search.can_reach(spot.parent_region, age=age, tod=TimeOfDay.DAMPE)", mode='eval').body
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
return ast.parse(f"state._oot_reach_at_time('{r.name}', TimeOfDay.DAMPE, [], player)", mode='eval').body
|
||||
return ast.NameConstant(True)
|
||||
|
||||
def at_night(self, node):
|
||||
@@ -468,7 +470,8 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
if self.world.ensure_tod_access:
|
||||
# tod has DAMPE or (tod == NONE and (ss or find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
return ast.parse("(tod & TimeOfDay.DAMPE) if tod else (state.has_all(('Ocarina', 'Suns Song')) or state.search.can_reach(spot.parent_region, age=age, tod=TimeOfDay.DAMPE))", mode='eval').body
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
return ast.parse(f"(state.has('Ocarina', player) and state.has('Suns Song', player)) or state._oot_reach_at_time('{r.name}', TimeOfDay.DAMPE, [], player)", mode='eval').body
|
||||
return ast.NameConstant(True)
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from collections import deque
|
||||
import logging
|
||||
|
||||
from .SaveContext import SaveContext
|
||||
from .Regions import TimeOfDay
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import set_rule, add_rule, add_item_rule, forbid_item
|
||||
@@ -42,6 +43,36 @@ class OOTLogic(LogicMixin):
|
||||
return can_reach
|
||||
return self.age[player] == age
|
||||
|
||||
def _oot_reach_at_time(self, regionname, tod, already_checked, player):
|
||||
name_map = {
|
||||
TimeOfDay.DAY: self.day_reachable_regions[player],
|
||||
TimeOfDay.DAMPE: self.dampe_reachable_regions[player],
|
||||
TimeOfDay.ALL: self.day_reachable_regions[player].intersection(self.dampe_reachable_regions[player])
|
||||
}
|
||||
if regionname in name_map[tod]:
|
||||
return True
|
||||
region = self.world.get_region(regionname, player)
|
||||
if region.provides_time == TimeOfDay.ALL or regionname == 'Root':
|
||||
self.day_reachable_regions[player].add(regionname)
|
||||
self.dampe_reachable_regions[player].add(regionname)
|
||||
return True
|
||||
if region.provides_time == TimeOfDay.DAMPE:
|
||||
self.dampe_reachable_regions[player].add(regionname)
|
||||
return tod == TimeOfDay.DAMPE
|
||||
for entrance in region.entrances:
|
||||
if entrance.parent_region.name in already_checked:
|
||||
continue
|
||||
if self._oot_reach_at_time(entrance.parent_region.name, tod, already_checked + [regionname], player):
|
||||
if tod == TimeOfDay.DAY:
|
||||
self.day_reachable_regions[player].add(regionname)
|
||||
elif tod == TimeOfDay.DAMPE:
|
||||
self.dampe_reachable_regions[player].add(regionname)
|
||||
elif tod == TimeOfDay.ALL:
|
||||
self.day_reachable_regions[player].add(regionname)
|
||||
self.dampe_reachable_regions[player].add(regionname)
|
||||
return True
|
||||
return False
|
||||
|
||||
# Store the age before calling this!
|
||||
def _oot_update_age_reachable_regions(self, player):
|
||||
self.stale[player] = False
|
||||
@@ -62,6 +93,8 @@ class OOTLogic(LogicMixin):
|
||||
while queue:
|
||||
connection = queue.popleft()
|
||||
new_region = connection.connected_region
|
||||
if new_region is None:
|
||||
continue
|
||||
if new_region in rrp:
|
||||
bc.remove(connection)
|
||||
elif connection.can_reach(self):
|
||||
|
||||
@@ -7,7 +7,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
|
||||
from .EntranceShuffle import shuffle_random_entrances, entrance_shuffle_table, EntranceShuffleError
|
||||
from .Items import OOTItem, item_table, oot_data_to_ap_id
|
||||
from .ItemPool import generate_itempool, add_dungeon_items, get_junk_item, get_junk_pool
|
||||
from .Regions import OOTRegion, TimeOfDay
|
||||
@@ -39,6 +39,11 @@ i_o_limiter = threading.Semaphore(2)
|
||||
|
||||
|
||||
class OOTWorld(World):
|
||||
"""
|
||||
The Legend of Zelda: Ocarina of Time is a 3D action/adventure game. Travel through Hyrule in two time periods,
|
||||
learn magical ocarina songs, and explore twelve dungeons on your quest. Use Link's many items and abilities
|
||||
to rescue the Seven Sages, and then confront Ganondorf to save Hyrule!
|
||||
"""
|
||||
game: str = "Ocarina of Time"
|
||||
options: dict = oot_options
|
||||
topology_present: bool = True
|
||||
@@ -61,6 +66,8 @@ class OOTWorld(World):
|
||||
self.adult_reachable_regions = {player: set() for player in range(1, parent.players + 1)}
|
||||
self.child_blocked_connections = {player: set() for player in range(1, parent.players + 1)}
|
||||
self.adult_blocked_connections = {player: set() for player in range(1, parent.players + 1)}
|
||||
self.day_reachable_regions = {player: set() for player in range(1, parent.players + 1)}
|
||||
self.dampe_reachable_regions = {player: set() for player in range(1, parent.players + 1)}
|
||||
self.age = {player: None for player in range(1, parent.players + 1)}
|
||||
|
||||
def oot_copy(self):
|
||||
@@ -73,6 +80,10 @@ class OOTWorld(World):
|
||||
range(1, self.world.players + 1)}
|
||||
ret.adult_blocked_connections = {player: copy.copy(self.adult_blocked_connections[player]) for player in
|
||||
range(1, self.world.players + 1)}
|
||||
ret.day_reachable_regions = {player: copy.copy(self.adult_reachable_regions[player]) for player in
|
||||
range(1, self.world.players + 1)}
|
||||
ret.dampe_reachable_regions = {player: copy.copy(self.adult_reachable_regions[player]) for player in
|
||||
range(1, self.world.players + 1)}
|
||||
return ret
|
||||
|
||||
CollectionState.__init__ = oot_init
|
||||
@@ -83,6 +94,8 @@ class OOTWorld(World):
|
||||
world.state.adult_reachable_regions = {player: set() for player in range(1, world.players + 1)}
|
||||
world.state.child_blocked_connections = {player: set() for player in range(1, world.players + 1)}
|
||||
world.state.adult_blocked_connections = {player: set() for player in range(1, world.players + 1)}
|
||||
world.state.day_reachable_regions = {player: set() for player in range(1, world.players + 1)}
|
||||
world.state.dampe_reachable_regions = {player: set() for player in range(1, world.players + 1)}
|
||||
world.state.age = {player: None for player in range(1, world.players + 1)}
|
||||
|
||||
return super().__new__(cls)
|
||||
@@ -173,14 +186,6 @@ class OOTWorld(World):
|
||||
self.mq_dungeons_random = False # this will be a deprecated option later
|
||||
self.ocarina_songs = False # just need to pull in the OcarinaSongs module
|
||||
self.big_poe_count = 1 # disabled due to client-side issues for now
|
||||
# ER options
|
||||
self.shuffle_interior_entrances = 'off'
|
||||
self.shuffle_grotto_entrances = False
|
||||
self.shuffle_dungeon_entrances = False
|
||||
self.shuffle_overworld_entrances = False
|
||||
self.owl_drops = False
|
||||
self.warp_songs = False
|
||||
self.spawn_positions = False
|
||||
|
||||
# Set internal names used by the OoT generator
|
||||
self.keysanity = self.shuffle_smallkeys in ['keysanity', 'remove', 'any_dungeon', 'overworld']
|
||||
@@ -313,7 +318,7 @@ class OOTWorld(World):
|
||||
new_location.show_in_spoiler = False
|
||||
if 'exits' in region:
|
||||
for exit, rule in region['exits'].items():
|
||||
new_exit = OOTEntrance(self.player, '%s => %s' % (new_region.name, exit), new_region)
|
||||
new_exit = OOTEntrance(self.player, self.world, '%s -> %s' % (new_region.name, exit), new_region)
|
||||
new_exit.vanilla_connected_region = exit
|
||||
new_exit.rule_string = rule
|
||||
if self.world.logic_rules != 'none':
|
||||
@@ -411,7 +416,8 @@ class OOTWorld(World):
|
||||
|
||||
def create_item(self, name: str):
|
||||
if name in item_table:
|
||||
return OOTItem(name, self.player, item_table[name], False, (name in self.nonadvancement_items))
|
||||
return OOTItem(name, self.player, item_table[name], False,
|
||||
(name in self.nonadvancement_items if getattr(self, 'nonadvancement_items', None) else False))
|
||||
return OOTItem(name, self.player, ('Event', True, None, None), True, False)
|
||||
|
||||
def make_event_item(self, name, location, item=None):
|
||||
@@ -431,7 +437,7 @@ class OOTWorld(World):
|
||||
world_type = 'Glitched World'
|
||||
overworld_data_path = data_path(world_type, 'Overworld.json')
|
||||
menu = OOTRegion('Menu', None, None, self.player)
|
||||
start = OOTEntrance(self.player, 'New Game', menu)
|
||||
start = OOTEntrance(self.player, self.world, 'New Game', menu)
|
||||
menu.exits.append(start)
|
||||
self.world.regions.append(menu)
|
||||
self.load_regions_from_json(overworld_data_path)
|
||||
@@ -443,14 +449,10 @@ class OOTWorld(World):
|
||||
self.random_shop_prices()
|
||||
self.set_scrub_prices()
|
||||
|
||||
# logger.info('Setting Entrances.')
|
||||
# set_entrances(self)
|
||||
# Enforce vanilla for now
|
||||
# Bind entrances to vanilla
|
||||
for region in self.regions:
|
||||
for exit in region.exits:
|
||||
exit.connect(self.world.get_region(exit.vanilla_connected_region, self.player))
|
||||
if self.entrance_shuffle:
|
||||
shuffle_random_entrances(self)
|
||||
|
||||
def create_items(self):
|
||||
# Generate itempool
|
||||
@@ -481,6 +483,50 @@ class OOTWorld(World):
|
||||
self.remove_from_start_inventory.extend(removed_items)
|
||||
|
||||
def set_rules(self):
|
||||
# This has to run AFTER creating items but BEFORE set_entrances_based_rules
|
||||
if self.entrance_shuffle:
|
||||
# 10 attempts at shuffling entrances
|
||||
tries = 10
|
||||
while tries:
|
||||
try:
|
||||
shuffle_random_entrances(self)
|
||||
except EntranceShuffleError as e:
|
||||
tries -= 1
|
||||
logging.getLogger('').debug(f"Failed shuffling entrances for world {self.player}, retrying {tries} more times")
|
||||
if tries == 0:
|
||||
raise e
|
||||
# Restore original state and delete assumed entrances
|
||||
for entrance in self.get_shuffled_entrances():
|
||||
entrance.connect(self.world.get_region(entrance.vanilla_connected_region, self.player))
|
||||
if entrance.assumed:
|
||||
assumed_entrance = entrance.assumed
|
||||
if assumed_entrance.connected_region is not None:
|
||||
assumed_entrance.disconnect()
|
||||
del assumed_entrance
|
||||
entrance.reverse = None
|
||||
entrance.replaces = None
|
||||
entrance.assumed = None
|
||||
entrance.shuffled = False
|
||||
# Clean up root entrances
|
||||
root = self.get_region("Root Exits")
|
||||
root.exits = root.exits[:8]
|
||||
else:
|
||||
break
|
||||
|
||||
# Write entrances to spoiler log
|
||||
all_entrances = self.get_shuffled_entrances()
|
||||
all_entrances.sort(key=lambda x: x.name)
|
||||
all_entrances.sort(key=lambda x: x.type)
|
||||
for loadzone in all_entrances:
|
||||
if loadzone.primary:
|
||||
entrance = loadzone
|
||||
else:
|
||||
entrance = loadzone.reverse
|
||||
if entrance.reverse is not None:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'both', self.player)
|
||||
else:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
|
||||
set_rules(self)
|
||||
set_entrances_based_rules(self)
|
||||
|
||||
@@ -506,7 +552,7 @@ class OOTWorld(World):
|
||||
all_locations = self.get_locations()
|
||||
reachable = self.world.get_reachable_locations(all_state, self.player)
|
||||
unreachable = [loc for loc in all_locations if
|
||||
loc.internal and loc.event and loc.locked and loc not in reachable]
|
||||
(loc.internal or loc.type == 'Drop') and loc.event and loc.locked and loc not in reachable]
|
||||
for loc in unreachable:
|
||||
loc.parent_region.locations.remove(loc)
|
||||
# Exception: Sell Big Poe is an event which is only reachable if Bottle with Big Poe is in the item pool.
|
||||
@@ -618,18 +664,35 @@ class OOTWorld(World):
|
||||
songs = list(filter(lambda item: item.player == self.player and item.type == 'Song', self.world.itempool))
|
||||
for song in songs:
|
||||
self.world.itempool.remove(song)
|
||||
|
||||
important_warps = (self.shuffle_special_interior_entrances or self.shuffle_overworld_entrances or
|
||||
self.warp_songs or self.spawn_positions)
|
||||
song_order = {
|
||||
'Zeldas Lullaby': 1,
|
||||
'Eponas Song': 1,
|
||||
'Sarias Song': 3 if important_warps else 0,
|
||||
'Suns Song': 0,
|
||||
'Song of Time': 0,
|
||||
'Song of Storms': 3,
|
||||
'Minuet of Forest': 2 if important_warps else 0,
|
||||
'Bolero of Fire': 2 if important_warps else 0,
|
||||
'Serenade of Water': 2 if important_warps else 0,
|
||||
'Requiem of Spirit': 2,
|
||||
'Nocturne of Shadow': 2,
|
||||
'Prelude of Light': 2 if important_warps else 0,
|
||||
}
|
||||
songs.sort(key=lambda song: song_order.get(song.name, 0))
|
||||
|
||||
while tries:
|
||||
try:
|
||||
self.world.random.shuffle(songs) # shuffling songs makes it less likely to fail by placing ZL last
|
||||
self.world.random.shuffle(song_locations)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), song_locations[:], songs[:],
|
||||
True, True)
|
||||
logger.debug(f"Successfully placed songs for player {self.player} after {6 - tries} attempt(s)")
|
||||
tries = 0
|
||||
except FillError as e:
|
||||
tries -= 1
|
||||
if tries == 0:
|
||||
raise e
|
||||
raise Exception(f"Failed placing songs for player {self.player}. Error cause: {e}")
|
||||
logger.debug(f"Failed placing songs for player {self.player}. Retries left: {tries}")
|
||||
# undo what was done
|
||||
for song in songs:
|
||||
@@ -639,6 +702,8 @@ class OOTWorld(World):
|
||||
location.item = None
|
||||
location.locked = False
|
||||
location.event = False
|
||||
else:
|
||||
break
|
||||
|
||||
# Place shop items
|
||||
# fast fill will fail because there is some logic on the shop items. we'll gather them up and place the shop items
|
||||
@@ -712,6 +777,9 @@ class OOTWorld(World):
|
||||
for trap in ice_traps:
|
||||
trap.looks_like_item = 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]
|
||||
|
||||
outfile_name = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_player_name(self.player)}"
|
||||
rom = Rom(file=get_options()['oot_options']['rom_file'])
|
||||
if self.hints != 'none':
|
||||
@@ -787,6 +855,23 @@ class OOTWorld(World):
|
||||
autoworld.hint_data_available.set()
|
||||
|
||||
def modify_multidata(self, multidata: dict):
|
||||
|
||||
hint_entrances = set()
|
||||
for entrance in entrance_shuffle_table:
|
||||
hint_entrances.add(entrance[1][0])
|
||||
if len(entrance) > 2:
|
||||
hint_entrances.add(entrance[2][0])
|
||||
|
||||
def get_entrance_to_region(region):
|
||||
if region.name == 'Root':
|
||||
return None
|
||||
for entrance in region.entrances:
|
||||
if entrance.name in hint_entrances:
|
||||
return entrance
|
||||
for entrance in region.entrances:
|
||||
return get_entrance_to_region(entrance.parent_region)
|
||||
|
||||
# Remove undesired items from start_inventory
|
||||
for item_name in self.remove_from_start_inventory:
|
||||
item_id = self.item_name_to_id.get(item_name, None)
|
||||
try:
|
||||
@@ -794,10 +879,26 @@ class OOTWorld(World):
|
||||
except ValueError as e:
|
||||
logger.warning(f"Attempted to remove nonexistent item id {item_id} from OoT precollected items ({item_name})")
|
||||
|
||||
# Add ER hint data
|
||||
if self.shuffle_interior_entrances != 'off' or self.shuffle_dungeon_entrances or self.shuffle_grotto_entrances:
|
||||
er_hint_data = {}
|
||||
for region in self.regions:
|
||||
main_entrance = get_entrance_to_region(region)
|
||||
if main_entrance is not None and main_entrance.shuffled:
|
||||
for location in region.locations:
|
||||
if type(location.address) == int:
|
||||
er_hint_data[location.address] = main_entrance.name
|
||||
multidata['er_hint_data'][self.player] = er_hint_data
|
||||
|
||||
|
||||
# Helper functions
|
||||
def get_shuffled_entrances(self):
|
||||
return [] # later this will return all entrances modified by ER. patching process needs it now though
|
||||
def get_shufflable_entrances(self, type=None, only_primary=False):
|
||||
return [entrance for entrance in self.world.get_entrances() if (entrance.player == self.player and
|
||||
(type == None or entrance.type == type) and
|
||||
(not only_primary or entrance.primary))]
|
||||
|
||||
def get_shuffled_entrances(self, type=None, only_primary=False):
|
||||
return [entrance for entrance in self.get_shufflable_entrances(type=type, only_primary=only_primary) if entrance.shuffled]
|
||||
|
||||
def get_locations(self):
|
||||
for region in self.regions:
|
||||
@@ -810,6 +911,9 @@ class OOTWorld(World):
|
||||
def get_region(self, region):
|
||||
return self.world.get_region(region, self.player)
|
||||
|
||||
def get_entrance(self, entrance):
|
||||
return self.world.get_entrance(entrance, self.player)
|
||||
|
||||
def is_major_item(self, item: OOTItem):
|
||||
if item.type == 'Token':
|
||||
return self.bridge == 'tokens' or self.lacs_condition == 'tokens'
|
||||
@@ -835,3 +939,29 @@ class OOTWorld(World):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Specifically ensures that only real items are gotten, not any events.
|
||||
# In particular, ensures that Time Travel needs to be found.
|
||||
def get_state_with_complete_itempool(self):
|
||||
all_state = self.world.get_all_state(use_cache=False)
|
||||
# Remove event progression items
|
||||
for item, player in all_state.prog_items:
|
||||
if (item not in item_table or item_table[item][2] is None) and player == self.player:
|
||||
all_state.prog_items[(item, player)] = 0
|
||||
# Remove all events and checked locations
|
||||
all_state.locations_checked = {loc for loc in all_state.locations_checked if loc.player != self.player}
|
||||
all_state.events = {loc for loc in all_state.events if loc.player != self.player}
|
||||
# If free_scarecrow give Scarecrow Song
|
||||
if self.free_scarecrow:
|
||||
all_state.collect(self.create_item("Scarecrow Song"), event=True)
|
||||
|
||||
# Invalidate caches
|
||||
all_state.child_reachable_regions[self.player] = set()
|
||||
all_state.adult_reachable_regions[self.player] = set()
|
||||
all_state.child_blocked_connections[self.player] = set()
|
||||
all_state.adult_blocked_connections[self.player] = set()
|
||||
all_state.day_reachable_regions[self.player] = set()
|
||||
all_state.dampe_reachable_regions[self.player] = set()
|
||||
all_state.stale[self.player] = True
|
||||
|
||||
return all_state
|
||||
|
||||
@@ -1720,7 +1720,8 @@
|
||||
"Lake Hylia": "is_child and can_dive",
|
||||
"ZD Behind King Zora": "
|
||||
Deliver_Letter or zora_fountain == 'open' or
|
||||
(zora_fountain == 'adult' and is_adult)",
|
||||
(zora_fountain == 'adult' and is_adult) or
|
||||
(logic_king_zora_skip and is_adult)",
|
||||
"ZD Shop": "is_child or Blue_Fire",
|
||||
"ZD Storms Grotto": "can_open_storm_grotto"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,21 +38,22 @@
|
||||
"CFG_TEXT_CURSOR_COLOR": "03480866",
|
||||
"CHAIN_HBA_REWARDS": "03483950",
|
||||
"CHEST_SIZE_MATCH_CONTENTS": "034826F0",
|
||||
"COMPLETE_MASK_QUEST": "0348B1C9",
|
||||
"COMPLETE_MASK_QUEST": "0348B1D1",
|
||||
"COOP_CONTEXT": "03480020",
|
||||
"COOP_VERSION": "03480020",
|
||||
"COSMETIC_CONTEXT": "03480844",
|
||||
"COSMETIC_FORMAT_VERSION": "03480844",
|
||||
"CURRENT_GROTTO_ID": "03482E82",
|
||||
"DEATH_LINK": "0348002A",
|
||||
"DEBUG_OFFSET": "034828A0",
|
||||
"DISABLE_TIMERS": "03480CDC",
|
||||
"DPAD_TEXTURE": "0348D748",
|
||||
"DPAD_TEXTURE": "0348D750",
|
||||
"DUNGEONS_SHUFFLED": "03480CDE",
|
||||
"EXTENDED_OBJECT_TABLE": "03480C9C",
|
||||
"EXTERN_DAMAGE_MULTIPLYER": "03482CB1",
|
||||
"FAST_BUNNY_HOOD_ENABLED": "03480CE0",
|
||||
"FAST_CHESTS": "03480CD6",
|
||||
"FONT_TEXTURE": "0348C280",
|
||||
"FONT_TEXTURE": "0348C288",
|
||||
"FREE_SCARECROW_ENABLED": "03480CCC",
|
||||
"GET_CHEST_OVERRIDE_COLOR_WRAPPER": "03482720",
|
||||
"GET_CHEST_OVERRIDE_SIZE_WRAPPER": "034826F4",
|
||||
@@ -68,12 +69,12 @@
|
||||
"LACS_CONDITION_COUNT": "03480CD2",
|
||||
"MALON_GAVE_ICETRAP": "0348368C",
|
||||
"MALON_TEXT_ID": "03480CDB",
|
||||
"MAX_RUPEES": "0348B1CB",
|
||||
"MAX_RUPEES": "0348B1D3",
|
||||
"MOVED_ADULT_KING_ZORA": "03482FFC",
|
||||
"NO_ESCAPE_SEQUENCE": "0348B194",
|
||||
"NO_ESCAPE_SEQUENCE": "0348B19C",
|
||||
"NO_FOG_STATE": "03480CDD",
|
||||
"OCARINAS_SHUFFLED": "03480CD5",
|
||||
"OPEN_KAKARIKO": "0348B1CA",
|
||||
"OPEN_KAKARIKO": "0348B1D2",
|
||||
"OUTGOING_ITEM": "03480030",
|
||||
"OUTGOING_KEY": "0348002C",
|
||||
"OUTGOING_PLAYER": "03480032",
|
||||
@@ -96,88 +97,88 @@
|
||||
"SPEED_MULTIPLIER": "03482760",
|
||||
"START_TWINROVA_FIGHT": "0348307C",
|
||||
"TIME_TRAVEL_SAVED_EQUIPS": "03481A64",
|
||||
"TRIFORCE_ICON_TEXTURE": "0348DF48",
|
||||
"TRIFORCE_ICON_TEXTURE": "0348DF50",
|
||||
"TWINROVA_ACTION_TIMER": "03483080",
|
||||
"WINDMILL_SONG_ID": "03480CD9",
|
||||
"WINDMILL_TEXT_ID": "03480CDA",
|
||||
"a_button": "0348B158",
|
||||
"a_note_b": "0348B144",
|
||||
"a_note_font_glow_base": "0348B12C",
|
||||
"a_note_font_glow_max": "0348B128",
|
||||
"a_note_g": "0348B148",
|
||||
"a_note_glow_base": "0348B134",
|
||||
"a_note_glow_max": "0348B130",
|
||||
"a_note_r": "0348B14C",
|
||||
"active_item_action_id": "0348B1AC",
|
||||
"active_item_fast_chest": "0348B19C",
|
||||
"active_item_graphic_id": "0348B1A0",
|
||||
"active_item_object_id": "0348B1A4",
|
||||
"active_item_row": "0348B1B0",
|
||||
"active_item_text_id": "0348B1A8",
|
||||
"active_override": "0348B1B8",
|
||||
"active_override_is_outgoing": "0348B1B4",
|
||||
"b_button": "0348B154",
|
||||
"beating_dd": "0348B160",
|
||||
"beating_no_dd": "0348B168",
|
||||
"c_button": "0348B150",
|
||||
"c_note_b": "0348B138",
|
||||
"c_note_font_glow_base": "0348B11C",
|
||||
"c_note_font_glow_max": "0348B118",
|
||||
"c_note_g": "0348B13C",
|
||||
"c_note_glow_base": "0348B124",
|
||||
"c_note_glow_max": "0348B120",
|
||||
"c_note_r": "0348B140",
|
||||
"cfg_dungeon_info_enable": "0348B0E4",
|
||||
"cfg_dungeon_info_mq_enable": "0348B188",
|
||||
"cfg_dungeon_info_mq_need_map": "0348B184",
|
||||
"cfg_dungeon_info_reward_enable": "0348B0E0",
|
||||
"cfg_dungeon_info_reward_need_altar": "0348B17C",
|
||||
"cfg_dungeon_info_reward_need_compass": "0348B180",
|
||||
"cfg_dungeon_is_mq": "0348B1E8",
|
||||
"cfg_dungeon_rewards": "03489EDC",
|
||||
"cfg_file_select_hash": "0348B190",
|
||||
"cfg_item_overrides": "0348B23C",
|
||||
"defaultDDHeart": "0348B16C",
|
||||
"defaultHeart": "0348B174",
|
||||
"dpad_sprite": "0348A050",
|
||||
"dummy_actor": "0348B1C0",
|
||||
"dungeon_count": "0348B0E8",
|
||||
"dungeons": "03489F00",
|
||||
"empty_dlist": "0348B100",
|
||||
"extern_ctxt": "03489F9C",
|
||||
"font_sprite": "0348A060",
|
||||
"freecam_modes": "03489C5C",
|
||||
"hash_sprites": "0348B0F4",
|
||||
"hash_symbols": "03489FB0",
|
||||
"heap_next": "0348B1E4",
|
||||
"heart_sprite": "03489FF0",
|
||||
"icon_sprites": "03489E20",
|
||||
"item_digit_sprite": "0348A010",
|
||||
"item_overrides_count": "0348B1C4",
|
||||
"item_table": "0348A0D8",
|
||||
"items_sprite": "0348A080",
|
||||
"key_rupee_clock_sprite": "0348A020",
|
||||
"last_fog_distance": "0348B0EC",
|
||||
"linkhead_skull_sprite": "0348A000",
|
||||
"medal_colors": "03489EEC",
|
||||
"medals_sprite": "0348A090",
|
||||
"normal_dd": "0348B15C",
|
||||
"normal_no_dd": "0348B164",
|
||||
"object_slots": "0348C23C",
|
||||
"pending_freezes": "0348B1C8",
|
||||
"pending_item_queue": "0348B224",
|
||||
"quest_items_sprite": "0348A070",
|
||||
"rupee_colors": "03489E2C",
|
||||
"satisified_pending_frames": "0348B198",
|
||||
"scene_fog_distance": "0348B0F0",
|
||||
"setup_db": "0348A0B0",
|
||||
"song_note_sprite": "0348A030",
|
||||
"stones_sprite": "0348A0A0",
|
||||
"text_cursor_border_base": "0348B10C",
|
||||
"text_cursor_border_max": "0348B108",
|
||||
"text_cursor_inner_base": "0348B114",
|
||||
"text_cursor_inner_max": "0348B110",
|
||||
"triforce_hunt_enabled": "0348B1D8",
|
||||
"triforce_pieces_requied": "0348B17A",
|
||||
"triforce_sprite": "0348A040"
|
||||
"a_button": "0348B160",
|
||||
"a_note_b": "0348B14C",
|
||||
"a_note_font_glow_base": "0348B134",
|
||||
"a_note_font_glow_max": "0348B130",
|
||||
"a_note_g": "0348B150",
|
||||
"a_note_glow_base": "0348B13C",
|
||||
"a_note_glow_max": "0348B138",
|
||||
"a_note_r": "0348B154",
|
||||
"active_item_action_id": "0348B1B4",
|
||||
"active_item_fast_chest": "0348B1A4",
|
||||
"active_item_graphic_id": "0348B1A8",
|
||||
"active_item_object_id": "0348B1AC",
|
||||
"active_item_row": "0348B1B8",
|
||||
"active_item_text_id": "0348B1B0",
|
||||
"active_override": "0348B1C0",
|
||||
"active_override_is_outgoing": "0348B1BC",
|
||||
"b_button": "0348B15C",
|
||||
"beating_dd": "0348B168",
|
||||
"beating_no_dd": "0348B170",
|
||||
"c_button": "0348B158",
|
||||
"c_note_b": "0348B140",
|
||||
"c_note_font_glow_base": "0348B124",
|
||||
"c_note_font_glow_max": "0348B120",
|
||||
"c_note_g": "0348B144",
|
||||
"c_note_glow_base": "0348B12C",
|
||||
"c_note_glow_max": "0348B128",
|
||||
"c_note_r": "0348B148",
|
||||
"cfg_dungeon_info_enable": "0348B0EC",
|
||||
"cfg_dungeon_info_mq_enable": "0348B190",
|
||||
"cfg_dungeon_info_mq_need_map": "0348B18C",
|
||||
"cfg_dungeon_info_reward_enable": "0348B0E8",
|
||||
"cfg_dungeon_info_reward_need_altar": "0348B184",
|
||||
"cfg_dungeon_info_reward_need_compass": "0348B188",
|
||||
"cfg_dungeon_is_mq": "0348B1F0",
|
||||
"cfg_dungeon_rewards": "03489EE4",
|
||||
"cfg_file_select_hash": "0348B198",
|
||||
"cfg_item_overrides": "0348B244",
|
||||
"defaultDDHeart": "0348B174",
|
||||
"defaultHeart": "0348B17C",
|
||||
"dpad_sprite": "0348A058",
|
||||
"dummy_actor": "0348B1C8",
|
||||
"dungeon_count": "0348B0F0",
|
||||
"dungeons": "03489F08",
|
||||
"empty_dlist": "0348B108",
|
||||
"extern_ctxt": "03489FA4",
|
||||
"font_sprite": "0348A068",
|
||||
"freecam_modes": "03489C60",
|
||||
"hash_sprites": "0348B0FC",
|
||||
"hash_symbols": "03489FB8",
|
||||
"heap_next": "0348B1EC",
|
||||
"heart_sprite": "03489FF8",
|
||||
"icon_sprites": "03489E24",
|
||||
"item_digit_sprite": "0348A018",
|
||||
"item_overrides_count": "0348B1CC",
|
||||
"item_table": "0348A0E0",
|
||||
"items_sprite": "0348A088",
|
||||
"key_rupee_clock_sprite": "0348A028",
|
||||
"last_fog_distance": "0348B0F4",
|
||||
"linkhead_skull_sprite": "0348A008",
|
||||
"medal_colors": "03489EF4",
|
||||
"medals_sprite": "0348A098",
|
||||
"normal_dd": "0348B164",
|
||||
"normal_no_dd": "0348B16C",
|
||||
"object_slots": "0348C244",
|
||||
"pending_freezes": "0348B1D0",
|
||||
"pending_item_queue": "0348B22C",
|
||||
"quest_items_sprite": "0348A078",
|
||||
"rupee_colors": "03489E30",
|
||||
"satisified_pending_frames": "0348B1A0",
|
||||
"scene_fog_distance": "0348B0F8",
|
||||
"setup_db": "0348A0B8",
|
||||
"song_note_sprite": "0348A038",
|
||||
"stones_sprite": "0348A0A8",
|
||||
"text_cursor_border_base": "0348B114",
|
||||
"text_cursor_border_max": "0348B110",
|
||||
"text_cursor_inner_base": "0348B11C",
|
||||
"text_cursor_inner_max": "0348B118",
|
||||
"triforce_hunt_enabled": "0348B1E0",
|
||||
"triforce_pieces_requied": "0348B182",
|
||||
"triforce_sprite": "0348A048"
|
||||
}
|
||||
@@ -42,4 +42,4 @@ def set_rules(world: MultiWorld, player: int):
|
||||
set_rule(world.get_location("Victory", player),
|
||||
lambda state: state._ror_has_items(player, 5 * items_per_level) and state.has("Beat Level Five", player))
|
||||
|
||||
world.completion_condition[player] = lambda state: state.has("Victory", player)
|
||||
world.completion_condition[player] = lambda state: state.has("Victory", player)
|
||||
|
||||
14
worlds/sm/Items.py
Normal file
14
worlds/sm/Items.py
Normal file
@@ -0,0 +1,14 @@
|
||||
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()}
|
||||
14
worlds/sm/Locations.py
Normal file
14
worlds/sm/Locations.py
Normal file
@@ -0,0 +1,14 @@
|
||||
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()}
|
||||
237
worlds/sm/Options.py
Normal file
237
worlds/sm/Options.py
Normal file
@@ -0,0 +1,237 @@
|
||||
import typing
|
||||
from Options import Choice, Range, OptionDict, OptionList, Option, Toggle, DefaultOnToggle, DeathLink
|
||||
|
||||
class StartItemsRemovesFromPool(Toggle):
|
||||
displayname = "StartItems Removes From Item Pool"
|
||||
|
||||
class Preset(Choice):
|
||||
"""choose one of the preset or specify "varia_custom" to use varia_custom_preset option or specify "custom" to use custom_preset option"""
|
||||
displayname = "Preset"
|
||||
option_newbie = 0
|
||||
option_casual = 1
|
||||
option_regular = 2
|
||||
option_veteran = 3
|
||||
option_expert = 4
|
||||
option_master = 5
|
||||
option_samus = 6
|
||||
option_Season_Races = 7
|
||||
option_SMRAT2021 = 8
|
||||
option_solution = 9
|
||||
option_custom = 10
|
||||
option_varia_custom = 11
|
||||
default = 2
|
||||
|
||||
class StartLocation(Choice):
|
||||
displayname = "Start Location"
|
||||
option_Ceres = 0
|
||||
option_Landing_Site = 1
|
||||
option_Gauntlet_Top = 2
|
||||
option_Green_Brinstar_Elevator = 3
|
||||
option_Big_Pink = 4
|
||||
option_Etecoons_Supers = 5
|
||||
option_Wrecked_Ship_Main = 6
|
||||
option_Firefleas_Top = 7
|
||||
option_Business_Center = 8
|
||||
option_Bubble_Mountain = 9
|
||||
option_Mama_Turtle = 10
|
||||
option_Watering_Hole = 11
|
||||
option_Aqueduct = 12
|
||||
option_Red_Brinstar_Elevator = 13
|
||||
option_Golden_Four = 14
|
||||
default = 1
|
||||
|
||||
class MaxDifficulty(Choice):
|
||||
displayname = "Maximum Difficulty"
|
||||
option_easy = 0
|
||||
option_medium = 1
|
||||
option_hard = 2
|
||||
option_harder = 3
|
||||
option_hardcore = 4
|
||||
option_mania = 5
|
||||
option_infinity = 6
|
||||
default = 4
|
||||
|
||||
class MorphPlacement(Choice):
|
||||
displayname = "Morph Placement"
|
||||
option_early = 0
|
||||
option_normal = 1
|
||||
default = 0
|
||||
|
||||
class SuitsRestriction(DefaultOnToggle):
|
||||
displayname = "Suits Restriction"
|
||||
|
||||
class StrictMinors(Toggle):
|
||||
displayname = "Strict Minors"
|
||||
|
||||
class MissileQty(Range):
|
||||
displayname = "Missile Quantity"
|
||||
range_start = 10
|
||||
range_end = 90
|
||||
default = 30
|
||||
|
||||
class SuperQty(Range):
|
||||
displayname = "Super Quantity"
|
||||
range_start = 10
|
||||
range_end = 90
|
||||
default = 20
|
||||
|
||||
class PowerBombQty(Range):
|
||||
displayname = "Power Bomb Quantity"
|
||||
range_start = 10
|
||||
range_end = 90
|
||||
default = 10
|
||||
|
||||
class MinorQty(Range):
|
||||
displayname = "Minor Quantity"
|
||||
range_start = 7
|
||||
range_end = 100
|
||||
default = 100
|
||||
|
||||
class EnergyQty(Choice):
|
||||
displayname = "Energy Quantity"
|
||||
option_ultra_sparse = 0
|
||||
option_sparse = 1
|
||||
option_medium = 2
|
||||
option_vanilla = 3
|
||||
default = 3
|
||||
|
||||
class AreaRandomization(Choice):
|
||||
displayname = "Area Randomization"
|
||||
option_off = 0
|
||||
option_light = 1
|
||||
option_on = 2
|
||||
alias_false = 0
|
||||
alias_true = 2
|
||||
default = 0
|
||||
|
||||
class AreaLayout(Toggle):
|
||||
displayname = "Area Layout"
|
||||
|
||||
class DoorsColorsRando(Toggle):
|
||||
displayname = "Doors Colors Rando"
|
||||
|
||||
class AllowGreyDoors(Toggle):
|
||||
displayname = "Allow Grey Doors"
|
||||
|
||||
class BossRandomization(Toggle):
|
||||
displayname = "Boss Randomization"
|
||||
|
||||
class FunCombat(Toggle):
|
||||
displayname = "Fun Combat"
|
||||
|
||||
class FunMovement(Toggle):
|
||||
displayname = "Fun Movement"
|
||||
|
||||
class FunSuits(Toggle):
|
||||
displayname = "Fun Suits"
|
||||
|
||||
class LayoutPatches(DefaultOnToggle):
|
||||
displayname = "Layout Patches"
|
||||
|
||||
class VariaTweaks(Toggle):
|
||||
displayname = "Varia Tweaks"
|
||||
|
||||
class NerfedCharge(Toggle):
|
||||
displayname = "Nerfed Charge"
|
||||
|
||||
class GravityBehaviour(Choice):
|
||||
displayname = "Gravity Behaviour"
|
||||
option_Vanilla = 0
|
||||
option_Balanced = 1
|
||||
option_Progressive = 2
|
||||
default = 1
|
||||
|
||||
class ElevatorsDoorsSpeed(DefaultOnToggle):
|
||||
displayname = "Elevators doors speed"
|
||||
|
||||
class SpinJumpRestart(Toggle):
|
||||
displayname = "Spin Jump Restart"
|
||||
|
||||
class InfiniteSpaceJump(Toggle):
|
||||
displayname = "Infinite Space Jump"
|
||||
|
||||
class RefillBeforeSave(Toggle):
|
||||
displayname = "Refill Before Save"
|
||||
|
||||
class Hud(Toggle):
|
||||
displayname = "Hud"
|
||||
|
||||
class Animals(Toggle):
|
||||
displayname = "Animals"
|
||||
|
||||
class NoMusic(Toggle):
|
||||
displayname = "No Music"
|
||||
|
||||
class RandomMusic(Toggle):
|
||||
displayname = "Random Music"
|
||||
|
||||
class CustomPreset(OptionDict):
|
||||
"""
|
||||
see https://randommetroidsolver.pythonanywhere.com/presets for detailed info on each preset settings
|
||||
knows: each skill (know) has a pair [can use, perceived difficulty using one of 1, 5, 10, 25, 50 or 100 each one matching a max_difficulty]
|
||||
settings: hard rooms, hellruns and bosses settings
|
||||
controller: predefined controller mapping and moon walk setting
|
||||
"""
|
||||
displayname = "Custom Preset"
|
||||
default = { "knows": {},
|
||||
"settings": {},
|
||||
"controller": {}
|
||||
}
|
||||
|
||||
class VariaCustomPreset(OptionList):
|
||||
"""use an entry from the preset list on https://randommetroidsolver.pythonanywhere.com/presets"""
|
||||
displayname = "Varia Custom Preset"
|
||||
default = {}
|
||||
|
||||
|
||||
sm_options: typing.Dict[str, type(Option)] = {
|
||||
"start_inventory_removes_from_pool": StartItemsRemovesFromPool,
|
||||
"preset": Preset,
|
||||
"start_location": StartLocation,
|
||||
"death_link": DeathLink,
|
||||
#"majors_split": "Full",
|
||||
#"scav_num_locs": "10",
|
||||
#"scav_randomized": "off",
|
||||
#"scav_escape": "off",
|
||||
"max_difficulty": MaxDifficulty,
|
||||
#"progression_speed": "medium",
|
||||
#"progression_difficulty": "normal",
|
||||
"morph_placement": MorphPlacement,
|
||||
"suits_restriction": SuitsRestriction,
|
||||
#"hide_items": "off",
|
||||
"strict_minors": StrictMinors,
|
||||
"missile_qty": MissileQty,
|
||||
"super_qty": SuperQty,
|
||||
"power_bomb_qty": PowerBombQty,
|
||||
"minor_qty": MinorQty,
|
||||
"energy_qty": EnergyQty,
|
||||
"area_randomization": AreaRandomization,
|
||||
"area_layout": AreaLayout,
|
||||
"doors_colors_rando": DoorsColorsRando,
|
||||
"allow_grey_doors": AllowGreyDoors,
|
||||
"boss_randomization": BossRandomization,
|
||||
#"minimizer": "off",
|
||||
#"minimizer_qty": "45",
|
||||
#"minimizer_tourian": "off",
|
||||
#"escape_rando": "off",
|
||||
#"remove_escape_enemies": "off",
|
||||
"fun_combat": FunCombat,
|
||||
"fun_movement": FunMovement,
|
||||
"fun_suits": FunSuits,
|
||||
"layout_patches": LayoutPatches,
|
||||
"varia_tweaks": VariaTweaks,
|
||||
"nerfed_charge": NerfedCharge,
|
||||
"gravity_behaviour": GravityBehaviour,
|
||||
#"item_sounds": "on",
|
||||
"elevators_doors_speed": ElevatorsDoorsSpeed,
|
||||
"spin_jump_restart": SpinJumpRestart,
|
||||
#"rando_speed": "off",
|
||||
"infinite_space_jump": InfiniteSpaceJump,
|
||||
"refill_before_save": RefillBeforeSave,
|
||||
"hud": Hud,
|
||||
"animals": Animals,
|
||||
"no_music": NoMusic,
|
||||
"random_music": RandomMusic,
|
||||
"custom_preset": CustomPreset,
|
||||
"varia_custom_preset": VariaCustomPreset
|
||||
}
|
||||
41
worlds/sm/Regions.py
Normal file
41
worlds/sm/Regions.py
Normal file
@@ -0,0 +1,41 @@
|
||||
def create_regions(self, world, player: int):
|
||||
from . import create_region
|
||||
from BaseClasses import Entrance
|
||||
from logic.logic import Logic
|
||||
from graph.vanilla.graph_locations import locationsDict
|
||||
|
||||
regions = []
|
||||
for accessPoint in Logic.accessPoints:
|
||||
regions.append(create_region( self,
|
||||
world,
|
||||
player,
|
||||
accessPoint.Name,
|
||||
None,
|
||||
[accessPoint.Name + "->" + key for key in accessPoint.intraTransitions.keys()]))
|
||||
|
||||
world.regions += regions
|
||||
|
||||
# create a region for each location and link each to what the location has access
|
||||
# we make them one way so that the filler (and spoiler log) doesnt try to use those region as intermediary path
|
||||
# this is required in AP because a location cant have multiple parent regions
|
||||
locationRegions = []
|
||||
for locationName, value in locationsDict.items():
|
||||
locationRegions.append(create_region( self,
|
||||
world,
|
||||
player,
|
||||
locationName,
|
||||
[locationName]))
|
||||
for key in value.AccessFrom.keys():
|
||||
currentRegion =world.get_region(key, player)
|
||||
currentRegion.exits.append(Entrance(player, key + "->"+ locationName, currentRegion))
|
||||
|
||||
world.regions += locationRegions
|
||||
#create entrances
|
||||
regionConcat = regions + locationRegions
|
||||
for region in regionConcat:
|
||||
for exit in region.exits:
|
||||
exit.connect(world.get_region(exit.name[exit.name.find("->") + 2:], player))
|
||||
|
||||
world.regions += [
|
||||
create_region(self, world, player, 'Menu', None, ['StartAP'])
|
||||
]
|
||||
30
worlds/sm/Rom.py
Normal file
30
worlds/sm/Rom.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import Utils
|
||||
from Patch import read_rom
|
||||
|
||||
JAP10HASH = '21f3e98df4780ee1c667b84e57d88675'
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
|
||||
def get_base_rom_bytes(file_name: str = "") -> bytes:
|
||||
base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
|
||||
if not base_rom_bytes:
|
||||
file_name = get_base_rom_path(file_name)
|
||||
base_rom_bytes = bytes(read_rom(open(file_name, "rb")))
|
||||
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(base_rom_bytes)
|
||||
if JAP10HASH != basemd5.hexdigest():
|
||||
raise Exception('Supplied Base Rom does not match known MD5 for JAP(1.0) release. '
|
||||
'Get the correct game and version, then dump it')
|
||||
get_base_rom_bytes.base_rom_bytes = base_rom_bytes
|
||||
return base_rom_bytes
|
||||
|
||||
def get_base_rom_path(file_name: str = "") -> str:
|
||||
options = Utils.get_options()
|
||||
if not file_name:
|
||||
file_name = options["sm_options"]["rom_file"]
|
||||
if not os.path.exists(file_name):
|
||||
file_name = Utils.local_path(file_name)
|
||||
return file_name
|
||||
40
worlds/sm/Rules.py
Normal file
40
worlds/sm/Rules.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from ..generic.Rules import set_rule, add_rule
|
||||
|
||||
from graph.vanilla.graph_locations import locationsDict
|
||||
from graph.graph_utils import vanillaTransitions, getAccessPoint
|
||||
from logic.logic import Logic
|
||||
from rom.rom_patches import RomPatches
|
||||
from utils.doorsmanager import DoorsManager
|
||||
|
||||
def evalSMBool(smbool, maxDiff):
|
||||
return smbool.bool == True and smbool.difficulty <= maxDiff
|
||||
|
||||
def add_accessFrom_rule(location, player, accessFrom):
|
||||
add_rule(location, lambda state: any((state.can_reach(accessName, player=player) and evalSMBool(rule(state.smbm[player]), state.smbm[player].maxDiff)) for accessName, rule in accessFrom.items()))
|
||||
|
||||
def add_postAvailable_rule(location, player, func):
|
||||
add_rule(location, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
||||
|
||||
def set_available_rule(location, player, func):
|
||||
set_rule(location, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
||||
|
||||
def set_entrance_rule(entrance, player, func):
|
||||
set_rule(entrance, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
||||
|
||||
def add_entrance_rule(entrance, player, func):
|
||||
add_rule(entrance, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
||||
|
||||
def set_rules(world, player):
|
||||
world.completion_condition[player] = lambda state: state.has('Mother Brain', player)
|
||||
|
||||
for key, value in locationsDict.items():
|
||||
location = world.get_location(key, player)
|
||||
set_available_rule(location, player, value.Available)
|
||||
if value.AccessFrom is not None:
|
||||
add_accessFrom_rule(location, player, value.AccessFrom)
|
||||
if value.PostAvailable is not None:
|
||||
add_postAvailable_rule(location, player, value.PostAvailable)
|
||||
|
||||
for accessPoint in Logic.accessPoints:
|
||||
for key, value1 in accessPoint.intraTransitions.items():
|
||||
set_entrance_rule(world.get_entrance(accessPoint.Name + "->" + key, player), player, value1)
|
||||
511
worlds/sm/__init__.py
Normal file
511
worlds/sm/__init__.py
Normal file
@@ -0,0 +1,511 @@
|
||||
import logging
|
||||
import copy
|
||||
import os
|
||||
import threading
|
||||
from typing import Set
|
||||
|
||||
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
|
||||
from .Rom import get_base_rom_path
|
||||
import Utils
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item, RegionType, CollectionState
|
||||
from ..AutoWorld import World
|
||||
import Patch
|
||||
|
||||
from logic.smboolmanager import SMBoolManager
|
||||
from graph.vanilla.graph_locations import locationsDict
|
||||
from graph.graph_utils import getAccessPoint
|
||||
from rando.ItemLocContainer import ItemLocation
|
||||
from rando.Items import ItemManager
|
||||
from utils.parameters import *
|
||||
from logic.logic import Logic
|
||||
from randomizer import VariaRandomizer
|
||||
|
||||
|
||||
class SMWorld(World):
|
||||
game: str = "Super Metroid"
|
||||
topology_present = True
|
||||
data_version = 1
|
||||
options = 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
|
||||
|
||||
remote_items: bool = False
|
||||
remote_start_inventory: bool = False
|
||||
|
||||
itemManager: ItemManager
|
||||
|
||||
locations = {}
|
||||
|
||||
Logic.factory('vanilla')
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
self.rom_name_available_event = threading.Event()
|
||||
super().__init__(world, player)
|
||||
|
||||
def __new__(cls, world, player):
|
||||
|
||||
# Add necessary objects to CollectionState on initialization
|
||||
orig_init = CollectionState.__init__
|
||||
orig_copy = CollectionState.copy
|
||||
|
||||
def sm_init(self, parent: MultiWorld):
|
||||
if (hasattr(parent, "state")): # for unit tests where MultiWorld is instanciated before worlds
|
||||
self.smbm = {player: SMBoolManager(player, parent.state.smbm[player].maxDiff) for player in parent.get_game_players("Super Metroid")}
|
||||
orig_init(self, parent)
|
||||
|
||||
|
||||
def sm_copy(self):
|
||||
ret = orig_copy(self)
|
||||
ret.smbm = {player: copy.deepcopy(self.smbm[player]) for player in self.world.get_game_players("Super Metroid")}
|
||||
return ret
|
||||
|
||||
CollectionState.__init__ = sm_init
|
||||
CollectionState.copy = sm_copy
|
||||
|
||||
if world:
|
||||
world.state.smbm = {}
|
||||
|
||||
return super().__new__(cls)
|
||||
|
||||
def generate_early(self):
|
||||
Logic.factory('vanilla')
|
||||
|
||||
self.variaRando = VariaRandomizer(self.world, get_base_rom_path(), self.player)
|
||||
self.world.state.smbm[self.player] = SMBoolManager(self.player, self.variaRando.maxDifficulty)
|
||||
|
||||
# keeps Nothing items local so no player will ever pickup Nothing
|
||||
# doing so reduces contribution of this world to the Multiworld the more Nothing there is though
|
||||
self.world.local_items[self.player].value.add('Nothing')
|
||||
|
||||
if (self.variaRando.args.morphPlacement == "early"):
|
||||
self.world.local_items[self.player].value.add('Morph')
|
||||
|
||||
def generate_basic(self):
|
||||
itemPool = self.variaRando.container.itemPool
|
||||
self.startItems = [variaItem for item in self.world.precollected_items[self.player] for variaItem in ItemManager.Items.values() if variaItem.Name == item.name]
|
||||
if self.world.start_inventory_removes_from_pool[self.player]:
|
||||
for item in self.startItems:
|
||||
if (item in itemPool):
|
||||
itemPool.remove(item)
|
||||
|
||||
missingPool = 105 - len(itemPool) + 1
|
||||
for i in range(1, missingPool):
|
||||
itemPool.append(ItemManager.Items['Nothing'])
|
||||
|
||||
# Generate item pool
|
||||
pool = []
|
||||
self.locked_items = {}
|
||||
weaponCount = [0, 0, 0]
|
||||
for item in itemPool:
|
||||
isAdvancement = True
|
||||
if item.Type == 'Missile':
|
||||
if weaponCount[0] < 3:
|
||||
weaponCount[0] += 1
|
||||
else:
|
||||
isAdvancement = False
|
||||
elif item.Type == 'Super':
|
||||
if weaponCount[1] < 2:
|
||||
weaponCount[1] += 1
|
||||
else:
|
||||
isAdvancement = False
|
||||
elif item.Type == 'PowerBomb':
|
||||
if weaponCount[2] < 3:
|
||||
weaponCount[2] += 1
|
||||
else:
|
||||
isAdvancement = False
|
||||
elif item.Type == 'Nothing':
|
||||
isAdvancement = False
|
||||
|
||||
itemClass = ItemManager.Items[item.Type].Class
|
||||
smitem = SMItem(item.Name, isAdvancement, item.Type, None if itemClass == 'Boss' else self.item_name_to_id[item.Name], player = self.player)
|
||||
if itemClass == 'Boss':
|
||||
self.locked_items[item.Name] = smitem
|
||||
else:
|
||||
pool.append(smitem)
|
||||
|
||||
self.world.itempool += pool
|
||||
|
||||
for (location, item) in self.locked_items.items():
|
||||
self.world.get_location(location, self.player).place_locked_item(item)
|
||||
self.world.get_location(location, self.player).address = None
|
||||
|
||||
startAP = self.world.get_entrance('StartAP', self.player)
|
||||
startAP.connect(self.world.get_region(self.variaRando.args.startLocation, self.player))
|
||||
|
||||
for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions:
|
||||
src_region = self.world.get_region(src.Name, self.player)
|
||||
dest_region = self.world.get_region(dest.Name, self.player)
|
||||
src_region.exits.append(Entrance(self.player, src.Name + "->" + dest.Name, src_region))
|
||||
srcDestEntrance = self.world.get_entrance(src.Name + "->" + dest.Name, self.player)
|
||||
srcDestEntrance.connect(dest_region)
|
||||
add_entrance_rule(self.world.get_entrance(src.Name + "->" + dest.Name, self.player), self.player, getAccessPoint(src.Name).traverse)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
|
||||
|
||||
def create_regions(self):
|
||||
create_locations(self, self.player)
|
||||
create_regions(self, self.world, self.player)
|
||||
|
||||
def getWord(self, w):
|
||||
return (w & 0x00FF, (w & 0xFF00) >> 8)
|
||||
|
||||
# used for remote location Credits Spoiler of local items
|
||||
class DummyLocation:
|
||||
def __init__(self, name):
|
||||
self.Name = name
|
||||
|
||||
def isBoss(self):
|
||||
return False
|
||||
|
||||
def convertToROMItemName(self, itemName):
|
||||
charMap = { "A" : 0x3CE0,
|
||||
"B" : 0x3CE1,
|
||||
"C" : 0x3CE2,
|
||||
"D" : 0x3CE3,
|
||||
"E" : 0x3CE4,
|
||||
"F" : 0x3CE5,
|
||||
"G" : 0x3CE6,
|
||||
"H" : 0x3CE7,
|
||||
"I" : 0x3CE8,
|
||||
"J" : 0x3CE9,
|
||||
"K" : 0x3CEA,
|
||||
"L" : 0x3CEB,
|
||||
"M" : 0x3CEC,
|
||||
"N" : 0x3CED,
|
||||
"O" : 0x3CEE,
|
||||
"P" : 0x3CEF,
|
||||
"Q" : 0x3CF0,
|
||||
"R" : 0x3CF1,
|
||||
"S" : 0x3CF2,
|
||||
"T" : 0x3CF3,
|
||||
"U" : 0x3CF4,
|
||||
"V" : 0x3CF5,
|
||||
"W" : 0x3CF6,
|
||||
"X" : 0x3CF7,
|
||||
"Y" : 0x3CF8,
|
||||
"Z" : 0x3CF9,
|
||||
" " : 0x3C4E,
|
||||
"!" : 0x3CFF,
|
||||
"?" : 0x3CFE,
|
||||
"'" : 0x3CFD,
|
||||
"," : 0x3CFB,
|
||||
"." : 0x3CFA,
|
||||
"-" : 0x3CCF,
|
||||
"_" : 0x000E,
|
||||
"1" : 0x3C00,
|
||||
"2" : 0x3C01,
|
||||
"3" : 0x3C02,
|
||||
"4" : 0x3C03,
|
||||
"5" : 0x3C04,
|
||||
"6" : 0x3C05,
|
||||
"7" : 0x3C06,
|
||||
"8" : 0x3C07,
|
||||
"9" : 0x3C08,
|
||||
"0" : 0x3C09,
|
||||
"%" : 0x3C0A}
|
||||
data = []
|
||||
|
||||
itemName = itemName.upper()[:26]
|
||||
itemName = itemName.strip()
|
||||
itemName = itemName.center(26, " ")
|
||||
itemName = "___" + itemName + "___"
|
||||
|
||||
for char in itemName:
|
||||
(w0, w1) = self.getWord(charMap.get(char, 0x3C4E))
|
||||
data.append(w0)
|
||||
data.append(w1)
|
||||
return data
|
||||
|
||||
def APPatchRom(self, romPatcher):
|
||||
multiWorldLocations = {}
|
||||
multiWorldItems = {}
|
||||
idx = 0
|
||||
itemId = 0
|
||||
for itemLoc in self.world.get_locations():
|
||||
if itemLoc.player == self.player and locationsDict[itemLoc.name].Id != None:
|
||||
if itemLoc.item.type in ItemManager.Items:
|
||||
itemId = ItemManager.Items[itemLoc.item.type].Id
|
||||
else:
|
||||
itemId = ItemManager.Items['ArchipelagoItem'].Id + idx
|
||||
multiWorldItems[0x029EA3 + idx*64] = self.convertToROMItemName(itemLoc.item.name)
|
||||
idx += 1
|
||||
(w0, w1) = self.getWord(0 if itemLoc.item.player == self.player else 1)
|
||||
(w2, w3) = self.getWord(itemId)
|
||||
(w4, w5) = self.getWord(itemLoc.item.player - 1)
|
||||
(w6, w7) = self.getWord(0 if itemLoc.item.advancement else 1)
|
||||
multiWorldLocations[0x1C6000 + locationsDict[itemLoc.name].Id*8] = [w0, w1, w2, w3, w4, w5, w6, w7]
|
||||
|
||||
|
||||
itemSprites = ["off_world_prog_item.bin", "off_world_item.bin"]
|
||||
idx = 0
|
||||
offworldSprites = {}
|
||||
for fileName in itemSprites:
|
||||
with open(Utils.local_path("lib", "worlds", "sm", "data", "custom_sprite", fileName) if Utils.is_frozen() else Utils.local_path("worlds", "sm", "data", "custom_sprite", fileName), 'rb') as stream:
|
||||
buffer = bytearray(stream.read())
|
||||
offworldSprites[0x027882 + 10*(21 + idx) + 2] = buffer[0:8]
|
||||
offworldSprites[0x049100 + idx*256] = buffer[8:264]
|
||||
idx += 1
|
||||
|
||||
openTourianGreyDoors = {0x07C823 + 5: [0x0C], 0x07C831 + 5: [0x0C]}
|
||||
|
||||
deathLink = {0x277f04: [int(self.world.death_link[self.player])]}
|
||||
patchDict = { 'MultiWorldLocations': multiWorldLocations,
|
||||
'MultiWorldItems': multiWorldItems,
|
||||
'offworldSprites': offworldSprites,
|
||||
'openTourianGreyDoors': openTourianGreyDoors,
|
||||
'deathLink': deathLink}
|
||||
romPatcher.applyIPSPatchDict(patchDict)
|
||||
|
||||
playerNames = {}
|
||||
for p in range(1, self.world.players + 1):
|
||||
playerNames[0x1C5000 + (p - 1) * 16] = self.world.player_name[p][:16].upper().center(16).encode()
|
||||
playerNames[0x1C5000 + (self.world.players) * 16] = "Archipelago".upper().center(16).encode()
|
||||
|
||||
romPatcher.applyIPSPatch('PlayerName', { 'PlayerName': playerNames })
|
||||
|
||||
# set rom name
|
||||
# 21 bytes
|
||||
from Main import __version__
|
||||
self.romName = bytearray(f'SM{__version__.replace(".", "")[0:3]}_{self.player}_{self.world.seed:11}\0', 'utf8')[:21]
|
||||
self.romName.extend([0] * (21 - len(self.romName)))
|
||||
romPatcher.applyIPSPatch('ROMName', { 'ROMName': {0x1C4F00 : self.romName, 0x007FC0 : self.romName} })
|
||||
|
||||
startItemROMAddressBase = 0x2FD8B9
|
||||
|
||||
# current, base value or bitmask, max, base value or bitmask
|
||||
startItemROMDict = {'ETank': [0x8, 0x64, 0xA, 0x64],
|
||||
'Missile': [0xC, 0x5, 0xE, 0x5],
|
||||
'Super': [0x10, 0x5, 0x12, 0x5],
|
||||
'PowerBomb': [0x14, 0x5, 0x16, 0x5],
|
||||
'Reserve': [0x1A, 0x64, 0x18, 0x64],
|
||||
'Morph': [0x2, 0x4, 0x0, 0x4],
|
||||
'Bomb': [0x3, 0x10, 0x1, 0x10],
|
||||
'SpringBall': [0x2, 0x2, 0x0, 0x2],
|
||||
'HiJump': [0x3, 0x1, 0x1, 0x1],
|
||||
'Varia': [0x2, 0x1, 0x0, 0x1],
|
||||
'Gravity': [0x2, 0x20, 0x0, 0x20],
|
||||
'SpeedBooster': [0x3, 0x20, 0x1, 0x20],
|
||||
'SpaceJump': [0x3, 0x2, 0x1, 0x2],
|
||||
'ScrewAttack': [0x2, 0x8, 0x0, 0x8],
|
||||
'Charge': [0x7, 0x10, 0x5, 0x10],
|
||||
'Ice': [0x6, 0x2, 0x4, 0x2],
|
||||
'Wave': [0x6, 0x1, 0x4, 0x1],
|
||||
'Spazer': [0x6, 0x4, 0x4, 0x4],
|
||||
'Plasma': [0x6, 0x8, 0x4, 0x8],
|
||||
'Grapple': [0x3, 0x40, 0x1, 0x40],
|
||||
'XRayScope': [0x3, 0x80, 0x1, 0x80]
|
||||
}
|
||||
mergedData = {}
|
||||
hasETank = False
|
||||
hasSpazer = False
|
||||
hasPlasma = False
|
||||
for startItem in self.startItems:
|
||||
item = startItem.Type
|
||||
if item == 'ETank': hasETank = True
|
||||
if item == 'Spazer': hasSpazer = True
|
||||
if item == 'Plasma': hasPlasma = True
|
||||
if (item in ['ETank', 'Missile', 'Super', 'PowerBomb', 'Reserve']):
|
||||
(currentValue, currentBase, maxValue, maxBase) = startItemROMDict[item]
|
||||
if (startItemROMAddressBase + currentValue) in mergedData:
|
||||
mergedData[startItemROMAddressBase + currentValue] += currentBase
|
||||
mergedData[startItemROMAddressBase + maxValue] += maxBase
|
||||
else:
|
||||
mergedData[startItemROMAddressBase + currentValue] = currentBase
|
||||
mergedData[startItemROMAddressBase + maxValue] = maxBase
|
||||
else:
|
||||
(collected, currentBitmask, equipped, maxBitmask) = startItemROMDict[item]
|
||||
if (startItemROMAddressBase + collected) in mergedData:
|
||||
mergedData[startItemROMAddressBase + collected] |= currentBitmask
|
||||
mergedData[startItemROMAddressBase + equipped] |= maxBitmask
|
||||
else:
|
||||
mergedData[startItemROMAddressBase + collected] = currentBitmask
|
||||
mergedData[startItemROMAddressBase + equipped] = maxBitmask
|
||||
|
||||
if hasETank:
|
||||
mergedData[startItemROMAddressBase + 0x8] += 99
|
||||
mergedData[startItemROMAddressBase + 0xA] += 99
|
||||
|
||||
if hasSpazer and hasPlasma:
|
||||
mergedData[startItemROMAddressBase + 0x4] &= ~0x4
|
||||
|
||||
for key, value in mergedData.items():
|
||||
if (key - startItemROMAddressBase > 7):
|
||||
(w0, w1) = self.getWord(value)
|
||||
mergedData[key] = [w0, w1]
|
||||
else:
|
||||
mergedData[key] = [value]
|
||||
|
||||
|
||||
startItemPatch = { 'startItemPatch': mergedData }
|
||||
romPatcher.applyIPSPatch('startItemPatch', startItemPatch)
|
||||
|
||||
romPatcher.commitIPS()
|
||||
|
||||
itemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type if itemLoc.item.type in ItemManager.Items else 'ArchipelagoItem'], locationsDict[itemLoc.name], True) for itemLoc in self.world.get_locations() if itemLoc.player == self.player]
|
||||
romPatcher.writeItemsLocs(itemLocs)
|
||||
|
||||
itemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.world.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.world.get_locations() if itemLoc.item.player == self.player]
|
||||
progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.world.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.world.get_locations() if itemLoc.item.player == self.player and itemLoc.item.advancement == True]
|
||||
# progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type if itemLoc.item.type in ItemManager.Items else 'ArchipelagoItem'], locationsDict[itemLoc.name], True) for itemLoc in self.world.get_locations() if itemLoc.player == self.player and itemLoc.item.player == self.player and itemLoc.item.advancement == True]
|
||||
|
||||
# romPatcher.writeSplitLocs(self.variaRando.args.majorsSplit, itemLocs, progItemLocs)
|
||||
romPatcher.writeSpoiler(itemLocs, progItemLocs)
|
||||
romPatcher.writeRandoSettings(self.variaRando.randoExec.randoSettings, itemLocs)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
try:
|
||||
outfilebase = 'AP_' + self.world.seed_name
|
||||
outfilepname = f'_P{self.player}'
|
||||
outfilepname += f"_{self.world.player_name[self.player].replace(' ', '_')}" \
|
||||
|
||||
outputFilename = os.path.join(output_directory, f'{outfilebase}{outfilepname}.sfc')
|
||||
self.variaRando.PatchRom(outputFilename, self.APPatchRom)
|
||||
|
||||
self.write_crc(outputFilename)
|
||||
|
||||
Patch.create_patch_file(outputFilename, player=self.player, player_name=self.world.player_name[self.player], game=Patch.GAME_SM)
|
||||
os.unlink(outputFilename)
|
||||
self.rom_name = self.romName
|
||||
except:
|
||||
raise
|
||||
finally:
|
||||
self.rom_name_available_event.set() # make sure threading continues and errors are collected
|
||||
|
||||
def checksum_mirror_sum(self, start, length, mask = 0x800000):
|
||||
while (not(length & mask) and mask):
|
||||
mask >>= 1
|
||||
|
||||
part1 = sum(start[:mask]) & 0xFFFF
|
||||
part2 = 0
|
||||
|
||||
next_length = length - mask
|
||||
if next_length:
|
||||
part2 = self.checksum_mirror_sum(start[mask:], next_length, mask >> 1)
|
||||
|
||||
while (next_length < mask):
|
||||
next_length += next_length
|
||||
part2 += part2
|
||||
|
||||
length = mask + mask
|
||||
|
||||
return (part1 + part2) & 0xFFFF
|
||||
|
||||
def write_bytes(self, buffer, startaddress: int, values):
|
||||
buffer[startaddress:startaddress + len(values)] = values
|
||||
|
||||
def write_crc(self, romName):
|
||||
with open(romName, 'rb') as stream:
|
||||
buffer = bytearray(stream.read())
|
||||
crc = self.checksum_mirror_sum(buffer, len(buffer))
|
||||
inv = crc ^ 0xFFFF
|
||||
self.write_bytes(buffer, 0x7FDC, [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF])
|
||||
with open(romName, 'wb') as outfile:
|
||||
outfile.write(buffer)
|
||||
|
||||
def modify_multidata(self, multidata: dict):
|
||||
import base64
|
||||
# wait for self.rom_name to be available.
|
||||
self.rom_name_available_event.wait()
|
||||
rom_name = getattr(self, "rom_name", None)
|
||||
# we skip in case of error, so that the original error in the output thread is the one that gets raised
|
||||
if rom_name:
|
||||
new_name = base64.b64encode(bytes(self.rom_name)).decode()
|
||||
payload = multidata["connect_names"][self.world.player_name[self.player]]
|
||||
multidata["connect_names"][new_name] = payload
|
||||
del (multidata["connect_names"][self.world.player_name[self.player]])
|
||||
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = {}
|
||||
return slot_data
|
||||
|
||||
def collect(self, state: CollectionState, item: Item) -> bool:
|
||||
state.smbm[item.player].addItem(item.type)
|
||||
if item.advancement:
|
||||
state.prog_items[item.name, item.player] += 1
|
||||
return True # indicate that a logical state change has occured
|
||||
return False
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
item = next(x for x in ItemManager.Items.values() if x.Name == name)
|
||||
return SMItem(item.Name, True, item.Type, self.item_name_to_id[item.Name], player = self.player)
|
||||
|
||||
def pre_fill(self):
|
||||
if (self.variaRando.args.morphPlacement == "early") and next((item for item in self.world.itempool if item.player == self.player and item.name == "Morph Ball"), False):
|
||||
viable = []
|
||||
for location in self.world.get_locations():
|
||||
if location.player == self.player \
|
||||
and location.item is None \
|
||||
and location.can_reach(self.world.state):
|
||||
viable.append(location)
|
||||
self.world.random.shuffle(viable)
|
||||
key = self.world.create_item("Morph Ball", self.player)
|
||||
loc = viable.pop()
|
||||
loc.place_locked_item(key)
|
||||
self.world.itempool[:] = [item for item in self.world.itempool if
|
||||
item.player != self.player or
|
||||
item.name != "Morph Ball"]
|
||||
|
||||
def post_fill(self):
|
||||
# increase maxDifficulty if only bosses is too difficult to beat game
|
||||
new_state = CollectionState(self.world)
|
||||
for item in self.world.itempool:
|
||||
if item.player == self.player:
|
||||
new_state.collect(item, True)
|
||||
new_state.sweep_for_events()
|
||||
if (any(not self.world.get_location(bossLoc, self.player).can_reach(new_state) for bossLoc in self.locked_items)):
|
||||
if (self.variaRando.randoExec.setup.services.onlyBossesLeft(self.variaRando.randoExec.setup.startAP, self.variaRando.randoExec.setup.container)):
|
||||
self.world.state.smbm[self.player].maxDiff = infinity
|
||||
|
||||
@classmethod
|
||||
def stage_fill_hook(cls, world, progitempool, nonexcludeditempool, localrestitempool, nonlocalrestitempool,
|
||||
restitempool, fill_locations):
|
||||
if world.get_game_players("Super Metroid"):
|
||||
progitempool.sort(
|
||||
key=lambda item: 1 if (item.name == 'Morph Ball') else 0)
|
||||
|
||||
def create_locations(self, player: int):
|
||||
for name, id in locations_lookup_name_to_id.items():
|
||||
self.locations[name] = SMLocation(player, name, id)
|
||||
|
||||
def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
ret = Region(name, RegionType.LightWorld, name, player)
|
||||
ret.world = world
|
||||
if locations:
|
||||
for loc in locations:
|
||||
location = self.locations[loc]
|
||||
location.parent_region = ret
|
||||
ret.locations.append(location)
|
||||
if exits:
|
||||
for exit in exits:
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
return ret
|
||||
|
||||
|
||||
class SMLocation(Location):
|
||||
game: str = "Super Metroid"
|
||||
|
||||
def __init__(self, player: int, name: str, address=None, parent=None):
|
||||
super(SMLocation, self).__init__(player, name, address, parent)
|
||||
|
||||
def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool:
|
||||
return self.always_allow(state, item) or (self.item_rule(item) and (not check_access or self.can_reach(state)))
|
||||
|
||||
|
||||
class SMItem(Item):
|
||||
game = "Super Metroid"
|
||||
|
||||
def __init__(self, name, advancement, type, code, player: int = None):
|
||||
super(SMItem, self).__init__(name, advancement, code, player)
|
||||
self.type = type
|
||||
BIN
worlds/sm/data/custom_sprite/off_world_item.bin
Normal file
BIN
worlds/sm/data/custom_sprite/off_world_item.bin
Normal file
Binary file not shown.
BIN
worlds/sm/data/custom_sprite/off_world_prog_item.bin
Normal file
BIN
worlds/sm/data/custom_sprite/off_world_prog_item.bin
Normal file
Binary file not shown.
674
worlds/sm/variaRandomizer/LICENSE
Normal file
674
worlds/sm/variaRandomizer/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
3
worlds/sm/variaRandomizer/__init__.py
Normal file
3
worlds/sm/variaRandomizer/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
0
worlds/sm/variaRandomizer/graph/__init__.py
Normal file
0
worlds/sm/variaRandomizer/graph/__init__.py
Normal file
413
worlds/sm/variaRandomizer/graph/graph.py
Normal file
413
worlds/sm/variaRandomizer/graph/graph.py
Normal file
@@ -0,0 +1,413 @@
|
||||
import copy, logging
|
||||
from operator import attrgetter
|
||||
import utils.log
|
||||
from logic.smbool import SMBool, smboolFalse
|
||||
from utils.parameters import infinity
|
||||
from logic.helpers import Bosses
|
||||
|
||||
class Path(object):
|
||||
__slots__ = ( 'path', 'pdiff', 'distance' )
|
||||
|
||||
def __init__(self, path, pdiff, distance):
|
||||
self.path = path
|
||||
self.pdiff = pdiff
|
||||
self.distance = distance
|
||||
|
||||
class AccessPoint(object):
|
||||
# name : AccessPoint name
|
||||
# graphArea : graph area the node is located in
|
||||
# transitions : intra-area transitions
|
||||
# traverse: traverse function, will be wand to the added transitions
|
||||
# exitInfo : dict carrying vanilla door information : 'DoorPtr': door address, 'direction', 'cap', 'screen', 'bitFlag', 'distanceToSpawn', 'doorAsmPtr' : door properties
|
||||
# entryInfo : dict carrying forced samus X/Y position with keys 'SamusX' and 'SamusY'.
|
||||
# (to be updated after reading vanillaTransitions and gather entry info from matching exit door)
|
||||
# roomInfo : dict with 'RoomPtr' : room address, 'area'
|
||||
# shortName : short name for the credits
|
||||
# internal : if true, shall not be used for connecting areas
|
||||
def __init__(self, name, graphArea, transitions,
|
||||
traverse=lambda sm: SMBool(True),
|
||||
exitInfo=None, entryInfo=None, roomInfo=None,
|
||||
internal=False, boss=False, escape=False,
|
||||
start=None,
|
||||
dotOrientation='w'):
|
||||
self.Name = name
|
||||
self.GraphArea = graphArea
|
||||
self.ExitInfo = exitInfo
|
||||
self.EntryInfo = entryInfo
|
||||
self.RoomInfo = roomInfo
|
||||
self.Internal = internal
|
||||
self.Boss = boss
|
||||
self.Escape = escape
|
||||
self.Start = start
|
||||
self.DotOrientation = dotOrientation
|
||||
self.intraTransitions = self.sortTransitions(transitions)
|
||||
self.transitions = copy.copy(self.intraTransitions)
|
||||
self.traverse = traverse
|
||||
self.distance = 0
|
||||
# inter-area connection
|
||||
self.ConnectedTo = None
|
||||
|
||||
def __copy__(self):
|
||||
exitInfo = copy.deepcopy(self.ExitInfo) if self.ExitInfo is not None else None
|
||||
entryInfo = copy.deepcopy(self.EntryInfo) if self.EntryInfo is not None else None
|
||||
roomInfo = copy.deepcopy(self.RoomInfo) if self.RoomInfo is not None else None
|
||||
start = copy.deepcopy(self.Start) if self.Start is not None else None
|
||||
# in any case, do not copy connections
|
||||
return AccessPoint(self.Name, self.GraphArea, self.intraTransitions, self.traverse,
|
||||
exitInfo, entryInfo, roomInfo,
|
||||
self.Internal, self.Boss, self.Escape,
|
||||
start, self.DotOrientation)
|
||||
|
||||
def __str__(self):
|
||||
return "[" + self.GraphArea + "] " + self.Name
|
||||
|
||||
def __repr__(self):
|
||||
return self.Name
|
||||
|
||||
def sortTransitions(self, transitions=None):
|
||||
# sort transitions before the loop in getNewAvailNodes.
|
||||
# as of python3.7 insertion order is guaranteed in dictionaires.
|
||||
if transitions is None:
|
||||
transitions = self.transitions
|
||||
return { key: transitions[key] for key in sorted(transitions.keys()) }
|
||||
|
||||
# connect to inter-area access point
|
||||
def connect(self, destName):
|
||||
self.disconnect()
|
||||
if self.Internal is False:
|
||||
self.transitions[destName] = self.traverse
|
||||
self.ConnectedTo = destName
|
||||
else:
|
||||
raise RuntimeError("Cannot add an internal access point as inter-are transition")
|
||||
self.transitions = self.sortTransitions()
|
||||
|
||||
def disconnect(self):
|
||||
if self.ConnectedTo is not None:
|
||||
if self.ConnectedTo not in self.intraTransitions:
|
||||
del self.transitions[self.ConnectedTo]
|
||||
else:
|
||||
self.transitions[self.ConnectedTo] = self.intraTransitions[self.ConnectedTo]
|
||||
self.ConnectedTo = None
|
||||
|
||||
# tells if this node is to connect areas together
|
||||
def isArea(self):
|
||||
return not self.Internal and not self.Boss and not self.Escape
|
||||
|
||||
# used by the solver to get area and boss APs
|
||||
def isInternal(self):
|
||||
return self.Internal or self.Escape
|
||||
|
||||
def isLoop(self):
|
||||
return self.ConnectedTo == self.Name
|
||||
|
||||
class AccessGraph(object):
|
||||
__slots__ = ( 'log', 'accessPoints', 'InterAreaTransitions',
|
||||
'EscapeAttributes', 'apCache', '_useCache',
|
||||
'availAccessPoints' )
|
||||
|
||||
def __init__(self, accessPointList, transitions, dotFile=None):
|
||||
self.log = utils.log.get('Graph')
|
||||
self.accessPoints = {}
|
||||
self.InterAreaTransitions = []
|
||||
self.EscapeAttributes = {
|
||||
'Timer': None,
|
||||
'Animals': None
|
||||
}
|
||||
for ap in accessPointList:
|
||||
self.addAccessPoint(ap)
|
||||
for srcName, dstName in transitions:
|
||||
self.addTransition(srcName, dstName)
|
||||
if dotFile is not None:
|
||||
self.toDot(dotFile)
|
||||
self.apCache = {}
|
||||
self._useCache = False
|
||||
# store the avail access points to display in vcr
|
||||
self.availAccessPoints = {}
|
||||
|
||||
def useCache(self, use):
|
||||
self._useCache = use
|
||||
if self._useCache:
|
||||
self.resetCache()
|
||||
|
||||
def resetCache(self):
|
||||
self.apCache = {}
|
||||
|
||||
def printGraph(self):
|
||||
if self.log.getEffectiveLevel() == logging.DEBUG:
|
||||
self.log("Area graph:")
|
||||
for s, d in self.InterAreaTransitions:
|
||||
self.log("{} -> {}".format(s.Name, d.Name))
|
||||
|
||||
def addAccessPoint(self, ap):
|
||||
ap.distance = 0
|
||||
self.accessPoints[ap.Name] = ap
|
||||
|
||||
def toDot(self, dotFile):
|
||||
colors = ['red', 'blue', 'green', 'yellow', 'skyblue', 'violet', 'orange',
|
||||
'lawngreen', 'crimson', 'chocolate', 'turquoise', 'tomato',
|
||||
'navyblue', 'darkturquoise', 'green', 'blue', 'maroon', 'magenta',
|
||||
'bisque', 'coral', 'chartreuse', 'chocolate', 'cyan']
|
||||
with open(dotFile, "w") as f:
|
||||
f.write("digraph {\n")
|
||||
f.write('size="30,30!";\n')
|
||||
f.write('rankdir=LR;\n')
|
||||
f.write('ranksep=2.2;\n')
|
||||
f.write('overlap=scale;\n')
|
||||
f.write('edge [dir="both",arrowhead="box",arrowtail="box",arrowsize=0.5,fontsize=7,style=dotted];\n')
|
||||
f.write('node [shape="box",fontsize=10];\n')
|
||||
for area in set([ap.GraphArea for ap in self.accessPoints.values()]):
|
||||
f.write(area + ";\n") # TODO area long name and color
|
||||
drawn = []
|
||||
i = 0
|
||||
for src, dst in self.InterAreaTransitions:
|
||||
if src.Name in drawn:
|
||||
continue
|
||||
f.write('%s:%s -> %s:%s [taillabel="%s",headlabel="%s",color=%s];\n' % (src.GraphArea, src.DotOrientation, dst.GraphArea, dst.DotOrientation, src.Name, dst.Name, colors[i]))
|
||||
drawn += [src.Name,dst.Name]
|
||||
i += 1
|
||||
f.write("}\n")
|
||||
|
||||
def addTransition(self, srcName, dstName, both=True):
|
||||
src = self.accessPoints[srcName]
|
||||
dst = self.accessPoints[dstName]
|
||||
src.connect(dstName)
|
||||
self.InterAreaTransitions.append((src, dst))
|
||||
if both is True:
|
||||
self.addTransition(dstName, srcName, False)
|
||||
|
||||
# availNodes: all already available nodes
|
||||
# nodesToCheck: nodes we have to check transitions for
|
||||
# smbm: smbm to test logic on. if None, discard logic check, assume we can reach everything
|
||||
# maxDiff: difficulty limit
|
||||
# return newly opened access points
|
||||
def getNewAvailNodes(self, availNodes, nodesToCheck, smbm, maxDiff, item=None):
|
||||
newAvailNodes = {}
|
||||
# with python >= 3.6 the insertion order in a dict is keeps when looping on the keys,
|
||||
# so we no longer have to sort them.
|
||||
for src in nodesToCheck:
|
||||
for dstName in src.transitions:
|
||||
dst = self.accessPoints[dstName]
|
||||
if dst in availNodes or dst in newAvailNodes:
|
||||
continue
|
||||
if smbm is not None:
|
||||
if self._useCache == True and (src, dst, item) in self.apCache:
|
||||
diff = self.apCache[(src, dst, item)]
|
||||
else:
|
||||
tFunc = src.transitions[dstName]
|
||||
diff = tFunc(smbm)
|
||||
if self._useCache == True:
|
||||
self.apCache[(src, dst, item)] = diff
|
||||
else:
|
||||
diff = SMBool(True)
|
||||
if diff.bool and diff.difficulty <= maxDiff:
|
||||
if src.GraphArea == dst.GraphArea:
|
||||
dst.distance = src.distance + 0.01
|
||||
else:
|
||||
dst.distance = src.distance + 1
|
||||
newAvailNodes[dst] = { 'difficulty': diff, 'from': src }
|
||||
|
||||
#self.log.debug("{} -> {}: {}".format(src.Name, dstName, diff))
|
||||
return newAvailNodes
|
||||
|
||||
# rootNode: starting AccessPoint instance
|
||||
# smbm: smbm to test logic on. if None, discard logic check, assume we can reach everything
|
||||
# maxDiff: difficulty limit.
|
||||
# smbm: if None, discard logic check, assume we can reach everything
|
||||
# return available AccessPoint list
|
||||
def getAvailableAccessPoints(self, rootNode, smbm, maxDiff, item=None):
|
||||
availNodes = { rootNode : { 'difficulty' : SMBool(True, 0), 'from' : None } }
|
||||
newAvailNodes = availNodes
|
||||
rootNode.distance = 0
|
||||
while len(newAvailNodes) > 0:
|
||||
newAvailNodes = self.getNewAvailNodes(availNodes, newAvailNodes, smbm, maxDiff, item)
|
||||
availNodes.update(newAvailNodes)
|
||||
return availNodes
|
||||
|
||||
# gets path from the root AP used to compute availAps
|
||||
def getPath(self, dstAp, availAps):
|
||||
path = []
|
||||
root = dstAp
|
||||
while root != None:
|
||||
path = [root] + path
|
||||
root = availAps[root]['from']
|
||||
|
||||
return path
|
||||
|
||||
def getAvailAPPaths(self, availAccessPoints, locsAPs):
|
||||
paths = {}
|
||||
for ap in availAccessPoints:
|
||||
if ap.Name in locsAPs:
|
||||
path = self.getPath(ap, availAccessPoints)
|
||||
pdiff = SMBool.wandmax(*(availAccessPoints[ap]['difficulty'] for ap in path))
|
||||
paths[ap.Name] = Path(path, pdiff, len(path))
|
||||
return paths
|
||||
|
||||
def getSortedAPs(self, paths, locAccessFrom):
|
||||
ret = []
|
||||
|
||||
for apName in locAccessFrom:
|
||||
path = paths.get(apName, None)
|
||||
if path is None:
|
||||
continue
|
||||
difficulty = paths[apName].pdiff.difficulty
|
||||
ret.append((difficulty if difficulty != -1 else infinity, path.distance, apName))
|
||||
ret.sort()
|
||||
return [apName for diff, dist, apName in ret]
|
||||
|
||||
# locations: locations to check
|
||||
# items: collected items
|
||||
# maxDiff: difficulty limit
|
||||
# rootNode: starting AccessPoint
|
||||
# return available locations list, also stores difficulty in locations
|
||||
def getAvailableLocations(self, locations, smbm, maxDiff, rootNode='Landing Site'):
|
||||
rootAp = self.accessPoints[rootNode]
|
||||
self.availAccessPoints = self.getAvailableAccessPoints(rootAp, smbm, maxDiff)
|
||||
availAreas = set([ap.GraphArea for ap in self.availAccessPoints.keys()])
|
||||
availLocs = []
|
||||
|
||||
# get all the current locations APs first to only compute these paths
|
||||
locsAPs = set()
|
||||
for loc in locations:
|
||||
for ap in loc.AccessFrom:
|
||||
locsAPs.add(ap)
|
||||
|
||||
# sort availAccessPoints based on difficulty to take easier paths first
|
||||
availAPPaths = self.getAvailAPPaths(self.availAccessPoints, locsAPs)
|
||||
|
||||
for loc in locations:
|
||||
if loc.GraphArea not in availAreas:
|
||||
loc.distance = 30000
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} locDiff is area nok".format(loc.Name))
|
||||
continue
|
||||
|
||||
locAPs = self.getSortedAPs(availAPPaths, loc.AccessFrom)
|
||||
if len(locAPs) == 0:
|
||||
loc.distance = 40000
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} no aps".format(loc.Name))
|
||||
continue
|
||||
|
||||
for apName in locAPs:
|
||||
if apName == None:
|
||||
loc.distance = 20000
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} ap is none".format(loc.Name))
|
||||
break
|
||||
|
||||
tFunc = loc.AccessFrom[apName]
|
||||
ap = self.accessPoints[apName]
|
||||
tdiff = tFunc(smbm)
|
||||
#if loc.Name == "Kraid":
|
||||
# print("{} root: {} ap: {}".format(loc.Name, rootNode, apName))
|
||||
if tdiff.bool == True and tdiff.difficulty <= maxDiff:
|
||||
diff = loc.Available(smbm)
|
||||
if diff.bool == True:
|
||||
path = availAPPaths[apName].path
|
||||
#if loc.Name == "Kraid":
|
||||
# print("{} path: {}".format(loc.Name, [a.Name for a in path]))
|
||||
pdiff = availAPPaths[apName].pdiff
|
||||
(allDiff, locDiff) = self.computeLocDiff(tdiff, diff, pdiff)
|
||||
if allDiff.bool == True and allDiff.difficulty <= maxDiff:
|
||||
loc.distance = ap.distance + 1
|
||||
loc.accessPoint = apName
|
||||
loc.difficulty = allDiff
|
||||
loc.path = path
|
||||
# used only by solver
|
||||
loc.pathDifficulty = pdiff
|
||||
loc.locDifficulty = locDiff
|
||||
availLocs.append(loc)
|
||||
#if loc.Name == "Kraid":
|
||||
# print("{} diff: {} tdiff: {} pdiff: {}".format(loc.Name, diff, tdiff, pdiff))
|
||||
break
|
||||
else:
|
||||
loc.distance = 1000 + tdiff.difficulty
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} allDiff is false".format(loc.Name))
|
||||
else:
|
||||
loc.distance = 1000 + tdiff.difficulty
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} allDiff is false".format(loc.Name))
|
||||
else:
|
||||
loc.distance = 10000 + tdiff.difficulty
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} tdiff is false".format(loc.Name))
|
||||
|
||||
if loc.difficulty is None:
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} no difficulty in loc".format(loc.Name))
|
||||
loc.distance = 100000
|
||||
loc.difficulty = smboolFalse
|
||||
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {}: {}".format(loc.Name, loc))
|
||||
|
||||
#print("availableLocs: {}".format([loc.Name for loc in availLocs]))
|
||||
return availLocs
|
||||
|
||||
# test access from an access point to another, given an optional item
|
||||
def canAccess(self, smbm, srcAccessPointName, destAccessPointName, maxDiff, item=None):
|
||||
if item is not None:
|
||||
smbm.addItem(item)
|
||||
#print("canAccess: item: {}, src: {}, dest: {}".format(item, srcAccessPointName, destAccessPointName))
|
||||
destAccessPoint = self.accessPoints[destAccessPointName]
|
||||
srcAccessPoint = self.accessPoints[srcAccessPointName]
|
||||
availAccessPoints = self.getAvailableAccessPoints(srcAccessPoint, smbm, maxDiff, item)
|
||||
can = destAccessPoint in availAccessPoints
|
||||
# if not can:
|
||||
# self.log.debug("canAccess KO: avail = {}".format([ap.Name for ap in availAccessPoints.keys()]))
|
||||
if item is not None:
|
||||
smbm.removeItem(item)
|
||||
#print("canAccess: {}".format(can))
|
||||
return can
|
||||
|
||||
# returns a list of AccessPoint instances from srcAccessPointName to destAccessPointName
|
||||
# (not including source ap)
|
||||
# or None if no possible path
|
||||
def accessPath(self, smbm, srcAccessPointName, destAccessPointName, maxDiff):
|
||||
destAccessPoint = self.accessPoints[destAccessPointName]
|
||||
srcAccessPoint = self.accessPoints[srcAccessPointName]
|
||||
availAccessPoints = self.getAvailableAccessPoints(srcAccessPoint, smbm, maxDiff)
|
||||
if destAccessPoint not in availAccessPoints:
|
||||
return None
|
||||
return self.getPath(destAccessPoint, availAccessPoints)
|
||||
|
||||
# gives theoretically accessible APs in the graph (no logic check)
|
||||
def getAccessibleAccessPoints(self, rootNode='Landing Site'):
|
||||
rootAp = self.accessPoints[rootNode]
|
||||
inBossChk = lambda ap: ap.Boss and ap.Name.endswith("In")
|
||||
allAreas = {dst.GraphArea for (src, dst) in self.InterAreaTransitions if not inBossChk(dst) and not dst.isLoop()}
|
||||
self.log.debug("allAreas="+str(allAreas))
|
||||
nonBossAPs = [ap for ap in self.getAvailableAccessPoints(rootAp, None, 0) if ap.GraphArea in allAreas]
|
||||
bossesAPs = [self.accessPoints[boss+'RoomIn'] for boss in Bosses.Golden4()] + [self.accessPoints['Draygon Room Bottom']]
|
||||
return nonBossAPs + bossesAPs
|
||||
|
||||
# gives theoretically accessible locations within a base list
|
||||
# returns locations with accessible GraphArea in this graph (no logic considered)
|
||||
def getAccessibleLocations(self, locations, rootNode='Landing Site'):
|
||||
availAccessPoints = self.getAccessibleAccessPoints(rootNode)
|
||||
self.log.debug("availAccessPoints="+str([ap.Name for ap in availAccessPoints]))
|
||||
return [loc for loc in locations if any(ap.Name in loc.AccessFrom for ap in availAccessPoints)]
|
||||
|
||||
class AccessGraphSolver(AccessGraph):
|
||||
def computeLocDiff(self, tdiff, diff, pdiff):
|
||||
# tdiff: difficulty from the location's access point to the location's room
|
||||
# diff: difficulty to reach the item in the location's room
|
||||
# pdiff: difficulty of the path from the current access point to the location's access point
|
||||
# in output we need the global difficulty but we also need to separate pdiff and (tdiff + diff)
|
||||
|
||||
locDiff = SMBool.wandmax(tdiff, diff)
|
||||
allDiff = SMBool.wandmax(locDiff, pdiff)
|
||||
|
||||
return (allDiff, locDiff)
|
||||
|
||||
class AccessGraphRando(AccessGraph):
|
||||
def computeLocDiff(self, tdiff, diff, pdiff):
|
||||
allDiff = SMBool.wandmax(tdiff, diff, pdiff)
|
||||
return (allDiff, None)
|
||||
575
worlds/sm/variaRandomizer/graph/graph_utils.py
Normal file
575
worlds/sm/variaRandomizer/graph/graph_utils.py
Normal file
@@ -0,0 +1,575 @@
|
||||
import copy
|
||||
import random
|
||||
from logic.logic import Logic
|
||||
from utils.parameters import Knows
|
||||
from graph.location import locationsDict
|
||||
from rom.rom import snes_to_pc
|
||||
import utils.log
|
||||
|
||||
# order expected by ROM patches
|
||||
graphAreas = [
|
||||
"Ceres",
|
||||
"Crateria",
|
||||
"GreenPinkBrinstar",
|
||||
"RedBrinstar",
|
||||
"WreckedShip",
|
||||
"Kraid",
|
||||
"Norfair",
|
||||
"Crocomire",
|
||||
"LowerNorfair",
|
||||
"WestMaridia",
|
||||
"EastMaridia",
|
||||
"Tourian"
|
||||
]
|
||||
|
||||
vanillaTransitions = [
|
||||
('Lower Mushrooms Left', 'Green Brinstar Elevator'),
|
||||
('Morph Ball Room Left', 'Green Hill Zone Top Right'),
|
||||
('Moat Right', 'West Ocean Left'),
|
||||
('Keyhunter Room Bottom', 'Red Brinstar Elevator'),
|
||||
('Noob Bridge Right', 'Red Tower Top Left'),
|
||||
('Crab Maze Left', 'Le Coude Right'),
|
||||
('Kronic Boost Room Bottom Left', 'Lava Dive Right'),
|
||||
('Crocomire Speedway Bottom', 'Crocomire Room Top'),
|
||||
('Three Muskateers Room Left', 'Single Chamber Top Right'),
|
||||
('Warehouse Entrance Left', 'East Tunnel Right'),
|
||||
('East Tunnel Top Right', 'Crab Hole Bottom Left'),
|
||||
('Caterpillar Room Top Right', 'Red Fish Room Left'),
|
||||
('Glass Tunnel Top', 'Main Street Bottom'),
|
||||
('Green Pirates Shaft Bottom Right', 'Golden Four'),
|
||||
('Warehouse Entrance Right', 'Warehouse Zeela Room Left'),
|
||||
('Crab Shaft Right', 'Aqueduct Top Left')
|
||||
]
|
||||
|
||||
vanillaBossesTransitions = [
|
||||
('KraidRoomOut', 'KraidRoomIn'),
|
||||
('PhantoonRoomOut', 'PhantoonRoomIn'),
|
||||
('DraygonRoomOut', 'DraygonRoomIn'),
|
||||
('RidleyRoomOut', 'RidleyRoomIn')
|
||||
]
|
||||
|
||||
# vanilla escape transition in first position
|
||||
vanillaEscapeTransitions = [
|
||||
('Tourian Escape Room 4 Top Right', 'Climb Bottom Left'),
|
||||
('Brinstar Pre-Map Room Right', 'Green Brinstar Main Shaft Top Left'),
|
||||
('Wrecked Ship Map Room', 'Basement Left'),
|
||||
('Norfair Map Room', 'Business Center Mid Left'),
|
||||
('Maridia Map Room', 'Crab Hole Bottom Right')
|
||||
]
|
||||
|
||||
vanillaEscapeAnimalsTransitions = [
|
||||
('Flyway Right 0', 'Bomb Torizo Room Left'),
|
||||
('Flyway Right 1', 'Bomb Torizo Room Left'),
|
||||
('Flyway Right 2', 'Bomb Torizo Room Left'),
|
||||
('Flyway Right 3', 'Bomb Torizo Room Left'),
|
||||
('Bomb Torizo Room Left Animals', 'Flyway Right')
|
||||
]
|
||||
|
||||
escapeSource = 'Tourian Escape Room 4 Top Right'
|
||||
escapeTargets = ['Green Brinstar Main Shaft Top Left', 'Basement Left', 'Business Center Mid Left', 'Crab Hole Bottom Right']
|
||||
|
||||
locIdsByAreaAddresses = {
|
||||
"Ceres": snes_to_pc(0xA1F568),
|
||||
"Crateria": snes_to_pc(0xA1F569),
|
||||
"GreenPinkBrinstar": snes_to_pc(0xA1F57B),
|
||||
"RedBrinstar": snes_to_pc(0xA1F58C),
|
||||
"WreckedShip": snes_to_pc(0xA1F592),
|
||||
"Kraid": snes_to_pc(0xA1F59E),
|
||||
"Norfair": snes_to_pc(0xA1F5A2),
|
||||
"Crocomire": snes_to_pc(0xA1F5B2),
|
||||
"LowerNorfair": snes_to_pc(0xA1F5B8),
|
||||
"WestMaridia": snes_to_pc(0xA1F5C3),
|
||||
"EastMaridia": snes_to_pc(0xA1F5CB),
|
||||
"Tourian": snes_to_pc(0xA1F5D7)
|
||||
}
|
||||
|
||||
def getAccessPoint(apName, apList=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
return next(ap for ap in apList if ap.Name == apName)
|
||||
|
||||
class GraphUtils:
|
||||
log = utils.log.get('GraphUtils')
|
||||
|
||||
def getStartAccessPointNames():
|
||||
return [ap.Name for ap in Logic.accessPoints if ap.Start is not None]
|
||||
|
||||
def getStartAccessPointNamesCategory():
|
||||
ret = {'regular': [], 'custom': [], 'area': []}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Start == None:
|
||||
continue
|
||||
elif 'areaMode' in ap.Start and ap.Start['areaMode'] == True:
|
||||
ret['area'].append(ap.Name)
|
||||
elif GraphUtils.isStandardStart(ap.Name):
|
||||
ret['regular'].append(ap.Name)
|
||||
else:
|
||||
ret['custom'].append(ap.Name)
|
||||
return ret
|
||||
|
||||
def isStandardStart(startApName):
|
||||
return startApName == 'Ceres' or startApName == 'Landing Site'
|
||||
|
||||
def getPossibleStartAPs(areaMode, maxDiff, morphPlacement, player):
|
||||
ret = []
|
||||
refused = {}
|
||||
allStartAPs = GraphUtils.getStartAccessPointNames()
|
||||
for apName in allStartAPs:
|
||||
start = getAccessPoint(apName).Start
|
||||
ok = True
|
||||
cause = ""
|
||||
if 'knows' in start:
|
||||
for k in start['knows']:
|
||||
if not Knows.knowsDict[player].knows(k, maxDiff):
|
||||
ok = False
|
||||
cause += Knows.desc[k]['display']+" is not known. "
|
||||
break
|
||||
if 'areaMode' in start and start['areaMode'] != areaMode:
|
||||
ok = False
|
||||
cause += "Start location available only with area randomization enabled. "
|
||||
if 'forcedEarlyMorph' in start and start['forcedEarlyMorph'] == True and morphPlacement == 'late':
|
||||
ok = False
|
||||
cause += "Start location unavailable with late morph placement. "
|
||||
if ok:
|
||||
ret.append(apName)
|
||||
else:
|
||||
refused[apName] = cause
|
||||
return ret, refused
|
||||
|
||||
def updateLocClassesStart(startGraphArea, split, possibleMajLocs, preserveMajLocs, nLocs):
|
||||
locs = locationsDict
|
||||
preserveMajLocs = [locs[locName] for locName in preserveMajLocs if locs[locName].isClass(split)]
|
||||
possLocs = [locs[locName] for locName in possibleMajLocs][:nLocs]
|
||||
GraphUtils.log.debug("possLocs="+str([loc.Name for loc in possLocs]))
|
||||
candidates = [loc for loc in locs.values() if loc.GraphArea == startGraphArea and loc.isClass(split) and loc not in preserveMajLocs]
|
||||
remLocs = [loc for loc in locs.values() if loc not in possLocs and loc not in candidates and loc.isClass(split)]
|
||||
newLocs = []
|
||||
while len(newLocs) < nLocs:
|
||||
if len(candidates) == 0:
|
||||
candidates = remLocs
|
||||
loc = possLocs.pop(random.randint(0,len(possLocs)-1))
|
||||
newLocs.append(loc)
|
||||
loc.setClass([split])
|
||||
if not loc in preserveMajLocs:
|
||||
GraphUtils.log.debug("newMajor="+loc.Name)
|
||||
loc = candidates.pop(random.randint(0,len(candidates)-1))
|
||||
loc.setClass(["Minor"])
|
||||
GraphUtils.log.debug("replaced="+loc.Name)
|
||||
|
||||
def getGraphPatches(startApName):
|
||||
ap = getAccessPoint(startApName)
|
||||
return ap.Start['patches'] if 'patches' in ap.Start else []
|
||||
|
||||
def createBossesTransitions():
|
||||
transitions = vanillaBossesTransitions
|
||||
def isVanilla():
|
||||
for t in vanillaBossesTransitions:
|
||||
if t not in transitions:
|
||||
return False
|
||||
return True
|
||||
while isVanilla():
|
||||
transitions = []
|
||||
srcs = []
|
||||
dsts = []
|
||||
for (src,dst) in vanillaBossesTransitions:
|
||||
srcs.append(src)
|
||||
dsts.append(dst)
|
||||
while len(srcs) > 0:
|
||||
src = srcs.pop(random.randint(0,len(srcs)-1))
|
||||
dst = dsts.pop(random.randint(0,len(dsts)-1))
|
||||
transitions.append((src,dst))
|
||||
return transitions
|
||||
|
||||
def createAreaTransitions(lightAreaRando=False):
|
||||
if lightAreaRando:
|
||||
return GraphUtils.createLightAreaTransitions()
|
||||
else:
|
||||
return GraphUtils.createRegularAreaTransitions()
|
||||
|
||||
def createRegularAreaTransitions(apList=None, apPred=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
if apPred is None:
|
||||
apPred = lambda ap: ap.isArea()
|
||||
tFrom = []
|
||||
tTo = []
|
||||
apNames = [ap.Name for ap in apList if apPred(ap) == True]
|
||||
transitions = []
|
||||
|
||||
def findTo(trFrom):
|
||||
ap = getAccessPoint(trFrom, apList)
|
||||
fromArea = ap.GraphArea
|
||||
targets = [apName for apName in apNames if apName not in tTo and getAccessPoint(apName, apList).GraphArea != fromArea]
|
||||
if len(targets) == 0: # fallback if no area transition is found
|
||||
targets = [apName for apName in apNames if apName != ap.Name]
|
||||
if len(targets) == 0: # extreme fallback: loop on itself
|
||||
targets = [ap.Name]
|
||||
return random.choice(targets)
|
||||
|
||||
def addTransition(src, dst):
|
||||
tFrom.append(src)
|
||||
tTo.append(dst)
|
||||
|
||||
while len(apNames) > 0:
|
||||
sources = [apName for apName in apNames if apName not in tFrom]
|
||||
src = random.choice(sources)
|
||||
dst = findTo(src)
|
||||
transitions.append((src, dst))
|
||||
addTransition(src, dst)
|
||||
addTransition(dst, src)
|
||||
toRemove = [apName for apName in apNames if apName in tFrom and apName in tTo]
|
||||
for apName in toRemove:
|
||||
apNames.remove(apName)
|
||||
return transitions
|
||||
|
||||
def getAPs(apPredicate, apList=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
return [ap for ap in apList if apPredicate(ap) == True]
|
||||
|
||||
def loopUnusedTransitions(transitions, apList=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
usedAPs = set()
|
||||
for (src,dst) in transitions:
|
||||
usedAPs.add(getAccessPoint(src, apList))
|
||||
usedAPs.add(getAccessPoint(dst, apList))
|
||||
unusedAPs = [ap for ap in apList if not ap.isInternal() and ap not in usedAPs]
|
||||
for ap in unusedAPs:
|
||||
transitions.append((ap.Name, ap.Name))
|
||||
|
||||
def createMinimizerTransitions(startApName, locLimit):
|
||||
if startApName == 'Ceres':
|
||||
startApName = 'Landing Site'
|
||||
startAp = getAccessPoint(startApName)
|
||||
def getNLocs(locsPredicate, locList=None):
|
||||
if locList is None:
|
||||
locList = Logic.locations
|
||||
# leave out bosses and count post boss locs systematically
|
||||
return len([loc for loc in locList if locsPredicate(loc) == True and not loc.SolveArea.endswith(" Boss") and not loc.isBoss()])
|
||||
availAreas = list(sorted({ap.GraphArea for ap in Logic.accessPoints if ap.GraphArea != startAp.GraphArea and getNLocs(lambda loc: loc.GraphArea == ap.GraphArea) > 0}))
|
||||
areas = [startAp.GraphArea]
|
||||
GraphUtils.log.debug("availAreas: {}".format(availAreas))
|
||||
GraphUtils.log.debug("areas: {}".format(areas))
|
||||
inBossCheck = lambda ap: ap.Boss and ap.Name.endswith("In")
|
||||
nLocs = 0
|
||||
transitions = []
|
||||
usedAPs = []
|
||||
trLimit = 5
|
||||
locLimit -= 3 # 3 "post boss" locs will always be available, and are filtered out in getNLocs
|
||||
def openTransitions():
|
||||
nonlocal areas, inBossCheck, usedAPs
|
||||
return GraphUtils.getAPs(lambda ap: ap.GraphArea in areas and not ap.isInternal() and not inBossCheck(ap) and not ap in usedAPs)
|
||||
while nLocs < locLimit or len(openTransitions()) < trLimit:
|
||||
GraphUtils.log.debug("openTransitions="+str([ap.Name for ap in openTransitions()]))
|
||||
fromAreas = availAreas
|
||||
if nLocs >= locLimit:
|
||||
GraphUtils.log.debug("not enough open transitions")
|
||||
# we just need transitions, avoid adding a huge area
|
||||
fromAreas = []
|
||||
n = trLimit - len(openTransitions())
|
||||
while len(fromAreas) == 0:
|
||||
fromAreas = [area for area in availAreas if len(GraphUtils.getAPs(lambda ap: not ap.isInternal())) > n]
|
||||
n -= 1
|
||||
minLocs = min([getNLocs(lambda loc: loc.GraphArea == area) for area in fromAreas])
|
||||
fromAreas = [area for area in fromAreas if getNLocs(lambda loc: loc.GraphArea == area) == minLocs]
|
||||
elif len(openTransitions()) <= 1: # dont' get stuck by adding dead ends
|
||||
fromAreas = [area for area in fromAreas if len(GraphUtils.getAPs(lambda ap: ap.GraphArea == area and not ap.isInternal())) > 1]
|
||||
nextArea = random.choice(fromAreas)
|
||||
GraphUtils.log.debug("nextArea="+str(nextArea))
|
||||
apCheck = lambda ap: not ap.isInternal() and not inBossCheck(ap) and ap not in usedAPs
|
||||
possibleSources = GraphUtils.getAPs(lambda ap: ap.GraphArea in areas and apCheck(ap))
|
||||
possibleTargets = GraphUtils.getAPs(lambda ap: ap.GraphArea == nextArea and apCheck(ap))
|
||||
src = random.choice(possibleSources)
|
||||
dst = random.choice(possibleTargets)
|
||||
usedAPs += [src,dst]
|
||||
GraphUtils.log.debug("add transition: (src: {}, dst: {})".format(src.Name, dst.Name))
|
||||
transitions.append((src.Name,dst.Name))
|
||||
availAreas.remove(nextArea)
|
||||
areas.append(nextArea)
|
||||
GraphUtils.log.debug("areas: {}".format(areas))
|
||||
nLocs = getNLocs(lambda loc:loc.GraphArea in areas)
|
||||
GraphUtils.log.debug("nLocs: {}".format(nLocs))
|
||||
# we picked the areas, add transitions (bosses and tourian first)
|
||||
sourceAPs = openTransitions()
|
||||
random.shuffle(sourceAPs)
|
||||
targetAPs = GraphUtils.getAPs(lambda ap: (inBossCheck(ap) or ap.Name == "Golden Four") and not ap in usedAPs)
|
||||
random.shuffle(targetAPs)
|
||||
assert len(sourceAPs) >= len(targetAPs), "Minimizer: less source than target APs"
|
||||
while len(targetAPs) > 0:
|
||||
transitions.append((sourceAPs.pop().Name, targetAPs.pop().Name))
|
||||
transitions += GraphUtils.createRegularAreaTransitions(sourceAPs, lambda ap: not ap.isInternal())
|
||||
GraphUtils.log.debug("FINAL MINIMIZER transitions: {}".format(transitions))
|
||||
GraphUtils.loopUnusedTransitions(transitions)
|
||||
GraphUtils.log.debug("FINAL MINIMIZER nLocs: "+str(nLocs+3))
|
||||
GraphUtils.log.debug("FINAL MINIMIZER areas: "+str(areas))
|
||||
return transitions
|
||||
|
||||
def createLightAreaTransitions():
|
||||
# group APs by area
|
||||
aps = {}
|
||||
totalCount = 0
|
||||
for ap in Logic.accessPoints:
|
||||
if not ap.isArea():
|
||||
continue
|
||||
if not ap.GraphArea in aps:
|
||||
aps[ap.GraphArea] = {'totalCount': 0, 'transCount': {}, 'apNames': []}
|
||||
aps[ap.GraphArea]['apNames'].append(ap.Name)
|
||||
# count number of vanilla transitions between each area
|
||||
for (srcName, destName) in vanillaTransitions:
|
||||
srcAP = getAccessPoint(srcName)
|
||||
destAP = getAccessPoint(destName)
|
||||
aps[srcAP.GraphArea]['transCount'][destAP.GraphArea] = aps[srcAP.GraphArea]['transCount'].get(destAP.GraphArea, 0) + 1
|
||||
aps[srcAP.GraphArea]['totalCount'] += 1
|
||||
aps[destAP.GraphArea]['transCount'][srcAP.GraphArea] = aps[destAP.GraphArea]['transCount'].get(srcAP.GraphArea, 0) + 1
|
||||
aps[destAP.GraphArea]['totalCount'] += 1
|
||||
totalCount += 1
|
||||
|
||||
transitions = []
|
||||
while totalCount > 0:
|
||||
# choose transition
|
||||
srcArea = random.choice(list(aps.keys()))
|
||||
srcName = random.choice(aps[srcArea]['apNames'])
|
||||
src = getAccessPoint(srcName)
|
||||
destArea = random.choice(list(aps[src.GraphArea]['transCount'].keys()))
|
||||
destName = random.choice(aps[destArea]['apNames'])
|
||||
transitions.append((srcName, destName))
|
||||
|
||||
# update counts
|
||||
totalCount -= 1
|
||||
aps[srcArea]['totalCount'] -= 1
|
||||
aps[destArea]['totalCount'] -= 1
|
||||
aps[srcArea]['transCount'][destArea] -= 1
|
||||
if aps[srcArea]['transCount'][destArea] == 0:
|
||||
del aps[srcArea]['transCount'][destArea]
|
||||
aps[destArea]['transCount'][srcArea] -= 1
|
||||
if aps[destArea]['transCount'][srcArea] == 0:
|
||||
del aps[destArea]['transCount'][srcArea]
|
||||
aps[srcArea]['apNames'].remove(srcName)
|
||||
aps[destArea]['apNames'].remove(destName)
|
||||
|
||||
if aps[srcArea]['totalCount'] == 0:
|
||||
del aps[srcArea]
|
||||
if aps[destArea]['totalCount'] == 0:
|
||||
del aps[destArea]
|
||||
|
||||
return transitions
|
||||
|
||||
def getVanillaExit(apName):
|
||||
allVanillaTransitions = vanillaTransitions + vanillaBossesTransitions + vanillaEscapeTransitions
|
||||
for (src,dst) in allVanillaTransitions:
|
||||
if apName == src:
|
||||
return dst
|
||||
if apName == dst:
|
||||
return src
|
||||
return None
|
||||
|
||||
def isEscapeAnimals(apName):
|
||||
return 'Flyway Right' in apName or 'Bomb Torizo Room Left' in apName
|
||||
|
||||
# gets dict like
|
||||
# (RoomPtr, (vanilla entry screen X, vanilla entry screen Y)): AP
|
||||
def getRooms():
|
||||
rooms = {}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Internal == True:
|
||||
continue
|
||||
# special ap for random escape animals surprise
|
||||
if GraphUtils.isEscapeAnimals(ap.Name):
|
||||
continue
|
||||
|
||||
roomPtr = ap.RoomInfo['RoomPtr']
|
||||
|
||||
vanillaExitName = GraphUtils.getVanillaExit(ap.Name)
|
||||
# special ap for random escape animals surprise
|
||||
if GraphUtils.isEscapeAnimals(vanillaExitName):
|
||||
continue
|
||||
|
||||
connAP = getAccessPoint(vanillaExitName)
|
||||
entryInfo = connAP.ExitInfo
|
||||
rooms[(roomPtr, entryInfo['screen'], entryInfo['direction'])] = ap
|
||||
rooms[(roomPtr, entryInfo['screen'], (ap.EntryInfo['SamusX'], ap.EntryInfo['SamusY']))] = ap
|
||||
# for boss rando with incompatible ridley transition, also register this one
|
||||
if ap.Name == 'RidleyRoomIn':
|
||||
rooms[(roomPtr, (0x0, 0x1), 0x5)] = ap
|
||||
rooms[(roomPtr, (0x0, 0x1), (0xbf, 0x198))] = ap
|
||||
|
||||
return rooms
|
||||
|
||||
def escapeAnimalsTransitions(graph, possibleTargets, firstEscape):
|
||||
n = len(possibleTargets)
|
||||
assert (n < 4 and firstEscape is not None) or (n <= 4 and firstEscape is None), "Invalid possibleTargets list: " + str(possibleTargets)
|
||||
# first get our list of 4 entries for escape patch
|
||||
if n >= 1:
|
||||
# get actual animals: pick one of the remaining targets
|
||||
animalsAccess = possibleTargets.pop()
|
||||
graph.EscapeAttributes['Animals'] = animalsAccess
|
||||
# we now have at most 3 targets left, fill up to fill cycling 4 targets for animals suprise
|
||||
possibleTargets.append('Climb Bottom Left')
|
||||
if firstEscape is not None:
|
||||
possibleTargets.append(firstEscape)
|
||||
poss = possibleTargets[:]
|
||||
while len(possibleTargets) < 4:
|
||||
possibleTargets.append(poss.pop(random.randint(0, len(poss)-1)))
|
||||
else:
|
||||
# failsafe: if not enough targets left, abort and do vanilla animals
|
||||
animalsAccess = 'Flyway Right'
|
||||
possibleTargets = ['Bomb Torizo Room Left'] * 4
|
||||
GraphUtils.log.debug("escapeAnimalsTransitions. animalsAccess="+animalsAccess)
|
||||
assert len(possibleTargets) == 4, "Invalid possibleTargets list: " + str(possibleTargets)
|
||||
# actually add the 4 connections for successive escapes challenge
|
||||
basePtr = 0xADAC
|
||||
btDoor = getAccessPoint('Flyway Right')
|
||||
for i in range(len(possibleTargets)):
|
||||
ap = copy.copy(btDoor)
|
||||
ap.Name += " " + str(i)
|
||||
ap.ExitInfo['DoorPtr'] = basePtr + i*24
|
||||
graph.addAccessPoint(ap)
|
||||
target = possibleTargets[i]
|
||||
graph.addTransition(ap.Name, target)
|
||||
# add the connection for animals access
|
||||
bt = getAccessPoint('Bomb Torizo Room Left')
|
||||
btCpy = copy.copy(bt)
|
||||
btCpy.Name += " Animals"
|
||||
btCpy.ExitInfo['DoorPtr'] = 0xAE00
|
||||
graph.addAccessPoint(btCpy)
|
||||
graph.addTransition(animalsAccess, btCpy.Name)
|
||||
|
||||
def isHorizontal(dir):
|
||||
# up: 0x3, 0x7
|
||||
# down: 0x2, 0x6
|
||||
# left: 0x1, 0x5
|
||||
# right: 0x0, 0x4
|
||||
return dir in [0x1, 0x5, 0x0, 0x4]
|
||||
|
||||
def removeCap(dir):
|
||||
if dir < 4:
|
||||
return dir
|
||||
return dir - 4
|
||||
|
||||
def getDirection(src, dst):
|
||||
exitDir = src.ExitInfo['direction']
|
||||
entryDir = dst.EntryInfo['direction']
|
||||
# compatible transition
|
||||
if exitDir == entryDir:
|
||||
return exitDir
|
||||
# if incompatible but horizontal we keep entry dir (looks more natural)
|
||||
if GraphUtils.isHorizontal(exitDir) and GraphUtils.isHorizontal(entryDir):
|
||||
return entryDir
|
||||
# otherwise keep exit direction and remove cap
|
||||
return GraphUtils.removeCap(exitDir)
|
||||
|
||||
def getBitFlag(srcArea, dstArea, origFlag):
|
||||
flags = origFlag
|
||||
if srcArea == dstArea:
|
||||
flags &= 0xBF
|
||||
else:
|
||||
flags |= 0x40
|
||||
return flags
|
||||
|
||||
def getDoorConnections(graph, areas=True, bosses=False,
|
||||
escape=True, escapeAnimals=True):
|
||||
transitions = []
|
||||
if areas:
|
||||
transitions += vanillaTransitions
|
||||
if bosses:
|
||||
transitions += vanillaBossesTransitions
|
||||
if escape:
|
||||
transitions += vanillaEscapeTransitions
|
||||
if escapeAnimals:
|
||||
transitions += vanillaEscapeAnimalsTransitions
|
||||
for srcName, dstName in transitions:
|
||||
src = graph.accessPoints[srcName]
|
||||
dst = graph.accessPoints[dstName]
|
||||
dst.EntryInfo.update(src.ExitInfo)
|
||||
src.EntryInfo.update(dst.ExitInfo)
|
||||
connections = []
|
||||
for src, dst in graph.InterAreaTransitions:
|
||||
if not (escape and src.Escape and dst.Escape):
|
||||
# area only
|
||||
if not bosses and src.Boss:
|
||||
continue
|
||||
# boss only
|
||||
if not areas and not src.Boss:
|
||||
continue
|
||||
# no random escape
|
||||
if not escape and src.Escape:
|
||||
continue
|
||||
|
||||
conn = {}
|
||||
conn['ID'] = str(src) + ' -> ' + str(dst)
|
||||
# remove duplicates (loop transitions)
|
||||
if any(c['ID'] == conn['ID'] for c in connections):
|
||||
continue
|
||||
# print(conn['ID'])
|
||||
# where to write
|
||||
conn['DoorPtr'] = src.ExitInfo['DoorPtr']
|
||||
# door properties
|
||||
conn['RoomPtr'] = dst.RoomInfo['RoomPtr']
|
||||
conn['doorAsmPtr'] = dst.EntryInfo['doorAsmPtr']
|
||||
if 'exitAsmPtr' in src.ExitInfo:
|
||||
conn['exitAsmPtr'] = src.ExitInfo['exitAsmPtr']
|
||||
conn['direction'] = GraphUtils.getDirection(src, dst)
|
||||
conn['bitFlag'] = GraphUtils.getBitFlag(src.RoomInfo['area'], dst.RoomInfo['area'],
|
||||
dst.EntryInfo['bitFlag'])
|
||||
conn['cap'] = dst.EntryInfo['cap']
|
||||
conn['screen'] = dst.EntryInfo['screen']
|
||||
if conn['direction'] != src.ExitInfo['direction']: # incompatible transition
|
||||
conn['distanceToSpawn'] = 0
|
||||
conn['SamusX'] = dst.EntryInfo['SamusX']
|
||||
conn['SamusY'] = dst.EntryInfo['SamusY']
|
||||
if dst.Name == 'RidleyRoomIn': # special case: spawn samus on ridley platform
|
||||
conn['screen'] = (0x0, 0x1)
|
||||
else:
|
||||
conn['distanceToSpawn'] = dst.EntryInfo['distanceToSpawn']
|
||||
if 'song' in dst.EntryInfo:
|
||||
conn['song'] = dst.EntryInfo['song']
|
||||
conn['songs'] = dst.RoomInfo['songs']
|
||||
connections.append(conn)
|
||||
return connections
|
||||
|
||||
def getDoorsPtrs2Aps():
|
||||
ret = {}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Internal == True:
|
||||
continue
|
||||
ret[ap.ExitInfo["DoorPtr"]] = ap.Name
|
||||
return ret
|
||||
|
||||
def getAps2DoorsPtrs():
|
||||
ret = {}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Internal == True:
|
||||
continue
|
||||
ret[ap.Name] = ap.ExitInfo["DoorPtr"]
|
||||
return ret
|
||||
|
||||
def getTransitions(addresses):
|
||||
# build address -> name dict
|
||||
doorsPtrs = GraphUtils.getDoorsPtrs2Aps()
|
||||
|
||||
transitions = []
|
||||
# (src.ExitInfo['DoorPtr'], dst.ExitInfo['DoorPtr'])
|
||||
for (srcDoorPtr, destDoorPtr) in addresses:
|
||||
transitions.append((doorsPtrs[srcDoorPtr], doorsPtrs[destDoorPtr]))
|
||||
|
||||
return transitions
|
||||
|
||||
def hasMixedTransitions(areaTransitions, bossTransitions):
|
||||
vanillaAPs = []
|
||||
for (src, dest) in vanillaTransitions:
|
||||
vanillaAPs += [src, dest]
|
||||
|
||||
vanillaBossesAPs = []
|
||||
for (src, dest) in vanillaBossesTransitions:
|
||||
vanillaBossesAPs += [src, dest]
|
||||
|
||||
for (src, dest) in areaTransitions:
|
||||
if src in vanillaBossesAPs or dest in vanillaBossesAPs:
|
||||
return True
|
||||
|
||||
for (src, dest) in bossTransitions:
|
||||
if src in vanillaAPs or dest in vanillaAPs:
|
||||
return True
|
||||
|
||||
return False
|
||||
1532
worlds/sm/variaRandomizer/graph/location.py
Normal file
1532
worlds/sm/variaRandomizer/graph/location.py
Normal file
File diff suppressed because it is too large
Load Diff
0
worlds/sm/variaRandomizer/graph/vanilla/__init__.py
Normal file
0
worlds/sm/variaRandomizer/graph/vanilla/__init__.py
Normal file
758
worlds/sm/variaRandomizer/graph/vanilla/graph_access.py
Normal file
758
worlds/sm/variaRandomizer/graph/vanilla/graph_access.py
Normal file
@@ -0,0 +1,758 @@
|
||||
from graph.graph import AccessPoint
|
||||
from utils.parameters import Settings
|
||||
from rom.rom_patches import RomPatches
|
||||
from logic.smbool import SMBool
|
||||
from logic.helpers import Bosses
|
||||
from logic.cache import Cache
|
||||
|
||||
# all access points and traverse functions
|
||||
accessPoints = [
|
||||
### Ceres Station
|
||||
AccessPoint('Ceres', 'Ceres', {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}, internal=True,
|
||||
start={'spawn': 0xfffe, 'doors':[0x32], 'patches':[RomPatches.BlueBrinstarBlueDoor], 'solveArea': "Crateria Landing Site"}),
|
||||
### Crateria and Blue Brinstar
|
||||
AccessPoint('Landing Site', 'Crateria', {
|
||||
'Lower Mushrooms Left': Cache.ldeco(lambda sm: sm.wand(sm.canPassTerminatorBombWall(),
|
||||
sm.canPassCrateriaGreenPirates())),
|
||||
'Keyhunter Room Bottom': Cache.ldeco(lambda sm: sm.traverse('LandingSiteRight')),
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}, internal=True,
|
||||
start={'spawn': 0x0000, 'doors':[0x32], 'patches':[RomPatches.BlueBrinstarBlueDoor], 'solveArea': "Crateria Landing Site"}),
|
||||
AccessPoint('Blue Brinstar Elevator Bottom', 'Crateria', {
|
||||
'Morph Ball Room Left': lambda sm: sm.canUsePowerBombs(),
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}, internal=True),
|
||||
AccessPoint('Gauntlet Top', 'Crateria', {
|
||||
'Green Pirates Shaft Bottom Right': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'), sm.canPassCrateriaGreenPirates()))
|
||||
}, internal=True,
|
||||
start={'spawn': 0x0006, 'solveArea': "Crateria Gauntlet", 'save':"Save_Gauntlet", 'forcedEarlyMorph':True}),
|
||||
AccessPoint('Lower Mushrooms Left', 'Crateria', {
|
||||
'Landing Site': Cache.ldeco(lambda sm: sm.wand(sm.canPassTerminatorBombWall(False),
|
||||
sm.canPassCrateriaGreenPirates())),
|
||||
'Green Pirates Shaft Bottom Right': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0x9969, "area": 0x0, 'songs':[0x997a]},
|
||||
exitInfo = {'DoorPtr':0x8c22, 'direction': 0x5, "cap": (0xe, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x36, 'SamusY':0x88, 'song': 0x9},
|
||||
dotOrientation = 'nw'),
|
||||
AccessPoint('Green Pirates Shaft Bottom Right', 'Crateria', {
|
||||
'Lower Mushrooms Left': lambda sm: SMBool(True)
|
||||
}, traverse = Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoMoreBlueDoors),
|
||||
sm.traverse('GreenPiratesShaftBottomRight'))),
|
||||
roomInfo = {'RoomPtr':0x99bd, "area": 0x0, 'songs':[0x99ce]},
|
||||
# the doorAsmPtr 7FE00 is set by the g4_skip.ips patch, we have to call it
|
||||
exitInfo = {'DoorPtr':0x8c52, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xfe00},
|
||||
entryInfo = {'SamusX':0xcc, 'SamusY':0x688, 'song': 0x9},
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('Moat Right', 'Crateria', {
|
||||
'Moat Left': lambda sm: sm.canPassMoatReverse()
|
||||
}, roomInfo = {'RoomPtr':0x95ff, "area": 0x0, 'songs':[0x9610]},
|
||||
exitInfo = {'DoorPtr':0x8aea, 'direction': 0x4, "cap": (0x1, 0x46), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x4), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x1cf, 'SamusY':0x88, 'song': 0xc},
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Moat Left', 'Crateria', {
|
||||
'Keyhunter Room Bottom': lambda sm: SMBool(True),
|
||||
'Moat Right': lambda sm: sm.canPassMoatFromMoat()
|
||||
}, internal=True),
|
||||
AccessPoint('Keyhunter Room Bottom', 'Crateria', {
|
||||
'Moat Left': Cache.ldeco(lambda sm: sm.traverse('KihunterRight')),
|
||||
'Moat Right': Cache.ldeco(lambda sm: sm.wand(sm.traverse('KihunterRight'), sm.canPassMoat())),
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}, traverse = Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoMoreBlueDoors),
|
||||
sm.traverse('KihunterBottom'))),
|
||||
roomInfo = { 'RoomPtr':0x948c, "area": 0x0, 'songs':[0x949d] },
|
||||
exitInfo = {'DoorPtr':0x8a42, 'direction': 0x6, "cap": (0x6, 0x2), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x14c, 'SamusY':0x2b8, 'song': 0xc},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('Morph Ball Room Left', 'Crateria', {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: sm.canUsePowerBombs()
|
||||
}, roomInfo = { 'RoomPtr':0x9e9f, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x8e9e, 'direction': 0x5, "cap": (0x1e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x288},
|
||||
dotOrientation = 'sw'),
|
||||
# Escape APs
|
||||
AccessPoint('Climb Bottom Left', 'Crateria', {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0x96ba, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8b6e, 'direction': 0x5, "cap": (0x2e, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x2, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x888},
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Flyway Right', 'Crateria', {},
|
||||
roomInfo = {'RoomPtr':0x9879, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8bc2, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000,
|
||||
"exitAsmPtr": 0xf030}, # setup_next_escape in rando_escape.asm
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True),
|
||||
AccessPoint('Bomb Torizo Room Left', 'Crateria', {},
|
||||
roomInfo = {'RoomPtr':0x9804, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8baa, 'direction': 0x5, "cap": (0x2e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x2, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0xb8},
|
||||
escape = True),
|
||||
### Green and Pink Brinstar
|
||||
AccessPoint('Green Brinstar Elevator', 'GreenPinkBrinstar', {
|
||||
'Big Pink': Cache.ldeco(lambda sm: sm.wand(sm.canPassDachoraRoom(),
|
||||
sm.traverse('MainShaftBottomRight'))),
|
||||
'Etecoons Bottom': lambda sm: sm.canAccessEtecoons()
|
||||
}, roomInfo = {'RoomPtr':0x9938, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8bfe, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xcc, 'SamusY':0x88},
|
||||
start = {'spawn': 0x0108, 'doors':[0x1f, 0x21, 0x26], 'patches':[RomPatches.BrinReserveBlueDoors], 'solveArea': "Green Brinstar"}, # XXX test if it would be better in brin reserve room with custom save
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Big Pink', 'GreenPinkBrinstar', {
|
||||
'Green Hill Zone Top Right': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.traverse('BigPinkBottomRight'))),
|
||||
'Green Brinstar Elevator': lambda sm: sm.canPassDachoraRoom()
|
||||
}, internal=True, start={'spawn': 0x0100, 'solveArea': "Pink Brinstar"}),
|
||||
AccessPoint('Green Hill Zone Top Right', 'GreenPinkBrinstar', {
|
||||
'Noob Bridge Right': lambda sm: SMBool(True),
|
||||
'Big Pink': Cache.ldeco(lambda sm: sm.haveItem('Morph'))
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoBlueDoors), sm.traverse('GreenHillZoneTopRight'))),
|
||||
roomInfo = {'RoomPtr':0x9e52, "area": 0x1 },
|
||||
exitInfo = {'DoorPtr':0x8e86, 'direction': 0x4, "cap": (0x1, 0x26), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x2), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x1c7, 'SamusY':0x88},
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('Noob Bridge Right', 'GreenPinkBrinstar', {
|
||||
'Green Hill Zone Top Right': Cache.ldeco(lambda sm: sm.wor(sm.haveItem('Wave'),
|
||||
sm.wor(sm.canBlueGateGlitch(),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther))))
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoBlueDoors), sm.traverse('NoobBridgeRight'))),
|
||||
roomInfo = {'RoomPtr':0x9fba, "area": 0x1 },
|
||||
exitInfo = {'DoorPtr':0x8f0a, 'direction': 0x4, "cap": (0x1, 0x46), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x4), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x5ce, 'SamusY':0x88},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('Green Brinstar Main Shaft Top Left', 'GreenPinkBrinstar', {
|
||||
'Green Brinstar Elevator': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0x9ad9, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x8cb2, 'direction': 0x5, "cap": (0x2e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x2, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x488},
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Brinstar Pre-Map Room Right', 'GreenPinkBrinstar', {
|
||||
}, roomInfo = {'RoomPtr':0x9b9d, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x8d42, 'direction': 0x4, "cap": (0x1, 0x46), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x4), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Etecoons Supers', 'GreenPinkBrinstar', {
|
||||
'Etecoons Bottom': lambda sm: SMBool(True)
|
||||
}, internal=True,
|
||||
start={'spawn': 0x0107, 'doors':[0x34], 'patches':[RomPatches.EtecoonSupersBlueDoor],
|
||||
'save':"Save_Etecoons" ,'solveArea': "Green Brinstar",
|
||||
'forcedEarlyMorph':True, 'needsPreRando': True}),
|
||||
AccessPoint('Etecoons Bottom', 'GreenPinkBrinstar', {
|
||||
'Etecoons Supers': Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.EtecoonSupersBlueDoor),
|
||||
sm.traverse('EtecoonEnergyTankLeft'))),
|
||||
'Green Brinstar Elevator': lambda sm: sm.canUsePowerBombs()
|
||||
}, internal=True),
|
||||
### Wrecked Ship
|
||||
AccessPoint('West Ocean Left', 'WreckedShip', {
|
||||
'Wrecked Ship Main': Cache.ldeco(lambda sm: sm.traverse('WestOceanRight'))
|
||||
}, roomInfo = {'RoomPtr':0x93fe, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x89ca, 'direction': 0x5, "cap": (0x1e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x488},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('Wrecked Ship Main', 'WreckedShip', {
|
||||
'West Ocean Left': lambda sm: SMBool(True),
|
||||
'Wrecked Ship Back': Cache.ldeco(lambda sm: sm.wor(sm.wand(Bosses.bossDead(sm, 'Phantoon'),
|
||||
sm.canPassSpongeBath()),
|
||||
sm.wand(sm.wnot(Bosses.bossDead(sm, 'Phantoon')),
|
||||
RomPatches.has(sm.player, RomPatches.SpongeBathBlueDoor)))),
|
||||
'PhantoonRoomOut': Cache.ldeco(lambda sm: sm.wand(sm.traverse('WreckedShipMainShaftBottom'), sm.canPassBombPassages()))
|
||||
}, internal=True,
|
||||
start={'spawn':0x0300,
|
||||
'doors':[0x83,0x8b], 'patches':[RomPatches.SpongeBathBlueDoor, RomPatches.WsEtankBlueDoor],
|
||||
'solveArea': "WreckedShip Main",
|
||||
'needsPreRando':True}),
|
||||
AccessPoint('Wrecked Ship Back', 'WreckedShip', {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True),
|
||||
'Crab Maze Left': Cache.ldeco(lambda sm: sm.canPassForgottenHighway(True))
|
||||
}, internal=True),
|
||||
AccessPoint('Crab Maze Left', 'WreckedShip', {
|
||||
'Wrecked Ship Back': Cache.ldeco(lambda sm: sm.canPassForgottenHighway(False))
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoBlueDoors),
|
||||
sm.traverse('LeCoudeBottom'))), # it is not exactly coude's door
|
||||
# but it's equivalent in vanilla anyway
|
||||
roomInfo = {'RoomPtr':0x957d, "area": 0x0, 'songs':[0x958e]},
|
||||
exitInfo = {'DoorPtr':0x8aae, 'direction': 0x5, "cap": (0xe, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x188, 'song': 0xc},
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('PhantoonRoomOut', 'WreckedShip', {
|
||||
'Wrecked Ship Main': lambda sm: sm.canPassBombPassages()
|
||||
}, boss = True,
|
||||
roomInfo = {'RoomPtr':0xcc6f, "area": 0x3},
|
||||
exitInfo = {'DoorPtr':0xa2ac, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x49f, 'SamusY':0xb8},
|
||||
traverse=lambda sm: sm.canOpenEyeDoors(),
|
||||
dotOrientation = 's'),
|
||||
AccessPoint('PhantoonRoomIn', 'WreckedShip', {},
|
||||
boss = True,
|
||||
roomInfo = {'RoomPtr':0xcd13, "area": 0x3},
|
||||
exitInfo = {'DoorPtr':0xa2c4, 'direction': 0x5, "cap": (0x4e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x4, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe1fe,
|
||||
"exitAsmPtr": 0xf7f0},
|
||||
entryInfo = {'SamusX':0x2e, 'SamusY':0xb8},
|
||||
dotOrientation = 's'),
|
||||
AccessPoint('Basement Left', 'WreckedShip', {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0xcc6f, "area": 0x3},
|
||||
exitInfo = {'DoorPtr':0xa2a0, 'direction': 0x5, "cap": (0xe, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x2e, 'SamusY':0x88},
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Wrecked Ship Map Room', 'WreckedShip', {
|
||||
}, roomInfo = {'RoomPtr':0xcccb, "area": 0x3},
|
||||
exitInfo = {'DoorPtr':0xa2b8, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
### Lower Norfair
|
||||
AccessPoint('Lava Dive Right', 'LowerNorfair', {
|
||||
'LN Entrance': lambda sm: sm.canPassLavaPit()
|
||||
}, roomInfo = {'RoomPtr':0xaf14, "area": 0x2, 'songs':[0xaf25]},
|
||||
exitInfo = {'DoorPtr':0x96d2, 'direction': 0x4, "cap": (0x11, 0x26), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x2), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x3d0, 'SamusY':0x88, 'song': 0x15},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('LN Entrance', 'LowerNorfair', {
|
||||
'Lava Dive Right': lambda sm: sm.canPassLavaPitReverse(),
|
||||
'LN Above GT': lambda sm: sm.canPassLowerNorfairChozo(),
|
||||
'Screw Attack Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canGreenGateGlitch(),
|
||||
sm.canDestroyBombWalls())),
|
||||
'Firefleas': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canPassWorstRoom(),
|
||||
sm.canUsePowerBombs()))
|
||||
}, internal=True),
|
||||
AccessPoint('LN Above GT', 'LowerNorfair', {
|
||||
'Screw Attack Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.enoughStuffGT()))
|
||||
}, internal=True),
|
||||
AccessPoint('Screw Attack Bottom', 'LowerNorfair', {
|
||||
'LN Entrance': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canExitScrewAttackArea(),
|
||||
sm.haveItem('Super'),
|
||||
sm.canUsePowerBombs()))
|
||||
}, internal=True),
|
||||
AccessPoint('Firefleas', 'LowerNorfair', {
|
||||
'LN Entrance': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canPassAmphitheaterReverse(),
|
||||
sm.canPassWorstRoomPirates(),
|
||||
sm.canUsePowerBombs())),
|
||||
'Three Muskateers Room Left': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.haveItem('Morph'),
|
||||
# check for only 3 ki hunters this way
|
||||
sm.canPassRedKiHunters())),
|
||||
'Ridley Zone': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.traverse('WastelandLeft'),
|
||||
sm.traverse('RedKihunterShaftBottom'),
|
||||
sm.canGetBackFromRidleyZone(),
|
||||
sm.canPassRedKiHunters(),
|
||||
sm.canPassWastelandDessgeegas(),
|
||||
sm.canPassNinjaPirates())),
|
||||
'Screw Attack Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canPassAmphitheaterReverse(),
|
||||
sm.canDestroyBombWalls(),
|
||||
sm.canGreenGateGlitch())),
|
||||
'Firefleas Top': Cache.ldeco(lambda sm: sm.wand(sm.canPassBombPassages(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])))
|
||||
}, internal=True),
|
||||
AccessPoint('Firefleas Top', 'LowerNorfair', {
|
||||
# this weird condition basically says: "if we start here, give heat protection"
|
||||
'Firefleas': Cache.ldeco(lambda sm: sm.wor(sm.wnot(RomPatches.has(sm.player, RomPatches.LowerNorfairPBRoomHeatDisable)),
|
||||
sm.heatProof()))
|
||||
}, internal=True,
|
||||
start={'spawn':0x0207,
|
||||
'rom_patches': ['LN_PB_Heat_Disable', 'LN_Firefleas_Remove_Fune','firefleas_shot_block.ips'],
|
||||
'patches':[RomPatches.LowerNorfairPBRoomHeatDisable, RomPatches.FirefleasRemoveFune],
|
||||
'knows': ["FirefleasWalljump"],
|
||||
'save': "Save_Firefleas", 'needsPreRando': True,
|
||||
'solveArea': "Lower Norfair After Amphitheater",
|
||||
'forcedEarlyMorph':True}),
|
||||
AccessPoint('Ridley Zone', 'LowerNorfair', {
|
||||
'Firefleas': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canGetBackFromRidleyZone(),
|
||||
sm.canPassWastelandDessgeegas(),
|
||||
sm.canPassRedKiHunters())),
|
||||
'RidleyRoomOut': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']))
|
||||
}, internal=True),
|
||||
AccessPoint('Three Muskateers Room Left', 'LowerNorfair', {
|
||||
'Firefleas': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.haveItem('Morph'),
|
||||
sm.canPassThreeMuskateers()))
|
||||
}, roomInfo = {'RoomPtr':0xb656, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x9a4a, 'direction': 0x5, "cap": (0x5e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x5, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x134, 'SamusY':0x88},
|
||||
dotOrientation = 'n'),
|
||||
AccessPoint('RidleyRoomOut', 'LowerNorfair', {
|
||||
'Ridley Zone': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']))
|
||||
}, boss = True,
|
||||
roomInfo = {'RoomPtr':0xb37a, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x98ca, 'direction': 0x5, "cap": (0xe, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x2e, 'SamusY':0x98},
|
||||
traverse=Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canOpenEyeDoors())),
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('RidleyRoomIn', 'LowerNorfair', {},
|
||||
boss = True,
|
||||
roomInfo = {'RoomPtr':0xb32e, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x98be, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0xbf, 'SamusY':0x198}, # on Ridley's platform. entry screen has to be changed (see getDoorConnections)
|
||||
dotOrientation = 'e'),
|
||||
### Kraid
|
||||
AccessPoint('Warehouse Zeela Room Left', 'Kraid', {
|
||||
'KraidRoomOut': lambda sm: sm.canPassBombPassages()
|
||||
}, roomInfo = {'RoomPtr': 0xa471, "area": 0x1, 'songs':[0xa482]},
|
||||
exitInfo = {'DoorPtr': 0x913e, 'direction': 0x5, "cap": (0x2e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x2, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xbd3f},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x88, 'song':0x12},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('KraidRoomOut', 'Kraid', {
|
||||
'Warehouse Zeela Room Left': lambda sm: sm.canPassBombPassages()
|
||||
}, boss = True,
|
||||
roomInfo = {'RoomPtr':0xa56b, "area": 0x1,
|
||||
# put red brin song in both pre-kraid rooms,
|
||||
# (vanilla music only makes sense if kraid is
|
||||
# vanilla)
|
||||
"songs":[0xa57c,0xa537,0xa551]},
|
||||
exitInfo = {'DoorPtr':0x91b6, 'direction': 0x4, "cap": (0x1, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x1cd, 'SamusY':0x188, 'song':0x12},
|
||||
traverse=lambda sm: sm.canOpenEyeDoors(),
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('KraidRoomIn', 'Kraid', {},
|
||||
boss = True,
|
||||
roomInfo = {'RoomPtr':0xa59f, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x91ce, 'direction': 0x5, "cap": (0x1e, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x188},
|
||||
dotOrientation = 'e'),
|
||||
### Norfair
|
||||
AccessPoint('Warehouse Entrance Left', 'Norfair', {
|
||||
'Warehouse Entrance Right': lambda sm: sm.canAccessKraidsLair(),
|
||||
'Business Center': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0xa6a1, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x922e, 'direction': 0x5, "cap": (0xe, 0x16), "bitFlag": 0x40,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xbdd1},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x88},
|
||||
dotOrientation = 'sw'),
|
||||
AccessPoint('Warehouse Entrance Right', 'Norfair', {
|
||||
'Warehouse Entrance Left': Cache.ldeco(lambda sm: sm.haveItem('Super'))
|
||||
}, roomInfo = {'RoomPtr': 0xa6a1, "area": 0x1},
|
||||
exitInfo = {'DoorPtr': 0x923a, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX': 0x2c7, 'SamusY': 0x98},
|
||||
dotOrientation = 'nw'),
|
||||
AccessPoint('Business Center', 'Norfair', {
|
||||
'Cathedral': Cache.ldeco(lambda sm: sm.canEnterCathedral(Settings.hellRunsTable['MainUpperNorfair']['Norfair Entrance -> Cathedral Missiles']['mult'])),
|
||||
'Bubble Mountain': Cache.ldeco(# go through cathedral
|
||||
lambda sm: sm.wand(sm.traverse('CathedralRight'),
|
||||
sm.canEnterCathedral(Settings.hellRunsTable['MainUpperNorfair']['Norfair Entrance -> Bubble']['mult']))),
|
||||
'Bubble Mountain Bottom': Cache.ldeco(lambda sm: sm.haveItem('SpeedBooster')), # frog speedway
|
||||
'Crocomire Speedway Bottom': Cache.ldeco(lambda sm: sm.wor(sm.wand(sm.haveItem('SpeedBooster'), # frog speedway
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Norfair Entrance -> Croc via Frog w/Wave' if sm.haveItem('Wave') else 'Norfair Entrance -> Croc via Frog']),
|
||||
sm.wor(sm.canBlueGateGlitch(),
|
||||
sm.haveItem('Wave'))),
|
||||
# below ice
|
||||
sm.wand(sm.traverse('BusinessCenterTopLeft'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.canUsePowerBombs(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['Ice']['Norfair Entrance -> Croc via Ice'])))),
|
||||
'Warehouse Entrance Left': lambda sm: SMBool(True)
|
||||
}, internal=True,
|
||||
start={'spawn':0x0208, 'doors':[0x4d], 'patches':[RomPatches.HiJumpAreaBlueDoor], 'solveArea': "Norfair Entrance", 'needsPreRando':True}),
|
||||
AccessPoint('Single Chamber Top Right', 'Norfair', {
|
||||
'Bubble Mountain Top': Cache.ldeco(lambda sm: sm.wand(sm.canDestroyBombWalls(),
|
||||
sm.haveItem('Morph'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Single Chamber <-> Bubble Mountain'])))
|
||||
}, roomInfo = {'RoomPtr':0xad5e, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x95fa, 'direction': 0x4, "cap": (0x11, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x5cf, 'SamusY':0x88},
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Cathedral', 'Norfair', {
|
||||
'Business Center': Cache.ldeco(lambda sm: sm.canExitCathedral(Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Cathedral Missiles'])),
|
||||
'Bubble Mountain': Cache.ldeco(lambda sm: sm.wand(sm.traverse('CathedralRight'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Norfair Entrance -> Cathedral Missiles'])))
|
||||
}, internal=True),
|
||||
AccessPoint('Kronic Boost Room Bottom Left', 'Norfair', {
|
||||
'Bubble Mountain Bottom': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Single Chamber <-> Bubble Mountain'])),
|
||||
'Bubble Mountain Top': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Kronic Boost Room -> Bubble Mountain Top']))), # go all the way around
|
||||
'Crocomire Speedway Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Kronic Boost Room <-> Croc']),
|
||||
sm.wor(sm.haveItem('Wave'),
|
||||
sm.canBlueGateGlitch()))),
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoBlueDoors), sm.traverse('KronicBoostBottomLeft'))),
|
||||
roomInfo = {'RoomPtr':0xae74, "area": 0x2, 'songs':[0xae85]},
|
||||
exitInfo = {'DoorPtr':0x967e, 'direction': 0x5, "cap": (0x3e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x3, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x134, 'SamusY':0x288, 'song': 0x15},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('Crocomire Speedway Bottom', 'Norfair', {
|
||||
'Business Center': Cache.ldeco(lambda sm: sm.wor(sm.wand(sm.canPassFrogSpeedwayRightToLeft(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['Ice']['Croc -> Norfair Entrance'])),
|
||||
sm.wand(sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Croc -> Norfair Entrance']),
|
||||
sm.canGrappleEscape(),
|
||||
sm.haveItem('Super')))),
|
||||
'Bubble Mountain Bottom': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['Ice']['Croc -> Bubble Mountain'])),
|
||||
'Kronic Boost Room Bottom Left': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Kronic Boost Room <-> Croc']),
|
||||
sm.haveItem('Morph')))
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.CrocBlueDoors), sm.traverse('CrocomireSpeedwayBottom'))),
|
||||
roomInfo = {'RoomPtr':0xa923, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x93d2, 'direction': 0x6, "cap": (0x36, 0x2), "bitFlag": 0x0,
|
||||
"screen": (0x3, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xc57, 'SamusY':0x2b8},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('Bubble Mountain', 'Norfair', {
|
||||
'Business Center': lambda sm: sm.canExitCathedral(Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Norfair Entrance']),
|
||||
'Bubble Mountain Top': lambda sm: sm.canClimbBubbleMountain(),
|
||||
'Cathedral': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Cathedral Missiles'])),
|
||||
'Bubble Mountain Bottom': lambda sm: sm.canPassBombPassages()
|
||||
}, internal=True,
|
||||
start={'spawn':0x0201, 'doors':[0x54,0x55], 'patches':[RomPatches.SpeedAreaBlueDoors], 'knows':['BubbleMountainWallJump'], 'solveArea': "Bubble Norfair Bottom"}),
|
||||
AccessPoint('Bubble Mountain Top', 'Norfair', {
|
||||
'Kronic Boost Room Bottom Left': Cache.ldeco(# go all the way around
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Kronic Boost Room wo/Bomb']))),
|
||||
'Single Chamber Top Right': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Single Chamber <-> Bubble Mountain']),
|
||||
sm.canDestroyBombWalls(),
|
||||
sm.haveItem('Morph'),
|
||||
RomPatches.has(sm.player, RomPatches.SingleChamberNoCrumble))),
|
||||
'Bubble Mountain': lambda sm: SMBool(True),
|
||||
# all the way around
|
||||
'Bubble Mountain Bottom': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble Top <-> Bubble Bottom'])))
|
||||
}, internal=True),
|
||||
AccessPoint('Bubble Mountain Bottom', 'Norfair', {
|
||||
'Bubble Mountain': lambda sm: sm.canPassBombPassages(),
|
||||
'Crocomire Speedway Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Croc']),
|
||||
sm.wor(sm.canBlueGateGlitch(),
|
||||
sm.haveItem('Wave')))),
|
||||
'Kronic Boost Room Bottom Left': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Kronic Boost Room'])),
|
||||
'Business Center': lambda sm: sm.canPassFrogSpeedwayRightToLeft(),
|
||||
# all the way around
|
||||
'Bubble Mountain Top': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble Top <-> Bubble Bottom'])))
|
||||
}, internal=True),
|
||||
AccessPoint('Business Center Mid Left', 'Norfair', {
|
||||
'Warehouse Entrance Left': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0xa7de, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x9306, 'direction': 0x5, "cap": (0xe, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x488},
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Norfair Map Room', 'Norfair', {
|
||||
}, roomInfo = {'RoomPtr':0xb0b4, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x97c2, 'direction': 0x4, "cap": (0x1, 0x46), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x4), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
### Croc
|
||||
AccessPoint('Crocomire Room Top', 'Crocomire', {
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.CrocBlueDoors), sm.enoughStuffCroc())),
|
||||
roomInfo = {'RoomPtr':0xa98d, "area": 0x2, 'songs':[0xa9bd]},
|
||||
exitInfo = {'DoorPtr':0x93ea, 'direction': 0x7, "cap": (0xc6, 0x2d), "bitFlag": 0x0,
|
||||
"screen": (0xc, 0x2), "distanceToSpawn": 0x1c0, "doorAsmPtr": 0x0000,
|
||||
"exitAsmPtr": 0xf7f0},
|
||||
entryInfo = {'SamusX':0x383, 'SamusY':0x98, 'song': 0x15},
|
||||
dotOrientation = 'se'),
|
||||
### West Maridia
|
||||
AccessPoint('Main Street Bottom', 'WestMaridia', {
|
||||
'Red Fish Room Left': Cache.ldeco(lambda sm: sm.wand(sm.canGoUpMtEverest(),
|
||||
sm.haveItem('Morph'))),
|
||||
'Crab Hole Bottom Left': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canTraverseCrabTunnelLeftToRight())),
|
||||
# this transition leads to EastMaridia directly
|
||||
'Oasis Bottom': Cache.ldeco(lambda sm: sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.MaridiaSandWarp)),
|
||||
sm.traverse('MainStreetBottomRight'),
|
||||
sm.wor(sm.haveItem('Super'),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther)),
|
||||
sm.canTraverseWestSandHallLeftToRight())),
|
||||
'Crab Shaft Left': lambda sm: sm.canPassMtEverest()
|
||||
}, roomInfo = {'RoomPtr':0xcfc9, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa39c, 'direction': 0x6, "cap": (0x6, 0x2), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x170, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x14a, 'SamusY':0x7a8},
|
||||
dotOrientation = 's'),
|
||||
AccessPoint('Mama Turtle', 'WestMaridia', {
|
||||
'Main Street Bottom': lambda sm: sm.canJumpUnderwater()
|
||||
}, internal=True,
|
||||
start = {'spawn': 0x0406, 'solveArea': "Maridia Green",
|
||||
'save':"Save_Mama", 'needsPreRando':True,
|
||||
'patches':[RomPatches.MamaTurtleBlueDoor],
|
||||
'rom_patches':['mama_save.ips'], 'doors': [0x8e]}),
|
||||
AccessPoint('Crab Hole Bottom Left', 'WestMaridia', {
|
||||
'Main Street Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canExitCrabHole(),
|
||||
sm.wor(sm.canGreenGateGlitch(),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther)))),
|
||||
# this transition leads to EastMaridia directly
|
||||
'Oasis Bottom': Cache.ldeco(lambda sm: sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.MaridiaSandWarp)),
|
||||
sm.canExitCrabHole(),
|
||||
sm.canTraverseWestSandHallLeftToRight()))
|
||||
}, roomInfo = {'RoomPtr':0xd21c, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa510, 'direction': 0x5,
|
||||
"cap": (0x3e, 0x6), "screen": (0x3, 0x0), "bitFlag": 0x0,
|
||||
"distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x28, 'SamusY':0x188},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('Red Fish Room Left', 'WestMaridia', {
|
||||
'Main Street Bottom': Cache.ldeco(lambda sm: sm.haveItem('Morph')) # just go down
|
||||
}, roomInfo = {'RoomPtr':0xd104, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa480, 'direction': 0x5, "cap": (0x2e, 0x36), "bitFlag": 0x40,
|
||||
"screen": (0x2, 0x3), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe367},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x88},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('Crab Shaft Left', 'WestMaridia', {
|
||||
'Main Street Bottom': lambda sm: SMBool(True), # fall down
|
||||
'Beach': lambda sm: sm.canDoOuterMaridia(),
|
||||
'Crab Shaft Right': lambda sm: SMBool(True)
|
||||
}, internal=True),
|
||||
AccessPoint('Watering Hole', 'WestMaridia', {
|
||||
'Beach': lambda sm: sm.haveItem('Morph'),
|
||||
'Watering Hole Bottom': lambda sm: SMBool(True)
|
||||
}, internal=True,
|
||||
start = {'spawn': 0x0407, 'solveArea': "Maridia Pink Bottom", 'save':"Save_Watering_Hole",
|
||||
'patches':[RomPatches.MaridiaTubeOpened], 'rom_patches':['wh_open_tube.ips'],
|
||||
'forcedEarlyMorph':True}),
|
||||
AccessPoint('Watering Hole Bottom', 'WestMaridia', {
|
||||
'Watering Hole': lambda sm: sm.canJumpUnderwater()
|
||||
}, internal=True),
|
||||
AccessPoint('Beach', 'WestMaridia', {
|
||||
'Crab Shaft Left': lambda sm: SMBool(True), # fall down
|
||||
'Watering Hole': Cache.ldeco(lambda sm: sm.wand(sm.wor(sm.canPassBombPassages(),
|
||||
sm.canUseSpringBall()),
|
||||
sm.canDoOuterMaridia()))
|
||||
}, internal=True),
|
||||
AccessPoint('Crab Shaft Right', 'WestMaridia', {
|
||||
'Crab Shaft Left': lambda sm: sm.canJumpUnderwater()
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.CrabShaftBlueDoor),
|
||||
sm.traverse('CrabShaftRight'))),
|
||||
roomInfo = {'RoomPtr':0xd1a3, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa4c8, 'direction': 0x4, "cap": (0x1, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x1ca, 'SamusY':0x388},
|
||||
dotOrientation = 'e'),
|
||||
# escape APs
|
||||
AccessPoint('Crab Hole Bottom Right', 'WestMaridia', {
|
||||
'Crab Hole Bottom Left': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0xd21c, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa51c, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xd7, 'SamusY':0x188},
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Maridia Map Room', 'WestMaridia', {
|
||||
}, roomInfo = {'RoomPtr':0xd3b6, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa5e8, 'direction': 0x5, "cap": (0xe, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe356},
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
### East Maridia
|
||||
AccessPoint('Aqueduct Top Left', 'EastMaridia', {
|
||||
'Aqueduct Bottom': lambda sm: sm.canUsePowerBombs()
|
||||
}, roomInfo = {'RoomPtr':0xd5a7, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa708, 'direction': 0x5, "cap": (0x1e, 0x36), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x3), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe398},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x188},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('Aqueduct Bottom', 'EastMaridia', {
|
||||
'Aqueduct Top Left': Cache.ldeco(lambda sm: sm.wand(sm.canDestroyBombWallsUnderwater(), # top left bomb blocks
|
||||
sm.canJumpUnderwater())),
|
||||
'Post Botwoon': Cache.ldeco(lambda sm: sm.wand(sm.canJumpUnderwater(),
|
||||
sm.canDefeatBotwoon())), # includes botwoon hallway conditions
|
||||
'Left Sandpit': lambda sm: sm.canAccessSandPits(),
|
||||
'Right Sandpit': lambda sm: sm.canAccessSandPits(),
|
||||
'Aqueduct': Cache.ldeco(lambda sm: sm.wand(sm.wor(sm.haveItem('SpeedBooster'),
|
||||
sm.wand(sm.knowsSnailClip(),
|
||||
sm.haveItem('Morph'))),
|
||||
sm.haveItem('Gravity')))
|
||||
}, internal=True),
|
||||
AccessPoint('Aqueduct', 'EastMaridia', {
|
||||
'Aqueduct Bottom': lambda sm: SMBool(True) # go down
|
||||
}, internal=True,
|
||||
start = {'spawn': 0x0405, 'solveArea': "Maridia Pink Bottom",
|
||||
'save':"Save_Aqueduct", 'needsPreRando':True,
|
||||
'doors': [0x96]}),
|
||||
AccessPoint('Post Botwoon', 'EastMaridia', {
|
||||
'Aqueduct Bottom': Cache.ldeco(lambda sm: sm.wor(sm.wand(sm.canJumpUnderwater(), # can't access the sand pits from the right side of the room
|
||||
sm.haveItem('Morph')),
|
||||
sm.wand(sm.haveItem('Gravity'),
|
||||
sm.haveItem('SpeedBooster')))),
|
||||
'Colosseum Top Right': lambda sm: sm.canBotwoonExitToColosseum(),
|
||||
'Toilet Top': Cache.ldeco(lambda sm: sm.wand(sm.canReachCacatacAlleyFromBotowoon(),
|
||||
sm.canPassCacatacAlley()))
|
||||
}, internal=True),
|
||||
AccessPoint('West Sand Hall Left', 'EastMaridia', {
|
||||
# XXX there might be some tech to do this suitless, but HJ+ice is not enough
|
||||
'Oasis Bottom': Cache.ldeco(lambda sm: sm.haveItem('Gravity')),
|
||||
'Aqueduct Bottom': Cache.ldeco(lambda sm: RomPatches.has(sm.player, RomPatches.MaridiaSandWarp)),
|
||||
# this goes directly to WestMaridia
|
||||
'Main Street Bottom': Cache.ldeco(lambda sm: sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.MaridiaSandWarp)),
|
||||
sm.wor(sm.canGreenGateGlitch(),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther)))),
|
||||
# this goes directly to WestMaridia
|
||||
'Crab Hole Bottom Left': Cache.ldeco(lambda sm: sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.MaridiaSandWarp)),
|
||||
sm.haveItem('Morph')))
|
||||
}, internal=True),
|
||||
AccessPoint('Left Sandpit', 'EastMaridia', {
|
||||
'West Sand Hall Left': lambda sm: sm.canAccessSandPits(),
|
||||
'Oasis Bottom': lambda sm: sm.canAccessSandPits()
|
||||
}, internal=True),
|
||||
AccessPoint('Oasis Bottom', 'EastMaridia', {
|
||||
'Toilet Top': Cache.ldeco(lambda sm: sm.wand(sm.traverse('OasisTop'), sm.canDestroyBombWallsUnderwater())),
|
||||
'West Sand Hall Left': lambda sm: sm.canAccessSandPits()
|
||||
}, internal=True),
|
||||
AccessPoint('Right Sandpit', 'EastMaridia', {
|
||||
'Oasis Bottom': lambda sm: sm.canAccessSandPits()
|
||||
}, internal=True),
|
||||
AccessPoint('Le Coude Right', 'EastMaridia', {
|
||||
'Toilet Top': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0x95a8, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8aa2, 'direction': 0x4, "cap": (0x1, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xd1, 'SamusY':0x88},
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Toilet Top', 'EastMaridia', {
|
||||
'Oasis Bottom': Cache.ldeco(lambda sm: sm.wand(sm.traverse('PlasmaSparkBottom'), sm.canDestroyBombWallsUnderwater())),
|
||||
'Le Coude Right': lambda sm: SMBool(True),
|
||||
'Colosseum Top Right': Cache.ldeco(lambda sm: sm.wand(Bosses.bossDead(sm, 'Draygon'),
|
||||
# suitless could be possible with this but unreasonable: https://youtu.be/rtLwytH-u8o
|
||||
sm.haveItem('Gravity'),
|
||||
sm.haveItem('Morph')))
|
||||
}, internal=True),
|
||||
AccessPoint('Colosseum Top Right', 'EastMaridia', {
|
||||
'Post Botwoon': lambda sm: sm.canColosseumToBotwoonExit(),
|
||||
'Precious Room Top': Cache.ldeco(lambda sm: sm.traverse('ColosseumBottomRight')), # go down
|
||||
}, internal = True),
|
||||
AccessPoint('Precious Room Top', 'EastMaridia', {
|
||||
'Colosseum Top Right': lambda sm: sm.canClimbColosseum(),
|
||||
'DraygonRoomOut': lambda sm: SMBool(True) # go down
|
||||
}, internal = True),
|
||||
# boss APs
|
||||
AccessPoint('DraygonRoomOut', 'EastMaridia', {
|
||||
'Precious Room Top': lambda sm: sm.canExitPreciousRoom()
|
||||
}, boss = True,
|
||||
roomInfo = {'RoomPtr':0xd78f, "area": 0x4, "songs":[0xd7a5]},
|
||||
exitInfo = {'DoorPtr':0xa840, 'direction': 0x5, "cap": (0x1e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x288, 'song':0x1b},
|
||||
traverse=lambda sm: sm.canOpenEyeDoors(),
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('DraygonRoomIn', 'EastMaridia', {
|
||||
'Draygon Room Bottom': Cache.ldeco(lambda sm: sm.wor(Bosses.bossDead(sm, "Draygon"),
|
||||
sm.wand(sm.canFightDraygon(),
|
||||
sm.enoughStuffsDraygon())))
|
||||
}, boss = True,
|
||||
roomInfo = {'RoomPtr':0xda60, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa96c, 'direction': 0x4, "cap": (0x1, 0x26), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x2), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe3d9,
|
||||
"exitAsmPtr": 0xf7f0},
|
||||
entryInfo = {'SamusX':0x1c8, 'SamusY':0x88},
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('Draygon Room Bottom', 'EastMaridia', {
|
||||
'DraygonRoomIn': Cache.ldeco(lambda sm: sm.wand(Bosses.bossDead(sm, 'Draygon'), sm.canExitDraygon()))
|
||||
}, internal = True),
|
||||
### Red Brinstar. Main nodes: Red Tower Top Left, East Tunnel Right
|
||||
AccessPoint('Red Tower Top Left', 'RedBrinstar', {
|
||||
# go up
|
||||
'Red Brinstar Elevator': lambda sm: sm.canClimbRedTower(),
|
||||
'Caterpillar Room Top Right': Cache.ldeco(lambda sm: sm.wand(sm.canPassRedTowerToMaridiaNode(),
|
||||
sm.canClimbRedTower())),
|
||||
# go down
|
||||
'East Tunnel Right': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0xa253, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x902a, 'direction': 0x5, "cap": (0x5e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x5, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x2f, 'SamusY':0x488},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('Caterpillar Room Top Right', 'RedBrinstar', {
|
||||
'Red Brinstar Elevator': lambda sm: sm.canPassMaridiaToRedTowerNode()
|
||||
}, roomInfo = {'RoomPtr':0xa322, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x90c6, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x40,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xbdaf},
|
||||
entryInfo = {'SamusX':0x2cd, 'SamusY':0x388},
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Red Brinstar Elevator', 'RedBrinstar', {
|
||||
'Caterpillar Room Top Right': lambda sm: sm.canPassRedTowerToMaridiaNode(),
|
||||
'Red Tower Top Left': Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.HellwayBlueDoor), sm.traverse('RedTowerElevatorLeft')))
|
||||
}, traverse=Cache.ldeco(lambda sm:sm.wor(RomPatches.has(sm.player, RomPatches.RedTowerBlueDoors), sm.traverse('RedBrinstarElevatorTop'))),
|
||||
roomInfo = {'RoomPtr':0x962a, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8af6, 'direction': 0x7, "cap": (0x16, 0x2d), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x2), "distanceToSpawn": 0x1c0, "doorAsmPtr": 0xb9f1},
|
||||
entryInfo = {'SamusX':0x80, 'SamusY':0x58},
|
||||
start={'spawn':0x010a, 'doors':[0x3c], 'patches':[RomPatches.HellwayBlueDoor], 'solveArea': "Red Brinstar Top", 'areaMode':True},
|
||||
dotOrientation = 'n'),
|
||||
AccessPoint('East Tunnel Right', 'RedBrinstar', {
|
||||
'East Tunnel Top Right': lambda sm: SMBool(True), # handled by room traverse function
|
||||
'Glass Tunnel Top': Cache.ldeco(lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.wor(sm.haveItem('Gravity'),
|
||||
sm.haveItem('HiJump')))),
|
||||
'Red Tower Top Left': lambda sm: sm.canClimbBottomRedTower()
|
||||
}, roomInfo = {'RoomPtr':0xcf80, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa384, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x40,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xce, 'SamusY':0x188},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('East Tunnel Top Right', 'RedBrinstar', {
|
||||
'East Tunnel Right': Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoGatesBase),
|
||||
sm.haveItem('Super')))
|
||||
}, traverse=Cache.ldeco(lambda sm: RomPatches.has(sm.player, RomPatches.AreaRandoGatesBase)),
|
||||
roomInfo = {'RoomPtr':0xcf80, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa390, 'direction': 0x4, "cap": (0x1, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe356},
|
||||
entryInfo = {'SamusX':0x3c6, 'SamusY':0x88},
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('Glass Tunnel Top', 'RedBrinstar', {
|
||||
'East Tunnel Right': Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.MaridiaTubeOpened),
|
||||
sm.canUsePowerBombs()))
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wand(sm.wor(sm.haveItem('Gravity'),
|
||||
sm.haveItem('HiJump')),
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.MaridiaTubeOpened),
|
||||
sm.canUsePowerBombs()))),
|
||||
roomInfo = {'RoomPtr':0xcefb, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa330, 'direction': 0x7, "cap": (0x16, 0x7d), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x7), "distanceToSpawn": 0x200, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x81, 'SamusY':0x78},
|
||||
dotOrientation = 's'),
|
||||
### Tourian
|
||||
AccessPoint('Golden Four', 'Tourian', {},
|
||||
roomInfo = {'RoomPtr':0xa5ed, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x91e6, 'direction': 0x5, "cap": (0xe, 0x66), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x6), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x88},
|
||||
start={'spawn':0x0007, 'solveArea': "Tourian", "save": "Save_G4", 'areaMode':True},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('Tourian Escape Room 4 Top Right', 'Tourian', {},
|
||||
roomInfo = {'RoomPtr':0xdede, "area": 0x5},
|
||||
exitInfo = {'DoorPtr':0xab34, 'direction': 0x4, "cap": (0x1, 0x86), "bitFlag": 0x40,
|
||||
"screen": (0x0, 0x8), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe4cf},
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
]
|
||||
766
worlds/sm/variaRandomizer/graph/vanilla/graph_helpers.py
Normal file
766
worlds/sm/variaRandomizer/graph/vanilla/graph_helpers.py
Normal file
@@ -0,0 +1,766 @@
|
||||
from math import ceil
|
||||
|
||||
from logic.smbool import SMBool
|
||||
from logic.helpers import Helpers, Bosses
|
||||
from logic.cache import Cache
|
||||
from rom.rom_patches import RomPatches
|
||||
from graph.graph_utils import getAccessPoint
|
||||
from utils.parameters import Settings
|
||||
|
||||
class HelpersGraph(Helpers):
|
||||
def __init__(self, smbm):
|
||||
self.smbm = smbm
|
||||
|
||||
def canEnterAndLeaveGauntletQty(self, nPB, nTanksSpark):
|
||||
sm = self.smbm
|
||||
# EXPLAINED: to access Gauntlet Entrance from Landing site we can either:
|
||||
# -fly to it (infinite bomb jumps or space jump)
|
||||
# -shinespark to it
|
||||
# -wall jump with high jump boots
|
||||
# -wall jump without high jump boots
|
||||
# then inside it to break the bomb wals:
|
||||
# -use screw attack (easy way)
|
||||
# -use power bombs
|
||||
# -use bombs
|
||||
# -perform a simple short charge on the way in
|
||||
# and use power bombs on the way out
|
||||
return sm.wand(sm.wor(sm.canFly(),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.wand(sm.knowsHiJumpGauntletAccess(),
|
||||
sm.haveItem('HiJump')),
|
||||
sm.knowsHiJumpLessGauntletAccess()),
|
||||
sm.wor(sm.haveItem('ScrewAttack'),
|
||||
sm.wor(sm.wand(sm.energyReserveCountOkHardRoom('Gauntlet'),
|
||||
sm.wand(sm.canUsePowerBombs(),
|
||||
sm.wor(sm.itemCountOk('PowerBomb', nPB),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.energyReserveCountOk(nTanksSpark))))),
|
||||
sm.wand(sm.energyReserveCountOkHardRoom('Gauntlet', 0.51),
|
||||
sm.canUseBombs()))))
|
||||
|
||||
@Cache.decorator
|
||||
def canEnterAndLeaveGauntlet(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.canShortCharge(),
|
||||
sm.canEnterAndLeaveGauntletQty(2, 2)),
|
||||
sm.canEnterAndLeaveGauntletQty(2, 3))
|
||||
|
||||
def canPassTerminatorBombWall(self, fromLandingSite=True):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.wor(SMBool(not fromLandingSite, 0), sm.knowsSimpleShortCharge(), sm.knowsShortCharge())),
|
||||
sm.canDestroyBombWalls())
|
||||
|
||||
@Cache.decorator
|
||||
def canPassCrateriaGreenPirates(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.canPassBombPassages(),
|
||||
sm.haveMissileOrSuper(),
|
||||
sm.energyReserveCountOk(1),
|
||||
sm.wor(sm.haveItem('Charge'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.haveItem('Wave'),
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.haveItem('Plasma'),
|
||||
sm.haveItem('ScrewAttack'))))
|
||||
|
||||
# from blue brin elevator
|
||||
@Cache.decorator
|
||||
def canAccessBillyMays(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.wor(RomPatches.has(sm.player, RomPatches.BlueBrinstarBlueDoor),
|
||||
sm.traverse('ConstructionZoneRight')),
|
||||
sm.canUsePowerBombs(),
|
||||
sm.wor(sm.knowsBillyMays(),
|
||||
sm.haveItem('Gravity'),
|
||||
sm.haveItem('SpaceJump')))
|
||||
|
||||
@Cache.decorator
|
||||
def canAccessKraidsLair(self):
|
||||
sm = self.smbm
|
||||
# EXPLAINED: access the upper right platform with either:
|
||||
# -hijump boots (easy regular way)
|
||||
# -fly (space jump or infinite bomb jump)
|
||||
# -know how to wall jump on the platform without the hijump boots
|
||||
return sm.wand(sm.haveItem('Super'),
|
||||
sm.wor(sm.haveItem('HiJump'),
|
||||
sm.canFly(),
|
||||
sm.knowsEarlyKraid()))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassMoat(self):
|
||||
sm = self.smbm
|
||||
# EXPLAINED: In the Moat we can either:
|
||||
# -use grapple or space jump (easy way)
|
||||
# -do a continuous wall jump (https://www.youtube.com/watch?v=4HVhTwwax6g)
|
||||
# -do a diagonal bomb jump from the middle platform (https://www.youtube.com/watch?v=5NRqQ7RbK3A&t=10m58s)
|
||||
# -do a short charge from the Keyhunter room (https://www.youtube.com/watch?v=kFAYji2gFok)
|
||||
# -do a gravity jump from below the right platform
|
||||
# -do a mock ball and a bounce ball (https://www.youtube.com/watch?v=WYxtRF--834)
|
||||
# -with gravity, either hijump or IBJ
|
||||
return sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.knowsContinuousWallJump(),
|
||||
sm.wand(sm.knowsDiagonalBombJump(), sm.canUseBombs()),
|
||||
sm.canSimpleShortCharge(),
|
||||
sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.knowsGravityJump(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.canInfiniteBombJump())),
|
||||
sm.wand(sm.knowsMockballWs(), sm.canUseSpringBall()))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassMoatFromMoat(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.knowsDiagonalBombJump(), sm.canUseBombs()),
|
||||
sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.knowsGravityJump(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.canInfiniteBombJump())))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassMoatReverse(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(RomPatches.has(sm.player, RomPatches.MoatShotBlock),
|
||||
sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.haveItem('Gravity'),
|
||||
sm.canPassBombPassages())
|
||||
|
||||
@Cache.decorator
|
||||
def canPassSpongeBath(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.canPassBombPassages(),
|
||||
sm.knowsSpongeBathBombJump()),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsSpongeBathHiJump()),
|
||||
sm.haveItem('Gravity'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.knowsSpongeBathSpeed()),
|
||||
sm.canSpringBallJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canPassBowling(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(Bosses.bossDead(sm, 'Phantoon'),
|
||||
sm.wor(SMBool(sm.getDmgReduction()[0] >= 2),
|
||||
sm.energyReserveCountOk(1),
|
||||
sm.haveItem("SpaceJump"),
|
||||
sm.haveItem("Grapple")))
|
||||
|
||||
@Cache.decorator
|
||||
def canAccessEtecoons(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.canUsePowerBombs(),
|
||||
sm.wand(sm.knowsMoondance(), sm.canUseBombs(), sm.traverse('MainShaftBottomRight')))
|
||||
|
||||
@Cache.decorator
|
||||
def canKillBeetoms(self):
|
||||
sm = self.smbm
|
||||
# can technically be killed with bomb, but it's harder
|
||||
return sm.wor(sm.haveMissileOrSuper(), sm.canUsePowerBombs(), sm.haveItem('ScrewAttack'))
|
||||
|
||||
# the water zone east of WS
|
||||
def canPassForgottenHighway(self, fromWs):
|
||||
sm = self.smbm
|
||||
suitless = sm.wand(sm.haveItem('HiJump'), sm.knowsGravLessLevel1())
|
||||
if fromWs is True and RomPatches.has(sm.player, RomPatches.EastOceanPlatforms).bool is False:
|
||||
suitless = sm.wand(suitless,
|
||||
sm.wor(sm.canSpringBallJump(), # two sbj on the far right
|
||||
# to break water line and go through the door on the right
|
||||
sm.haveItem('SpaceJump')))
|
||||
return sm.wand(sm.wor(sm.haveItem('Gravity'),
|
||||
suitless),
|
||||
sm.haveItem('Morph')) # for crab maze
|
||||
|
||||
@Cache.decorator
|
||||
def canExitCrabHole(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'), # morph to exit the hole
|
||||
sm.wor(sm.wand(sm.haveItem('Gravity'), # even with gravity you need some way to climb...
|
||||
sm.wor(sm.haveItem('Ice'), # ...on crabs...
|
||||
sm.wand(sm.haveItem('HiJump'), sm.knowsMaridiaWallJumps()), # ...or by jumping
|
||||
sm.knowsGravityJump(),
|
||||
sm.canFly())),
|
||||
sm.wand(sm.haveItem('Ice'), sm.canDoSuitlessOuterMaridia()), # climbing crabs
|
||||
sm.canDoubleSpringBallJump()))
|
||||
|
||||
# bottom sandpits with the evirs except west sand hall left to right
|
||||
@Cache.decorator
|
||||
def canTraverseSandPits(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel3(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.haveItem('Ice')))
|
||||
|
||||
@Cache.decorator
|
||||
def canTraverseWestSandHallLeftToRight(self):
|
||||
sm = self.smbm
|
||||
return sm.haveItem('Gravity') # FIXME find suitless condition
|
||||
|
||||
@Cache.decorator
|
||||
def canPassMaridiaToRedTowerNode(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'),
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoGatesBase),
|
||||
sm.haveItem('Super')))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassRedTowerToMaridiaNode(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesBase))
|
||||
|
||||
def canEnterCathedral(self, mult=1.0):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.traverse('CathedralEntranceRight'),
|
||||
sm.wor(sm.wand(sm.canHellRun('MainUpperNorfair', mult),
|
||||
sm.wor(sm.wor(RomPatches.has(sm.player, RomPatches.CathedralEntranceWallJump),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.canFly()),
|
||||
sm.wor(sm.haveItem('SpeedBooster'), # spark
|
||||
sm.canSpringBallJump()))),
|
||||
sm.wand(sm.canHellRun('MainUpperNorfair', 0.5*mult),
|
||||
sm.haveItem('Morph'),
|
||||
sm.knowsNovaBoost())))
|
||||
|
||||
@Cache.decorator
|
||||
def canClimbBubbleMountain(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('HiJump'),
|
||||
sm.canFly(),
|
||||
sm.haveItem('Ice'),
|
||||
sm.knowsBubbleMountainWallJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canHellRunToSpeedBooster(self):
|
||||
sm = self.smbm
|
||||
return sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Speed Booster w/Speed' if sm.haveItem('SpeedBooster') else 'Bubble -> Speed Booster'])
|
||||
|
||||
@Cache.decorator
|
||||
def canAccessDoubleChamberItems(self):
|
||||
sm = self.smbm
|
||||
hellRun = Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Wave']
|
||||
return sm.wor(sm.wand(sm.traverse('SingleChamberRight'),
|
||||
sm.canHellRun(**hellRun)),
|
||||
sm.wand(sm.wor(sm.haveItem('HiJump'),
|
||||
sm.canSimpleShortCharge(),
|
||||
sm.canFly(),
|
||||
sm.knowsDoubleChamberWallJump()),
|
||||
sm.canHellRun(hellRun['hellRun'], hellRun['mult']*0.8, hellRun['minE'])))
|
||||
|
||||
def canExitCathedral(self, hellRun):
|
||||
# from top: can use bomb/powerbomb jumps
|
||||
# from bottom: can do a shinespark or use space jump
|
||||
# can do it with highjump + wall jump
|
||||
# can do it with only two wall jumps (the first one is delayed like on alcatraz)
|
||||
# can do it with a spring ball jump from wall
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.wor(sm.canHellRun(**hellRun),
|
||||
sm.heatProof()),
|
||||
sm.wor(sm.wor(sm.canPassBombPassages(),
|
||||
sm.haveItem("SpeedBooster")),
|
||||
sm.wor(sm.haveItem("SpaceJump"),
|
||||
sm.haveItem("HiJump"),
|
||||
sm.knowsWallJumpCathedralExit(),
|
||||
sm.wand(sm.knowsSpringBallJumpFromWall(), sm.canUseSpringBall()))))
|
||||
|
||||
@Cache.decorator
|
||||
def canGrappleEscape(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wor(sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.canInfiniteBombJump(), # IBJ from lava...either have grav or freeze the enemy there if hellrunning (otherwise single DBJ at the end)
|
||||
sm.wor(sm.heatProof(),
|
||||
sm.haveItem('Gravity'),
|
||||
sm.haveItem('Ice')))),
|
||||
sm.haveItem('Grapple'),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.haveItem('HiJump'), # jump from the blocks below
|
||||
sm.knowsShortCharge())), # spark from across the grapple blocks
|
||||
sm.wand(sm.haveItem('HiJump'), sm.canSpringBallJump())) # jump from the blocks below
|
||||
|
||||
@Cache.decorator
|
||||
def canPassFrogSpeedwayRightToLeft(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('SpeedBooster'),
|
||||
sm.wand(sm.knowsFrogSpeedwayWithoutSpeed(),
|
||||
sm.haveItem('Wave'),
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.haveItem('Plasma'))))
|
||||
|
||||
@Cache.decorator
|
||||
def canEnterNorfairReserveAreaFromBubbleMoutain(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.traverse('BubbleMountainTopLeft'),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.haveItem('Ice'),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsGetAroundWallJump()),
|
||||
sm.wand(sm.canUseSpringBall(),
|
||||
sm.knowsSpringBallJumpFromWall())))
|
||||
|
||||
@Cache.decorator
|
||||
def canEnterNorfairReserveAreaFromBubbleMoutainTop(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.traverse('BubbleMountainTopLeft'),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.knowsNorfairReserveDBoost()))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassLavaPit(self):
|
||||
sm = self.smbm
|
||||
nTanks4Dive = 8 / sm.getDmgReduction()[0]
|
||||
if sm.haveItem('HiJump').bool == False:
|
||||
nTanks4Dive = ceil(nTanks4Dive * 1.25)
|
||||
return sm.wand(sm.wor(sm.wand(sm.haveItem('Gravity'), sm.haveItem('SpaceJump')),
|
||||
sm.wand(sm.knowsGravityJump(), sm.haveItem('Gravity'), sm.wor(sm.haveItem('HiJump'), sm.knowsLavaDive())),
|
||||
sm.wand(sm.wor(sm.wand(sm.knowsLavaDive(), sm.haveItem('HiJump')),
|
||||
sm.knowsLavaDiveNoHiJump()),
|
||||
sm.energyReserveCountOk(nTanks4Dive))),
|
||||
sm.canUsePowerBombs()) # power bomb blocks left and right of LN entrance without any items before
|
||||
|
||||
@Cache.decorator
|
||||
def canPassLavaPitReverse(self):
|
||||
sm = self.smbm
|
||||
nTanks = 2
|
||||
if sm.heatProof().bool == False:
|
||||
nTanks = 6
|
||||
return sm.energyReserveCountOk(nTanks)
|
||||
|
||||
@Cache.decorator
|
||||
def canPassLowerNorfairChozo(self):
|
||||
sm = self.smbm
|
||||
# to require one more CF if no heat protection because of distance to cover, wait times, acid...
|
||||
return sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Entrance -> GT via Chozo']),
|
||||
sm.canUsePowerBombs(),
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.LNChozoSJCheckDisabled), sm.haveItem('SpaceJump')))
|
||||
|
||||
@Cache.decorator
|
||||
def canExitScrewAttackArea(self):
|
||||
sm = self.smbm
|
||||
|
||||
return sm.wand(sm.canDestroyBombWalls(),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.wand(sm.haveItem('ScrewAttack'), sm.knowsScrewAttackExit()),
|
||||
sm.knowsScrewAttackExitWithoutScrew())),
|
||||
sm.wand(sm.canUseSpringBall(),
|
||||
sm.knowsSpringBallJumpFromWall()),
|
||||
sm.wand(sm.canSimpleShortCharge(), # fight GT and spark out
|
||||
sm.enoughStuffGT())))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassWorstRoom(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canDestroyBombWalls(),
|
||||
sm.canPassWorstRoomPirates(),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.wand(sm.knowsWorstRoomIceCharge(), sm.haveItem('Ice'), sm.canFireChargedShots()),
|
||||
sm.wor(sm.wand(sm.knowsGetAroundWallJump(), sm.haveItem('HiJump')),
|
||||
sm.knowsWorstRoomWallJump()),
|
||||
sm.wand(sm.knowsSpringBallJumpFromWall(), sm.canUseSpringBall())))
|
||||
|
||||
# checks mix of super missiles/health
|
||||
def canGoThroughLowerNorfairEnemy(self, nmyHealth, nbNmy, nmyHitDmg, supDmg=300.0):
|
||||
sm = self.smbm
|
||||
# supers only
|
||||
if sm.itemCount('Super')*5*supDmg >= nbNmy*nmyHealth:
|
||||
return SMBool(True, 0, items=['Super'])
|
||||
|
||||
# - or with taking damage as well?
|
||||
(dmgRed, redItems) = sm.getDmgReduction(envDmg=False)
|
||||
dmg = nmyHitDmg / dmgRed
|
||||
if sm.heatProof() and (sm.itemCount('Super')*5*supDmg)/nmyHealth + (sm.energyReserveCount()*100 - 2)/dmg >= nbNmy:
|
||||
# require heat proof as long as taking damage is necessary.
|
||||
# display all the available energy in the solver.
|
||||
return sm.wand(sm.heatProof(), SMBool(True, 0, items=redItems+['Super', '{}-ETank - {}-Reserve'.format(self.smbm.itemCount('ETank'), self.smbm.itemCount('Reserve'))]))
|
||||
|
||||
return sm.knowsDodgeLowerNorfairEnemies()
|
||||
|
||||
def canKillRedKiHunters(self, n):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Plasma'),
|
||||
sm.haveItem('ScrewAttack'),
|
||||
sm.wand(sm.heatProof(), # this takes a loooong time ...
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.wand(sm.haveItem('Charge'),
|
||||
sm.haveItem('Wave')))),
|
||||
sm.canGoThroughLowerNorfairEnemy(1800.0, float(n), 200.0))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassThreeMuskateers(self):
|
||||
sm = self.smbm
|
||||
return sm.canKillRedKiHunters(6)
|
||||
|
||||
@Cache.decorator
|
||||
def canPassRedKiHunters(self):
|
||||
sm = self.smbm
|
||||
return sm.canKillRedKiHunters(3)
|
||||
|
||||
@Cache.decorator
|
||||
def canPassWastelandDessgeegas(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Plasma'),
|
||||
sm.haveItem('ScrewAttack'),
|
||||
sm.wand(sm.heatProof(), # this takes a loooong time ...
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.wand(sm.haveItem('Charge'),
|
||||
sm.haveItem('Wave')))),
|
||||
sm.itemCountOk('PowerBomb', 4),
|
||||
sm.canGoThroughLowerNorfairEnemy(800.0, 3.0, 160.0))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassNinjaPirates(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.itemCountOk('Missile', 10),
|
||||
sm.itemCountOk('Super', 2),
|
||||
sm.haveItem('Plasma'),
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.wand(sm.haveItem('Charge'),
|
||||
sm.wor(sm.haveItem('Wave'),
|
||||
sm.haveItem('Ice')))),
|
||||
sm.canShortCharge()) # echoes kill
|
||||
|
||||
@Cache.decorator
|
||||
def canPassWorstRoomPirates(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('ScrewAttack'),
|
||||
sm.itemCountOk('Missile', 6),
|
||||
sm.itemCountOk('Super', 3),
|
||||
sm.wand(sm.canFireChargedShots(), sm.haveItem('Plasma')),
|
||||
sm.wand(sm.haveItem('Charge'),
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.haveItem('Wave'),
|
||||
sm.haveItem('Ice'))),
|
||||
sm.knowsDodgeLowerNorfairEnemies())
|
||||
|
||||
# go though the pirates room filled with acid
|
||||
@Cache.decorator
|
||||
def canPassAmphitheaterReverse(self):
|
||||
sm = self.smbm
|
||||
dmgRed = sm.getDmgReduction()[0]
|
||||
nTanksGrav = 4 * 4/dmgRed
|
||||
nTanksNoGrav = 6 * 4/dmgRed
|
||||
return sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.energyReserveCountOk(nTanksGrav)),
|
||||
sm.wand(sm.energyReserveCountOk(nTanksNoGrav),
|
||||
sm.knowsLavaDive())) # should be a good enough skill filter for acid wall jumps with no grav...
|
||||
|
||||
@Cache.decorator
|
||||
def canGetBackFromRidleyZone(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canUsePowerBombs(),
|
||||
sm.wor(sm.canUseSpringBall(),
|
||||
sm.canUseBombs(),
|
||||
sm.itemCountOk('PowerBomb', 2),
|
||||
sm.haveItem('ScrewAttack'),
|
||||
sm.canShortCharge()), # speedball
|
||||
# in escape you don't have PBs and can't shoot bomb blocks in long tunnels
|
||||
# in wasteland and ki hunter room
|
||||
sm.wnot(sm.canUseHyperBeam()))
|
||||
|
||||
@Cache.decorator
|
||||
def canClimbRedTower(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.knowsRedTowerClimb(),
|
||||
sm.haveItem('Ice'),
|
||||
sm.haveItem('SpaceJump'))
|
||||
|
||||
@Cache.decorator
|
||||
def canClimbBottomRedTower(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(RomPatches.has(sm.player, RomPatches.RedTowerLeftPassage),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.canFly(),
|
||||
sm.canShortCharge())
|
||||
|
||||
@Cache.decorator
|
||||
def canGoUpMtEverest(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.canFly(),
|
||||
sm.wand(sm.knowsGravityJump(),
|
||||
sm.wor(sm.haveItem('HiJump'),
|
||||
sm.knowsMtEverestGravJump())))),
|
||||
sm.wand(sm.canDoSuitlessOuterMaridia(),
|
||||
sm.haveItem('Grapple')))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassMtEverest(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.canFly(),
|
||||
sm.knowsGravityJump())),
|
||||
sm.wand(sm.canDoSuitlessOuterMaridia(),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.wand(sm.haveItem('Ice'), sm.knowsTediousMountEverest(), sm.haveItem('Super')),
|
||||
sm.canDoubleSpringBallJump())))
|
||||
|
||||
@Cache.decorator
|
||||
def canJumpUnderwater(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel1(),
|
||||
sm.haveItem('HiJump')))
|
||||
|
||||
@Cache.decorator
|
||||
def canDoSuitlessOuterMaridia(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.knowsGravLessLevel1(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.wor(sm.haveItem('Ice'),
|
||||
sm.canSpringBallJump()))
|
||||
|
||||
@Cache.decorator
|
||||
def canDoOuterMaridia(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.canDoSuitlessOuterMaridia())
|
||||
|
||||
@Cache.decorator
|
||||
def canPassBotwoonHallway(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.haveItem('Gravity')),
|
||||
sm.wand(sm.knowsMochtroidClip(), sm.haveItem('Ice')),
|
||||
sm.canCrystalFlashClip())
|
||||
|
||||
@Cache.decorator
|
||||
def canDefeatBotwoon(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.enoughStuffBotwoon(),
|
||||
sm.canPassBotwoonHallway())
|
||||
|
||||
# the sandpits from aqueduct
|
||||
@Cache.decorator
|
||||
def canAccessSandPits(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsGravLessLevel3()))
|
||||
|
||||
@Cache.decorator
|
||||
def canReachCacatacAlleyFromBotowoon(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel2(),
|
||||
sm.haveItem("HiJump"),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.canDoubleSpringBallJump())))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassCacatacAlley(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(Bosses.bossDead(sm, 'Draygon'),
|
||||
sm.haveItem('Morph'),
|
||||
sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel2(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.haveItem('SpaceJump'))))
|
||||
|
||||
@Cache.decorator
|
||||
def canGoThroughColosseumSuitless(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.haveItem('Ice'),
|
||||
sm.energyReserveCountOk(int(7.0/sm.getDmgReduction(False)[0])), # mochtroid dmg
|
||||
sm.knowsBotwoonToDraygonWithIce()))
|
||||
|
||||
@Cache.decorator
|
||||
def canBotwoonExitToColosseum(self):
|
||||
sm = self.smbm
|
||||
# traverse Botwoon Energy Tank Room
|
||||
return sm.wand(sm.wor(sm.wand(sm.haveItem('Gravity'), sm.haveItem('SpeedBooster')),
|
||||
sm.wand(sm.haveItem('Morph'), sm.canJumpUnderwater())),
|
||||
# after Botwoon Energy Tank Room
|
||||
sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel2(),
|
||||
sm.haveItem("HiJump"),
|
||||
# get to top right door
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('Ice'), # climb mochtroids
|
||||
sm.wand(sm.canDoubleSpringBallJump(),
|
||||
sm.haveItem('SpaceJump'))),
|
||||
sm.canGoThroughColosseumSuitless())))
|
||||
|
||||
@Cache.decorator
|
||||
def canColosseumToBotwoonExit(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel2(),
|
||||
sm.haveItem("HiJump"),
|
||||
sm.canGoThroughColosseumSuitless()))
|
||||
|
||||
@Cache.decorator
|
||||
def canClimbColosseum(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel2(),
|
||||
sm.haveItem("HiJump"),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.knowsPreciousRoomGravJumpExit())))
|
||||
|
||||
@Cache.decorator
|
||||
def canClimbWestSandHole(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsGravLessLevel3(),
|
||||
sm.wor(sm.haveItem('SpaceJump'),
|
||||
sm.canSpringBallJump(),
|
||||
sm.knowsWestSandHoleSuitlessWallJumps())))
|
||||
|
||||
@Cache.decorator
|
||||
def canAccessItemsInWestSandHole(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('HiJump'), # vanilla strat
|
||||
sm.canUseSpringBall()),
|
||||
sm.wand(sm.haveItem('SpaceJump'), # alternate strat with possible double bomb jump but no difficult wj
|
||||
sm.wor(sm.canUseSpringBall(),
|
||||
sm.canUseBombs())),
|
||||
sm.wand(sm.canPassBombPassages(), # wjs and/or 3 tile mid air morph
|
||||
sm.knowsMaridiaWallJumps()))
|
||||
|
||||
@Cache.decorator
|
||||
def getDraygonConnection(self):
|
||||
return getAccessPoint('DraygonRoomOut').ConnectedTo
|
||||
|
||||
@Cache.decorator
|
||||
def isVanillaDraygon(self):
|
||||
return SMBool(self.getDraygonConnection() == 'DraygonRoomIn')
|
||||
|
||||
@Cache.decorator
|
||||
def canUseCrocRoomToChargeSpeed(self):
|
||||
sm = self.smbm
|
||||
crocRoom = getAccessPoint('Crocomire Room Top')
|
||||
speedway = getAccessPoint('Crocomire Speedway Bottom')
|
||||
return sm.wand(SMBool(crocRoom.ConnectedTo == 'Crocomire Speedway Bottom'),
|
||||
crocRoom.traverse(sm),
|
||||
speedway.traverse(sm))
|
||||
|
||||
@Cache.decorator
|
||||
def canFightDraygon(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.wor(sm.knowsGravLessLevel2(),
|
||||
sm.knowsGravLessLevel3())))
|
||||
|
||||
@Cache.decorator
|
||||
def canDraygonCrystalFlashSuit(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canCrystalFlash(),
|
||||
sm.knowsDraygonRoomCrystalFlash(),
|
||||
# ask for 4 PB pack as an ugly workaround for
|
||||
# a rando bug which can place a PB at space
|
||||
# jump to "get you out" (this check is in
|
||||
# PostAvailable condition of the Dray/Space
|
||||
# Jump locs)
|
||||
sm.itemCountOk('PowerBomb', 4))
|
||||
|
||||
@Cache.decorator
|
||||
def canExitDraygonRoomWithGravity(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.knowsGravityJump(),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.haveItem('SpeedBooster'))))
|
||||
|
||||
@Cache.decorator
|
||||
def canGrappleExitDraygon(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Grapple'),
|
||||
sm.knowsDraygonRoomGrappleExit())
|
||||
|
||||
@Cache.decorator
|
||||
def canExitDraygonVanilla(self):
|
||||
sm = self.smbm
|
||||
# to get out of draygon room:
|
||||
# with gravity but without highjump/bomb/space jump: gravity jump
|
||||
# to exit draygon room: grapple or crystal flash (for free shine spark)
|
||||
# to exit precious room: spring ball jump, xray scope glitch or stored spark
|
||||
return sm.wor(sm.canExitDraygonRoomWithGravity(),
|
||||
sm.wand(sm.canDraygonCrystalFlashSuit(),
|
||||
# use the spark either to exit draygon room or precious room
|
||||
sm.wor(sm.canGrappleExitDraygon(),
|
||||
sm.wand(sm.haveItem('XRayScope'),
|
||||
sm.knowsPreciousRoomXRayExit()),
|
||||
sm.canSpringBallJump())),
|
||||
# spark-less exit (no CF)
|
||||
sm.wand(sm.canGrappleExitDraygon(),
|
||||
sm.wor(sm.wand(sm.haveItem('XRayScope'),
|
||||
sm.knowsPreciousRoomXRayExit()),
|
||||
sm.canSpringBallJump())),
|
||||
sm.canDoubleSpringBallJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canExitDraygonRandomized(self):
|
||||
sm = self.smbm
|
||||
# disregard precious room
|
||||
return sm.wor(sm.canExitDraygonRoomWithGravity(),
|
||||
sm.canDraygonCrystalFlashSuit(),
|
||||
sm.canGrappleExitDraygon(),
|
||||
sm.canDoubleSpringBallJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canExitDraygon(self):
|
||||
sm = self.smbm
|
||||
if self.isVanillaDraygon():
|
||||
return self.canExitDraygonVanilla()
|
||||
else:
|
||||
return self.canExitDraygonRandomized()
|
||||
|
||||
@Cache.decorator
|
||||
def canExitPreciousRoomVanilla(self):
|
||||
return SMBool(True) # handled by canExitDraygonVanilla
|
||||
|
||||
@Cache.decorator
|
||||
def canExitPreciousRoomRandomized(self):
|
||||
sm = self.smbm
|
||||
suitlessRoomExit = sm.canSpringBallJump()
|
||||
if suitlessRoomExit.bool == False:
|
||||
if self.getDraygonConnection() == 'KraidRoomIn':
|
||||
suitlessRoomExit = sm.canShortCharge() # charge spark in kraid's room
|
||||
elif self.getDraygonConnection() == 'RidleyRoomIn':
|
||||
suitlessRoomExit = sm.wand(sm.haveItem('XRayScope'), # get doorstuck in compatible transition
|
||||
sm.knowsPreciousRoomXRayExit())
|
||||
return sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.knowsGravityJump(),
|
||||
sm.haveItem('HiJump'))),
|
||||
suitlessRoomExit)
|
||||
|
||||
@Cache.decorator
|
||||
def canExitPreciousRoom(self):
|
||||
if self.isVanillaDraygon():
|
||||
return self.canExitPreciousRoomVanilla()
|
||||
else:
|
||||
return self.canExitPreciousRoomRandomized()
|
||||
|
||||
@Cache.decorator
|
||||
def canPassDachoraRoom(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('SpeedBooster'), sm.canDestroyBombWalls())
|
||||
|
||||
@Cache.decorator
|
||||
def canTraverseCrabTunnelLeftToRight(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.traverse('MainStreetBottomRight'),
|
||||
sm.wor(sm.haveItem('Super'),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther)))
|
||||
994
worlds/sm/variaRandomizer/graph/vanilla/graph_locations.py
Normal file
994
worlds/sm/variaRandomizer/graph/vanilla/graph_locations.py
Normal file
@@ -0,0 +1,994 @@
|
||||
from logic.helpers import Bosses
|
||||
from utils.parameters import Settings
|
||||
from rom.rom_patches import RomPatches
|
||||
from logic.smbool import SMBool
|
||||
from graph.location import locationsDict
|
||||
|
||||
locationsDict["Energy Tank, Gauntlet"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Gauntlet"].Available = (
|
||||
lambda sm: sm.wor(sm.canEnterAndLeaveGauntlet(),
|
||||
sm.wand(sm.canShortCharge(),
|
||||
sm.canEnterAndLeaveGauntletQty(1, 0)), # thanks ponk! https://youtu.be/jil5zTBCF1s
|
||||
sm.canDoLowGauntlet())
|
||||
)
|
||||
locationsDict["Bomb"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Bomb"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.traverse('FlywayRight'))
|
||||
)
|
||||
locationsDict["Bomb"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.knowsAlcatrazEscape(),
|
||||
sm.canPassBombPassages())
|
||||
)
|
||||
locationsDict["Energy Tank, Terminator"].AccessFrom = {
|
||||
'Landing Site': lambda sm: sm.canPassTerminatorBombWall(),
|
||||
'Lower Mushrooms Left': lambda sm: sm.canPassCrateriaGreenPirates(),
|
||||
'Gauntlet Top': lambda sm: sm.haveItem('Morph')
|
||||
}
|
||||
locationsDict["Energy Tank, Terminator"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Reserve Tank, Brinstar"].AccessFrom = {
|
||||
'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight'))
|
||||
}
|
||||
locationsDict["Reserve Tank, Brinstar"].Available = (
|
||||
lambda sm: sm.wand(sm.wor(sm.canMockball(),
|
||||
sm.haveItem('SpeedBooster')),
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('EarlySupersRight')))
|
||||
)
|
||||
locationsDict["Charge Beam"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Charge Beam"].Available = (
|
||||
lambda sm: sm.canPassBombPassages()
|
||||
)
|
||||
locationsDict["Morphing Ball"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Morphing Ball"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Energy Tank, Brinstar Ceiling"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BlueBrinstarBlueDoor), sm.traverse('ConstructionZoneRight'))
|
||||
}
|
||||
locationsDict["Energy Tank, Brinstar Ceiling"].Available = (
|
||||
|
||||
lambda sm: sm.wor(sm.knowsCeilingDBoost(),
|
||||
sm.canFly(),
|
||||
sm.wor(sm.haveItem('HiJump'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.wand(sm.canUsePowerBombs(),
|
||||
sm.haveItem('SpeedBooster')),
|
||||
sm.canSimpleShortCharge()))
|
||||
)
|
||||
locationsDict["Energy Tank, Etecoons"].AccessFrom = {
|
||||
'Etecoons Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Etecoons"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Energy Tank, Waterway"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Waterway"].Available = (
|
||||
lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.traverse('BigPinkBottomLeft'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.haveItem('Gravity'),
|
||||
sm.canSimpleShortCharge())) # from the blocks above the water
|
||||
)
|
||||
locationsDict["Energy Tank, Brinstar Gate"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Brinstar Gate"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('BigPinkRight'),
|
||||
sm.wor(sm.haveItem('Wave'),
|
||||
sm.wand(sm.haveItem('Super'),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.knowsReverseGateGlitch()),
|
||||
sm.wand(sm.haveItem('Super'),
|
||||
sm.knowsReverseGateGlitchHiJumpLess())))
|
||||
)
|
||||
locationsDict["X-Ray Scope"].AccessFrom = {
|
||||
'Red Tower Top Left': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["X-Ray Scope"].Available = (
|
||||
lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.traverse('RedTowerLeft'),
|
||||
sm.traverse('RedBrinstarFirefleaLeft'),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.energyReserveCountOkHardRoom('X-Ray'),
|
||||
sm.wor(sm.knowsXrayDboost(),
|
||||
sm.wand(sm.haveItem('Ice'),
|
||||
sm.wor(sm.haveItem('HiJump'), sm.knowsXrayIce())),
|
||||
sm.canInfiniteBombJump(),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.wor(sm.haveItem('SpeedBooster'),
|
||||
sm.canSpringBallJump()))))))
|
||||
)
|
||||
locationsDict["Spazer"].AccessFrom = {
|
||||
'East Tunnel Right': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Spazer"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('BelowSpazerTopRight'),
|
||||
sm.wor(sm.canPassBombPassages(),
|
||||
sm.wand(sm.haveItem('Morph'),
|
||||
RomPatches.has(sm.player, RomPatches.SpazerShotBlock))))
|
||||
)
|
||||
locationsDict["Energy Tank, Kraid"].AccessFrom = {
|
||||
'Warehouse Zeela Room Left': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Kraid"].Available = (
|
||||
lambda sm: sm.wand(Bosses.bossDead(sm, 'Kraid'),
|
||||
# kill the beetoms to unlock the door to get out
|
||||
sm.canKillBeetoms())
|
||||
)
|
||||
locationsDict["Kraid"].AccessFrom = {
|
||||
'KraidRoomIn': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Kraid"].Available = (
|
||||
lambda sm: sm.enoughStuffsKraid()
|
||||
)
|
||||
locationsDict["Varia Suit"].AccessFrom = {
|
||||
'KraidRoomIn': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Varia Suit"].Available = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Kraid')
|
||||
)
|
||||
locationsDict["Ice Beam"].AccessFrom = {
|
||||
'Business Center': lambda sm: sm.traverse('BusinessCenterTopLeft')
|
||||
}
|
||||
locationsDict["Ice Beam"].Available = (
|
||||
lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['Ice']['Norfair Entrance -> Ice Beam']),
|
||||
sm.wor(sm.canPassBombPassages(), # to exit, or if you fail entrance
|
||||
sm.wand(sm.haveItem('Ice'), # harder strat
|
||||
sm.haveItem('Morph'),
|
||||
sm.knowsIceEscape())),
|
||||
sm.wor(sm.wand(sm.haveItem('Morph'),
|
||||
sm.knowsMockball()),
|
||||
sm.haveItem('SpeedBooster')))
|
||||
)
|
||||
locationsDict["Energy Tank, Crocomire"].AccessFrom = {
|
||||
'Crocomire Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Crocomire"].Available = (
|
||||
lambda sm: sm.wand(sm.enoughStuffCroc(),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.energyReserveCountOk(3/sm.getDmgReduction()[0])))
|
||||
)
|
||||
locationsDict["Hi-Jump Boots"].AccessFrom = {
|
||||
'Business Center': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.HiJumpAreaBlueDoor), sm.traverse('BusinessCenterBottomLeft'))
|
||||
}
|
||||
locationsDict["Hi-Jump Boots"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Hi-Jump Boots"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.canPassBombPassages(),
|
||||
sm.wand(sm.haveItem('Morph'), RomPatches.has(sm.player, RomPatches.HiJumpShotBlock)))
|
||||
)
|
||||
locationsDict["Grapple Beam"].AccessFrom = {
|
||||
'Crocomire Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Grapple Beam"].Available = (
|
||||
lambda sm: sm.wand(sm.enoughStuffCroc(),
|
||||
sm.wor(sm.wand(sm.haveItem('Morph'),
|
||||
sm.canFly()),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.knowsShortCharge(),
|
||||
sm.canUsePowerBombs())),
|
||||
sm.wand(sm.haveItem('Morph'),
|
||||
sm.wor(sm.haveItem('SpeedBooster'),
|
||||
sm.canSpringBallJump()),
|
||||
sm.haveItem('HiJump')), # jump from the yellow plateform ennemy
|
||||
sm.canGreenGateGlitch()))
|
||||
)
|
||||
locationsDict["Grapple Beam"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.haveItem('Morph'), # regular exit
|
||||
sm.wand(sm.haveItem('Super'), # grapple escape reverse
|
||||
sm.wor(sm.canFly(), # Grapple Tutorial Room 2
|
||||
sm.haveItem('HiJump'),
|
||||
sm.haveItem('Grapple')),
|
||||
sm.wor(sm.haveItem('Gravity'), # Grapple Tutorial Room 3
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.haveItem('Grapple'))))
|
||||
)
|
||||
locationsDict["Reserve Tank, Norfair"].AccessFrom = {
|
||||
'Bubble Mountain': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutain(),
|
||||
'Bubble Mountain Top': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutainTop(),
|
||||
}
|
||||
locationsDict["Reserve Tank, Norfair"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'), sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Norfair Reserve']))
|
||||
)
|
||||
locationsDict["Speed Booster"].AccessFrom = {
|
||||
'Bubble Mountain Top': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.SpeedAreaBlueDoors),
|
||||
sm.wand(sm.traverse('BubbleMountainTopRight'),
|
||||
sm.traverse('SpeedBoosterHallRight')))
|
||||
}
|
||||
locationsDict["Speed Booster"].Available = (
|
||||
lambda sm: sm.canHellRunToSpeedBooster()
|
||||
)
|
||||
locationsDict["Wave Beam"].AccessFrom = {
|
||||
'Bubble Mountain Top': lambda sm: sm.canAccessDoubleChamberItems()
|
||||
}
|
||||
locationsDict["Wave Beam"].Available = (
|
||||
lambda sm: sm.traverse('DoubleChamberRight')
|
||||
)
|
||||
locationsDict["Wave Beam"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.haveItem('Morph'), # exit through lower passage under the spikes
|
||||
sm.wand(sm.wor(sm.haveItem('SpaceJump'), # exit through blue gate
|
||||
sm.haveItem('Grapple')),
|
||||
sm.wor(sm.wand(sm.canBlueGateGlitch(), sm.heatProof()), # hell run + green gate glitch is too much
|
||||
sm.haveItem('Wave'))))
|
||||
)
|
||||
locationsDict["Ridley"].AccessFrom = {
|
||||
'RidleyRoomIn': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Ridley"].Available = (
|
||||
lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']), sm.enoughStuffsRidley())
|
||||
)
|
||||
locationsDict["Energy Tank, Ridley"].AccessFrom = {
|
||||
'RidleyRoomIn': lambda sm: sm.haveItem('Ridley')
|
||||
}
|
||||
locationsDict["Energy Tank, Ridley"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Screw Attack"].AccessFrom = {
|
||||
'Screw Attack Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Screw Attack"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Screw Attack"].PostAvailable = (
|
||||
lambda sm: sm.canExitScrewAttackArea()
|
||||
)
|
||||
locationsDict["Energy Tank, Firefleas"].AccessFrom = {
|
||||
'Firefleas': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Firefleas"].Available = (
|
||||
lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.FirefleasRemoveFune),
|
||||
# get past the fune
|
||||
sm.haveItem('Super'),
|
||||
sm.canPassBombPassages(),
|
||||
sm.canUseSpringBall())
|
||||
)
|
||||
locationsDict["Energy Tank, Firefleas"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.knowsFirefleasWalljump(),
|
||||
sm.wor(sm.haveItem('Ice'),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.canFly(),
|
||||
sm.canSpringBallJump()))
|
||||
)
|
||||
locationsDict["Reserve Tank, Wrecked Ship"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Reserve Tank, Wrecked Ship"].Available = (
|
||||
lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.canPassBowling())
|
||||
)
|
||||
locationsDict["Energy Tank, Wrecked Ship"].AccessFrom = {
|
||||
'Wrecked Ship Back': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.WsEtankBlueDoor),
|
||||
sm.traverse('ElectricDeathRoomTopLeft'))
|
||||
}
|
||||
locationsDict["Energy Tank, Wrecked Ship"].Available = (
|
||||
lambda sm: sm.wor(Bosses.bossDead(sm, 'Phantoon'),
|
||||
RomPatches.has(sm.player, RomPatches.WsEtankPhantoonAlive))
|
||||
)
|
||||
locationsDict["Phantoon"].AccessFrom = {
|
||||
'PhantoonRoomIn': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Phantoon"].Available = (
|
||||
lambda sm: sm.enoughStuffsPhantoon()
|
||||
)
|
||||
locationsDict["Right Super, Wrecked Ship"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: Bosses.bossDead(sm, 'Phantoon')
|
||||
}
|
||||
locationsDict["Right Super, Wrecked Ship"].Available = (
|
||||
lambda sm: sm.canPassBombPassages()
|
||||
)
|
||||
locationsDict["Gravity Suit"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Gravity Suit"].Available = (
|
||||
lambda sm: sm.wand(sm.canPassBombPassages(),
|
||||
sm.canPassBowling())
|
||||
)
|
||||
locationsDict["Energy Tank, Mama turtle"].AccessFrom = {
|
||||
'Main Street Bottom': lambda sm: sm.wand(sm.canDoOuterMaridia(),
|
||||
sm.wor(sm.traverse('FishTankRight'),
|
||||
RomPatches.has(sm.player, RomPatches.MamaTurtleBlueDoor)),
|
||||
sm.wor(sm.wor(sm.canFly(),
|
||||
sm.wand(sm.haveItem('Gravity'),
|
||||
sm.haveItem('SpeedBooster')),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.knowsHiJumpMamaTurtle())),
|
||||
sm.wor(sm.wand(sm.canUseSpringBall(),
|
||||
sm.wor(sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsSpringBallJump()),
|
||||
sm.knowsSpringBallJumpFromWall())),
|
||||
sm.haveItem('Grapple')))),
|
||||
'Mama Turtle': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Mama turtle"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Plasma Beam"].AccessFrom = {
|
||||
'Toilet Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Plasma Beam"].Available = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Draygon')
|
||||
)
|
||||
locationsDict["Plasma Beam"].PostAvailable = (
|
||||
lambda sm: sm.wand(sm.wor(sm.wand(sm.canShortCharge(),
|
||||
sm.knowsKillPlasmaPiratesWithSpark()),
|
||||
sm.wand(sm.canFireChargedShots(),
|
||||
sm.knowsKillPlasmaPiratesWithCharge(),
|
||||
# 160/80/40 dmg * 4 ground plasma pirates
|
||||
# => 640/320/160 damage take required
|
||||
# check below is 1099/599/299 (give margin for taking dmg a bit)
|
||||
# (* 4 for nerfed charge, since you need to take hits 4 times instead of one)
|
||||
sm.energyReserveCountOk(int(10.0 * sm.getPiratesPseudoScrewCoeff()/sm.getDmgReduction(False)[0]))),
|
||||
sm.haveItem('ScrewAttack'),
|
||||
sm.haveItem('Plasma')),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsGetAroundWallJump()),
|
||||
sm.canShortCharge(),
|
||||
sm.wand(sm.canSpringBallJump(),
|
||||
sm.knowsSpringBallJumpFromWall())))
|
||||
)
|
||||
locationsDict["Reserve Tank, Maridia"].AccessFrom = {
|
||||
'Left Sandpit': lambda sm: sm.canClimbWestSandHole()
|
||||
}
|
||||
locationsDict["Reserve Tank, Maridia"].Available = (
|
||||
lambda sm: sm.canAccessItemsInWestSandHole()
|
||||
)
|
||||
locationsDict["Spring Ball"].AccessFrom = {
|
||||
'Oasis Bottom': lambda sm: sm.canTraverseSandPits()
|
||||
}
|
||||
locationsDict["Spring Ball"].Available = (
|
||||
lambda sm: sm.wand(sm.canUsePowerBombs(), # in Shaktool room to let Shaktool access the sand blocks
|
||||
sm.wor(sm.wand(sm.haveItem('Ice'), # puyo clip
|
||||
sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.knowsPuyoClip()),
|
||||
sm.wand(sm.haveItem('Gravity'),
|
||||
sm.haveItem('XRayScope'),
|
||||
sm.knowsPuyoClipXRay()),
|
||||
sm.knowsSuitlessPuyoClip())),
|
||||
sm.wand(sm.haveItem('Grapple'), # go through grapple block
|
||||
sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.wor(sm.wand(sm.haveItem('HiJump'), sm.knowsAccessSpringBallWithHiJump()),
|
||||
sm.haveItem('SpaceJump')),
|
||||
sm.knowsAccessSpringBallWithGravJump(),
|
||||
sm.wand(sm.haveItem('Bomb'),
|
||||
sm.wor(sm.knowsAccessSpringBallWithBombJumps(),
|
||||
sm.wand(sm.haveItem('SpringBall'),
|
||||
sm.knowsAccessSpringBallWithSpringBallBombJumps()))),
|
||||
sm.wand(sm.haveItem('SpringBall'), sm.knowsAccessSpringBallWithSpringBallJump()))),
|
||||
sm.wand(sm.haveItem('SpaceJump'), sm.knowsAccessSpringBallWithFlatley()))),
|
||||
sm.wand(sm.haveItem('XRayScope'), sm.knowsAccessSpringBallWithXRayClimb()), # XRay climb
|
||||
sm.canCrystalFlashClip()),
|
||||
sm.wor(sm.haveItem('Gravity'), sm.canUseSpringBall())) # acess the item in spring ball room
|
||||
)
|
||||
locationsDict["Spring Ball"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.haveItem('HiJump'),
|
||||
sm.canFly(),
|
||||
sm.knowsMaridiaWallJumps())),
|
||||
sm.canSpringBallJump())
|
||||
)
|
||||
locationsDict["Energy Tank, Botwoon"].AccessFrom = {
|
||||
'Post Botwoon': lambda sm: sm.canJumpUnderwater()
|
||||
}
|
||||
locationsDict["Energy Tank, Botwoon"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Draygon"].AccessFrom = {
|
||||
'Draygon Room Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Draygon"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Space Jump"].AccessFrom = {
|
||||
'Draygon Room Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Space Jump"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Space Jump"].PostAvailable = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Draygon')
|
||||
)
|
||||
locationsDict["Mother Brain"].AccessFrom = {
|
||||
'Golden Four': lambda sm: Bosses.allBossesDead(sm)
|
||||
}
|
||||
locationsDict["Mother Brain"].Available = (
|
||||
lambda sm: sm.enoughStuffTourian()
|
||||
)
|
||||
locationsDict["Power Bomb (Crateria surface)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (Crateria surface)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('LandingSiteTopRight'),
|
||||
sm.wor(sm.haveItem('SpeedBooster'),
|
||||
sm.canFly()))
|
||||
)
|
||||
locationsDict["Missile (outside Wrecked Ship bottom)"].AccessFrom = {
|
||||
'West Ocean Left': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (outside Wrecked Ship bottom)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Missile (outside Wrecked Ship bottom)"].PostAvailable = (
|
||||
lambda sm: sm.canPassBombPassages()
|
||||
)
|
||||
locationsDict["Missile (outside Wrecked Ship top)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (outside Wrecked Ship top)"].Available = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Phantoon')
|
||||
)
|
||||
locationsDict["Missile (outside Wrecked Ship middle)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (outside Wrecked Ship middle)"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Super'), sm.haveItem('Morph'), Bosses.bossDead(sm, 'Phantoon'))
|
||||
)
|
||||
locationsDict["Missile (Crateria moat)"].AccessFrom = {
|
||||
'Moat Left': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Crateria moat)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (Crateria bottom)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Crateria bottom)"].Available = (
|
||||
lambda sm: sm.wor(sm.canDestroyBombWalls(),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.knowsOldMBWithSpeed()))
|
||||
)
|
||||
locationsDict["Missile (Crateria gauntlet right)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: sm.wor(sm.wand(sm.canEnterAndLeaveGauntlet(),
|
||||
sm.canPassBombPassages()),
|
||||
sm.canDoLowGauntlet()),
|
||||
'Gauntlet Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Crateria gauntlet right)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (Crateria gauntlet left)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: sm.wor(sm.wand(sm.canEnterAndLeaveGauntlet(),
|
||||
sm.canPassBombPassages()),
|
||||
sm.canDoLowGauntlet()),
|
||||
'Gauntlet Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Crateria gauntlet left)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Super Missile (Crateria)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (Crateria)"].Available = (
|
||||
lambda sm: sm.wand(sm.canPassBombPassages(),
|
||||
sm.traverse("ClimbRight"),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
# reserves are hard to trigger midspark when not having ETanks
|
||||
sm.wor(sm.wand(sm.energyReserveCountOk(2), sm.itemCountOk('ETank', 1)), # need energy to get out
|
||||
sm.wand(sm.itemCountOk('ETank', 1),
|
||||
sm.wor(sm.haveItem('Grapple'), # use grapple/space or dmg protection to get out
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.heatProof()))),
|
||||
sm.wor(sm.haveItem('Ice'),
|
||||
sm.wand(sm.canSimpleShortCharge(), sm.canUsePowerBombs()))) # there's also a dboost involved in simple short charge or you have to kill the yellow enemies with some power bombs
|
||||
)
|
||||
locationsDict["Missile (Crateria middle)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Crateria middle)"].Available = (
|
||||
lambda sm: sm.canPassBombPassages()
|
||||
)
|
||||
locationsDict["Power Bomb (green Brinstar bottom)"].AccessFrom = {
|
||||
'Etecoons Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (green Brinstar bottom)"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canKillBeetoms())
|
||||
)
|
||||
locationsDict["Super Missile (pink Brinstar)"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (pink Brinstar)"].Available = (
|
||||
lambda sm: sm.wor(sm.wand(sm.traverse('BigPinkTopRight'),
|
||||
sm.enoughStuffSporeSpawn()),
|
||||
# back way into spore spawn
|
||||
sm.wand(sm.canOpenGreenDoors(),
|
||||
sm.canPassBombPassages()))
|
||||
)
|
||||
locationsDict["Super Missile (pink Brinstar)"].PostAvailable = (
|
||||
lambda sm: sm.wand(sm.canOpenGreenDoors(),
|
||||
sm.canPassBombPassages())
|
||||
)
|
||||
locationsDict["Missile (green Brinstar below super missile)"].AccessFrom = {
|
||||
'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight'))
|
||||
}
|
||||
locationsDict["Missile (green Brinstar below super missile)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (green Brinstar below super missile)"].PostAvailable = (
|
||||
lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.EarlySupersShotBlock), sm.canPassBombPassages())
|
||||
)
|
||||
locationsDict["Super Missile (green Brinstar top)"].AccessFrom = {
|
||||
'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight'))
|
||||
}
|
||||
locationsDict["Super Missile (green Brinstar top)"].Available = (
|
||||
lambda sm: sm.wor(sm.canMockball(),
|
||||
sm.haveItem('SpeedBooster'))
|
||||
)
|
||||
locationsDict["Missile (green Brinstar behind missile)"].AccessFrom = {
|
||||
'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight'))
|
||||
}
|
||||
locationsDict["Missile (green Brinstar behind missile)"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.wor(sm.canMockball(),
|
||||
sm.haveItem('SpeedBooster')),
|
||||
sm.traverse('EarlySupersRight'),
|
||||
sm.wor(sm.canPassBombPassages(),
|
||||
sm.wand(sm.knowsRonPopeilScrew(),
|
||||
sm.haveItem('ScrewAttack'))))
|
||||
)
|
||||
locationsDict["Missile (green Brinstar behind reserve tank)"].AccessFrom = {
|
||||
'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight'))
|
||||
}
|
||||
locationsDict["Missile (green Brinstar behind reserve tank)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('EarlySupersRight'),
|
||||
sm.haveItem('Morph'),
|
||||
sm.wor(sm.canMockball(),
|
||||
sm.haveItem('SpeedBooster')))
|
||||
)
|
||||
locationsDict["Missile (pink Brinstar top)"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (pink Brinstar top)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (pink Brinstar bottom)"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (pink Brinstar bottom)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Power Bomb (pink Brinstar)"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (pink Brinstar)"].Available = (
|
||||
lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.haveItem('Super'))
|
||||
)
|
||||
locationsDict["Missile (green Brinstar pipe)"].AccessFrom = {
|
||||
'Green Hill Zone Top Right': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (green Brinstar pipe)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Power Bomb (blue Brinstar)"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: sm.canUsePowerBombs(),
|
||||
'Morph Ball Room Left': lambda sm: sm.wor(sm.canPassBombPassages(),
|
||||
sm.wand(sm.haveItem('Morph'),
|
||||
sm.canShortCharge())) # speedball
|
||||
}
|
||||
locationsDict["Power Bomb (blue Brinstar)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (blue Brinstar middle)"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (blue Brinstar middle)"].Available = (
|
||||
lambda sm: sm.wand(sm.wor(RomPatches.has(sm.player, RomPatches.BlueBrinstarMissile), sm.haveItem('Morph')),
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.BlueBrinstarBlueDoor), sm.traverse('ConstructionZoneRight')))
|
||||
)
|
||||
locationsDict["Super Missile (green Brinstar bottom)"].AccessFrom = {
|
||||
'Etecoons Supers': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (green Brinstar bottom)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (blue Brinstar bottom)"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (blue Brinstar bottom)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Missile (blue Brinstar top)"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (blue Brinstar top)"].Available = (
|
||||
lambda sm: sm.canAccessBillyMays()
|
||||
)
|
||||
locationsDict["Missile (blue Brinstar behind missile)"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (blue Brinstar behind missile)"].Available = (
|
||||
lambda sm: sm.canAccessBillyMays()
|
||||
)
|
||||
locationsDict["Power Bomb (red Brinstar sidehopper room)"].AccessFrom = {
|
||||
'Red Brinstar Elevator': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (red Brinstar sidehopper room)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('RedTowerElevatorTopLeft'),
|
||||
sm.canUsePowerBombs())
|
||||
)
|
||||
locationsDict["Power Bomb (red Brinstar spike room)"].AccessFrom = {
|
||||
'Red Brinstar Elevator': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (red Brinstar spike room)"].Available = (
|
||||
lambda sm: sm.traverse('RedTowerElevatorBottomLeft')
|
||||
)
|
||||
locationsDict["Missile (red Brinstar spike room)"].AccessFrom = {
|
||||
'Red Brinstar Elevator': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (red Brinstar spike room)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('RedTowerElevatorBottomLeft'),
|
||||
sm.canUsePowerBombs())
|
||||
)
|
||||
locationsDict["Missile (Kraid)"].AccessFrom = {
|
||||
'Warehouse Zeela Room Left': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Kraid)"].Available = (
|
||||
lambda sm: sm.canUsePowerBombs()
|
||||
)
|
||||
locationsDict["Missile (lava room)"].AccessFrom = {
|
||||
'Cathedral': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (lava room)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Missile (below Ice Beam)"].AccessFrom = {
|
||||
'Business Center': lambda sm: sm.wand(sm.traverse('BusinessCenterTopLeft'),
|
||||
sm.canUsePowerBombs(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['Ice']['Norfair Entrance -> Ice Beam']),
|
||||
sm.wor(sm.wand(sm.haveItem('Morph'),
|
||||
sm.knowsMockball()),
|
||||
sm.haveItem('SpeedBooster'))),
|
||||
'Crocomire Speedway Bottom': lambda sm: sm.wand(sm.canUseCrocRoomToChargeSpeed(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Croc -> Ice Missiles']),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.knowsIceMissileFromCroc())
|
||||
}
|
||||
locationsDict["Missile (below Ice Beam)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (above Crocomire)"].AccessFrom = {
|
||||
'Crocomire Speedway Bottom': lambda sm: sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Croc -> Grapple Escape Missiles'])
|
||||
}
|
||||
locationsDict["Missile (above Crocomire)"].Available = (
|
||||
lambda sm: sm.canGrappleEscape()
|
||||
)
|
||||
locationsDict["Missile (Hi-Jump Boots)"].AccessFrom = {
|
||||
'Business Center': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.HiJumpAreaBlueDoor), sm.traverse('BusinessCenterBottomLeft'))
|
||||
}
|
||||
locationsDict["Missile (Hi-Jump Boots)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Missile (Hi-Jump Boots)"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.canPassBombPassages(),
|
||||
sm.wand(RomPatches.has(sm.player, RomPatches.HiJumpShotBlock), sm.haveItem('Morph')))
|
||||
)
|
||||
locationsDict["Energy Tank (Hi-Jump Boots)"].AccessFrom = {
|
||||
'Business Center': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.HiJumpAreaBlueDoor), sm.traverse('BusinessCenterBottomLeft'))
|
||||
}
|
||||
locationsDict["Energy Tank (Hi-Jump Boots)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Power Bomb (Crocomire)"].AccessFrom = {
|
||||
'Crocomire Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (Crocomire)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('PostCrocomireUpperLeft'),
|
||||
sm.enoughStuffCroc(),
|
||||
sm.wor(sm.wor(sm.canFly(),
|
||||
sm.haveItem('Grapple'),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.heatProof(),
|
||||
sm.energyReserveCountOk(1)))), # spark from the room before
|
||||
sm.wor(sm.haveItem('HiJump'), # run and jump from yellow platform
|
||||
sm.wand(sm.haveItem('Ice'),
|
||||
sm.knowsCrocPBsIce()),
|
||||
sm.knowsCrocPBsDBoost())))
|
||||
)
|
||||
locationsDict["Missile (below Crocomire)"].AccessFrom = {
|
||||
'Crocomire Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (below Crocomire)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('PostCrocomireShaftRight'), sm.enoughStuffCroc(), sm.haveItem('Morph'))
|
||||
)
|
||||
locationsDict["Missile (Grapple Beam)"].AccessFrom = {
|
||||
'Crocomire Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Grapple Beam)"].Available = (
|
||||
lambda sm: sm.wand(sm.enoughStuffCroc(),
|
||||
sm.wor(sm.wor(sm.wand(sm.haveItem('Morph'), # from below
|
||||
sm.canFly()),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.knowsShortCharge(),
|
||||
sm.canUsePowerBombs()))),
|
||||
sm.wand(sm.canGreenGateGlitch(), # from grapple room
|
||||
sm.canFly()))) # TODO::test if accessible with a spark (short charge), and how many etanks required
|
||||
)
|
||||
locationsDict["Missile (Grapple Beam)"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.haveItem('Morph'), # normal exit
|
||||
sm.wand(sm.haveItem('Super'), # go back to grapple room
|
||||
sm.wor(sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.haveItem('SpeedBooster'), sm.haveItem('HiJump'))))) # jump from the yellow plateform ennemy
|
||||
)
|
||||
locationsDict["Missile (Norfair Reserve Tank)"].AccessFrom = {
|
||||
'Bubble Mountain': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutain(),
|
||||
'Bubble Mountain Top': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutainTop()
|
||||
}
|
||||
locationsDict["Missile (Norfair Reserve Tank)"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'), sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Norfair Reserve']))
|
||||
)
|
||||
locationsDict["Missile (bubble Norfair green door)"].AccessFrom = {
|
||||
'Bubble Mountain': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutain(),
|
||||
'Bubble Mountain Top': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutainTop()
|
||||
}
|
||||
locationsDict["Missile (bubble Norfair green door)"].Available = (
|
||||
lambda sm: sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Norfair Reserve Missiles'])
|
||||
)
|
||||
locationsDict["Missile (bubble Norfair)"].AccessFrom = {
|
||||
'Bubble Mountain': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (bubble Norfair)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (Speed Booster)"].AccessFrom = {
|
||||
'Bubble Mountain Top': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.SpeedAreaBlueDoors),
|
||||
sm.traverse('BubbleMountainTopRight'))
|
||||
}
|
||||
locationsDict["Missile (Speed Booster)"].Available = (
|
||||
lambda sm: sm.canHellRunToSpeedBooster()
|
||||
)
|
||||
locationsDict["Missile (Wave Beam)"].AccessFrom = {
|
||||
'Bubble Mountain Top': lambda sm: sm.canAccessDoubleChamberItems()
|
||||
}
|
||||
locationsDict["Missile (Wave Beam)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (Gold Torizo)"].AccessFrom = {
|
||||
'LN Above GT': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Gold Torizo)"].Available = (
|
||||
lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])
|
||||
)
|
||||
locationsDict["Missile (Gold Torizo)"].PostAvailable = (
|
||||
lambda sm: sm.enoughStuffGT()
|
||||
)
|
||||
locationsDict["Super Missile (Gold Torizo)"].AccessFrom = {
|
||||
'Screw Attack Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (Gold Torizo)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Super Missile (Gold Torizo)"].PostAvailable = (
|
||||
lambda sm: sm.enoughStuffGT()
|
||||
)
|
||||
locationsDict["Missile (Mickey Mouse room)"].AccessFrom = {
|
||||
'LN Entrance': lambda sm: sm.wand(sm.canUsePowerBombs(), sm.canPassWorstRoom()),
|
||||
}
|
||||
locationsDict["Missile (Mickey Mouse room)"].Available = (
|
||||
lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])
|
||||
)
|
||||
locationsDict["Missile (lower Norfair above fire flea room)"].AccessFrom = {
|
||||
'Firefleas': lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])
|
||||
}
|
||||
locationsDict["Missile (lower Norfair above fire flea room)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Power Bomb (lower Norfair above fire flea room)"].AccessFrom = {
|
||||
'Firefleas Top': lambda sm: SMBool(True)
|
||||
}
|
||||
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()
|
||||
}
|
||||
locationsDict["Power Bomb (Power Bombs of shame)"].Available = (
|
||||
lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])
|
||||
)
|
||||
locationsDict["Missile (lower Norfair near Wave Beam)"].AccessFrom = {
|
||||
'Firefleas': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (lower Norfair near Wave Beam)"].Available = (
|
||||
lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canDestroyBombWalls(),
|
||||
sm.haveItem('Morph'))
|
||||
)
|
||||
locationsDict["Missile (Wrecked Ship middle)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Wrecked Ship middle)"].Available = (
|
||||
lambda sm: sm.canPassBombPassages()
|
||||
)
|
||||
locationsDict["Missile (Gravity Suit)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Gravity Suit)"].Available = (
|
||||
lambda sm: sm.wand(sm.canPassBowling(),
|
||||
sm.canPassBombPassages())
|
||||
)
|
||||
locationsDict["Missile (Wrecked Ship top)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Wrecked Ship top)"].Available = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Phantoon')
|
||||
)
|
||||
locationsDict["Super Missile (Wrecked Ship left)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (Wrecked Ship left)"].Available = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Phantoon')
|
||||
)
|
||||
locationsDict["Missile (green Maridia shinespark)"].AccessFrom = {
|
||||
'Main Street Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (green Maridia shinespark)"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Gravity'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.wand(sm.traverse('MainStreetBottomRight'), # run from room on the right
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther),
|
||||
sm.haveItem('Super')),
|
||||
sm.itemCountOk('ETank', 1)), # etank for the spark since sparking from low ground
|
||||
sm.canSimpleShortCharge())) # run from above
|
||||
)
|
||||
locationsDict["Super Missile (green Maridia)"].AccessFrom = {
|
||||
'Main Street Bottom': lambda sm: sm.canDoOuterMaridia()
|
||||
}
|
||||
locationsDict["Super Missile (green Maridia)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Missile (green Maridia tatori)"].AccessFrom = {
|
||||
'Main Street Bottom': lambda sm: sm.wand(sm.wor(sm.traverse('FishTankRight'),
|
||||
RomPatches.has(sm.player, RomPatches.MamaTurtleBlueDoor)),
|
||||
sm.canDoOuterMaridia()),
|
||||
'Mama Turtle': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (green Maridia tatori)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Super Missile (yellow Maridia)"].AccessFrom = {
|
||||
'Watering Hole Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (yellow Maridia)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (yellow Maridia super missile)"].AccessFrom = {
|
||||
'Watering Hole Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (yellow Maridia super missile)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (yellow Maridia false wall)"].AccessFrom = {
|
||||
'Beach': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (yellow Maridia false wall)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (left Maridia sand pit room)"].AccessFrom = {
|
||||
'Left Sandpit': lambda sm: sm.canClimbWestSandHole()
|
||||
}
|
||||
locationsDict["Missile (left Maridia sand pit room)"].Available = (
|
||||
lambda sm: sm.canAccessItemsInWestSandHole()
|
||||
)
|
||||
locationsDict["Missile (right Maridia sand pit room)"].AccessFrom = {
|
||||
'Right Sandpit': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (right Maridia sand pit room)"].Available = (
|
||||
lambda sm: sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsGravLessLevel3()))
|
||||
)
|
||||
locationsDict["Power Bomb (right Maridia sand pit room)"].AccessFrom = {
|
||||
'Right Sandpit': lambda sm: sm.haveItem('Morph')
|
||||
}
|
||||
locationsDict["Power Bomb (right Maridia sand pit room)"].Available = (
|
||||
lambda sm: sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel3(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.canSpringBallJump())) # https://www.youtube.com/watch?v=7LYYxphRRT0
|
||||
)
|
||||
locationsDict["Missile (pink Maridia)"].AccessFrom = {
|
||||
'Aqueduct': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (pink Maridia)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Super Missile (pink Maridia)"].AccessFrom = {
|
||||
'Aqueduct': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (pink Maridia)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (Draygon)"].AccessFrom = {
|
||||
'Precious Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Draygon)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
|
||||
# TODO::use the dict in solver/randomizer
|
||||
# create the list that the solver/randomizer use
|
||||
locations = [loc for loc in locationsDict.values()]
|
||||
|
||||
class LocationsHelper:
|
||||
# used by FillerRandom to know how many front fill steps it must perform
|
||||
def getRandomFillHelp(startLocation):
|
||||
helpByAp = {
|
||||
"Firefleas Top": 3,
|
||||
"Aqueduct": 1,
|
||||
"Mama Turtle": 1,
|
||||
"Watering Hole": 2,
|
||||
"Etecoons Supers": 2,
|
||||
"Gauntlet Top":1,
|
||||
"Bubble Mountain":1
|
||||
}
|
||||
return helpByAp[startLocation] if startLocation in helpByAp else 0
|
||||
|
||||
# for a given start AP, gives:
|
||||
# - locations that can be used as majors/chozo in the start area
|
||||
# - locations to preserve in the split
|
||||
# - number of necessary majors locations to add in the start area,
|
||||
# - number of necessary chozo locations to add in the start area
|
||||
# locs are taken in the first n in the list
|
||||
def getStartMajors(startLocation):
|
||||
majLocsByAp = {
|
||||
'Gauntlet Top': ([
|
||||
"Missile (Crateria gauntlet right)",
|
||||
"Missile (Crateria gauntlet left)"
|
||||
], ["Energy Tank, Terminator"], 1, 2),
|
||||
'Green Brinstar Elevator': ([
|
||||
"Missile (green Brinstar below super missile)"
|
||||
], ["Reserve Tank, Brinstar"], 1, 1),
|
||||
'Big Pink': ([
|
||||
"Missile (pink Brinstar top)",
|
||||
"Missile (pink Brinstar bottom)"
|
||||
], ["Charge Beam"], 1, 2),
|
||||
'Etecoons Supers': ([
|
||||
"Energy Tank, Etecoons",
|
||||
"Super Missile (green Brinstar bottom)",
|
||||
], ["Energy Tank, Etecoons"], 1, 2),
|
||||
'Firefleas Top': ([
|
||||
"Power Bomb (lower Norfair above fire flea room)",
|
||||
"Energy Tank, Firefleas",
|
||||
"Missile (lower Norfair near Wave Beam)",
|
||||
"Missile (lower Norfair above fire flea room)"
|
||||
], ["Energy Tank, Firefleas"], 3, 4),
|
||||
'Business Center': ([
|
||||
"Energy Tank (Hi-Jump Boots)",
|
||||
], ["Hi-Jump Boots"], 1, 1),
|
||||
'Bubble Mountain': ([
|
||||
"Missile (bubble Norfair)"
|
||||
], ["Speed Booster", "Wave Beam"], 1, 1),
|
||||
'Mama Turtle': ([
|
||||
"Energy Tank, Mama turtle",
|
||||
"Missile (green Maridia tatori)",
|
||||
"Super Missile (green Maridia)"
|
||||
], ["Energy Tank, Mama turtle"], 2, 3),
|
||||
'Watering Hole': ([
|
||||
"Missile (yellow Maridia super missile)",
|
||||
"Super Missile (yellow Maridia)",
|
||||
"Missile (yellow Maridia false wall)"
|
||||
], [], 2, 3),
|
||||
'Aqueduct': ([
|
||||
"Missile (pink Maridia)",
|
||||
"Super Missile (pink Maridia)",
|
||||
"Missile (right Maridia sand pit room)"
|
||||
], ["Reserve Tank, Maridia"], 2, 3)
|
||||
}
|
||||
return majLocsByAp[startLocation] if startLocation in majLocsByAp else ([],[],0,0)
|
||||
0
worlds/sm/variaRandomizer/logic/__init__.py
Normal file
0
worlds/sm/variaRandomizer/logic/__init__.py
Normal file
63
worlds/sm/variaRandomizer/logic/cache.py
Normal file
63
worlds/sm/variaRandomizer/logic/cache.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# the caching decorator for helpers functions
|
||||
class VersionedCache(object):
|
||||
__slots__ = ( 'cache', 'masterCache', 'nextSlot', 'size')
|
||||
|
||||
def __init__(self):
|
||||
self.cache = []
|
||||
self.masterCache = {}
|
||||
self.nextSlot = 0
|
||||
self.size = 0
|
||||
|
||||
def reset(self):
|
||||
# reinit the whole cache
|
||||
self.masterCache = {}
|
||||
self.update(0)
|
||||
|
||||
def update(self, newKey):
|
||||
cache = self.masterCache.get(newKey, None)
|
||||
if cache is None:
|
||||
cache = [ None ] * self.size
|
||||
self.masterCache[newKey] = cache
|
||||
self.cache = cache
|
||||
|
||||
def decorator(self, func):
|
||||
return self._decorate(func.__name__, self._new_slot(), func)
|
||||
|
||||
# for lambdas
|
||||
def ldeco(self, func):
|
||||
return self._decorate(func.__code__, self._new_slot(), func)
|
||||
|
||||
def _new_slot(self):
|
||||
slot = self.nextSlot
|
||||
self.nextSlot += 1
|
||||
self.size += 1
|
||||
return slot
|
||||
|
||||
def _decorate(self, name, slot, func):
|
||||
def _decorator(arg):
|
||||
#ret = self.cache[slot]
|
||||
#if ret is not None:
|
||||
# return ret
|
||||
#else:
|
||||
ret = func(arg)
|
||||
# self.cache[slot] = ret
|
||||
return ret
|
||||
return _decorator
|
||||
|
||||
Cache = VersionedCache()
|
||||
|
||||
class RequestCache(object):
|
||||
def __init__(self):
|
||||
self.results = {}
|
||||
|
||||
def request(self, request, *args):
|
||||
return ''.join([request] + [str(arg) for arg in args])
|
||||
|
||||
def store(self, request, result):
|
||||
self.results[request] = result
|
||||
|
||||
def get(self, request):
|
||||
return self.results[request] if request in self.results else None
|
||||
|
||||
def reset(self):
|
||||
self.results.clear()
|
||||
831
worlds/sm/variaRandomizer/logic/helpers.py
Normal file
831
worlds/sm/variaRandomizer/logic/helpers.py
Normal file
@@ -0,0 +1,831 @@
|
||||
|
||||
import math
|
||||
|
||||
from logic.cache import Cache
|
||||
from logic.smbool import SMBool, smboolFalse
|
||||
from utils.parameters import Settings, easy, medium, diff2text
|
||||
from rom.rom_patches import RomPatches
|
||||
from utils.utils import normalizeRounding
|
||||
|
||||
|
||||
class Helpers(object):
|
||||
def __init__(self, smbm):
|
||||
self.smbm = smbm
|
||||
|
||||
# return bool
|
||||
def haveItemCount(self, item, count):
|
||||
return self.smbm.itemCount(item) >= count
|
||||
|
||||
# return integer
|
||||
@Cache.decorator
|
||||
def energyReserveCount(self):
|
||||
return self.smbm.itemCount('ETank') + self.smbm.itemCount('Reserve')
|
||||
|
||||
def energyReserveCountOkDiff(self, difficulties, mult=1.0):
|
||||
if difficulties is None or len(difficulties) == 0:
|
||||
return smboolFalse
|
||||
|
||||
def f(difficulty):
|
||||
return self.smbm.energyReserveCountOk(normalizeRounding(difficulty[0] / mult), difficulty=difficulty[1])
|
||||
|
||||
result = f(difficulties[0])
|
||||
for difficulty in difficulties[1:]:
|
||||
result = self.smbm.wor(result, f(difficulty))
|
||||
return result
|
||||
|
||||
def energyReserveCountOkHellRun(self, hellRunName, mult=1.0):
|
||||
difficulties = Settings.hellRuns[hellRunName]
|
||||
result = self.energyReserveCountOkDiff(difficulties, mult)
|
||||
|
||||
if result == True:
|
||||
result.knows = [hellRunName+'HellRun']
|
||||
|
||||
return result
|
||||
|
||||
# gives damage reduction factor with the current suits
|
||||
# envDmg : if true (default) will return environmental damage reduction
|
||||
def getDmgReduction(self, envDmg=True):
|
||||
ret = 1.0
|
||||
sm = self.smbm
|
||||
hasVaria = sm.haveItem('Varia')
|
||||
hasGrav = sm.haveItem('Gravity')
|
||||
items = []
|
||||
if RomPatches.has(sm.player, RomPatches.NoGravityEnvProtection):
|
||||
if hasVaria:
|
||||
items = ['Varia']
|
||||
if envDmg:
|
||||
ret = 4.0
|
||||
else:
|
||||
ret = 2.0
|
||||
if hasGrav and not envDmg:
|
||||
ret = 4.0
|
||||
items = ['Gravity']
|
||||
elif RomPatches.has(sm.player, RomPatches.ProgressiveSuits):
|
||||
if hasVaria:
|
||||
items.append('Varia')
|
||||
ret *= 2
|
||||
if hasGrav:
|
||||
items.append('Gravity')
|
||||
ret *= 2
|
||||
else:
|
||||
if hasVaria:
|
||||
ret = 2.0
|
||||
items = ['Varia']
|
||||
if hasGrav:
|
||||
ret = 4.0
|
||||
items = ['Gravity']
|
||||
return (ret, items)
|
||||
|
||||
# higher values for mult means room is that much "easier" (HP mult)
|
||||
def energyReserveCountOkHardRoom(self, roomName, mult=1.0):
|
||||
difficulties = Settings.hardRooms[roomName]
|
||||
(dmgRed, items) = self.getDmgReduction()
|
||||
mult *= dmgRed
|
||||
result = self.energyReserveCountOkDiff(difficulties, mult)
|
||||
|
||||
if result == True:
|
||||
result.knows = ['HardRoom-'+roomName]
|
||||
if dmgRed != 1.0:
|
||||
result._items.append(items)
|
||||
return result
|
||||
|
||||
@Cache.decorator
|
||||
def heatProof(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Varia'),
|
||||
sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.NoGravityEnvProtection)),
|
||||
sm.wnot(RomPatches.has(sm.player, RomPatches.ProgressiveSuits)),
|
||||
sm.haveItem('Gravity')))
|
||||
|
||||
# helper here because we can't define "sublambdas" in locations
|
||||
def getPiratesPseudoScrewCoeff(self):
|
||||
sm = self.smbm
|
||||
ret = 1.0
|
||||
if RomPatches.has(sm.player, RomPatches.NerfedCharge).bool == True:
|
||||
ret = 4.0
|
||||
return ret
|
||||
|
||||
@Cache.decorator
|
||||
def canFireChargedShots(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Charge'), RomPatches.has(sm.player, RomPatches.NerfedCharge))
|
||||
|
||||
# higher values for mult means hell run is that much "easier" (HP mult)
|
||||
def canHellRun(self, hellRun, mult=1.0, minE=2):
|
||||
sm = self.smbm
|
||||
|
||||
items = []
|
||||
isHeatProof = sm.heatProof()
|
||||
if isHeatProof == True:
|
||||
return isHeatProof
|
||||
if sm.wand(RomPatches.has(sm.player, RomPatches.ProgressiveSuits), sm.haveItem('Gravity')).bool == True:
|
||||
# half heat protection
|
||||
mult *= 2.0
|
||||
minE /= 2.0
|
||||
items.append('Gravity')
|
||||
if self.energyReserveCount() >= minE:
|
||||
if hellRun != 'LowerNorfair':
|
||||
ret = self.energyReserveCountOkHellRun(hellRun, mult)
|
||||
if ret.bool == True:
|
||||
ret._items.append(items)
|
||||
return ret
|
||||
else:
|
||||
tanks = self.energyReserveCount()
|
||||
multCF = mult
|
||||
if tanks >= 14:
|
||||
multCF *= 2.0
|
||||
nCF = int(math.ceil(2/multCF))
|
||||
ret = sm.wand(self.energyReserveCountOkHellRun(hellRun, mult),
|
||||
self.canCrystalFlash(nCF))
|
||||
if ret.bool == True:
|
||||
if sm.haveItem('Gravity') == True:
|
||||
ret.difficulty *= 0.7
|
||||
ret._items.append('Gravity')
|
||||
elif sm.haveItem('ScrewAttack') == True:
|
||||
ret.difficulty *= 0.7
|
||||
ret._items.append('ScrewAttack')
|
||||
#nPB = self.smbm.itemCount('PowerBomb')
|
||||
#print("canHellRun LN. tanks=" + str(tanks) + ", nCF=" + str(nCF) + ", nPB=" + str(nPB) + ", mult=" + str(mult) + ", heatProof=" + str(isHeatProof.bool) + ", ret=" + str(ret))
|
||||
return ret
|
||||
else:
|
||||
return smboolFalse
|
||||
|
||||
@Cache.decorator
|
||||
def canMockball(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'),
|
||||
sm.knowsMockball())
|
||||
|
||||
@Cache.decorator
|
||||
def canFly(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('SpaceJump'),
|
||||
sm.canInfiniteBombJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canSimpleShortCharge(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.knowsSimpleShortCharge(),
|
||||
sm.knowsShortCharge()))
|
||||
|
||||
@Cache.decorator
|
||||
def canShortCharge(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('SpeedBooster'), sm.knowsShortCharge())
|
||||
|
||||
@Cache.decorator
|
||||
def canUseBombs(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'), sm.haveItem('Bomb'))
|
||||
|
||||
@Cache.decorator
|
||||
def canInfiniteBombJump(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'), sm.haveItem('Bomb'), sm.knowsInfiniteBombJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canInfiniteBombJumpSuitless(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'), sm.haveItem('Bomb'), sm.knowsInfiniteBombJumpSuitless())
|
||||
|
||||
@Cache.decorator
|
||||
def haveMissileOrSuper(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Missile'), sm.haveItem('Super'))
|
||||
|
||||
@Cache.decorator
|
||||
def canOpenRedDoors(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.RedDoorsMissileOnly)),
|
||||
sm.haveMissileOrSuper()),
|
||||
sm.haveItem('Missile'))
|
||||
|
||||
@Cache.decorator
|
||||
def canOpenEyeDoors(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(RomPatches.has(sm.player, RomPatches.NoGadoras),
|
||||
sm.haveMissileOrSuper())
|
||||
|
||||
@Cache.decorator
|
||||
def canOpenGreenDoors(self):
|
||||
return self.smbm.haveItem('Super')
|
||||
|
||||
@Cache.decorator
|
||||
def canGreenGateGlitch(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Super'),
|
||||
sm.knowsGreenGateGlitch())
|
||||
|
||||
@Cache.decorator
|
||||
def canBlueGateGlitch(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveMissileOrSuper(),
|
||||
sm.knowsGreenGateGlitch())
|
||||
@Cache.decorator
|
||||
def canUsePowerBombs(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'), sm.haveItem('PowerBomb'))
|
||||
|
||||
canOpenYellowDoors = canUsePowerBombs
|
||||
|
||||
@Cache.decorator
|
||||
def canUseSpringBall(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'),
|
||||
sm.haveItem('SpringBall'))
|
||||
|
||||
@Cache.decorator
|
||||
def canSpringBallJump(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canUseSpringBall(),
|
||||
sm.knowsSpringBallJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canDoubleSpringBallJump(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canUseSpringBall(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.knowsDoubleSpringBallJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canSpringBallJumpFromWall(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canUseSpringBall(),
|
||||
sm.knowsSpringBallJumpFromWall())
|
||||
|
||||
@Cache.decorator
|
||||
def canDestroyBombWalls(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('Morph'),
|
||||
sm.wor(sm.haveItem('Bomb'),
|
||||
sm.haveItem('PowerBomb'))),
|
||||
sm.haveItem('ScrewAttack'))
|
||||
|
||||
@Cache.decorator
|
||||
def canDestroyBombWallsUnderwater(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.canDestroyBombWalls()),
|
||||
sm.wand(sm.haveItem('Morph'),
|
||||
sm.wor(sm.haveItem('Bomb'),
|
||||
sm.haveItem('PowerBomb'))))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassBombPassages(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.canUseBombs(),
|
||||
sm.canUsePowerBombs())
|
||||
|
||||
@Cache.decorator
|
||||
def canMorphJump(self):
|
||||
# small hop in morph ball form
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.canPassBombPassages(), sm.haveItem('SpringBall'))
|
||||
|
||||
def canCrystalFlash(self, n=1):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canUsePowerBombs(),
|
||||
sm.itemCountOk('Missile', 2*n),
|
||||
sm.itemCountOk('Super', 2*n),
|
||||
sm.itemCountOk('PowerBomb', 2*n+1))
|
||||
|
||||
@Cache.decorator
|
||||
def canCrystalFlashClip(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canCrystalFlash(),
|
||||
sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.canUseBombs(),
|
||||
sm.knowsCrystalFlashClip()),
|
||||
sm.wand(sm.knowsSuitlessCrystalFlashClip(),
|
||||
sm.itemCountOk('PowerBomb', 4))))
|
||||
|
||||
@Cache.decorator
|
||||
def canDoLowGauntlet(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canShortCharge(),
|
||||
sm.canUsePowerBombs(),
|
||||
sm.itemCountOk('ETank', 1),
|
||||
sm.knowsLowGauntlet())
|
||||
|
||||
@Cache.decorator
|
||||
def canUseHyperBeam(self):
|
||||
sm = self.smbm
|
||||
return sm.haveItem('Hyper')
|
||||
|
||||
@Cache.decorator
|
||||
def getBeamDamage(self):
|
||||
sm = self.smbm
|
||||
standardDamage = 20
|
||||
|
||||
if sm.wand(sm.haveItem('Ice'),
|
||||
sm.haveItem('Wave'),
|
||||
sm.haveItem('Plasma')) == True:
|
||||
standardDamage = 300
|
||||
elif sm.wand(sm.haveItem('Wave'),
|
||||
sm.haveItem('Plasma')) == True:
|
||||
standardDamage = 250
|
||||
elif sm.wand(sm.haveItem('Ice'),
|
||||
sm.haveItem('Plasma')) == True:
|
||||
standardDamage = 200
|
||||
elif sm.haveItem('Plasma') == True:
|
||||
standardDamage = 150
|
||||
elif sm.wand(sm.haveItem('Ice'),
|
||||
sm.haveItem('Wave'),
|
||||
sm.haveItem('Spazer')) == True:
|
||||
standardDamage = 100
|
||||
elif sm.wand(sm.haveItem('Wave'),
|
||||
sm.haveItem('Spazer')) == True:
|
||||
standardDamage = 70
|
||||
elif sm.wand(sm.haveItem('Ice'),
|
||||
sm.haveItem('Spazer')) == True:
|
||||
standardDamage = 60
|
||||
elif sm.wand(sm.haveItem('Ice'),
|
||||
sm.haveItem('Wave')) == True:
|
||||
standardDamage = 60
|
||||
elif sm.haveItem('Wave') == True:
|
||||
standardDamage = 50
|
||||
elif sm.haveItem('Spazer') == True:
|
||||
standardDamage = 40
|
||||
elif sm.haveItem('Ice') == True:
|
||||
standardDamage = 30
|
||||
|
||||
return standardDamage
|
||||
|
||||
# returns a tuple with :
|
||||
#
|
||||
# - a floating point number : 0 if boss is unbeatable with
|
||||
# current equipment, and an ammo "margin" (ex : 1.5 means we have 50%
|
||||
# more firepower than absolutely necessary). Useful to compute boss
|
||||
# difficulty when not having charge. If player has charge, the actual
|
||||
# value is not useful, and is guaranteed to be > 2.
|
||||
#
|
||||
# - estimation of the fight duration in seconds (well not really, it
|
||||
# is if you fire and land shots perfectly and constantly), giving info
|
||||
# to compute boss fight difficulty
|
||||
def canInflictEnoughDamages(self, bossEnergy, doubleSuper=False, charge=True, power=False, givesDrops=True, ignoreMissiles=False, ignoreSupers=False):
|
||||
# TODO: handle special beam attacks ? (http://deanyd.net/sm/index.php?title=Charge_Beam_Combos)
|
||||
sm = self.smbm
|
||||
items = []
|
||||
|
||||
# http://deanyd.net/sm/index.php?title=Damage
|
||||
standardDamage = 0
|
||||
if sm.canFireChargedShots().bool == True and charge == True:
|
||||
standardDamage = self.getBeamDamage()
|
||||
items.append('Charge')
|
||||
# charge triples the damage
|
||||
chargeDamage = standardDamage
|
||||
if sm.haveItem('Charge').bool == True:
|
||||
chargeDamage *= 3.0
|
||||
|
||||
# missile 100 damages, super missile 300 damages, PBs 200 dmg, 5 in each extension
|
||||
missilesAmount = sm.itemCount('Missile') * 5
|
||||
if ignoreMissiles == True:
|
||||
missilesDamage = 0
|
||||
else:
|
||||
missilesDamage = missilesAmount * 100
|
||||
if missilesAmount > 0:
|
||||
items.append('Missile')
|
||||
supersAmount = sm.itemCount('Super') * 5
|
||||
if ignoreSupers == True:
|
||||
oneSuper = 0
|
||||
else:
|
||||
oneSuper = 300.0
|
||||
if supersAmount > 0:
|
||||
items.append('Super')
|
||||
if doubleSuper == True:
|
||||
oneSuper *= 2
|
||||
supersDamage = supersAmount * oneSuper
|
||||
powerDamage = 0
|
||||
powerAmount = 0
|
||||
if power == True and sm.haveItem('PowerBomb') == True:
|
||||
powerAmount = sm.itemCount('PowerBomb') * 5
|
||||
powerDamage = powerAmount * 200
|
||||
items.append('PowerBomb')
|
||||
|
||||
canBeatBoss = chargeDamage > 0 or givesDrops or (missilesDamage + supersDamage + powerDamage) >= bossEnergy
|
||||
if not canBeatBoss:
|
||||
return (0, 0, [])
|
||||
|
||||
ammoMargin = (missilesDamage + supersDamage + powerDamage) / bossEnergy
|
||||
if chargeDamage > 0:
|
||||
ammoMargin += 2
|
||||
|
||||
missilesDPS = Settings.algoSettings['missilesPerSecond'] * 100.0
|
||||
supersDPS = Settings.algoSettings['supersPerSecond'] * 300.0
|
||||
if doubleSuper == True:
|
||||
supersDPS *= 2
|
||||
if powerDamage > 0:
|
||||
powerDPS = Settings.algoSettings['powerBombsPerSecond'] * 200.0
|
||||
else:
|
||||
powerDPS = 0.0
|
||||
chargeDPS = chargeDamage * Settings.algoSettings['chargedShotsPerSecond']
|
||||
# print("chargeDPS=" + str(chargeDPS))
|
||||
dpsDict = {
|
||||
missilesDPS: (missilesAmount, 100.0),
|
||||
supersDPS: (supersAmount, oneSuper),
|
||||
powerDPS: (powerAmount, 200.0),
|
||||
# no boss will take more 10000 charged shots
|
||||
chargeDPS: (10000, chargeDamage)
|
||||
}
|
||||
secs = 0
|
||||
for dps in sorted(dpsDict, reverse=True):
|
||||
amount = dpsDict[dps][0]
|
||||
one = dpsDict[dps][1]
|
||||
if dps == 0 or one == 0 or amount == 0:
|
||||
continue
|
||||
fire = min(bossEnergy / one, amount)
|
||||
secs += fire * (one / dps)
|
||||
bossEnergy -= fire * one
|
||||
if bossEnergy <= 0:
|
||||
break
|
||||
if bossEnergy > 0:
|
||||
# print ('!! drops !! ')
|
||||
secs += bossEnergy * Settings.algoSettings['missileDropsPerMinute'] * 100 / 60
|
||||
# print('ammoMargin = ' + str(ammoMargin) + ', secs = ' + str(secs))
|
||||
|
||||
return (ammoMargin, secs, items)
|
||||
|
||||
# return diff score, or -1 if below minimum energy in diffTbl
|
||||
def computeBossDifficulty(self, ammoMargin, secs, diffTbl, energyDiff=0):
|
||||
sm = self.smbm
|
||||
|
||||
# actual fight duration :
|
||||
rate = None
|
||||
if 'Rate' in diffTbl:
|
||||
rate = float(diffTbl['Rate'])
|
||||
if rate is None:
|
||||
duration = 120.0
|
||||
else:
|
||||
duration = secs / rate
|
||||
# print('rate=' + str(rate) + ', duration=' + str(duration))
|
||||
(suitsCoeff, items) = sm.getDmgReduction(envDmg=False)
|
||||
suitsCoeff /= 2.0
|
||||
energyCount = self.energyReserveCount()
|
||||
energy = suitsCoeff * (1 + energyCount + energyDiff)
|
||||
# print("energy="+str(energy)+", energyCount="+str(energyCount)+",energyDiff="+str(energyDiff)+",suitsCoeff="+str(suitsCoeff))
|
||||
|
||||
# add all energy in used items
|
||||
items += sm.energyReserveCountOk(energyCount).items
|
||||
|
||||
energyDict = None
|
||||
if 'Energy' in diffTbl:
|
||||
energyDict = diffTbl['Energy']
|
||||
difficulty = medium
|
||||
# get difficulty by energy
|
||||
if energyDict:
|
||||
energyDict = {float(k):float(v) for k,v in energyDict.items()}
|
||||
keyz = sorted(energyDict.keys())
|
||||
if len(keyz) > 0:
|
||||
current = keyz[0]
|
||||
if energy < current:
|
||||
return (-1, [])
|
||||
sup = None
|
||||
difficulty = energyDict[current]
|
||||
for k in keyz:
|
||||
if k > energy:
|
||||
sup=k
|
||||
break
|
||||
current = k
|
||||
difficulty = energyDict[k]
|
||||
# interpolate if we can
|
||||
if energy > current and sup is not None:
|
||||
difficulty += (energyDict[sup] - difficulty)/(sup - current) * (energy - current)
|
||||
# print("energy=" + str(energy) + ", base diff=" + str(difficulty))
|
||||
# adjust by fight duration
|
||||
difficulty *= (duration / 120)
|
||||
# and by ammo margin
|
||||
# only augment difficulty in case of no charge, don't lower it.
|
||||
# if we have charge, ammoMargin will have a huge value (see canInflictEnoughDamages),
|
||||
# so this does not apply
|
||||
diffAdjust = (1 - (ammoMargin - Settings.algoSettings['ammoMarginIfNoCharge']))
|
||||
if diffAdjust > 1:
|
||||
difficulty *= diffAdjust
|
||||
# print("final diff: "+str(round(difficulty, 2)))
|
||||
|
||||
return (round(difficulty, 2), items)
|
||||
|
||||
@Cache.decorator
|
||||
def enoughStuffSporeSpawn(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Missile'), sm.haveItem('Super'), sm.haveItem('Charge'))
|
||||
|
||||
@Cache.decorator
|
||||
def enoughStuffCroc(self):
|
||||
sm = self.smbm
|
||||
# say croc has ~5000 energy, and ignore its useless drops
|
||||
(ammoMargin, secs, items) = self.canInflictEnoughDamages(5000, givesDrops=False)
|
||||
if ammoMargin == 0:
|
||||
return sm.wand(sm.knowsLowAmmoCroc(),
|
||||
sm.wor(sm.itemCountOk("Missile", 2),
|
||||
sm.wand(sm.haveItem('Missile'),
|
||||
sm.haveItem('Super'))))
|
||||
else:
|
||||
return SMBool(True, easy, items=items)
|
||||
|
||||
@Cache.decorator
|
||||
def enoughStuffBotwoon(self):
|
||||
sm = self.smbm
|
||||
(ammoMargin, secs, items) = self.canInflictEnoughDamages(6000, givesDrops=False)
|
||||
diff = SMBool(True, easy, [], items)
|
||||
lowStuff = sm.knowsLowStuffBotwoon()
|
||||
if ammoMargin == 0 and lowStuff.bool:
|
||||
(ammoMargin, secs, items) = self.canInflictEnoughDamages(3500, givesDrops=False)
|
||||
diff = SMBool(lowStuff.bool, lowStuff.difficulty, lowStuff.knows, items)
|
||||
if ammoMargin == 0:
|
||||
return smboolFalse
|
||||
fight = sm.wor(sm.energyReserveCountOk(math.ceil(4/sm.getDmgReduction(envDmg=False)[0])),
|
||||
lowStuff)
|
||||
return sm.wandmax(fight, diff)
|
||||
|
||||
@Cache.decorator
|
||||
def enoughStuffGT(self):
|
||||
sm = self.smbm
|
||||
hasBeams = sm.wand(sm.haveItem('Charge'), sm.haveItem('Plasma')).bool
|
||||
(ammoMargin, secs, items) = self.canInflictEnoughDamages(9000, ignoreMissiles=True, givesDrops=hasBeams)
|
||||
diff = SMBool(True, easy, [], items)
|
||||
lowStuff = sm.knowsLowStuffGT()
|
||||
if ammoMargin == 0 and lowStuff.bool:
|
||||
(ammoMargin, secs, items) = self.canInflictEnoughDamages(3000, ignoreMissiles=True)
|
||||
diff = SMBool(lowStuff.bool, lowStuff.difficulty, lowStuff.knows, items)
|
||||
if ammoMargin == 0:
|
||||
return smboolFalse
|
||||
fight = sm.wor(sm.energyReserveCountOk(math.ceil(8/sm.getDmgReduction(envDmg=False)[0])),
|
||||
lowStuff)
|
||||
return sm.wandmax(fight, diff)
|
||||
|
||||
@Cache.decorator
|
||||
def enoughStuffsRidley(self):
|
||||
sm = self.smbm
|
||||
if not sm.haveItem('Morph') and not sm.haveItem('ScrewAttack'):
|
||||
return smboolFalse
|
||||
(ammoMargin, secs, ammoItems) = self.canInflictEnoughDamages(18000, doubleSuper=True, power=True, givesDrops=False)
|
||||
if ammoMargin == 0:
|
||||
return smboolFalse
|
||||
|
||||
# print('RIDLEY', ammoMargin, secs)
|
||||
(diff, defenseItems) = self.computeBossDifficulty(ammoMargin, secs,
|
||||
Settings.bossesDifficulty['Ridley'])
|
||||
if diff < 0:
|
||||
return smboolFalse
|
||||
else:
|
||||
return SMBool(True, diff, items=ammoItems+defenseItems)
|
||||
|
||||
@Cache.decorator
|
||||
def enoughStuffsKraid(self):
|
||||
sm = self.smbm
|
||||
(ammoMargin, secs, ammoItems) = self.canInflictEnoughDamages(1000)
|
||||
if ammoMargin == 0:
|
||||
return smboolFalse
|
||||
#print('KRAID True ', ammoMargin, secs)
|
||||
(diff, defenseItems) = self.computeBossDifficulty(ammoMargin, secs,
|
||||
Settings.bossesDifficulty['Kraid'])
|
||||
if diff < 0:
|
||||
return smboolFalse
|
||||
|
||||
return SMBool(True, diff, items=ammoItems+defenseItems)
|
||||
|
||||
def adjustHealthDropDiff(self, difficulty):
|
||||
(dmgRed, items) = self.getDmgReduction(envDmg=False)
|
||||
# 2 is Varia suit, considered standard eqt for boss fights
|
||||
# there's certainly a smarter way to do this but...
|
||||
if dmgRed < 2:
|
||||
difficulty *= Settings.algoSettings['dmgReductionDifficultyFactor']
|
||||
elif dmgRed > 2:
|
||||
difficulty /= Settings.algoSettings['dmgReductionDifficultyFactor']
|
||||
return difficulty
|
||||
|
||||
@Cache.decorator
|
||||
def enoughStuffsDraygon(self):
|
||||
sm = self.smbm
|
||||
if not sm.haveItem('Morph') and not sm.haveItem('Gravity'):
|
||||
return smboolFalse
|
||||
# some ammo to destroy the turrets during the fight
|
||||
if not sm.haveMissileOrSuper():
|
||||
return smboolFalse
|
||||
(ammoMargin, secs, ammoItems) = self.canInflictEnoughDamages(6000)
|
||||
# print('DRAY', ammoMargin, secs)
|
||||
if ammoMargin > 0:
|
||||
(diff, defenseItems) = self.computeBossDifficulty(ammoMargin, secs,
|
||||
Settings.bossesDifficulty['Draygon'])
|
||||
if diff < 0:
|
||||
fight = smboolFalse
|
||||
else:
|
||||
fight = SMBool(True, diff, items=ammoItems+defenseItems)
|
||||
if sm.haveItem('Gravity') == False:
|
||||
fight.difficulty *= Settings.algoSettings['draygonNoGravityMalus']
|
||||
else:
|
||||
fight._items.append('Gravity')
|
||||
if not sm.haveItem('Morph'):
|
||||
fight.difficulty *= Settings.algoSettings['draygonNoMorphMalus']
|
||||
if sm.haveItem('Gravity') and sm.haveItem('ScrewAttack'):
|
||||
fight.difficulty /= Settings.algoSettings['draygonScrewBonus']
|
||||
fight.difficulty = self.adjustHealthDropDiff(fight.difficulty)
|
||||
else:
|
||||
fight = smboolFalse
|
||||
# for grapple kill considers energy drained by wall socket + 2 spankings by Dray
|
||||
# (original 99 energy used for rounding)
|
||||
nTanksGrapple = (240/sm.getDmgReduction(envDmg=True)[0] + 2*160/sm.getDmgReduction(envDmg=False)[0])/100
|
||||
return sm.wor(fight,
|
||||
sm.wand(sm.knowsDraygonGrappleKill(),
|
||||
sm.haveItem('Grapple'),
|
||||
sm.energyReserveCountOk(nTanksGrapple)),
|
||||
sm.wand(sm.knowsMicrowaveDraygon(),
|
||||
sm.haveItem('Plasma'),
|
||||
sm.canFireChargedShots(),
|
||||
sm.haveItem('XRayScope')),
|
||||
sm.wand(sm.haveItem('Gravity'),
|
||||
sm.energyReserveCountOk(3),
|
||||
sm.knowsDraygonSparkKill(),
|
||||
sm.haveItem('SpeedBooster')))
|
||||
|
||||
@Cache.decorator
|
||||
def enoughStuffsPhantoon(self):
|
||||
sm = self.smbm
|
||||
(ammoMargin, secs, ammoItems) = self.canInflictEnoughDamages(2500, doubleSuper=True)
|
||||
if ammoMargin == 0:
|
||||
return smboolFalse
|
||||
# print('PHANTOON', ammoMargin, secs)
|
||||
(difficulty, defenseItems) = self.computeBossDifficulty(ammoMargin, secs,
|
||||
Settings.bossesDifficulty['Phantoon'])
|
||||
if difficulty < 0:
|
||||
return smboolFalse
|
||||
hasCharge = sm.canFireChargedShots()
|
||||
hasScrew = sm.haveItem('ScrewAttack')
|
||||
if hasScrew:
|
||||
difficulty /= Settings.algoSettings['phantoonFlamesAvoidBonusScrew']
|
||||
defenseItems += hasScrew.items
|
||||
elif hasCharge:
|
||||
difficulty /= Settings.algoSettings['phantoonFlamesAvoidBonusCharge']
|
||||
defenseItems += hasCharge.items
|
||||
elif not hasCharge and sm.itemCount('Missile') <= 2: # few missiles is harder
|
||||
difficulty *= Settings.algoSettings['phantoonLowMissileMalus']
|
||||
difficulty = self.adjustHealthDropDiff(difficulty)
|
||||
fight = SMBool(True, difficulty, items=ammoItems+defenseItems)
|
||||
|
||||
return sm.wor(fight,
|
||||
sm.wand(sm.knowsMicrowavePhantoon(),
|
||||
sm.haveItem('Plasma'),
|
||||
sm.canFireChargedShots(),
|
||||
sm.haveItem('XRayScope')))
|
||||
|
||||
def mbEtankCheck(self):
|
||||
sm = self.smbm
|
||||
if sm.wor(RomPatches.has(sm.player, RomPatches.NerfedRainbowBeam), RomPatches.has(sm.player, RomPatches.TourianSpeedup)):
|
||||
# "add" energy for difficulty calculations
|
||||
energy = 2.8 if sm.haveItem('Varia') else 2.6
|
||||
return (True, energy)
|
||||
nTanks = sm.energyReserveCount()
|
||||
energyDiff = 0
|
||||
if sm.haveItem('Varia') == False:
|
||||
# "remove" 3 etanks (accounting for rainbow beam damage without varia)
|
||||
if nTanks < 6:
|
||||
return (False, 0)
|
||||
energyDiff = -3
|
||||
elif nTanks < 3:
|
||||
return (False, 0)
|
||||
return (True, energyDiff)
|
||||
|
||||
@Cache.decorator
|
||||
def enoughStuffsMotherbrain(self):
|
||||
sm = self.smbm
|
||||
# MB1 can't be hit by charge beam
|
||||
(ammoMargin, secs, _) = self.canInflictEnoughDamages(3000, charge=False, givesDrops=False)
|
||||
if ammoMargin == 0:
|
||||
return smboolFalse
|
||||
# requires 10-10 to break the glass
|
||||
if sm.itemCount('Missile') <= 1 or sm.itemCount('Super') <= 1:
|
||||
return smboolFalse
|
||||
# we actually don't give a shit about MB1 difficulty,
|
||||
# since we embark its health in the following calc
|
||||
(ammoMargin, secs, ammoItems) = self.canInflictEnoughDamages(18000 + 3000, givesDrops=False)
|
||||
if ammoMargin == 0:
|
||||
return smboolFalse
|
||||
(possible, energyDiff) = self.mbEtankCheck()
|
||||
if possible == False:
|
||||
return smboolFalse
|
||||
# print('MB2', ammoMargin, secs)
|
||||
#print("ammoMargin: {}, secs: {}, settings: {}, energyDiff: {}".format(ammoMargin, secs, Settings.bossesDifficulty['MotherBrain'], energyDiff))
|
||||
(diff, defenseItems) = self.computeBossDifficulty(ammoMargin, secs, Settings.bossesDifficulty['MotherBrain'], energyDiff)
|
||||
if diff < 0:
|
||||
return smboolFalse
|
||||
return SMBool(True, diff, items=ammoItems+defenseItems)
|
||||
|
||||
@Cache.decorator
|
||||
def canPassMetroids(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('Ice'), sm.haveMissileOrSuper()),
|
||||
# to avoid leaving tourian to refill power bombs
|
||||
sm.itemCountOk('PowerBomb', 3))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassZebetites(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('Ice'), sm.knowsIceZebSkip()),
|
||||
sm.wand(sm.haveItem('SpeedBooster'), sm.knowsSpeedZebSkip()),
|
||||
# account for one zebetite, refill may be necessary
|
||||
SMBool(self.canInflictEnoughDamages(1100, charge=False, givesDrops=False, ignoreSupers=True)[0] >= 1, 0))
|
||||
|
||||
@Cache.decorator
|
||||
def enoughStuffTourian(self):
|
||||
sm = self.smbm
|
||||
ret = self.smbm.wand(sm.wor(RomPatches.has(sm.player, RomPatches.TourianSpeedup),
|
||||
sm.wand(sm.canPassMetroids(), sm.canPassZebetites())),
|
||||
sm.canOpenRedDoors(),
|
||||
sm.enoughStuffsMotherbrain(),
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.OpenZebetites), sm.haveItem('Morph')))
|
||||
return ret
|
||||
|
||||
class Pickup:
|
||||
def __init__(self, itemsPickup):
|
||||
self.itemsPickup = itemsPickup
|
||||
|
||||
def enoughMinors(self, smbm, minorLocations):
|
||||
if self.itemsPickup == 'all':
|
||||
return len(minorLocations) == 0
|
||||
else:
|
||||
return True
|
||||
|
||||
def enoughMajors(self, smbm, majorLocations):
|
||||
if self.itemsPickup == 'all':
|
||||
return len(majorLocations) == 0
|
||||
else:
|
||||
return True
|
||||
|
||||
class Bosses:
|
||||
# bosses helpers to know if they are dead
|
||||
areaBosses = {
|
||||
# classic areas
|
||||
'Brinstar': 'Kraid',
|
||||
'Norfair': 'Ridley',
|
||||
'LowerNorfair': 'Ridley',
|
||||
'WreckedShip': 'Phantoon',
|
||||
'Maridia': 'Draygon',
|
||||
# solver areas
|
||||
'Blue Brinstar': 'Kraid',
|
||||
'Brinstar Hills': 'Kraid',
|
||||
'Bubble Norfair': 'Ridley',
|
||||
'Bubble Norfair Bottom': 'Ridley',
|
||||
'Bubble Norfair Reserve': 'Ridley',
|
||||
'Bubble Norfair Speed': 'Ridley',
|
||||
'Bubble Norfair Wave': 'Ridley',
|
||||
'Draygon Boss': 'Draygon',
|
||||
'Green Brinstar': 'Kraid',
|
||||
'Green Brinstar Reserve': 'Kraid',
|
||||
'Kraid': 'Kraid',
|
||||
'Kraid Boss': 'Kraid',
|
||||
'Left Sandpit': 'Draygon',
|
||||
'Lower Norfair After Amphitheater': 'Ridley',
|
||||
'Lower Norfair Before Amphitheater': 'Ridley',
|
||||
'Lower Norfair Screw Attack': 'Ridley',
|
||||
'Maridia Forgotten Highway': 'Draygon',
|
||||
'Maridia Green': 'Draygon',
|
||||
'Maridia Pink Bottom': 'Draygon',
|
||||
'Maridia Pink Top': 'Draygon',
|
||||
'Maridia Sandpits': 'Draygon',
|
||||
'Norfair Entrance': 'Ridley',
|
||||
'Norfair Grapple Escape': 'Ridley',
|
||||
'Norfair Ice': 'Ridley',
|
||||
'Phantoon Boss': 'Phantoon',
|
||||
'Pink Brinstar': 'Kraid',
|
||||
'Red Brinstar': 'Kraid',
|
||||
'Red Brinstar Top': 'Kraid',
|
||||
'Ridley Boss': 'Ridley',
|
||||
'Right Sandpit': 'Draygon',
|
||||
'Warehouse': 'Kraid',
|
||||
'WreckedShip': 'Phantoon',
|
||||
'WreckedShip Back': 'Phantoon',
|
||||
'WreckedShip Bottom': 'Phantoon',
|
||||
'WreckedShip Gravity': 'Phantoon',
|
||||
'WreckedShip Main': 'Phantoon',
|
||||
'WreckedShip Top': 'Phantoon'
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def Golden4():
|
||||
return ['Draygon', 'Kraid', 'Phantoon', 'Ridley']
|
||||
|
||||
@staticmethod
|
||||
def bossDead(sm, boss):
|
||||
return sm.haveItem(boss)
|
||||
|
||||
@staticmethod
|
||||
def areaBossDead(sm, area):
|
||||
if area not in Bosses.areaBosses:
|
||||
return True
|
||||
return Bosses.bossDead(sm, Bosses.areaBosses[area])
|
||||
|
||||
@staticmethod
|
||||
def allBossesDead(smbm):
|
||||
return smbm.wand(Bosses.bossDead(smbm, 'Kraid'),
|
||||
Bosses.bossDead(smbm, 'Phantoon'),
|
||||
Bosses.bossDead(smbm, 'Draygon'),
|
||||
Bosses.bossDead(smbm, 'Ridley'))
|
||||
|
||||
def diffValue2txt(diff):
|
||||
last = 0
|
||||
for d in sorted(diff2text.keys()):
|
||||
if diff >= last and diff < d:
|
||||
return diff2text[last]
|
||||
last = d
|
||||
return None
|
||||
26
worlds/sm/variaRandomizer/logic/logic.py
Normal file
26
worlds/sm/variaRandomizer/logic/logic.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# entry point for the logic implementation
|
||||
|
||||
class Logic(object):
|
||||
@staticmethod
|
||||
def factory(implementation):
|
||||
if implementation == 'vanilla':
|
||||
from graph.vanilla.graph_helpers import HelpersGraph
|
||||
from graph.vanilla.graph_access import accessPoints
|
||||
from graph.vanilla.graph_locations import locations
|
||||
from graph.vanilla.graph_locations import LocationsHelper
|
||||
Logic.locations = locations
|
||||
Logic.accessPoints = accessPoints
|
||||
Logic.HelpersGraph = HelpersGraph
|
||||
Logic.patches = implementation
|
||||
Logic.LocationsHelper = LocationsHelper
|
||||
elif implementation == 'rotation':
|
||||
from graph.rotation.graph_helpers import HelpersGraph
|
||||
from graph.rotation.graph_access import accessPoints
|
||||
from graph.rotation.graph_locations import locations
|
||||
from graph.rotation.graph_locations import LocationsHelper
|
||||
Logic.locations = locations
|
||||
Logic.accessPoints = accessPoints
|
||||
Logic.HelpersGraph = HelpersGraph
|
||||
Logic.patches = implementation
|
||||
Logic.LocationsHelper = LocationsHelper
|
||||
Logic.implementation = implementation
|
||||
122
worlds/sm/variaRandomizer/logic/smbool.py
Normal file
122
worlds/sm/variaRandomizer/logic/smbool.py
Normal file
@@ -0,0 +1,122 @@
|
||||
def flatten(l):
|
||||
if type(l) is list:
|
||||
return [ y for x in l for y in flatten(x) ]
|
||||
else:
|
||||
return [ l ]
|
||||
|
||||
# super metroid boolean
|
||||
class SMBool:
|
||||
__slots__ = ('bool', 'difficulty', '_knows', '_items')
|
||||
def __init__(self, boolean, difficulty=0, knows=[], items=[]):
|
||||
self.bool = boolean
|
||||
self.difficulty = difficulty
|
||||
self._knows = knows
|
||||
self._items = items
|
||||
|
||||
@property
|
||||
def knows(self):
|
||||
self._knows = list(set(flatten(self._knows)))
|
||||
return self._knows
|
||||
|
||||
@knows.setter
|
||||
def knows(self, knows):
|
||||
self._knows = knows
|
||||
|
||||
@property
|
||||
def items(self):
|
||||
self._items = list(set(flatten(self._items)))
|
||||
return self._items
|
||||
|
||||
@items.setter
|
||||
def items(self, items):
|
||||
self._items = items
|
||||
|
||||
def __repr__(self):
|
||||
# to display the smbool as a string
|
||||
return 'SMBool({}, {}, {}, {})'.format(self.bool, self.difficulty, sorted(self.knows), sorted(self.items))
|
||||
|
||||
def __getitem__(self, index):
|
||||
# to acces the smbool as [0] for the bool and [1] for the difficulty.
|
||||
# required when we load a json preset where the smbool is stored as a list,
|
||||
# and we add missing smbools to it, so we have a mix of lists and smbools.
|
||||
if index == 0:
|
||||
return self.bool
|
||||
elif index == 1:
|
||||
return self.difficulty
|
||||
|
||||
def __bool__(self):
|
||||
# when used in boolean expressions (with and/or/not) (python3)
|
||||
return self.bool
|
||||
|
||||
def __eq__(self, other):
|
||||
# for ==
|
||||
return self.bool == other
|
||||
|
||||
def __ne__(self, other):
|
||||
# for !=
|
||||
return self.bool != other
|
||||
|
||||
def __lt__(self, other):
|
||||
# for <
|
||||
if self.bool and other.bool:
|
||||
return self.difficulty < other.difficulty
|
||||
else:
|
||||
return self.bool
|
||||
|
||||
def __copy__(self):
|
||||
return SMBool(self.bool, self.difficulty, self._knows, self._items)
|
||||
|
||||
def json(self):
|
||||
# as we have slots instead of dict
|
||||
return {'bool': self.bool, 'difficulty': self.difficulty, 'knows': self.knows, 'items': self.items}
|
||||
|
||||
def wand(*args):
|
||||
# looping here is faster than using "if ... in" construct
|
||||
for smb in args:
|
||||
if not smb.bool:
|
||||
return smboolFalse
|
||||
|
||||
difficulty = 0
|
||||
|
||||
for smb in args:
|
||||
difficulty += smb.difficulty
|
||||
|
||||
return SMBool(True,
|
||||
difficulty,
|
||||
[ smb._knows for smb in args ],
|
||||
[ smb._items for smb in args ])
|
||||
|
||||
def wandmax(*args):
|
||||
# looping here is faster than using "if ... in" construct
|
||||
for smb in args:
|
||||
if not smb.bool:
|
||||
return smboolFalse
|
||||
|
||||
difficulty = 0
|
||||
|
||||
for smb in args:
|
||||
if smb.difficulty > difficulty:
|
||||
difficulty = smb.difficulty
|
||||
|
||||
return SMBool(True,
|
||||
difficulty,
|
||||
[ smb._knows for smb in args ],
|
||||
[ smb._items for smb in args ])
|
||||
|
||||
def wor(*args):
|
||||
# looping here is faster than using "if ... in" construct
|
||||
for smb in args:
|
||||
if smb.bool:
|
||||
return min(args)
|
||||
|
||||
return smboolFalse
|
||||
|
||||
# negates boolean part of the SMBool
|
||||
def wnot(a):
|
||||
return smboolFalse if a.bool else SMBool(True, a.difficulty)
|
||||
|
||||
__and__ = wand
|
||||
__or__ = wor
|
||||
__not__ = wnot
|
||||
|
||||
smboolFalse = SMBool(False)
|
||||
241
worlds/sm/variaRandomizer/logic/smboolmanager.py
Normal file
241
worlds/sm/variaRandomizer/logic/smboolmanager.py
Normal file
@@ -0,0 +1,241 @@
|
||||
# object to handle the smbools and optimize them
|
||||
|
||||
from logic.cache import Cache
|
||||
from logic.smbool import SMBool, smboolFalse
|
||||
from logic.helpers import Bosses
|
||||
from logic.logic import Logic
|
||||
from utils.doorsmanager import DoorsManager
|
||||
from utils.parameters import Knows, isKnows
|
||||
import logging
|
||||
import sys
|
||||
|
||||
class SMBoolManager(object):
|
||||
items = ['ETank', 'Missile', 'Super', 'PowerBomb', 'Bomb', 'Charge', 'Ice', 'HiJump', 'SpeedBooster', 'Wave', 'Spazer', 'SpringBall', 'Varia', 'Plasma', 'Grapple', 'Morph', 'Reserve', 'Gravity', 'XRayScope', 'SpaceJump', 'ScrewAttack', 'Nothing', 'NoEnergy', 'MotherBrain', 'Hyper'] + Bosses.Golden4()
|
||||
countItems = ['Missile', 'Super', 'PowerBomb', 'ETank', 'Reserve']
|
||||
|
||||
def __init__(self, player=0, maxDiff=sys.maxsize):
|
||||
self._items = { }
|
||||
self._counts = { }
|
||||
|
||||
self.player = player
|
||||
self.maxDiff = maxDiff
|
||||
|
||||
# cache related
|
||||
self.cacheKey = 0
|
||||
self.computeItemsPositions()
|
||||
Cache.reset()
|
||||
Logic.factory('vanilla')
|
||||
self.helpers = Logic.HelpersGraph(self)
|
||||
self.doorsManager = DoorsManager()
|
||||
self.createFacadeFunctions()
|
||||
self.createKnowsFunctions(player)
|
||||
self.resetItems()
|
||||
|
||||
def computeItemsPositions(self):
|
||||
# compute index in cache key for each items
|
||||
self.itemsPositions = {}
|
||||
maxBitsForCountItem = 7 # 128 values with 7 bits
|
||||
for (i, item) in enumerate(self.countItems):
|
||||
pos = i*maxBitsForCountItem
|
||||
bitMask = (2<<(maxBitsForCountItem-1))-1
|
||||
bitMask = bitMask << pos
|
||||
self.itemsPositions[item] = (pos, bitMask)
|
||||
for (i, item) in enumerate(self.items, (i+1)*maxBitsForCountItem+1):
|
||||
if item in self.countItems:
|
||||
continue
|
||||
self.itemsPositions[item] = (i, 1<<i)
|
||||
|
||||
def computeNewCacheKey(self, item, value):
|
||||
# generate an unique integer for each items combinations which is use as key in the cache.
|
||||
if item in ['Nothing', 'NoEnergy']:
|
||||
return
|
||||
(pos, bitMask) = self.itemsPositions[item]
|
||||
# print("--------------------- {} {} ----------------------------".format(item, value))
|
||||
# print("old: "+format(self.cacheKey, '#067b'))
|
||||
self.cacheKey = (self.cacheKey & (~bitMask)) | (value<<pos)
|
||||
# print("new: "+format(self.cacheKey, '#067b'))
|
||||
# self.printItemsInKey(self.cacheKey)
|
||||
|
||||
def printItemsInKey(self, key):
|
||||
# for debug purpose
|
||||
print("key: "+format(key, '#067b'))
|
||||
msg = ""
|
||||
for (item, (pos, bitMask)) in self.itemsPositions.items():
|
||||
value = (key & bitMask) >> pos
|
||||
if value != 0:
|
||||
msg += " {}: {}".format(item, value)
|
||||
print("items:{}".format(msg))
|
||||
|
||||
def isEmpty(self):
|
||||
for item in self.items:
|
||||
if self.haveItem(item):
|
||||
return False
|
||||
for item in self.countItems:
|
||||
if self.itemCount(item) > 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def getItems(self):
|
||||
# get a dict of collected items and how many (to be displayed on the solver spoiler)
|
||||
itemsDict = {}
|
||||
for item in self.items:
|
||||
itemsDict[item] = 1 if self._items[item] == True else 0
|
||||
for item in self.countItems:
|
||||
itemsDict[item] = self._counts[item]
|
||||
return itemsDict
|
||||
|
||||
def withItem(self, item, func):
|
||||
self.addItem(item)
|
||||
ret = func(self)
|
||||
self.removeItem(item)
|
||||
return ret
|
||||
|
||||
def resetItems(self):
|
||||
self._items = { item : smboolFalse for item in self.items }
|
||||
self._counts = { item : 0 for item in self.countItems }
|
||||
|
||||
self.cacheKey = 0
|
||||
Cache.update(self.cacheKey)
|
||||
|
||||
def addItem(self, item):
|
||||
# a new item is available
|
||||
self._items[item] = SMBool(True, items=[item])
|
||||
if self.isCountItem(item):
|
||||
count = self._counts[item] + 1
|
||||
self._counts[item] = count
|
||||
self.computeNewCacheKey(item, count)
|
||||
else:
|
||||
self.computeNewCacheKey(item, 1)
|
||||
|
||||
Cache.update(self.cacheKey)
|
||||
|
||||
def addItems(self, items):
|
||||
if len(items) == 0:
|
||||
return
|
||||
for item in items:
|
||||
self._items[item] = SMBool(True, items=[item])
|
||||
if self.isCountItem(item):
|
||||
count = self._counts[item] + 1
|
||||
self._counts[item] = count
|
||||
self.computeNewCacheKey(item, count)
|
||||
else:
|
||||
self.computeNewCacheKey(item, 1)
|
||||
|
||||
Cache.update(self.cacheKey)
|
||||
|
||||
def removeItem(self, item):
|
||||
# randomizer removed an item (or the item was added to test a post available)
|
||||
if self.isCountItem(item):
|
||||
count = self._counts[item] - 1
|
||||
self._counts[item] = count
|
||||
if count == 0:
|
||||
self._items[item] = smboolFalse
|
||||
self.computeNewCacheKey(item, count)
|
||||
else:
|
||||
self._items[item] = smboolFalse
|
||||
self.computeNewCacheKey(item, 0)
|
||||
|
||||
Cache.update(self.cacheKey)
|
||||
|
||||
def createFacadeFunctions(self):
|
||||
for fun in dir(self.helpers):
|
||||
if fun != 'smbm' and fun[0:2] != '__':
|
||||
setattr(self, fun, getattr(self.helpers, fun))
|
||||
|
||||
def traverse(self, doorName):
|
||||
return self.doorsManager.traverse(self, doorName)
|
||||
|
||||
def createKnowsFunctions(self, player):
|
||||
# for each knows we have a function knowsKnows (ex: knowsAlcatrazEscape()) which
|
||||
# take no parameter
|
||||
for knows in Knows.__dict__:
|
||||
if isKnows(knows):
|
||||
if knows in Knows.knowsDict[player].__dict__:
|
||||
setattr(self, 'knows'+knows, lambda knows=knows: SMBool(Knows.knowsDict[player].__dict__[knows].bool,
|
||||
Knows.knowsDict[player].__dict__[knows].difficulty,
|
||||
knows=[knows]))
|
||||
else:
|
||||
# if knows not in preset, use default values
|
||||
setattr(self, 'knows'+knows, lambda knows=knows: SMBool(Knows.__dict__[knows].bool,
|
||||
Knows.__dict__[knows].difficulty,
|
||||
knows=[knows]))
|
||||
|
||||
def isCountItem(self, item):
|
||||
return item in self.countItems
|
||||
|
||||
def itemCount(self, item):
|
||||
# return integer
|
||||
#self.state.item_count(item, self.player)
|
||||
return self._counts[item]
|
||||
|
||||
def haveItem(self, item):
|
||||
#return self.state.has(item, self.player)
|
||||
return self._items[item]
|
||||
|
||||
wand = staticmethod(SMBool.wand)
|
||||
wandmax = staticmethod(SMBool.wandmax)
|
||||
wor = staticmethod(SMBool.wor)
|
||||
wnot = staticmethod(SMBool.wnot)
|
||||
|
||||
def itemCountOk(self, item, count, difficulty=0):
|
||||
if self.itemCount(item) >= count:
|
||||
if item in ['ETank', 'Reserve']:
|
||||
item = str(count)+'-'+item
|
||||
return SMBool(True, difficulty, items = [item])
|
||||
else:
|
||||
return smboolFalse
|
||||
|
||||
def energyReserveCountOk(self, count, difficulty=0):
|
||||
if self.energyReserveCount() >= count:
|
||||
nEtank = self.itemCount('ETank')
|
||||
if nEtank > count:
|
||||
nEtank = int(count)
|
||||
items = str(nEtank)+'-ETank'
|
||||
nReserve = self.itemCount('Reserve')
|
||||
if nEtank < count:
|
||||
nReserve = int(count) - nEtank
|
||||
items += ' - '+str(nReserve)+'-Reserve'
|
||||
return SMBool(True, difficulty, items = [items])
|
||||
else:
|
||||
return smboolFalse
|
||||
|
||||
class SMBoolManagerPlando(SMBoolManager):
|
||||
def __init__(self):
|
||||
super(SMBoolManagerPlando, self).__init__()
|
||||
|
||||
def addItem(self, item):
|
||||
# a new item is available
|
||||
already = self.haveItem(item)
|
||||
isCount = self.isCountItem(item)
|
||||
if isCount or not already:
|
||||
self._items[item] = SMBool(True, items=[item])
|
||||
else:
|
||||
# handle duplicate major items (plandos)
|
||||
self._items['dup_'+item] = True
|
||||
if isCount:
|
||||
count = self._counts[item] + 1
|
||||
self._counts[item] = count
|
||||
self.computeNewCacheKey(item, count)
|
||||
else:
|
||||
self.computeNewCacheKey(item, 1)
|
||||
|
||||
Cache.update(self.cacheKey)
|
||||
|
||||
def removeItem(self, item):
|
||||
# randomizer removed an item (or the item was added to test a post available)
|
||||
if self.isCountItem(item):
|
||||
count = self._counts[item] - 1
|
||||
self._counts[item] = count
|
||||
if count == 0:
|
||||
self._items[item] = smboolFalse
|
||||
self.computeNewCacheKey(item, count)
|
||||
else:
|
||||
dup = 'dup_'+item
|
||||
if self._items.get(dup, None) is None:
|
||||
self._items[item] = smboolFalse
|
||||
self.computeNewCacheKey(item, 0)
|
||||
else:
|
||||
del self._items[dup]
|
||||
self.computeNewCacheKey(item, 1)
|
||||
|
||||
Cache.update(self.cacheKey)
|
||||
0
worlds/sm/variaRandomizer/patches/__init__.py
Normal file
0
worlds/sm/variaRandomizer/patches/__init__.py
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/animal_enemies.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/animal_enemies.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/animals.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/animals.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/area_ids_alt.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/area_ids_alt.ips
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/basepatch.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/basepatch.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/beam_doors_gfx.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/beam_doors_gfx.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/beam_doors_plms.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/beam_doors_plms.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/bomb_torizo.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/bomb_torizo.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/crab_shaft.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/crab_shaft.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/door_transition.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/door_transition.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/draygonimals.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/draygonimals.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/east_ocean.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/east_ocean.ips
Normal file
Binary file not shown.
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/escapimals.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/escapimals.ips
Normal file
Binary file not shown.
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/g4_skip.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/g4_skip.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/gameend.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/gameend.ips
Normal file
Binary file not shown.
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/itemsounds.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/itemsounds.ips
Normal file
Binary file not shown.
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/low_timer.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/low_timer.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/mama_save.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/mama_save.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/metalimals.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/metalimals.ips
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/nerfed_charge.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/nerfed_charge.ips
Normal file
Binary file not shown.
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/no_demo.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/no_demo.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/open_zebetites.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/open_zebetites.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/phantoonimals.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/phantoonimals.ips
Normal file
Binary file not shown.
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/rando_escape.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/rando_escape.ips
Normal file
Binary file not shown.
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/rando_speed.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/rando_speed.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/random_music.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/random_music.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/red_doors.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/red_doors.ips
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/ridley_platform.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/ridley_platform.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/ridleyimals.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/ridleyimals.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/spinjumprestart.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/spinjumprestart.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/vanilla_music.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/vanilla_music.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/varia_hud.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/varia_hud.ips
Normal file
Binary file not shown.
BIN
worlds/sm/variaRandomizer/patches/common/ips/wh_open_tube.ips
Normal file
BIN
worlds/sm/variaRandomizer/patches/common/ips/wh_open_tube.ips
Normal file
Binary file not shown.
553
worlds/sm/variaRandomizer/patches/common/patches.py
Normal file
553
worlds/sm/variaRandomizer/patches/common/patches.py
Normal file
@@ -0,0 +1,553 @@
|
||||
patches = {
|
||||
"Removes_Gravity_Suit_heat_protection": {
|
||||
0x06e37d: [0x01],
|
||||
0x0869dd: [0x01]},
|
||||
"Mother_Brain_Cutscene_Edits": {
|
||||
0x148824: [0x01,0x00],
|
||||
0x148848: [0x01,0x00],
|
||||
0x148867: [0x01,0x00],
|
||||
0x14887f: [0x01,0x00],
|
||||
0x148bdb: [0x04,0x00],
|
||||
0x14897d: [0x10,0x00],
|
||||
0x1489af: [0x10,0x00],
|
||||
0x1489e1: [0x10,0x00],
|
||||
0x148a09: [0x10,0x00],
|
||||
0x148a31: [0x10,0x00],
|
||||
0x148a63: [0x10,0x00],
|
||||
0x148a95: [0x10,0x00],
|
||||
0x148b33: [0x10,0x00],
|
||||
0x148dc6: [0xb0],
|
||||
0x148b8d: [0x12,0x00],
|
||||
0x148d74: [0x00,0x00],
|
||||
0x148d86: [0x00,0x00],
|
||||
0x148daf: [0x00,0x01],
|
||||
0x148e51: [0x01,0x00],
|
||||
0x14b93a: [0x00,0x01],
|
||||
0x148eef: [0x0a,0x00],
|
||||
0x148f0f: [0x60,0x00],
|
||||
0x14af4e: [0x0a,0x00],
|
||||
0x14af0d: [0x0a,0x00],
|
||||
0x14b00d: [0x00,0x00],
|
||||
0x14b132: [0x40,0x00],
|
||||
0x14b16d: [0x00,0x00],
|
||||
0x14b19f: [0x20,0x00],
|
||||
0x14b1b2: [0x30,0x00],
|
||||
0x14b20c: [0x03,0x00]},
|
||||
"No_Music":{
|
||||
0x278413: [0x6f]},
|
||||
"Escape_Rando_Enable_Enemies":{
|
||||
0x10F000: [0x0, 0x0]},
|
||||
"Escape_Rando_Disable_Enemies":{
|
||||
0x10F000: [0x1]},
|
||||
"Escape_Animals_Open_Brinstar": {
|
||||
0x784BD: [0x10]
|
||||
},
|
||||
"Escape_Animals_Open_Norfair": {
|
||||
0x78B0B: [0x10]
|
||||
},
|
||||
"Escape_Animals_Open_Maridia": {
|
||||
0x7C54C: [0x10]
|
||||
},
|
||||
"Enable_Backup_Saves": {
|
||||
0xef20: [0x1]
|
||||
},
|
||||
'Escape_Scavenger' : {
|
||||
0x10F5FC: [0x1]
|
||||
},
|
||||
# vanilla data to restore setup asm for plandos
|
||||
"Escape_Animals_Disable": {
|
||||
0x79867: [0xb2, 0x91],
|
||||
0x798dc: [0xbb, 0x91]
|
||||
},
|
||||
# with animals suprise make the bomb blocks at alcatraz disapear with event "Zebes timebomb set" instead of "critters escaped"
|
||||
"Escape_Animals_Change_Event": {
|
||||
0x023B0A: [0x0E]
|
||||
},
|
||||
"LN_Chozo_SpaceJump_Check_Disable": {
|
||||
0x2518f: [0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea]
|
||||
},
|
||||
"LN_PB_Heat_Disable": {
|
||||
0x18878: [0x80, 0x00]
|
||||
},
|
||||
"LN_Firefleas_Remove_Fune": {
|
||||
0x10ABC2: [0xff, 0x7f, 0xff, 0x7f],
|
||||
},
|
||||
"WS_Main_Open_Grey": {
|
||||
0x10BE92: [0x0]
|
||||
},
|
||||
"WS_Save_Active": {
|
||||
0x7ceb0: [0xC9]
|
||||
},
|
||||
"WS_Etank": {
|
||||
0x7cc4d: [0x37, 0xc3],
|
||||
0x7cbfb: [0x23, 0xc3]
|
||||
},
|
||||
"Phantoon_Eye_Door":{
|
||||
0x7CCAF: [0x91, 0xC2]
|
||||
},
|
||||
# has to be applied along with WS_Main_Open_Grey
|
||||
"Sponge_Bath_Blinking_Door": {
|
||||
0x7C276: [0x0C],
|
||||
0x10CE69: [0x00]
|
||||
},
|
||||
"Infinite_Space_Jump": {
|
||||
0x82493: [0x80, 0x0D]
|
||||
},
|
||||
"SpriteSomething_Disable_Spin_Attack": {
|
||||
0xD93FE: [0x0, 0x0]
|
||||
},
|
||||
# custom load points for non standard start APs
|
||||
"Save_G4": {
|
||||
# load point entry
|
||||
0x4527: [0xED, 0xA5, 0x16, 0x92, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xA8, 0x00, 0x60, 0x00],
|
||||
# map icon X/Y
|
||||
0x1486f: [0x78, 0x00, 0x48, 0x00]
|
||||
},
|
||||
"Save_Gauntlet": {
|
||||
# load point entry
|
||||
0x4519: [0xBD, 0x99, 0x1A, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x50, 0x00],
|
||||
# music in room state header
|
||||
0x799ce: [0x09],
|
||||
# map icon X/Y
|
||||
0x1486b: [0x58, 0x00, 0x18, 0x00]
|
||||
},
|
||||
"Save_Watering_Hole": {
|
||||
# load point entry
|
||||
0x4979: [0x3B, 0xD1, 0x98, 0xA4, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x88, 0x00, 0xD0, 0xFF],
|
||||
# music in room state header
|
||||
0x7d14c: [0x1b, 0x06],
|
||||
# map icon X/Y
|
||||
0x14a0f: [0x68, 0x00, 0x28, 0x00]
|
||||
},
|
||||
"Save_Mama": {
|
||||
# load point entry
|
||||
0x496B: [0x55, 0xD0, 0xE4, 0xA3, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x98, 0x00, 0xF0, 0xFF],
|
||||
# music in room state header
|
||||
0x7d066: [0x1b, 0x06],
|
||||
# map icon X/Y
|
||||
0x14a0b: [0x97, 0x00, 0x67, 0x00]
|
||||
},
|
||||
"Save_Aqueduct": {
|
||||
# load point entry
|
||||
0x495D: [0xA7, 0xD5, 0xD4, 0xA7, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x78, 0x00, 0x20, 0x00],
|
||||
# map icon X/Y
|
||||
0x14a07: [0xc4, 0x00, 0x50, 0x00]
|
||||
},
|
||||
"Save_Etecoons": {
|
||||
# load point entry
|
||||
0x4631: [0x51, 0xA0, 0x3A, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x00, 0xD0, 0xFF],
|
||||
# music in room state header
|
||||
0x7a062: [0x0f, 0x05],
|
||||
# map icon X/Y
|
||||
0x148d9: [0x28, 0x00, 0x58, 0x00]
|
||||
},
|
||||
"Save_Firefleas": {
|
||||
# load point entry
|
||||
0x473b: [0x5A, 0xB5, 0x9E, 0x9A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00],
|
||||
# music in room state header
|
||||
0x7b56b: [0x18, 0x05],
|
||||
# map icon X/Y
|
||||
0x1493f: [0x28, 0x01, 0x38, 0x00]
|
||||
},
|
||||
# custom load points for west maridia additional saves in area rando
|
||||
"Save_Crab_Shaft": {
|
||||
# load point entry
|
||||
0x4995: [0xa3, 0xd1, 0x68, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x78, 0x00, 0x60, 0x00],
|
||||
# map icon X/Y
|
||||
0x14a17: [0x90, 0x00, 0x50, 0x00]
|
||||
},
|
||||
"Save_Main_Street": {
|
||||
0x49a3: [0xC9, 0xCF, 0xD8, 0xA3, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x78, 0x00, 0x10, 0x00],
|
||||
# map icon X/Y
|
||||
0x14a1b: [0x58, 0x00, 0x78, 0x00]
|
||||
},
|
||||
# blinking doors for area APs
|
||||
'Blinking[Keyhunter Room Bottom]': {
|
||||
0x78228: [0x4e, 0xc8, 0x16, 0x2d, 0x0e, 0x8c],
|
||||
0x108F7B: [0x0]
|
||||
},
|
||||
'Blinking[Moat Right]': {
|
||||
0x1085E0: [0x0]
|
||||
},
|
||||
'Blinking[Morph Ball Room Left]': {
|
||||
0x78746: [0x48, 0xc8, 0x01, 0x26, 0x31, 0x8c],
|
||||
0x1093A8: [0x0]
|
||||
},
|
||||
'Blinking[Green Pirates Shaft Bottom Right]': {
|
||||
0x78470: [0x42, 0xc8, 0x0e, 0x66, 0x63, 0x8c],
|
||||
0x108572: [0x0]
|
||||
},
|
||||
'Blinking[Lower Mushrooms Left]': {
|
||||
0x108C0C: [0x0]
|
||||
},
|
||||
'Blinking[Golden Four]': {
|
||||
0x109F60: [0x0]
|
||||
},
|
||||
'Blinking[Green Brinstar Elevator]': {
|
||||
0x108585: [0x0]
|
||||
},
|
||||
'Blinking[Green Hill Zone Top Right]': {
|
||||
0x78670: [0x42, 0xc8, 0x1e, 0x06, 0x63, 0x8c],
|
||||
0x109D5B: [0x0]
|
||||
},
|
||||
'Blinking[Noob Bridge Right]': {
|
||||
0x787A6: [0x42, 0xc8, 0x5e, 0x06, 0x63, 0x8c],
|
||||
0x109325: [0x0]
|
||||
},
|
||||
'Blinking[Warehouse Zeela Room Left]': {
|
||||
0x109451: [0x0]
|
||||
},
|
||||
'Blinking[KraidRoomOut]': {
|
||||
# removes gadora by ending PLM list
|
||||
0x78A1A: [0x42, 0xc8, 0x1e, 0x16, 0x63, 0x8c, 0x00, 0x00],
|
||||
0x10A056: [0x0]
|
||||
},
|
||||
'Blinking[Warehouse Entrance Right]': {
|
||||
0x1098F6: [0x0]
|
||||
},
|
||||
'Blinking[Warehouse Entrance Left]': {
|
||||
0x1098F6: [0x0]
|
||||
},
|
||||
'Blinking[Single Chamber Top Right]': {
|
||||
0x10B88E: [0x0]
|
||||
},
|
||||
'Blinking[Kronic Boost Room Bottom Left]': {
|
||||
0x78D4E: [0x48, 0xc8, 0x11, 0x26, 0x58, 0x8c],
|
||||
0x10B9D7: [0x0]
|
||||
},
|
||||
'Blinking[Three Muskateers Room Left]': {
|
||||
0x10BB0D: [0x0]
|
||||
},
|
||||
'Blinking[Lava Dive Right]': {
|
||||
0x10AD6B: [0x0]
|
||||
},
|
||||
'Blinking[RidleyRoomOut]': {
|
||||
# removes gadora by ending PLM list
|
||||
0x78EA6: [0x48, 0xc8, 0x01, 0x06, 0x63, 0x8c, 0x00, 0x00],
|
||||
0x10B81B: [0x0]
|
||||
},
|
||||
'Blinking[West Ocean Left]': {
|
||||
0x1086F6: [0x0]
|
||||
},
|
||||
'Blinking[PhantoonRoomOut]': {
|
||||
# removes gadora by ending PLM list
|
||||
0x7C29D: [0x42, 0xc8, 0x4e, 0x06, 0x63, 0x8c, 0x00, 0x00],
|
||||
# zero needed enemy count for both room states
|
||||
0x10C3E5: [0x0],
|
||||
0x10C19B: [0x0]
|
||||
},
|
||||
'Blinking[Crab Maze Left]': {
|
||||
0x108B3A: [0x0]
|
||||
},
|
||||
'Blinking[Crab Hole Bottom Left]': {
|
||||
0x10DE59: [0x0]
|
||||
},
|
||||
'Blinking[Main Street Bottom]': {
|
||||
0x10DF2F: [0x0]
|
||||
},
|
||||
'Blinking[Red Fish Room Left]': {
|
||||
0x10D3EC: [0x0]
|
||||
},
|
||||
'Blinking[Le Coude Right]': {
|
||||
0x7823E: [0x42, 0xc8, 0x0e, 0x06, 0x63, 0x8c],
|
||||
0x1085DD: [0x0]
|
||||
},
|
||||
'Blinking[DraygonRoomOut]': {
|
||||
# overwrites a gadoras PLMs and replace the rest with useless arrow PLMs
|
||||
# (we cannot end the list because the item is after in the list)
|
||||
0x7C73B: [0x48, 0xc8, 0x01, 0x26, 0x63, 0x8c] + [0x3b, 0xb6, 0x31, 0x26, 0x00, 0x00]*2,
|
||||
0x10D111: [0x0]
|
||||
},
|
||||
'Blinking[East Tunnel Top Right]': {
|
||||
0x10D5E1: [0x0]
|
||||
},
|
||||
'Blinking[East Tunnel Right]': {
|
||||
0x10D5E1: [0x0]
|
||||
},
|
||||
'Blinking[Glass Tunnel Top]': {
|
||||
0x10D53B: [0x0]
|
||||
},
|
||||
'Blinking[Red Tower Top Left]': {
|
||||
0x109504: [0x0]
|
||||
},
|
||||
'Blinking[Caterpillar Room Top Right]': {
|
||||
0x10A0B9: [0x0]
|
||||
},
|
||||
'Blinking[Red Brinstar Elevator]': {
|
||||
0x78256: [0x54, 0xc8, 0x06, 0x02, 0x10, 0x8c],
|
||||
0x1089F1: [0x0]
|
||||
},
|
||||
'Blinking[Crocomire Speedway Bottom]': {
|
||||
0x78B96: [0x4e, 0xc8, 0xc6, 0x2d, 0x4e, 0x8c],
|
||||
0x10AA8C: [0x0]
|
||||
},
|
||||
'Blinking[Crocomire Room Top]': {
|
||||
0x78B9E: [0x54, 0xc8, 0x36, 0x02, 0x4f, 0x8c],
|
||||
0x10BB30: [0x0]
|
||||
},
|
||||
'Blinking[Below Botwoon Energy Tank Right]': {
|
||||
0x10DD9A: [0x0]
|
||||
},
|
||||
'Blinking[West Sand Hall Left]': {
|
||||
0x10DACF: [0x0]
|
||||
},
|
||||
'Blinking[Aqueduct Top Left]': {
|
||||
0x10D3A9: [0x0]
|
||||
},
|
||||
'Blinking[Crab Shaft Right]': {
|
||||
0x7C4FB: [0x42, 0xc8, 0x1e, 0x36, 0x8f, 0x8c],
|
||||
0x10D005: [0x0]
|
||||
},
|
||||
'Blinking[RidleyRoomIn]': {
|
||||
0x78E98: [0x42, 0xc8, 0x0e, 0x06, 0x5a, 0x8c],
|
||||
0x10A638: [0x0]
|
||||
},
|
||||
'Blinking[DraygonRoomIn]': {
|
||||
0x7C7BB: [0x42, 0xc8, 0x1e, 0x06, 0x9e, 0x8c],
|
||||
0x10D356: [0x0]
|
||||
},
|
||||
'Blinking[PhantoonRoomIn]': {
|
||||
0x7C2B3: [0x48, 0xc8, 0x01, 0x06, 0x86, 0x8c],
|
||||
0x10CD16: [0x0]
|
||||
},
|
||||
'Blinking[KraidRoomIn]': {
|
||||
0x78A34: [0x48, 0xc8, 0x01, 0x16, 0x47, 0x8c],
|
||||
0x109F37: [0x0]
|
||||
},
|
||||
}
|
||||
|
||||
additional_PLMs = {
|
||||
# for escape rando seeds
|
||||
"WS_Map_Grey_Door": {
|
||||
'room': 0Xcc6f,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x1, 0x6, 0x61, 0x90]
|
||||
]
|
||||
},
|
||||
"WS_Map_Grey_Door_Openable": {
|
||||
'room': 0Xcc6f,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x1, 0x6, 0x61, 0x10]
|
||||
]
|
||||
},
|
||||
# area/boss seeds
|
||||
# has to be applied along with WS_Main_Open_Grey
|
||||
"WS_Save_Blinking_Door": {
|
||||
'room': 0xcaf6,
|
||||
'state': 0xcb08,
|
||||
'plm_bytes_list': [
|
||||
[0x42, 0xC8, 0x4E, 0x36, 0x62, 0x0C]
|
||||
]
|
||||
},
|
||||
# non standard start AP seeds (morph item is not in vanilla PLM list w/ zebes awake)
|
||||
"Morph_Zebes_Awake": {
|
||||
'room': 0x9e9f,
|
||||
'state': 0x9ecb,
|
||||
'plm_bytes_list': [
|
||||
[0xff, 0xff, 0x45, 0x29, 0x1A, 0x00]
|
||||
],
|
||||
'locations': [("Morphing Ball", 0)]
|
||||
},
|
||||
# seal west/east maridia connection in area rando
|
||||
'Maridia Sand Hall Seal': {
|
||||
'room': 0xd252,
|
||||
'plm_bytes_list': [
|
||||
[0x42, 0xc8, 0x0e, 0x06, 0x63, 0x90]
|
||||
]
|
||||
},
|
||||
# custom save points for non standard start APs
|
||||
"Save_G4": {
|
||||
"room": 0xa5ed,
|
||||
'plm_bytes_list': [
|
||||
[0x6F, 0xB7, 0x3D, 0x0C, 0x07, 0x00]
|
||||
]
|
||||
},
|
||||
"Save_Gauntlet": {
|
||||
"room": 0x99bd,
|
||||
'plm_bytes_list': [
|
||||
[0x6F, 0xB7, 0x0C, 0x0A, 0x06, 0x00]
|
||||
]
|
||||
},
|
||||
"Save_Watering_Hole": {
|
||||
"room": 0xd13b,
|
||||
'plm_bytes_list': [
|
||||
[0x6F, 0xB7, 0x14, 0x0A, 0x07, 0x00]
|
||||
]
|
||||
},
|
||||
"Save_Mama": {
|
||||
"room": 0xd055,
|
||||
'plm_bytes_list': [
|
||||
[0x6F, 0xB7, 0x26, 0x0B, 0x06, 0x00]
|
||||
]
|
||||
},
|
||||
"Save_Aqueduct": {
|
||||
"room": 0xd5a7,
|
||||
'plm_bytes_list': [
|
||||
[0x6F, 0xB7, 0x59, 0x09, 0x05, 0x00]
|
||||
]
|
||||
},
|
||||
"Save_Etecoons": {
|
||||
"room": 0xa051,
|
||||
'plm_bytes_list': [
|
||||
[0x6F, 0xB7, 0x04, 0x0B, 0x07, 0x00]
|
||||
]
|
||||
},
|
||||
"Save_Firefleas": {
|
||||
"room": 0xb55a,
|
||||
'plm_bytes_list': [
|
||||
[0x6F, 0xB7, 0x07, 0x09, 0x07, 0x00]
|
||||
]
|
||||
},
|
||||
# additional saves in west maridia for area rando
|
||||
"Save_Crab_Shaft": {
|
||||
"room": 0xd1a3,
|
||||
'plm_bytes_list': [
|
||||
[0x6F, 0xB7, 0x0D, 0x29, 0x09, 0x00]
|
||||
]
|
||||
},
|
||||
"Save_Main_Street": {
|
||||
"room": 0xcfc9,
|
||||
'plm_bytes_list': [
|
||||
[0x6F, 0xB7, 0x18, 0x59, 0x0A, 0x00]
|
||||
]
|
||||
},
|
||||
# blinking doors for area APs
|
||||
'Blinking[Moat Right]': {
|
||||
'room': 0x95ff,
|
||||
'plm_bytes_list': [
|
||||
[0x42, 0xc8, 0x1e, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Lower Mushrooms Left]': {
|
||||
'room': 0x9969,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x01, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Golden Four]': {
|
||||
'room': 0xa5ed,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x01, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Green Brinstar Elevator]': {
|
||||
'room': 0x9938,
|
||||
'plm_bytes_list': [
|
||||
[0x42, 0xc8, 0x0e, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Warehouse Zeela Room Left]': {
|
||||
'room': 0xa471,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x01, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Warehouse Entrance Right]': {
|
||||
'room': 0xa6a1,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x01, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Warehouse Entrance Left]': {
|
||||
'room': 0xa6a1,
|
||||
'plm_bytes_list': [
|
||||
[0x42, 0xc8, 0x2e, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Single Chamber Top Right]': {
|
||||
'room': 0xad5e,
|
||||
'plm_bytes_list': [
|
||||
[0x42, 0xc8, 0x5e, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Three Muskateers Room Left]': {
|
||||
'room': 0xb656,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x11, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Lava Dive Right]': {
|
||||
'room': 0xaf14,
|
||||
'plm_bytes_list': [
|
||||
[0x42, 0xc8, 0x3e, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[West Ocean Left]': {
|
||||
'room': 0x93fe,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x01, 0x46, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Crab Maze Left]': {
|
||||
'room': 0x957d,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x01, 0x16, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Crab Hole Bottom Left]': {
|
||||
'room': 0xd21c,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x01, 0x16, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Main Street Bottom]': {
|
||||
'room': 0xcfc9,
|
||||
'plm_bytes_list': [
|
||||
[0x4e, 0xc8, 0x16, 0x7d, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Red Fish Room Left]': {
|
||||
'room': 0xd104,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x01, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[East Tunnel Top Right]': {
|
||||
'room': 0xcf80,
|
||||
'plm_bytes_list': [
|
||||
[0x42, 0xc8, 0x3e, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[East Tunnel Right]': {
|
||||
'room': 0xcf80,
|
||||
'plm_bytes_list': [
|
||||
[0x42, 0xc8, 0x0e, 0x16, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Glass Tunnel Top]': {
|
||||
'room': 0xcefb,
|
||||
'plm_bytes_list': [
|
||||
[0x54, 0xc8, 0x06, 0x02, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Red Tower Top Left]': {
|
||||
'room': 0xa253,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x01, 0x46, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Caterpillar Room Top Right]': {
|
||||
'room': 0xa322,
|
||||
'plm_bytes_list': [
|
||||
[0x42, 0xc8, 0x2e, 0x36, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Below Botwoon Energy Tank Right]': {
|
||||
'room': 0xd6fd,
|
||||
'plm_bytes_list': [
|
||||
[0x42, 0xc8, 0x3e, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[West Sand Hall Left]': {
|
||||
'room': 0xd461,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x01, 0x06, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
'Blinking[Aqueduct Top Left]': {
|
||||
'room': 0xd5a7,
|
||||
'plm_bytes_list': [
|
||||
[0x48, 0xc8, 0x01, 0x16, 0x63, 0x8c]
|
||||
]
|
||||
},
|
||||
}
|
||||
77
worlds/sm/variaRandomizer/patches/ips.pl
Normal file
77
worlds/sm/variaRandomizer/patches/ips.pl
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
# ips.pl
|
||||
# version 0.01
|
||||
#
|
||||
# This is a quick hack to apply IPS patches. It is distributed under
|
||||
# the terms of the GNU General Public License.
|
||||
|
||||
if (@ARGV != 1)
|
||||
{
|
||||
print "manger"
|
||||
}
|
||||
|
||||
open PAT, "$ARGV[0]" or die "Can't open $ARGV[1]";
|
||||
|
||||
read PAT, $data, 5;
|
||||
die "Bad magic bytes in $ARGV[1]" if $data ne "PATCH";
|
||||
printf("'%s': {", $ARGV[0]);
|
||||
my $first = 1;
|
||||
while(1)
|
||||
{
|
||||
read PAT, $data, 3 or die "Read error";
|
||||
if ($data eq "EOF")
|
||||
{
|
||||
printf("},\n");
|
||||
exit;
|
||||
}
|
||||
if($first == 1) {
|
||||
$first = 2;
|
||||
} else {
|
||||
printf(",\n");
|
||||
}
|
||||
# This is ugly, but unpack doesn't have anything that's
|
||||
# very helpful for THREE-byte numbers.
|
||||
$address = ord(substr($data,0,1))*256*256 +
|
||||
ord(substr($data,1,1))*256 +
|
||||
ord(substr($data,2,1));
|
||||
|
||||
read PAT, $data, 2 or die "Read error";
|
||||
$length = ord(substr($data,0,1))*256 + ord(substr($data,1,1));
|
||||
if ($length)
|
||||
{
|
||||
read(PAT, $data, $length) == $length or die "Read error";
|
||||
|
||||
my @chars = split("", $data);
|
||||
|
||||
printf ("0x%lX: [", $address);
|
||||
for(my $i=0; $i < $length; $i++) {
|
||||
printf ("0x%lX", ord($chars[$i]));
|
||||
if($i != $length - 1) {
|
||||
printf(",");
|
||||
}
|
||||
if(($i % 38) == 0 && $i != 0) {
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
printf ("]");
|
||||
}
|
||||
else # RLE mode
|
||||
{
|
||||
read PAT, $data, 2 or die "Read error";
|
||||
$length = ord(substr($data,0,1))*256 + ord(substr($data,1,1));
|
||||
read PAT, $byte, 1 or die "Read error";
|
||||
|
||||
printf ("0x%lX: [", $address);
|
||||
for(my $i=0; $i < $length; $i++) {
|
||||
printf ("0x%lX", ord($byte));
|
||||
if($i != $length - 1) {
|
||||
printf(",");
|
||||
}
|
||||
if(($i % 38) == 0 && $i != 0) {
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
printf ("]");
|
||||
}
|
||||
}
|
||||
43
worlds/sm/variaRandomizer/patches/patchaccess.py
Normal file
43
worlds/sm/variaRandomizer/patches/patchaccess.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import os, importlib
|
||||
from logic.logic import Logic
|
||||
from patches.common.patches import patches, additional_PLMs
|
||||
from utils.parameters import appDir
|
||||
from Utils import is_frozen
|
||||
|
||||
class PatchAccess(object):
|
||||
def __init__(self):
|
||||
# load all ips patches
|
||||
self.patchesPath = {}
|
||||
commonDir = os.path.join(appDir, 'lib' if is_frozen() else '', 'worlds/sm/variaRandomizer/patches/common/ips/')
|
||||
for patch in os.listdir(commonDir):
|
||||
self.patchesPath[patch] = commonDir
|
||||
logicDir = os.path.join(appDir, 'lib' if is_frozen() else '', 'worlds/sm/variaRandomizer/patches/{}/ips/'.format(Logic.patches))
|
||||
for patch in os.listdir(logicDir):
|
||||
self.patchesPath[patch] = logicDir
|
||||
|
||||
# load dict patches
|
||||
self.dictPatches = patches
|
||||
logicPatches = importlib.import_module("patches.{}.patches".format(Logic.patches)).patches
|
||||
self.dictPatches.update(logicPatches)
|
||||
|
||||
# load additional PLMs
|
||||
self.additionalPLMs = additional_PLMs
|
||||
logicPLMs = importlib.import_module("patches.{}.patches".format(Logic.patches)).additional_PLMs
|
||||
self.additionalPLMs.update(logicPLMs)
|
||||
|
||||
def getPatchPath(self, patch):
|
||||
# is patch preloaded
|
||||
if patch in self.patchesPath:
|
||||
return os.path.join(self.patchesPath[patch], patch)
|
||||
else:
|
||||
# patchs from varia_repository used by the customizer for permalinks
|
||||
if os.path.exists(patch):
|
||||
return patch
|
||||
else:
|
||||
raise Exception("unknown patch: {}".format(patch))
|
||||
|
||||
def getDictPatches(self):
|
||||
return self.dictPatches
|
||||
|
||||
def getAdditionalPLMs(self):
|
||||
return self.additionalPLMs
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user