Added Super Metroid support (#46)
Varia Randomizer based implementation LttPClient -> SNIClient
This commit is contained in:
parent
61ae51b30c
commit
77ec8d4141
|
@ -4,6 +4,7 @@
|
|||
*_Spoiler.txt
|
||||
*.bmbp
|
||||
*.apbp
|
||||
*.apm3
|
||||
*.apmc
|
||||
*.apz5
|
||||
*.pyc
|
||||
|
|
1
Fill.py
1
Fill.py
|
@ -56,7 +56,6 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations,
|
|||
logging.warning(
|
||||
f'Not all items placed. Game beatable anyway. (Could not place {item_to_place})')
|
||||
continue
|
||||
|
||||
raise FillError(f'No more spots to place {item_to_place}, locations {locations} are invalid. '
|
||||
f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}')
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ def mystery_argparse():
|
|||
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
||||
parser.add_argument('--multi', default=defaults["players"], type=lambda value: max(int(value), 1))
|
||||
parser.add_argument('--spoiler', type=int, default=defaults["spoiler"])
|
||||
parser.add_argument('--rom', default=options["lttp_options"]["rom_file"], help="Path to the 1.0 JP LttP Baserom.")
|
||||
parser.add_argument('--lttp_rom', default=options["lttp_options"]["rom_file"], help="Path to the 1.0 JP LttP Baserom.")
|
||||
parser.add_argument('--sm_rom', default=options["sm_options"]["rom_file"], help="Path to the 1.0 JP SM Baserom.")
|
||||
parser.add_argument('--enemizercli', default=defaults["enemizer_path"])
|
||||
parser.add_argument('--outputpath', default=options["general_options"]["output_path"])
|
||||
parser.add_argument('--race', action='store_true', default=defaults["race"])
|
||||
|
@ -127,7 +128,8 @@ def main(args=None, callback=ERmain):
|
|||
|
||||
Utils.init_logging(f"Generate_{seed}.txt", loglevel=args.log_level)
|
||||
|
||||
erargs.rom = args.rom
|
||||
erargs.lttp_rom = args.lttp_rom
|
||||
erargs.sm_rom = args.sm_rom
|
||||
erargs.enemizercli = args.enemizercli
|
||||
|
||||
settings_cache = {k: (roll_settings(v, args.plando) if args.samesettings else None)
|
||||
|
|
1
Main.py
1
Main.py
|
@ -34,7 +34,6 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
|||
output_path.cached_path = args.outputpath
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
# initialize the world
|
||||
world = MultiWorld(args.multi)
|
||||
|
||||
|
|
61
Patch.py
61
Patch.py
|
@ -11,44 +11,69 @@ from typing import Tuple, Optional
|
|||
import Utils
|
||||
|
||||
|
||||
current_patch_version = 2
|
||||
current_patch_version = 3
|
||||
|
||||
GAME_ALTTP = 0
|
||||
GAME_SM = 1
|
||||
supported_games = ["A Link to the Past", "Super Metroid"]
|
||||
|
||||
def generate_yaml(patch: bytes, metadata: Optional[dict] = None, game: int = GAME_ALTTP) -> bytes:
|
||||
if game == GAME_ALTTP:
|
||||
from worlds.alttp.Rom import JAP10HASH
|
||||
elif game == GAME_SM:
|
||||
from worlds.sm.Rom import JAP10HASH
|
||||
else:
|
||||
raise RuntimeError("Selected game for base rom not found.")
|
||||
|
||||
def generate_yaml(patch: bytes, metadata: Optional[dict] = None) -> bytes:
|
||||
from worlds.alttp.Rom import JAP10HASH
|
||||
patch = yaml.dump({"meta": metadata,
|
||||
"patch": patch,
|
||||
"game": "A Link to the Past",
|
||||
"game": supported_games[game],
|
||||
# minimum version of patch system expected for patching to be successful
|
||||
"compatible_version": 1,
|
||||
"compatible_version": 3,
|
||||
"version": current_patch_version,
|
||||
"base_checksum": JAP10HASH})
|
||||
return patch.encode(encoding="utf-8-sig")
|
||||
|
||||
|
||||
def generate_patch(rom: bytes, metadata: Optional[dict] = None) -> bytes:
|
||||
from worlds.alttp.Rom import get_base_rom_bytes
|
||||
def generate_patch(rom: bytes, metadata: Optional[dict] = None, game: int = GAME_ALTTP) -> bytes:
|
||||
if game == GAME_ALTTP:
|
||||
from worlds.alttp.Rom import get_base_rom_bytes
|
||||
elif game == GAME_SM:
|
||||
from worlds.sm.Rom import get_base_rom_bytes
|
||||
else:
|
||||
raise RuntimeError("Selected game for base rom not found.")
|
||||
|
||||
if metadata is None:
|
||||
metadata = {}
|
||||
patch = bsdiff4.diff(get_base_rom_bytes(), rom)
|
||||
return generate_yaml(patch, metadata)
|
||||
return generate_yaml(patch, metadata, game)
|
||||
|
||||
|
||||
def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str = None,
|
||||
player: int = 0, player_name: str = "") -> str:
|
||||
player: int = 0, player_name: str = "", game: int = GAME_ALTTP) -> str:
|
||||
meta = {"server": server, # allow immediate connection to server in multiworld. Empty string otherwise
|
||||
"player_id": player,
|
||||
"player_name": player_name}
|
||||
bytes = generate_patch(load_bytes(rom_file_to_patch),
|
||||
meta)
|
||||
target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + ".apbp"
|
||||
meta,
|
||||
game)
|
||||
target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + (".apbp" if game == GAME_ALTTP else ".apm3")
|
||||
write_lzma(bytes, target)
|
||||
return target
|
||||
|
||||
|
||||
def create_rom_bytes(patch_file: str, ignore_version: bool = False) -> Tuple[dict, str, bytearray]:
|
||||
from worlds.alttp.Rom import get_base_rom_bytes
|
||||
data = Utils.parse_yaml(lzma.decompress(load_bytes(patch_file)).decode("utf-8-sig"))
|
||||
game_name = data["game"]
|
||||
if game_name in supported_games:
|
||||
game_index = supported_games.index(game_name)
|
||||
if game_index == GAME_ALTTP:
|
||||
from worlds.alttp.Rom import get_base_rom_bytes
|
||||
elif game_index == GAME_SM:
|
||||
from worlds.sm.Rom import get_base_rom_bytes
|
||||
else:
|
||||
from worlds.alttp.Rom import get_base_rom_bytes
|
||||
|
||||
if not ignore_version and data["compatible_version"] > current_patch_version:
|
||||
raise RuntimeError("Patch file is incompatible with this patcher, likely an update is required.")
|
||||
patched_data = bsdiff4.patch(get_base_rom_bytes(), data["patch"])
|
||||
|
@ -68,7 +93,7 @@ def create_rom_file(patch_file: str) -> Tuple[dict, str]:
|
|||
def update_patch_data(patch_data: bytes, server: str = "") -> bytes:
|
||||
data = Utils.parse_yaml(lzma.decompress(patch_data).decode("utf-8-sig"))
|
||||
data["meta"]["server"] = server
|
||||
bytes = generate_yaml(data["patch"], data["meta"])
|
||||
bytes = generate_yaml(data["patch"], data["meta"], data["game"])
|
||||
return lzma.compress(bytes)
|
||||
|
||||
|
||||
|
@ -113,7 +138,13 @@ if __name__ == "__main__":
|
|||
if 'server' in data:
|
||||
Utils.persistent_store("servers", data['hash'], data['server'])
|
||||
print(f"Host is {data['server']}")
|
||||
|
||||
elif rom.endswith(".apm3"):
|
||||
print(f"Applying patch {rom}")
|
||||
data, target = create_rom_file(rom)
|
||||
print(f"Created rom {target}.")
|
||||
if 'server' in data:
|
||||
Utils.persistent_store("servers", data['hash'], data['server'])
|
||||
print(f"Host is {data['server']}")
|
||||
elif rom.endswith(".archipelago"):
|
||||
import json
|
||||
import zlib
|
||||
|
@ -139,7 +170,7 @@ if __name__ == "__main__":
|
|||
|
||||
def _handle_zip_file_entry(zfinfo: zipfile.ZipInfo, server: str):
|
||||
data = zfr.read(zfinfo)
|
||||
if zfinfo.filename.endswith(".apbp"):
|
||||
if zfinfo.filename.endswith(".apbp") or zfinfo.filename.endswith(".apm3"):
|
||||
data = update_patch_data(data, server)
|
||||
with ziplock:
|
||||
zfw.writestr(zfinfo, data)
|
||||
|
|
|
@ -14,7 +14,7 @@ from json import loads, dumps
|
|||
from Utils import get_item_name_from_id, init_logging
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_logging("LttPClient")
|
||||
init_logging("SNIClient")
|
||||
|
||||
import colorama
|
||||
|
||||
|
@ -24,6 +24,7 @@ from worlds.alttp import Items
|
|||
from worlds.alttp.Rom import ROM_PLAYER_LIMIT
|
||||
import Utils
|
||||
from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor, gui_enabled, get_base_parser
|
||||
from Patch import GAME_ALTTP, GAME_SM
|
||||
|
||||
|
||||
snes_logger = logging.getLogger("SNES")
|
||||
|
@ -121,6 +122,7 @@ class Context(CommonContext):
|
|||
self.awaiting_rom = False
|
||||
self.rom = None
|
||||
self.prev_rom = None
|
||||
self.gameID = None
|
||||
|
||||
async def connection_closed(self):
|
||||
await super(Context, self).connection_closed()
|
||||
|
@ -146,7 +148,7 @@ class Context(CommonContext):
|
|||
await self.send_msgs([{"cmd": 'Connect',
|
||||
'password': self.password, 'name': auth, 'version': Utils.version_tuple,
|
||||
'tags': self.tags,
|
||||
'uuid': Utils.get_unique_identifier(), 'game': "A Link to the Past"
|
||||
'uuid': Utils.get_unique_identifier(), 'game': "Super Metroid" if self.rom[:2] == b"SM" else "A Link to the Past"
|
||||
}])
|
||||
|
||||
def on_deathlink(self, data: dict):
|
||||
|
@ -159,12 +161,19 @@ async def deathlink_kill_player(ctx: Context):
|
|||
ctx.death_state = DeathState.killing_player
|
||||
while ctx.death_state == DeathState.killing_player and \
|
||||
ctx.snes_state == SNESState.SNES_ATTACHED:
|
||||
snes_buffered_write(ctx, WRAM_START + 0xF36D, bytes([0])) # set current health to 0
|
||||
snes_buffered_write(ctx, WRAM_START + 0x0373, bytes([8])) # deal 1 full heart of damage at next opportunity
|
||||
if ctx.gameID == GAME_ALTTP:
|
||||
snes_buffered_write(ctx, WRAM_START + 0xF36D, bytes([0])) # set current health to 0
|
||||
snes_buffered_write(ctx, WRAM_START + 0x0373, bytes([8])) # deal 1 full heart of damage at next opportunity
|
||||
elif ctx.gameID == GAME_SM:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x09C2, bytes([0, 0])) # set current health to 0
|
||||
await snes_flush_writes(ctx)
|
||||
await asyncio.sleep(1)
|
||||
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
|
||||
if not gamemode or gamemode[0] in DEATH_MODES:
|
||||
gamemode = None
|
||||
if ctx.gameID == GAME_ALTTP:
|
||||
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
|
||||
elif ctx.gameID == GAME_SM:
|
||||
gamemode = await snes_read(ctx, WRAM_START + 0x0998, 1)
|
||||
if not gamemode or gamemode[0] in (DEATH_MODES if ctx.gameID == GAME_ALTTP else SM_DEATH_MODES):
|
||||
ctx.death_state = DeathState.dead
|
||||
ctx.last_death_link = time.time()
|
||||
|
||||
|
@ -181,6 +190,7 @@ def color_item(item_id: int, green: bool = False) -> str:
|
|||
|
||||
SNES_RECONNECT_DELAY = 5
|
||||
|
||||
# LttP
|
||||
ROM_START = 0x000000
|
||||
WRAM_START = 0xF50000
|
||||
WRAM_SIZE = 0x20000
|
||||
|
@ -207,7 +217,20 @@ SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte
|
|||
SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte
|
||||
SHOP_ADDR = SAVEDATA_START + 0x302 # 2 bytes
|
||||
|
||||
DEATH_LINK_ACTIVE_ADDR = ROMNAME_START + 0x15 # 1 byte
|
||||
DEATH_LINK_ACTIVE_ADDR = ROMNAME_START + 0x15 # 1 byte
|
||||
|
||||
# SM
|
||||
SM_ROMNAME_START = 0x1C4F00
|
||||
|
||||
SM_INGAME_MODES = {0x07, 0x09, 0x0b}
|
||||
SM_ENDGAME_MODES = {0x26, 0x27}
|
||||
SM_DEATH_MODES = {0x15, 0x17, 0x18, 0x19, 0x1A}
|
||||
|
||||
SM_RECV_PROGRESS_ADDR = SRAM_START + 0x2000 # 2 bytes
|
||||
SM_RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
|
||||
SM_RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
|
||||
|
||||
SM_DEATH_LINK_ACTIVE_ADDR = ROM_START + 0x277f04 # 1 byte
|
||||
|
||||
location_shop_ids = set([info[0] for name, info in Shops.shop_table.items()])
|
||||
|
||||
|
@ -859,12 +882,20 @@ async def game_watcher(ctx: Context):
|
|||
|
||||
if not ctx.rom:
|
||||
ctx.finished_game = False
|
||||
rom = await snes_read(ctx, ROMNAME_START, ROMNAME_SIZE)
|
||||
gameName = await snes_read(ctx, SM_ROMNAME_START, 2)
|
||||
if gameName is None:
|
||||
continue
|
||||
elif gameName == b"SM":
|
||||
ctx.gameID = GAME_SM
|
||||
else:
|
||||
ctx.gameID = GAME_ALTTP
|
||||
|
||||
rom = await snes_read(ctx, SM_ROMNAME_START if ctx.gameID == GAME_SM else ROMNAME_START, ROMNAME_SIZE)
|
||||
if rom is None or rom == bytes([0] * ROMNAME_SIZE):
|
||||
continue
|
||||
|
||||
ctx.rom = rom
|
||||
death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR, 1)
|
||||
death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR if ctx.gameID == GAME_ALTTP else SM_DEATH_LINK_ACTIVE_ADDR, 1)
|
||||
if death_link:
|
||||
death_link = bool(death_link[0] & 0b1)
|
||||
old_tags = ctx.tags.copy()
|
||||
|
@ -886,87 +917,156 @@ async def game_watcher(ctx: Context):
|
|||
snes_logger.warning("ROM change detected, please reconnect to the multiworld server")
|
||||
await ctx.disconnect()
|
||||
|
||||
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
|
||||
if "DeathLink" in ctx.tags and gamemode and ctx.last_death_link + 1 < time.time():
|
||||
currently_dead = gamemode[0] in DEATH_MODES
|
||||
# in this state we only care about triggering a death send
|
||||
if ctx.death_state == DeathState.alive:
|
||||
if currently_dead:
|
||||
ctx.death_state = DeathState.dead
|
||||
await ctx.send_death()
|
||||
# in this state we care about confirming a kill, to move state to dead
|
||||
elif ctx.death_state == DeathState.killing_player:
|
||||
# this is being handled in deathlink_kill_player(ctx) already
|
||||
pass
|
||||
# in this state we wait until the player is alive again
|
||||
elif ctx.death_state == DeathState.dead:
|
||||
if not currently_dead:
|
||||
ctx.death_state = DeathState.alive
|
||||
if ctx.gameID == GAME_ALTTP:
|
||||
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
|
||||
if "DeathLink" in ctx.tags and gamemode and ctx.last_death_link + 1 < time.time():
|
||||
currently_dead = gamemode[0] in DEATH_MODES
|
||||
# in this state we only care about triggering a death send
|
||||
if ctx.death_state == DeathState.alive:
|
||||
if currently_dead:
|
||||
ctx.death_state = DeathState.dead
|
||||
await ctx.send_death()
|
||||
# in this state we care about confirming a kill, to move state to dead
|
||||
elif ctx.death_state == DeathState.killing_player:
|
||||
# this is being handled in deathlink_kill_player(ctx) already
|
||||
pass
|
||||
# in this state we wait until the player is alive again
|
||||
elif ctx.death_state == DeathState.dead:
|
||||
if not currently_dead:
|
||||
ctx.death_state = DeathState.alive
|
||||
|
||||
gameend = await snes_read(ctx, SAVEDATA_START + 0x443, 1)
|
||||
game_timer = await snes_read(ctx, SAVEDATA_START + 0x42E, 4)
|
||||
if gamemode is None or gameend is None or game_timer is None or \
|
||||
(gamemode[0] not in INGAME_MODES and gamemode[0] not in ENDGAME_MODES):
|
||||
continue
|
||||
|
||||
delay = 7 if ctx.slow_mode else 2
|
||||
if gameend[0]:
|
||||
if not ctx.finished_game:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
ctx.finished_game = True
|
||||
|
||||
if time.perf_counter() - perf_counter < delay:
|
||||
gameend = await snes_read(ctx, SAVEDATA_START + 0x443, 1)
|
||||
game_timer = await snes_read(ctx, SAVEDATA_START + 0x42E, 4)
|
||||
if gamemode is None or gameend is None or game_timer is None or \
|
||||
(gamemode[0] not in INGAME_MODES and gamemode[0] not in ENDGAME_MODES):
|
||||
continue
|
||||
|
||||
delay = 7 if ctx.slow_mode else 2
|
||||
if gameend[0]:
|
||||
if not ctx.finished_game:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
ctx.finished_game = True
|
||||
|
||||
if time.perf_counter() - perf_counter < delay:
|
||||
continue
|
||||
else:
|
||||
perf_counter = time.perf_counter()
|
||||
else:
|
||||
perf_counter = time.perf_counter()
|
||||
else:
|
||||
game_timer = game_timer[0] | (game_timer[1] << 8) | (game_timer[2] << 16) | (game_timer[3] << 24)
|
||||
if abs(game_timer - prev_game_timer) < (delay * 60):
|
||||
game_timer = game_timer[0] | (game_timer[1] << 8) | (game_timer[2] << 16) | (game_timer[3] << 24)
|
||||
if abs(game_timer - prev_game_timer) < (delay * 60):
|
||||
continue
|
||||
else:
|
||||
prev_game_timer = game_timer
|
||||
|
||||
if gamemode in ENDGAME_MODES: # triforce room and credits
|
||||
continue
|
||||
else:
|
||||
prev_game_timer = game_timer
|
||||
|
||||
if gamemode in ENDGAME_MODES: # triforce room and credits
|
||||
continue
|
||||
data = await snes_read(ctx, RECV_PROGRESS_ADDR, 8)
|
||||
if data is None:
|
||||
continue
|
||||
|
||||
data = await snes_read(ctx, RECV_PROGRESS_ADDR, 8)
|
||||
if data is None:
|
||||
continue
|
||||
recv_index = data[0] | (data[1] << 8)
|
||||
recv_item = data[2]
|
||||
roomid = data[4] | (data[5] << 8)
|
||||
roomdata = data[6]
|
||||
scout_location = data[7]
|
||||
|
||||
recv_index = data[0] | (data[1] << 8)
|
||||
recv_item = data[2]
|
||||
roomid = data[4] | (data[5] << 8)
|
||||
roomdata = data[6]
|
||||
scout_location = data[7]
|
||||
if recv_index < len(ctx.items_received) and recv_item == 0:
|
||||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_name_getter(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_name_getter(item.location), recv_index, len(ctx.items_received)))
|
||||
|
||||
if recv_index < len(ctx.items_received) and recv_item == 0:
|
||||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_name_getter(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_name_getter(item.location), recv_index, len(ctx.items_received)))
|
||||
snes_buffered_write(ctx, RECV_PROGRESS_ADDR,
|
||||
bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||
snes_buffered_write(ctx, RECV_ITEM_ADDR,
|
||||
bytes([item.item]))
|
||||
snes_buffered_write(ctx, RECV_ITEM_PLAYER_ADDR,
|
||||
bytes([min(ROM_PLAYER_LIMIT, item.player) if item.player != ctx.slot else 0]))
|
||||
if scout_location > 0 and scout_location in ctx.locations_info:
|
||||
snes_buffered_write(ctx, SCOUTREPLY_LOCATION_ADDR,
|
||||
bytes([scout_location]))
|
||||
snes_buffered_write(ctx, SCOUTREPLY_ITEM_ADDR,
|
||||
bytes([ctx.locations_info[scout_location][0]]))
|
||||
snes_buffered_write(ctx, SCOUTREPLY_PLAYER_ADDR,
|
||||
bytes([min(ROM_PLAYER_LIMIT, ctx.locations_info[scout_location][1])]))
|
||||
|
||||
snes_buffered_write(ctx, RECV_PROGRESS_ADDR,
|
||||
bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||
snes_buffered_write(ctx, RECV_ITEM_ADDR,
|
||||
bytes([item.item]))
|
||||
snes_buffered_write(ctx, RECV_ITEM_PLAYER_ADDR,
|
||||
bytes([min(ROM_PLAYER_LIMIT, item.player) if item.player != ctx.slot else 0]))
|
||||
if scout_location > 0 and scout_location in ctx.locations_info:
|
||||
snes_buffered_write(ctx, SCOUTREPLY_LOCATION_ADDR,
|
||||
bytes([scout_location]))
|
||||
snes_buffered_write(ctx, SCOUTREPLY_ITEM_ADDR,
|
||||
bytes([ctx.locations_info[scout_location][0]]))
|
||||
snes_buffered_write(ctx, SCOUTREPLY_PLAYER_ADDR,
|
||||
bytes([min(ROM_PLAYER_LIMIT, ctx.locations_info[scout_location][1])]))
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
await snes_flush_writes(ctx)
|
||||
if scout_location > 0 and scout_location not in ctx.locations_scouted:
|
||||
ctx.locations_scouted.add(scout_location)
|
||||
await ctx.send_msgs([{"cmd": "LocationScouts", "locations": [scout_location]}])
|
||||
await track_locations(ctx, roomid, roomdata)
|
||||
elif ctx.gameID == GAME_SM:
|
||||
gamemode = await snes_read(ctx, WRAM_START + 0x0998, 1)
|
||||
if "DeathLink" in ctx.tags and gamemode and ctx.last_death_link + 1 < time.time():
|
||||
currently_dead = gamemode[0] in SM_DEATH_MODES
|
||||
# in this state we only care about triggering a death send
|
||||
if ctx.death_state == DeathState.alive:
|
||||
if currently_dead:
|
||||
ctx.death_state = DeathState.dead
|
||||
await ctx.send_death()
|
||||
# in this state we care about confirming a kill, to move state to dead
|
||||
elif ctx.death_state == DeathState.killing_player:
|
||||
# this is being handled in deathlink_kill_player(ctx) already
|
||||
pass
|
||||
# in this state we wait until the player is alive again
|
||||
elif ctx.death_state == DeathState.dead:
|
||||
if not currently_dead:
|
||||
ctx.death_state = DeathState.alive
|
||||
if gamemode is not None and gamemode[0] in SM_ENDGAME_MODES:
|
||||
if not ctx.finished_game:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
ctx.finished_game = True
|
||||
continue
|
||||
|
||||
if scout_location > 0 and scout_location not in ctx.locations_scouted:
|
||||
ctx.locations_scouted.add(scout_location)
|
||||
await ctx.send_msgs([{"cmd": "LocationScouts", "locations": [scout_location]}])
|
||||
await track_locations(ctx, roomid, roomdata)
|
||||
data = await snes_read(ctx, SM_RECV_PROGRESS_ADDR + 0x680, 4)
|
||||
if data is None:
|
||||
continue
|
||||
|
||||
recv_index = data[0] | (data[1] << 8)
|
||||
recv_item = data[2] | (data[3] << 8)
|
||||
|
||||
while (recv_index < recv_item):
|
||||
itemAdress = recv_index * 8
|
||||
message = await snes_read(ctx, SM_RECV_PROGRESS_ADDR + 0x700 + itemAdress, 8)
|
||||
worldId = message[0] | (message[1] << 8)
|
||||
itemId = message[2] | (message[3] << 8)
|
||||
itemIndex = (message[4] | (message[5] << 8)) >> 3
|
||||
seq = recv_index
|
||||
|
||||
recv_index += 1
|
||||
snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + 0x680, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||
|
||||
from worlds.sm.Locations import locations_start_id
|
||||
location_id = locations_start_id + itemIndex
|
||||
|
||||
ctx.locations_checked.add(location_id)
|
||||
location = ctx.location_name_getter(location_id)
|
||||
snes_logger.info(f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [location_id]}])
|
||||
|
||||
data = await snes_read(ctx, SM_RECV_PROGRESS_ADDR + 0x600, 4)
|
||||
if data is None:
|
||||
continue
|
||||
|
||||
recv_itemOutPtr = data[0] | (data[1] << 8)
|
||||
itemOutPtr = data[2] | (data[3] << 8)
|
||||
|
||||
from worlds.sm.Items import items_start_id
|
||||
if itemOutPtr < len(ctx.items_received):
|
||||
item = ctx.items_received[itemOutPtr]
|
||||
itemId = item.item - items_start_id
|
||||
|
||||
playerID = (item.player-1) if item.player != 0 else (len(ctx.player_names)-1)
|
||||
snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + itemOutPtr * 4, bytes([playerID & 0xFF, (playerID >> 8) & 0xFF, itemId & 0xFF, (itemId >> 8) & 0xFF]))
|
||||
itemOutPtr += 1
|
||||
snes_buffered_write(ctx, SM_RECV_PROGRESS_ADDR + 0x602, bytes([itemOutPtr & 0xFF, (itemOutPtr >> 8) & 0xFF]))
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_name_getter(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_name_getter(item.location), itemOutPtr, len(ctx.items_received)))
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
async def run_game(romfile):
|
||||
auto_start = Utils.get_options()["lttp_options"].get("rom_start", True)
|
||||
|
@ -1008,8 +1108,8 @@ async def main():
|
|||
|
||||
if gui_enabled:
|
||||
input_task = None
|
||||
from kvui import LttPManager
|
||||
ctx.ui = LttPManager(ctx)
|
||||
from kvui import SNIManager
|
||||
ctx.ui = SNIManager(ctx)
|
||||
ui_task = asyncio.create_task(ctx.ui.async_run(), name="UI")
|
||||
else:
|
||||
input_task = asyncio.create_task(console_loop(ctx), name="Input")
|
5
Utils.py
5
Utils.py
|
@ -161,6 +161,11 @@ def get_default_options() -> dict:
|
|||
"factorio_options": {
|
||||
"executable": "factorio\\bin\\x64\\factorio",
|
||||
},
|
||||
"sm_options": {
|
||||
"rom_file": "Super Metroid (JU)[!].sfc",
|
||||
"sni": "SNI",
|
||||
"rom_start": True,
|
||||
},
|
||||
"lttp_options": {
|
||||
"rom_file": "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc",
|
||||
"sni": "SNI",
|
||||
|
|
12
host.yaml
12
host.yaml
|
@ -91,6 +91,15 @@ lttp_options:
|
|||
# True for operating system default program
|
||||
# Alternatively, a path to a program to open the .sfc file with
|
||||
rom_start: true
|
||||
sm_options:
|
||||
# File name of the v1.0 J rom
|
||||
rom_file: "Super Metroid (JU)[!].sfc"
|
||||
# Set this to your SNI folder location if you want the MultiClient to attempt an auto start, does nothing if not found
|
||||
sni: "SNI"
|
||||
# Set this to false to never autostart a rom (such as after patching)
|
||||
# True for operating system default program
|
||||
# Alternatively, a path to a program to open the .sfc file with
|
||||
rom_start: true
|
||||
factorio_options:
|
||||
executable: "factorio\\bin\\x64\\factorio"
|
||||
minecraft_options:
|
||||
|
@ -98,4 +107,5 @@ minecraft_options:
|
|||
max_heap_size: "2G"
|
||||
oot_options:
|
||||
# File name of the OoT v1.0 ROM
|
||||
rom_file: "The Legend of Zelda - Ocarina of Time.z64"
|
||||
rom_file: "The Legend of Zelda - Ocarina of Time.z64"
|
||||
rom_file: "The Legend of Zelda - Ocarina of Time.z64"
|
||||
|
|
4
kvui.py
4
kvui.py
|
@ -287,12 +287,12 @@ class FactorioManager(GameManager):
|
|||
base_title = "Archipelago Factorio Client"
|
||||
|
||||
|
||||
class LttPManager(GameManager):
|
||||
class SNIManager(GameManager):
|
||||
logging_pairs = [
|
||||
("Client", "Archipelago"),
|
||||
("SNES", "SNES"),
|
||||
]
|
||||
base_title = "Archipelago LttP Client"
|
||||
base_title = "Archipelago SNI Client"
|
||||
|
||||
|
||||
class TextManager(GameManager):
|
||||
|
|
|
@ -30,6 +30,8 @@ game: # Pick a game to play
|
|||
Subnautica: 0
|
||||
Slay the Spire: 0
|
||||
Ocarina of Time: 0
|
||||
Super Metroid: 0
|
||||
|
||||
requires:
|
||||
version: 0.1.7 # Version of Archipelago required for this yaml to work as expected.
|
||||
# Shared Options supported by all games:
|
||||
|
@ -57,6 +59,270 @@ progression_balancing:
|
|||
# exclude_locations: # Force certain locations to never contain progression items, and always be filled with junk.
|
||||
# - "Master Sword Pedestal"
|
||||
|
||||
Super Metroid: # see https://randommetroidsolver.pythonanywhere.com/randomizer advanced tab for detailed info on each option
|
||||
# start_inventory: # Begin the file with the listed items/upgrades
|
||||
# Screw Attack: 1
|
||||
# Bomb: 1
|
||||
# Speed Booster: 1
|
||||
# Grappling Beam: 1
|
||||
# Space Jump: 1
|
||||
# Hi-Jump Boots: 1
|
||||
# Spring Ball: 1
|
||||
# Charge Beam: 1
|
||||
# Ice Beam: 1
|
||||
# Spazer: 1
|
||||
# Reserve Tank: 4
|
||||
# Missile: 46
|
||||
# Super Missile: 20
|
||||
# Power Bomb: 20
|
||||
# Energy Tank: 14
|
||||
# Morph Ball: 1
|
||||
# X-Ray Scope: 1
|
||||
# Wave Beam: 1
|
||||
# Plasma Beam: 1
|
||||
# Varia Suit: 1
|
||||
# Gravity Suit: 1
|
||||
start_inventory_removes_from_pool:
|
||||
on: 0
|
||||
off: 1
|
||||
death_link:
|
||||
on: 0
|
||||
off: 1
|
||||
preset: # choose one of the preset or specify "custom" to use customPreset option
|
||||
newbie: 0
|
||||
casual: 0
|
||||
regular: 1
|
||||
veteran: 0
|
||||
expert: 0
|
||||
master: 0
|
||||
samus: 0
|
||||
Season_Races: 0
|
||||
SMRAT2021: 0
|
||||
solution: 0
|
||||
custom: 0 # see https://randommetroidsolver.pythonanywhere.com/presets for detailed info on each preset settings
|
||||
varia_custom: 0 # use an entry from the preset list on https://randommetroidsolver.pythonanywhere.com/presets
|
||||
varia_custom_preset: # use an entry from the preset list on https://randommetroidsolver.pythonanywhere.com/presets
|
||||
regular
|
||||
start_location:
|
||||
Ceres: 0
|
||||
Landing_Site: 1
|
||||
Gauntlet_Top: 0
|
||||
Green_Brinstar_Elevator: 0
|
||||
Big_Pink: 0
|
||||
Etecoons_Supers: 0
|
||||
Wrecked_Ship_Main: 0
|
||||
Firefleas_Top: 0
|
||||
Business_Center: 0
|
||||
Bubble_Mountain: 0
|
||||
Mama_Turtle: 0
|
||||
Watering_Hole: 0
|
||||
Aqueduct: 0
|
||||
Red_Brinstar_Elevator: 0
|
||||
Golden_Four: 0
|
||||
max_difficulty:
|
||||
easy: 0
|
||||
medium: 0
|
||||
hard: 0
|
||||
harder: 0
|
||||
hardcore: 1
|
||||
mania: 0
|
||||
infinity: 0
|
||||
morph_placement:
|
||||
early: 1
|
||||
normal: 0
|
||||
suits_restriction:
|
||||
on: 1
|
||||
off: 0
|
||||
strict_minors:
|
||||
on: 0
|
||||
off: 1
|
||||
missile_qty: 30 # a range between 10 and 90 that is divided by 10 as a float
|
||||
super_qty: 20 # a range between 10 and 90 that is divided by 10 as a float
|
||||
power_bomb_qty: 10 # a range between 10 and 90 that is divided by 10 as a float
|
||||
minor_qty: 100 # a range between 7 (minimum to beat the game) and 100
|
||||
energy_qty:
|
||||
ultra_sparse: 0
|
||||
sparse: 0
|
||||
medium: 0
|
||||
vanilla: 1
|
||||
area_randomization:
|
||||
on: 0
|
||||
light: 0
|
||||
off: 1
|
||||
area_layout:
|
||||
on: 0
|
||||
off: 1
|
||||
doors_colors_rando:
|
||||
on: 0
|
||||
off: 1
|
||||
allow_grey_doors:
|
||||
on: 0
|
||||
off: 1
|
||||
boss_randomization:
|
||||
on: 0
|
||||
off: 1
|
||||
fun_combat:
|
||||
on: 0
|
||||
off: 1
|
||||
fun_movement:
|
||||
on: 0
|
||||
off: 1
|
||||
fun_suits:
|
||||
on: 0
|
||||
off: 1
|
||||
layout_patches:
|
||||
on: 1
|
||||
off: 0
|
||||
varia_tweaks:
|
||||
on: 0
|
||||
off: 1
|
||||
nerfed_charge:
|
||||
on: 0
|
||||
off: 1
|
||||
gravity_behaviour:
|
||||
Vanilla: 0
|
||||
Balanced: 1
|
||||
Progressive: 0
|
||||
elevators_doors_speed:
|
||||
on: 1
|
||||
off: 0
|
||||
spin_jump_restart:
|
||||
on: 0
|
||||
off: 1
|
||||
infinite_space_jump:
|
||||
on: 0
|
||||
off: 1
|
||||
refill_before_save:
|
||||
on: 0
|
||||
off: 1
|
||||
hud:
|
||||
on: 0
|
||||
off: 1
|
||||
animals:
|
||||
on: 0
|
||||
off: 1
|
||||
no_music:
|
||||
on: 0
|
||||
off: 1
|
||||
random_music:
|
||||
on: 0
|
||||
off: 1
|
||||
#item_sounds: always forced on due to a conflict in patching
|
||||
#majors_split: not supported always "Full"
|
||||
#scav_num_locs: not supported always off
|
||||
#scav_randomized: not supported always off
|
||||
#scav_escape: not supported always off
|
||||
#progression_speed: not supported always random
|
||||
#progression_difficulty: not supported always random
|
||||
#hide_items: not supported always off
|
||||
#minimizer: not supported always off
|
||||
#minimizer_qty: not supported always off
|
||||
#minimizer_tourian: not supported always off
|
||||
#escape_rando: not supported always off
|
||||
#remove_escape_enemies: not supported always off
|
||||
#rando_speed: not supported always off
|
||||
custom_preset: # 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 the following values]
|
||||
# easy = 1
|
||||
# medium = 5
|
||||
# hard = 10
|
||||
# harder = 25
|
||||
# hardcore = 50
|
||||
# mania = 100
|
||||
Mockball: [True, 1]
|
||||
SimpleShortCharge: [True, 1]
|
||||
InfiniteBombJump: [True, 5]
|
||||
GreenGateGlitch: [True, 5]
|
||||
ShortCharge: [False, 0]
|
||||
GravityJump: [True, 10]
|
||||
SpringBallJump: [True, 10]
|
||||
SpringBallJumpFromWall: [False, 0]
|
||||
GetAroundWallJump: [True, 10]
|
||||
DraygonGrappleKill: [True, 5]
|
||||
DraygonSparkKill: [False, 0]
|
||||
MicrowaveDraygon: [True, 1]
|
||||
MicrowavePhantoon: [True, 5]
|
||||
IceZebSkip: [False, 0]
|
||||
SpeedZebSkip: [False, 0]
|
||||
HiJumpMamaTurtle: [False, 0]
|
||||
GravLessLevel1: [True, 50]
|
||||
GravLessLevel2: [False, 0]
|
||||
GravLessLevel3: [False, 0]
|
||||
CeilingDBoost: [True, 1]
|
||||
BillyMays: [True, 1]
|
||||
AlcatrazEscape: [True, 25]
|
||||
ReverseGateGlitch: [True, 5]
|
||||
ReverseGateGlitchHiJumpLess: [False, 0]
|
||||
EarlyKraid: [True, 1]
|
||||
XrayDboost: [False, 0]
|
||||
XrayIce: [True, 10]
|
||||
RedTowerClimb: [True, 25]
|
||||
RonPopeilScrew: [False, 0]
|
||||
OldMBWithSpeed: [False, 0]
|
||||
Moondance: [False, 0]
|
||||
HiJumpLessGauntletAccess: [True, 50]
|
||||
HiJumpGauntletAccess: [True, 25]
|
||||
LowGauntlet: [False, 0]
|
||||
IceEscape: [False, 0]
|
||||
WallJumpCathedralExit: [True, 5]
|
||||
BubbleMountainWallJump: [True, 5]
|
||||
NovaBoost: [False, 0]
|
||||
NorfairReserveDBoost: [False, 0]
|
||||
CrocPBsDBoost: [False, 0]
|
||||
CrocPBsIce: [False, 0]
|
||||
IceMissileFromCroc: [False, 0]
|
||||
FrogSpeedwayWithoutSpeed: [False, 0]
|
||||
LavaDive: [True, 50]
|
||||
LavaDiveNoHiJump: [False, 0]
|
||||
WorstRoomIceCharge: [False, 0]
|
||||
ScrewAttackExit: [False, 0]
|
||||
ScrewAttackExitWithoutScrew: [False, 0]
|
||||
FirefleasWalljump: [True, 25]
|
||||
ContinuousWallJump: [False, 0]
|
||||
DiagonalBombJump: [False, 0]
|
||||
MockballWs: [False, 0]
|
||||
SpongeBathBombJump: [False, 0]
|
||||
SpongeBathHiJump: [True, 1]
|
||||
SpongeBathSpeed: [True, 5]
|
||||
TediousMountEverest: [False, 0]
|
||||
DoubleSpringBallJump: [False, 0]
|
||||
BotwoonToDraygonWithIce: [False, 0]
|
||||
DraygonRoomGrappleExit: [False, 0]
|
||||
DraygonRoomCrystalFlash: [False, 0]
|
||||
PreciousRoomXRayExit: [False, 0]
|
||||
MochtroidClip: [True, 5]
|
||||
PuyoClip: [False, 0]
|
||||
PuyoClipXRay: [False, 0]
|
||||
SnailClip: [False, 0]
|
||||
SuitlessPuyoClip: [False, 0]
|
||||
KillPlasmaPiratesWithSpark: [False, 0]
|
||||
KillPlasmaPiratesWithCharge: [True, 5]
|
||||
AccessSpringBallWithHiJump: [True, 1]
|
||||
AccessSpringBallWithSpringBallBombJumps: [True, 10]
|
||||
AccessSpringBallWithBombJumps: [False, 0]
|
||||
AccessSpringBallWithSpringBallJump: [False, 0]
|
||||
AccessSpringBallWithXRayClimb: [False, 0]
|
||||
AccessSpringBallWithGravJump: [False, 0]
|
||||
Controller:
|
||||
A: Jump
|
||||
B: Dash
|
||||
X: Shoot
|
||||
Y: Item Cancel
|
||||
L: Angle Down
|
||||
R: Angle Up
|
||||
Select: Item Select
|
||||
Moonwalk: False
|
||||
Settings:
|
||||
Ice: "Gimme energy"
|
||||
MainUpperNorfair: "Gimme energy"
|
||||
LowerNorfair: "Default"
|
||||
Kraid: "Default"
|
||||
Phantoon: "Default"
|
||||
Draygon: "Default"
|
||||
Ridley: "Default"
|
||||
MotherBrain: "Default"
|
||||
X-Ray: "I don't like spikes"
|
||||
Gauntlet: "I don't like acid"
|
||||
Subnautica: {}
|
||||
Slay the Spire:
|
||||
character: # Pick What Character you wish to play with.
|
||||
|
@ -1415,4 +1681,4 @@ triggers:
|
|||
percentage: 0 # AND has a 0 percent chance (meaning this is default disabled, just to show how it works)
|
||||
options: # then inserts these options
|
||||
A Link to the Past:
|
||||
swordless: off
|
||||
swordless: off
|
||||
|
|
4
setup.py
4
setup.py
|
@ -73,8 +73,8 @@ scripts = {
|
|||
"MultiServer.py": ("ArchipelagoServer", False, icon),
|
||||
"Generate.py": ("ArchipelagoGenerate", False, icon),
|
||||
"CommonClient.py": ("ArchipelagoTextClient", True, icon),
|
||||
# LttP
|
||||
"LttPClient.py": ("ArchipelagoLttPClient", True, icon),
|
||||
# SNI
|
||||
"SNIClient.py": ("ArchipelagoSNIClient", True, icon),
|
||||
"LttPAdjuster.py": ("ArchipelagoLttPAdjuster", True, icon),
|
||||
# Factorio
|
||||
"FactorioClient.py": ("ArchipelagoFactorioClient", True, icon),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()}
|
|
@ -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()}
|
|
@ -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
|
||||
}
|
|
@ -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'])
|
||||
]
|
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1,514 @@
|
|||
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
|
||||
|
||||
|
||||
from Utils import output_path
|
||||
from shutil import copy2
|
||||
|
||||
class SMWorld(World):
|
||||
game: str = "Super Metroid"
|
||||
topology_present = True
|
||||
data_version = 0
|
||||
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
|
Binary file not shown.
Binary file not shown.
|
@ -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>.
|
|
@ -0,0 +1,3 @@
|
|||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__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)
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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'),
|
||||
]
|
|
@ -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)))
|
|
@ -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,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()
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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]
|
||||
]
|
||||
},
|
||||
}
|
|
@ -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 ("]");
|
||||
}
|
||||
}
|
|
@ -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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue