Add SMZ3 support (#270)

This commit is contained in:
lordlou 2022-03-15 08:55:57 -04:00 committed by GitHub
parent 8921baecd0
commit cfa49ee757
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 7047 additions and 16 deletions

View File

@ -18,12 +18,14 @@ current_patch_version = 3
GAME_ALTTP = "A Link to the Past"
GAME_SM = "Super Metroid"
GAME_SOE = "Secret of Evermore"
supported_games = {"A Link to the Past", "Super Metroid", "Secret of Evermore"}
GAME_SMZ3 = "SMZ3"
supported_games = {"A Link to the Past", "Super Metroid", "Secret of Evermore", "SMZ3"}
preferred_endings = {
GAME_ALTTP: "apbp",
GAME_SM: "apm3",
GAME_SOE: "apsoe"
GAME_SOE: "apsoe",
GAME_SMZ3: "apsmz"
}
@ -34,6 +36,10 @@ def generate_yaml(patch: bytes, metadata: Optional[dict] = None, game: str = GAM
from worlds.sm.Rom import JAP10HASH as HASH
elif game == GAME_SOE:
from worlds.soe.Patch import USHASH as HASH
elif game == GAME_SMZ3:
from worlds.alttp.Rom import JAP10HASH as ALTTPHASH
from worlds.sm.Rom import JAP10HASH as SMHASH
HASH = ALTTPHASH + SMHASH
else:
raise RuntimeError(f"Selected game {game} for base rom not found.")
@ -63,7 +69,7 @@ def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str
meta,
game)
target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + (
".apbp" if game == GAME_ALTTP else ".apm3")
".apbp" if game == GAME_ALTTP else ".apsmz" if game == GAME_SMZ3 else ".apm3")
write_lzma(bytes, target)
return target
@ -90,6 +96,8 @@ def get_base_rom_data(game: str):
elif game == GAME_SOE:
file_name = Utils.get_options()["soe_options"]["rom_file"]
get_base_rom_bytes = lambda: bytes(read_rom(open(file_name, "rb")))
elif game == GAME_SMZ3:
from worlds.smz3.Rom import get_base_rom_bytes
else:
raise RuntimeError("Selected game for base rom not found.")
return get_base_rom_bytes()
@ -218,6 +226,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(".apsmz"):
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

View File

@ -23,9 +23,10 @@ from NetUtils import *
from worlds.alttp import Regions, Shops
from worlds.alttp.Rom import ROM_PLAYER_LIMIT
from worlds.sm.Rom import ROM_PLAYER_LIMIT as SM_ROM_PLAYER_LIMIT
from worlds.smz3.Rom import ROM_PLAYER_LIMIT as SMZ3_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
from Patch import GAME_ALTTP, GAME_SM, GAME_SMZ3
snes_logger = logging.getLogger("SNES")
@ -266,6 +267,18 @@ SM_RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
SM_DEATH_LINK_ACTIVE_ADDR = ROM_START + 0x277f04 # 1 byte
# SMZ3
SMZ3_ROMNAME_START = 0x00FFC0
SMZ3_INGAME_MODES = {0x07, 0x09, 0x0b}
SMZ3_ENDGAME_MODES = {0x26, 0x27}
SMZ3_DEATH_MODES = {0x15, 0x17, 0x18, 0x19, 0x1A}
SMZ3_RECV_PROGRESS_ADDR = SRAM_START + 0x4000 # 2 bytes
SMZ3_RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
SMZ3_RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
location_shop_ids = set([info[0] for name, info in Shops.shop_table.items()])
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
@ -968,23 +981,29 @@ async def game_watcher(ctx: Context):
game_name = await snes_read(ctx, SM_ROMNAME_START, 2)
if game_name is None:
continue
elif game_name == b"SM":
elif game_name[:2] == b"SM":
ctx.game = GAME_SM
ctx.items_handling = 0b001 # full local
else:
ctx.game = GAME_ALTTP
ctx.items_handling = 0b001 # full local
game_name = await snes_read(ctx, SMZ3_ROMNAME_START, 3)
if game_name == b"ZSM":
ctx.game = GAME_SMZ3
ctx.items_handling = 0b101 # local items and remote start inventory
else:
ctx.game = GAME_ALTTP
ctx.items_handling = 0b001 # full local
rom = await snes_read(ctx, SM_ROMNAME_START if ctx.game == GAME_SM else ROMNAME_START, ROMNAME_SIZE)
rom = await snes_read(ctx, SM_ROMNAME_START if ctx.game == GAME_SM else SMZ3_ROMNAME_START if ctx.game == GAME_SMZ3 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 if ctx.game == GAME_ALTTP else
SM_DEATH_LINK_ACTIVE_ADDR, 1)
if death_link:
ctx.death_link_allow_survive = bool(death_link[0] & 0b10)
await ctx.update_death_link(bool(death_link[0] & 0b1))
if ctx.game != GAME_SMZ3:
death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR if ctx.game == GAME_ALTTP else
SM_DEATH_LINK_ACTIVE_ADDR, 1)
if death_link:
ctx.death_link_allow_survive = bool(death_link[0] & 0b10)
await ctx.update_death_link(bool(death_link[0] & 0b1))
if not ctx.prev_rom or ctx.prev_rom != ctx.rom:
ctx.locations_checked = set()
ctx.locations_scouted = set()
@ -1129,6 +1148,69 @@ async def game_watcher(ctx: Context):
color(ctx.player_names[item.player], 'yellow'),
ctx.location_name_getter(item.location), itemOutPtr, len(ctx.items_received)))
await snes_flush_writes(ctx)
elif ctx.game == GAME_SMZ3:
currentGame = await snes_read(ctx, SRAM_START + 0x33FE, 2)
if (currentGame is not None):
if (currentGame[0] != 0):
gamemode = await snes_read(ctx, WRAM_START + 0x0998, 1)
endGameModes = SM_ENDGAME_MODES
else:
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
endGameModes = ENDGAME_MODES
if gamemode is not None and (gamemode[0] in endGameModes):
if not ctx.finished_game:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
continue
data = await snes_read(ctx, SMZ3_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, SMZ3_RECV_PROGRESS_ADDR + 0x700 + itemAdress, 8)
# worldId = message[0] | (message[1] << 8) # unused
# itemId = message[2] | (message[3] << 8) # unused
isZ3Item = ((message[5] & 0x80) != 0)
maskedPart = (message[5] & 0x7F) if isZ3Item else message[5]
itemIndex = ((message[4] | (maskedPart << 8)) >> 3) + (256 if isZ3Item else 0)
recv_index += 1
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + 0x680, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
from worlds.smz3.TotalSMZ3.Location 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, SMZ3_RECV_PROGRESS_ADDR + 0x600, 4)
if data is None:
continue
# recv_itemOutPtr = data[0] | (data[1] << 8) # unused
itemOutPtr = data[2] | (data[3] << 8)
from worlds.smz3.TotalSMZ3.Item import items_start_id
if itemOutPtr < len(ctx.items_received):
item = ctx.items_received[itemOutPtr]
itemId = item.item - items_start_id
playerID = item.player if item.player <= SMZ3_ROM_PLAYER_LIMIT else 0
snes_buffered_write(ctx, SMZ3_RECV_PROGRESS_ADDR + itemOutPtr * 4, bytes([playerID & 0xFF, (playerID >> 8) & 0xFF, itemId & 0xFF, (itemId >> 8) & 0xFF]))
itemOutPtr += 1
snes_buffered_write(ctx, SMZ3_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):

View File

@ -0,0 +1,35 @@
# SMZ3
## Where is the settings page?
The [player settings page for this game](../player-settings) contains all the options you need to configure and export a
config file.
## What does randomization do to this game?
Items which the player would normally acquire throughout the game have been moved around. Logic remains, so the game is
always able to be completed, but because of the item shuffle the player may need to access certain areas before they
would in the vanilla game.
## What items and locations get shuffled?
All main inventory items, collectables, power-ups and ammunition can be shuffled, and all locations in the game which
could contain any of those items may have their contents changed.
## Which items can be in another player's world?
Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to limit
certain items to your own world.
## What does another world's item look like in Super Metroid?
A unique item sprite has been added to the game to represent items belonging to another world.
## What does another world's item look like in LttP?
Items belonging to other worlds are represented by a Power Star from Super Mario World.
## When the player receives an item, what happens?
When the player receives an item, a text box will appear to show which item was received, and from whom.

View File

@ -0,0 +1,146 @@
# Super Metroid Setup Guide
## Required Software
- One of the client programs:
- [SNIClient](https://github.com/ArchipelagoMW/Archipelago/releases), included with the main
Archipelago install. Make sure to check the box for `SNI Client - Super Metroid Patch Setup` and
`SNI Client - A Link to the Past Patch Setup`
- Hardware or software capable of loading and playing SNES ROM files
- An emulator capable of connecting to SNI such as:
- snes9x Multitroid
from: [snes9x Multitroid Download](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz),
- BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html)
- An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other
compatible hardware
- Your legally obtained Super Metroid ROM file, probably named `Super Metroid (Japan, USA).sfc` and
Your Japanese Zelda3 v1.0 ROM file, probably named `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc`
## Installation Procedures
### Windows Setup
1. During the installation of Archipelago, you will have been asked to install the SNI Client. If you did not do this,
or you are on an older version, you may run the installer again to install the SNI Client.
2. During setup, you will be asked to locate your base ROM files. This is your Super Metroid and Zelda3 ROM files.
3. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM
files.
1. Extract your emulator's folder to your Desktop, or somewhere you will remember.
2. Right-click on a ROM file and select **Open with...**
3. Check the box next to **Always use this app to open .sfc files**
4. Scroll to the bottom of the list and click the grey text **Look for another App on this PC**
5. Browse for your emulator's `.exe` file and click **Open**. This file should be located inside the folder you
extracted in step one.
## Create a Config (.yaml) File
### What is a config file and why do I need one?
See the guide on setting up a basic YAML at the Archipelago setup
guide: [Basic Multiworld Setup Guide](/tutorial/archipelago/setup/en)
### Where do I get a config file?
The Player Settings page on the website allows you to configure your personal settings and export a config file from
them. Player settings page: [SMZ3 Player Settings Page](/games/SMZ3/player-settings)
### Verifying your config file
If you would like to validate your config file to make sure it works, you may do so on the YAML Validator page. YAML
validator page: [YAML Validation page](/mysterycheck)
## Generating a Single-Player Game
1. Navigate to the Player Settings page, configure your options, and click the "Generate Game" button.
- Player Settings page: [SMZ3 Player Settings Page](/games/SMZ3/player-settings)
2. You will be presented with a "Seed Info" page.
3. Click the "Create New Room" link.
4. You will be presented with a server page, from which you can download your patch file.
5. Double-click on your patch file, and the SMZ3 Client will launch automatically, create your ROM from the
patch file, and open your emulator for you.
6. Since this is a single-player game, you will no longer need the client, so feel free to close it.
## Joining a MultiWorld Game
### Obtain your patch file and create your ROM
When you join a multiworld game, you will be asked to provide your config file to whoever is hosting. Once that is done,
the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch
files. Your patch file should have a `.apsmz` extension.
Put your patch file on your desktop or somewhere convenient, and double click it. This should automatically launch the
client, and will also create your ROM in the same place as your patch file.
### Connect to the client
#### With an emulator
When the client launched automatically, SNI should have also automatically launched in the background. If this is its
first time launching, you may be prompted to allow it to communicate through the Windows Firewall.
##### snes9x Multitroid
1. Load your ROM file if it hasn't already been loaded.
2. Click on the File menu and hover on **Lua Scripting**
3. Click on **New Lua Script Window...**
4. In the new window, click **Browse...**
5. Select the connector lua file included with your client
- SuperNintendoClient users should download `sniConnector.lua` from the client download page
- SNIClient users should look in their Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the
emulator is 64-bit or 32-bit.
##### BizHawk
1. Ensure you have the BSNES core loaded. You may do this by clicking on the Tools menu in BizHawk and following these
menu options:
`Config --> Cores --> SNES --> BSNES`
Once you have changed the loaded core, you must restart BizHawk.
2. Load your ROM file if it hasn't already been loaded.
3. Click on the Tools menu and click on **Lua Console**
4. Click the button to open a new Lua script.
5. Select the `Connector.lua` file included with your client
- SuperNintendoClient users should download `sniConnector.lua` from the client download page
- SNIClient users should look in their Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the
emulator is 64-bit or 32-bit. Please note the most recent versions of BizHawk are 64-bit only.
#### With hardware
This guide assumes you have downloaded the correct firmware for your device. If you have not done so already, please do
this now. SD2SNES and FXPak Pro users may download the appropriate firmware on the SD2SNES releases page. SD2SNES
releases page: [SD2SNES Releases Page](https://github.com/RedGuyyyy/sd2snes/releases)
Other hardware may find helpful information on the usb2snes platforms
page: [usb2snes Supported Platforms Page](http://usb2snes.com/#supported-platforms)
1. Close your emulator, which may have auto-launched.
2. Power on your device and load the ROM.
### Connect to the Archipelago Server
The patch file which launched your client should have automatically connected you to the AP Server. There are a few
reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the
client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it
into the "Server" input field then press enter.
The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected".
### Play the game
When the client shows both SNES Device and Server as connected, you're ready to begin playing. Congratulations on
successfully joining a multiworld game!
## Hosting a MultiWorld game
The recommended way to host a game is to use our hosting service. The process is relatively simple:
1. Collect config files from your players.
2. Create a zip file containing your players' config files.
3. Upload that zip file to the Generate page above.
- Generate page: [WebHost Seed Generation Page](/generate)
4. Wait a moment while the seed is generated.
5. When the seed is generated, you will be redirected to a "Seed Info" page.
6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players, so
they may download their patch files from there.
7. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the progress of all
players in the game. Any observers may also be given the link to this page.
8. Once all players have joined, you may begin playing.

View File

@ -40,7 +40,7 @@
{% elif patch.game == "Super Mario 64" and room.seed.slots|length == 1 %}
<a href="{{ url_for("download_slot_file", room_id=room.id, player_id=patch.player_id) }}" download>
Download APSM64EX File...</a>
{% elif patch.game in ["A Link to the Past", "Secret of Evermore", "Super Metroid"] %}
{% elif patch.game in ["A Link to the Past", "Secret of Evermore", "Super Metroid", "SMZ3"] %}
<a href="{{ url_for("download_patch", patch_id=patch.id, room_id=room.id) }}" download>
Download Patch File...</a>
{% else %}

View File

@ -102,10 +102,10 @@ sm_options:
rom_start: true
factorio_options:
executable: "factorio\\bin\\x64\\factorio"
minecraft_options:
minecraft_options:
forge_directory: "Minecraft Forge server"
max_heap_size: "2G"
oot_options:
oot_options:
# File name of the OoT v1.0 ROM
rom_file: "The Legend of Zelda - Ocarina of Time.z64"
soe_options:
@ -113,3 +113,10 @@ soe_options:
rom_file: "Secret of Evermore (USA).sfc"
ffr_options:
display_msgs: true
smz3_options:
# 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

21
worlds/smz3/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Thomas Backmark
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

63
worlds/smz3/Options.py Normal file
View File

@ -0,0 +1,63 @@
import typing
from Options import Choice, Option
class SMLogic(Choice):
"""This option selects what kind of logic to use for item placement inside
Super Metroid.
Normal - Normal logic includes only what Super Metroid teaches players
itself. Anything that's not demonstrated in-game or by the intro cutscenes
will not be required here.
Hard - Hard logic is based upon the "no major glitches" ruleset and
includes most tricks that are considered minor glitches, with some
restrictions. You'll want to be somewhat of a Super Metroid veteran for
this logic.
See https://samus.link/information for required moves."""
display_name = "SMLogic"
option_Normal = 0
option_Hard = 1
default = 0
class SwordLocation(Choice):
"""This option decides where the first sword will be placed.
Randomized - The sword can be placed anywhere.
Early - The sword will be placed in a location accessible from the start of
the game.
Unce assured - The sword will always be placed on Link's Uncle."""
display_name = "Sword Location"
option_Randomized = 0
option_Early = 1
option_Uncle = 2
default = 0
class MorphLocation(Choice):
"""This option decides where the morph ball will be placed.
Randomized - The morph ball can be placed anywhere.
Early - The morph ball will be placed in a location accessible from the
start of the game.
Original location - The morph ball will always be placed at its original
location."""
display_name = "Morph Location"
option_Randomized = 0
option_Early = 1
option_Original = 2
default = 0
class KeyShuffle(Choice):
"""This option decides how dungeon items such as keys are shuffled.
None - A Link to the Past dungeon items can only be placed inside the
dungeon they belong to, and there are no changes to Super Metroid.
Keysanity - See https://samus.link/information"""
display_name = "Key Shuffle"
option_None = 0
option_Keysanity = 1
default = 0
smz3_options: typing.Dict[str, type(Option)] = {
"sm_logic": SMLogic,
"sword_location": SwordLocation,
"morph_location": MorphLocation,
"key_shuffle": KeyShuffle
}

76
worlds/smz3/Rom.py Normal file
View File

@ -0,0 +1,76 @@
import Utils
from Patch import read_rom
SMJAP10HASH = '21f3e98df4780ee1c667b84e57d88675'
LTTPJAP10HASH = '03a63945398191337e896e5771f77173'
ROM_PLAYER_LIMIT = 256
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:
sm_file_name = get_sm_base_rom_path()
sm_base_rom_bytes = bytes(read_rom(open(sm_file_name, "rb")))
basemd5 = hashlib.md5()
basemd5.update(sm_base_rom_bytes)
if SMJAP10HASH != 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')
lttp_file_name = get_lttp_base_rom_path()
lttp_base_rom_bytes = bytes(read_rom(open(lttp_file_name, "rb")))
basemd5 = hashlib.md5()
basemd5.update(lttp_base_rom_bytes)
if LTTPJAP10HASH != 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 = bytes(combine_smz3_rom(sm_base_rom_bytes, lttp_base_rom_bytes))
return get_base_rom_bytes.base_rom_bytes
def get_sm_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
def get_lttp_base_rom_path(file_name: str = "") -> str:
options = Utils.get_options()
if not file_name:
file_name = options["lttp_options"]["rom_file"]
if not os.path.exists(file_name):
file_name = Utils.local_path(file_name)
return file_name
def combine_smz3_rom(sm_rom: bytes, lttp_rom: bytes):
combined = bytearray(0x600000)
# SM hi bank
pos = 0
srcpos = 0
for i in range(0x40):
combined[pos + 0x8000:pos + 0x8000 + 0x8000] = sm_rom[srcpos:srcpos + 0x8000]
srcpos += 0x8000
pos += 0x10000
# SM lo bank
pos = 0
for i in range(0x20):
combined[pos:pos + 0x8000] = sm_rom[srcpos:srcpos + 0x8000]
srcpos += 0x8000
pos += 0x10000
# Z3 hi bank
pos = 0x400000
srcpos = 0
for i in range(0x20):
combined[pos + 0x8000:pos + 0x8000 + 0x8000] = lttp_rom[srcpos:srcpos + 0x8000]
srcpos += 0x8000
pos += 0x10000
return combined

View File

@ -0,0 +1,107 @@
from enum import Enum
from typing import Dict, List
class GameMode(Enum):
Normal = 0
Multiworld = 1
class Z3Logic(Enum):
Normal = 0
Nmg = 1
Owg = 2
class SMLogic(Enum):
Normal = 0
Hard = 1
class SwordLocation(Enum):
Randomized = 0
Early = 1
Uncle = 2
class MorphLocation(Enum):
Randomized = 0
Early = 1
Original = 2
class Goal(Enum):
DefeatBoth = 0
class KeyShuffle(Enum):
Null = 0
Keysanity = 1
class GanonInvincible(Enum):
Never = 0
BeforeCrystals = 1
BeforeAllDungeons = 2
Always = 3
class Config:
GameMode: GameMode = GameMode.Multiworld
Z3Logic: Z3Logic = Z3Logic.Normal
SMLogic: SMLogic = SMLogic.Normal
SwordLocation: SwordLocation= SwordLocation.Randomized
MorphLocation: MorphLocation = MorphLocation.Randomized
Goal: Goal = Goal.DefeatBoth
KeyShuffle: KeyShuffle = KeyShuffle.Null
Keysanity: bool = KeyShuffle != KeyShuffle.Null
Race: bool = False
GanonInvincible: GanonInvincible = GanonInvincible.BeforeCrystals
def __init__(self, options: Dict[str, str]):
self.GameMode = self.ParseOption(options, GameMode.Multiworld)
self.Z3Logic = self.ParseOption(options, Z3Logic.Normal)
self.SMLogic = self.ParseOption(options, SMLogic.Normal)
self.SwordLocation = self.ParseOption(options, SwordLocation.Randomized)
self.MorphLocation = self.ParseOption(options, MorphLocation.Randomized)
self.Goal = self.ParseOption(options, Goal.DefeatBoth)
self.GanonInvincible = self.ParseOption(options, GanonInvincible.BeforeCrystals)
self.KeyShuffle = self.ParseOption(options, KeyShuffle.Null)
self.Keysanity = self.KeyShuffle != KeyShuffle.Null
self.Race = self.ParseOptionWith(options, "Race", False)
def ParseOption(self, options:Dict[str, str], defaultValue:Enum):
enumKey = defaultValue.__class__.__name__.lower()
if (enumKey in options):
return defaultValue.__class__[options[enumKey]]
return defaultValue
def ParseOptionWith(self, options:Dict[str, str], option:str, defaultValue:bool):
if (option.lower() in options):
return options[option.lower()]
return defaultValue
""" public static RandomizerOption GetRandomizerOption<T>(string description, string defaultOption = "") where T : Enum {
var enumType = typeof(T);
var values = Enum.GetValues(enumType).Cast<Enum>();
return new RandomizerOption {
Key = enumType.Name.ToLower(),
Description = description,
Type = RandomizerOptionType.Dropdown,
Default = string.IsNullOrEmpty(defaultOption) ? GetDefaultValue<T>().ToLString() : defaultOption,
Values = values.ToDictionary(k => k.ToLString(), v => v.GetDescription())
};
}
public static RandomizerOption GetRandomizerOption(string name, string description, bool defaultOption = false) {
return new RandomizerOption {
Key = name.ToLower(),
Description = description,
Type = RandomizerOptionType.Checkbox,
Default = defaultOption.ToString().ToLower(),
Values = new Dictionary<string, string>()
};
}
public static TEnum GetDefaultValue<TEnum>() where TEnum : Enum {
Type t = typeof(TEnum);
var attributes = (DefaultValueAttribute[])t.GetCustomAttributes(typeof(DefaultValueAttribute), false);
if ((attributes?.Length ?? 0) > 0) {
return (TEnum)attributes.First().Value;
}
else {
return default;
}
} """

View File

@ -0,0 +1,782 @@
from enum import Enum
import re
import copy
from typing import List
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
class ItemType(Enum):
Nothing = 0
MapHC = 0x7F
MapEP = 0x7D
MapDP = 0x7C
MapTH = 0x75
MapPD = 0x79
MapSP = 0x7A
MapSW = 0x77
MapTT = 0x74
MapIP = 0x76
MapMM = 0x78
MapTR = 0x73
MapGT = 0x72
CompassEP = 0x8D
CompassDP = 0x8C
CompassTH = 0x85
CompassPD = 0x89
CompassSP = 0x8A
CompassSW = 0x87
CompassTT = 0x84
CompassIP = 0x86
CompassMM = 0x88
CompassTR = 0x83
CompassGT = 0x82
BigKeyEP = 0x9D
BigKeyDP = 0x9C
BigKeyTH = 0x95
BigKeyPD = 0x99
BigKeySP = 0x9A
BigKeySW = 0x97
BigKeyTT = 0x94
BigKeyIP = 0x96
BigKeyMM = 0x98
BigKeyTR = 0x93
BigKeyGT = 0x92
KeyHC = 0xA0
KeyCT = 0xA4
KeyDP = 0xA3
KeyTH = 0xAA
KeyPD = 0xA6
KeySP = 0xA5
KeySW = 0xA8
KeyTT = 0xAB
KeyIP = 0xA9
KeyMM = 0xA7
KeyTR = 0xAC
KeyGT = 0xAD
Key = 0x24
Compass = 0x25
BigKey = 0x32
Map = 0x33
Something = 0x6B
ProgressiveTunic = 0x60
ProgressiveShield = 0x5F
ProgressiveSword = 0x5E
Bow = 0x0B
SilverArrows = 0x58
BlueBoomerang = 0x0C
RedBoomerang = 0x2A
Hookshot = 0x0A
Mushroom = 0x29
Powder = 0x0D
Firerod = 0x07
Icerod = 0x08
Bombos = 0x0f
Ether = 0x10
Quake = 0x11
Lamp = 0x12
Hammer = 0x09
Shovel = 0x13
Flute = 0x14
Bugnet = 0x21
Book = 0x1D
Bottle = 0x16
Somaria = 0x15
Byrna = 0x18
Cape = 0x19
Mirror = 0x1A
Boots = 0x4B
ProgressiveGlove = 0x61
Flippers = 0x1E
MoonPearl = 0x1F
HalfMagic = 0x4E
HeartPiece = 0x17
HeartContainer = 0x3E
HeartContainerRefill = 0x3F
ThreeBombs = 0x28
Arrow = 0x43
TenArrows = 0x44
OneRupee = 0x34
FiveRupees = 0x35
TwentyRupees = 0x36
TwentyRupees2 = 0x47
FiftyRupees = 0x41
OneHundredRupees = 0x40
ThreeHundredRupees = 0x46
BombUpgrade5 = 0x51
BombUpgrade10 = 0x52
ArrowUpgrade5 = 0x53
ArrowUpgrade10 = 0x54
CardCrateriaL1 = 0xD0
CardCrateriaL2 = 0xD1
CardCrateriaBoss = 0xD2
CardBrinstarL1 = 0xD3
CardBrinstarL2 = 0xD4
CardBrinstarBoss = 0xD5
CardNorfairL1 = 0xD6
CardNorfairL2 = 0xD7
CardNorfairBoss = 0xD8
CardMaridiaL1 = 0xD9
CardMaridiaL2 = 0xDA
CardMaridiaBoss = 0xDB
CardWreckedShipL1 = 0xDC
CardWreckedShipBoss = 0xDD
CardLowerNorfairL1 = 0xDE
CardLowerNorfairBoss = 0xDF
Missile = 0xC2
Super = 0xC3
PowerBomb = 0xC4
Grapple = 0xB0
XRay = 0xB1
ETank = 0xC0
ReserveTank = 0xC1
Charge = 0xBB
Ice = 0xBC
Wave = 0xBD
Spazer = 0xBE
Plasma = 0xBF
Varia = 0xB2
Gravity = 0xB6
Morph = 0xB4
Bombs = 0xB9
SpringBall = 0xB3
ScrewAttack = 0xB5
HiJump = 0xB7
SpaceJump = 0xB8
SpeedBooster = 0xBA
BottleWithRedPotion = 0x2B
BottleWithGreenPotion = 0x2C
BottleWithBluePotion = 0x2D
BottleWithFairy = 0x3D
BottleWithBee = 0x3C
BottleWithGoldBee = 0x48
RedContent = 0x2E
GreenContent = 0x2F
BlueContent = 0x30
BeeContent = 0x0E
class Item:
Name: str
Type: ItemType
Progression: bool
dungeon = re.compile("^(BigKey|Key|Map|Compass)")
bigKey = re.compile("^BigKey")
key = re.compile("^Key")
map = re.compile("^Map")
compass = re.compile("^Compass")
keycard = re.compile("^Card")
def IsDungeonItem(self): return self.dungeon.match(self.Type.name)
def IsBigKey(self): return self.bigKey.match(self.Type.name)
def IsKey(self): return self.key.match(self.Type.name)
def IsMap(self): return self.map.match(self.Type.name)
def IsCompass(self): return self.compass.match(self.Type.name)
def IsKeycard(self): return self.keycard.match(self.Type.name)
def Is(self, type: ItemType, world):
return self.Type == type and self.World == world
def IsNot(self, type: ItemType, world):
return not self.Is(type, world)
def __init__(self, itemType: ItemType, world = None):
self.Type = itemType
self.World = world
self.Progression = False
#self.Name = itemType.GetDescription()
@staticmethod
def Nothing(world):
return Item(ItemType.Nothing, world)
@staticmethod
def AddRange(itemPool, count, item):
for i in range(count):
itemPool.append(copy.copy(item))
@staticmethod
def CreateProgressionPool(world):
itemPool = [
Item(ItemType.ProgressiveShield),
Item(ItemType.ProgressiveShield),
Item(ItemType.ProgressiveShield),
Item(ItemType.ProgressiveSword),
Item(ItemType.ProgressiveSword),
Item(ItemType.Bow),
Item(ItemType.Hookshot),
Item(ItemType.Mushroom),
Item(ItemType.Powder),
Item(ItemType.Firerod),
Item(ItemType.Icerod),
Item(ItemType.Bombos),
Item(ItemType.Ether),
Item(ItemType.Quake),
Item(ItemType.Lamp),
Item(ItemType.Hammer),
Item(ItemType.Shovel),
Item(ItemType.Flute),
Item(ItemType.Bugnet),
Item(ItemType.Book),
Item(ItemType.Bottle),
Item(ItemType.Somaria),
Item(ItemType.Byrna),
Item(ItemType.Cape),
Item(ItemType.Mirror),
Item(ItemType.Boots),
Item(ItemType.ProgressiveGlove),
Item(ItemType.ProgressiveGlove),
Item(ItemType.Flippers),
Item(ItemType.MoonPearl),
Item(ItemType.HalfMagic),
Item(ItemType.Grapple),
Item(ItemType.Charge),
Item(ItemType.Ice),
Item(ItemType.Wave),
Item(ItemType.Plasma),
Item(ItemType.Varia),
Item(ItemType.Gravity),
Item(ItemType.Morph),
Item(ItemType.Bombs),
Item(ItemType.SpringBall),
Item(ItemType.ScrewAttack),
Item(ItemType.HiJump),
Item(ItemType.SpaceJump),
Item(ItemType.SpeedBooster),
Item(ItemType.Missile),
Item(ItemType.Super),
Item(ItemType.PowerBomb),
Item(ItemType.PowerBomb),
Item(ItemType.ETank),
Item(ItemType.ETank),
Item(ItemType.ETank),
Item(ItemType.ETank),
Item(ItemType.ETank),
Item(ItemType.ReserveTank),
Item(ItemType.ReserveTank),
Item(ItemType.ReserveTank),
Item(ItemType.ReserveTank),
]
for item in itemPool:
item.Progression = True
item.World = world
return itemPool
@staticmethod
def CreateNicePool(world):
itemPool = [
Item(ItemType.ProgressiveTunic),
Item(ItemType.ProgressiveTunic),
Item(ItemType.ProgressiveSword),
Item(ItemType.ProgressiveSword),
Item(ItemType.SilverArrows),
Item(ItemType.BlueBoomerang),
Item(ItemType.RedBoomerang),
Item(ItemType.Bottle),
Item(ItemType.Bottle),
Item(ItemType.Bottle),
Item(ItemType.HeartContainerRefill),
Item(ItemType.Spazer),
Item(ItemType.XRay),
]
Item.AddRange(itemPool, 10, Item(ItemType.HeartContainer, world))
for item in itemPool:
item.World = world
return itemPool
@staticmethod
def CreateJunkPool(world):
itemPool = [
Item(ItemType.Arrow),
Item(ItemType.OneHundredRupees)
]
Item.AddRange(itemPool, 24, Item(ItemType.HeartPiece))
Item.AddRange(itemPool, 8, Item(ItemType.TenArrows))
Item.AddRange(itemPool, 13, Item(ItemType.ThreeBombs))
Item.AddRange(itemPool, 4, Item(ItemType.ArrowUpgrade5))
Item.AddRange(itemPool, 4, Item(ItemType.BombUpgrade5))
Item.AddRange(itemPool, 2, Item(ItemType.OneRupee))
Item.AddRange(itemPool, 4, Item(ItemType.FiveRupees))
Item.AddRange(itemPool, 25 if world.Config.Keysanity else 28, Item(ItemType.TwentyRupees))
Item.AddRange(itemPool, 7, Item(ItemType.FiftyRupees))
Item.AddRange(itemPool, 5, Item(ItemType.ThreeHundredRupees))
Item.AddRange(itemPool, 9, Item(ItemType.ETank))
Item.AddRange(itemPool, 39, Item(ItemType.Missile))
Item.AddRange(itemPool, 15, Item(ItemType.Super))
Item.AddRange(itemPool, 8, Item(ItemType.PowerBomb))
for item in itemPool:
item.World = world
return itemPool
# The order of the dungeon pool is significant
@staticmethod
def CreateDungeonPool(world):
itemPool = [Item(ItemType.BigKeyGT)]
Item.AddRange(itemPool, 4, Item(ItemType.KeyGT))
if (not world.Config.Keysanity):
itemPool += [
Item(ItemType.MapGT),
Item(ItemType.CompassGT),
]
itemPool += [
Item(ItemType.BigKeyEP),
Item(ItemType.BigKeyDP),
Item(ItemType.BigKeyTH),
Item(ItemType.BigKeyPD),
Item(ItemType.BigKeySP),
Item(ItemType.BigKeySW),
Item(ItemType.BigKeyTT),
Item(ItemType.BigKeyIP),
Item(ItemType.BigKeyMM),
Item(ItemType.BigKeyTR),
]
Item.AddRange(itemPool, 1, Item(ItemType.KeyHC))
Item.AddRange(itemPool, 2, Item(ItemType.KeyCT))
Item.AddRange(itemPool, 1, Item(ItemType.KeyDP))
Item.AddRange(itemPool, 1, Item(ItemType.KeyTH))
Item.AddRange(itemPool, 6, Item(ItemType.KeyPD))
Item.AddRange(itemPool, 1, Item(ItemType.KeySP))
Item.AddRange(itemPool, 3, Item(ItemType.KeySW))
Item.AddRange(itemPool, 1, Item(ItemType.KeyTT))
Item.AddRange(itemPool, 2, Item(ItemType.KeyIP))
Item.AddRange(itemPool, 3, Item(ItemType.KeyMM))
Item.AddRange(itemPool, 4, Item(ItemType.KeyTR))
itemPool += [
Item(ItemType.MapEP),
Item(ItemType.MapDP),
Item(ItemType.MapTH),
Item(ItemType.MapPD),
Item(ItemType.MapSP),
Item(ItemType.MapSW),
Item(ItemType.MapTT),
Item(ItemType.MapIP),
Item(ItemType.MapMM),
Item(ItemType.MapTR),
]
if (not world.Config.Keysanity):
itemPool += [
Item(ItemType.MapHC),
Item(ItemType.CompassEP),
Item(ItemType.CompassDP),
Item(ItemType.CompassTH),
Item(ItemType.CompassPD),
Item(ItemType.CompassSP),
Item(ItemType.CompassSW),
Item(ItemType.CompassTT),
Item(ItemType.CompassIP),
Item(ItemType.CompassMM),
Item(ItemType.CompassTR),
]
for item in itemPool:
item.World = world
return itemPool
@staticmethod
def CreateKeycards(world):
itemPool = [
Item(ItemType.CardCrateriaL1, world),
Item(ItemType.CardCrateriaL2, world),
Item(ItemType.CardCrateriaBoss, world),
Item(ItemType.CardBrinstarL1, world),
Item(ItemType.CardBrinstarL2, world),
Item(ItemType.CardBrinstarBoss, world),
Item(ItemType.CardNorfairL1, world),
Item(ItemType.CardNorfairL2, world),
Item(ItemType.CardNorfairBoss, world),
Item(ItemType.CardMaridiaL1, world),
Item(ItemType.CardMaridiaL2, world),
Item(ItemType.CardMaridiaBoss, world),
Item(ItemType.CardWreckedShipL1, world),
Item(ItemType.CardWreckedShipBoss, world),
Item(ItemType.CardLowerNorfairL1, world),
Item(ItemType.CardLowerNorfairBoss, world),
]
for item in itemPool:
item.Progression = True
item.World = world
return itemPool
@staticmethod
def Get(items, itemType:ItemType):
item = next((i for i in items if i.Type == itemType), None)
if (item == None):
raise Exception(f"Could not find an item of type {itemType}")
return item
@staticmethod
def Get(items, itemType:ItemType, world):
item = next((i for i in items if i.Is(itemType, world)), None)
if (item == None):
raise Exception(f"Could not find an item of type {itemType} in world {world.Id}")
return item
class Progression:
BigKeyEP: bool
BigKeyDP: bool
BigKeyTH: bool
BigKeyPD: bool
BigKeySP: bool
BigKeySW: bool
BigKeyTT: bool
BigKeyIP: bool
BigKeyMM: bool
BigKeyTR: bool
BigKeyGT: bool
KeyHC: bool
KeyDP: bool
KeyTH: bool
KeySP: bool
KeyTT: bool
KeyCT: bool
KeyPD: bool
KeySW: bool
KeyIP: bool
KeyMM: bool
KeyTR: bool
KeyGT: bool
CardCrateriaL1: bool
CardCrateriaL2: bool
CardCrateriaBoss: bool
CardBrinstarL1: bool
CardBrinstarL2: bool
CardBrinstarBoss: bool
CardNorfairL1: bool
CardNorfairL2: bool
CardNorfairBoss: bool
CardMaridiaL1: bool
CardMaridiaL2: bool
CardMaridiaBoss: bool
CardWreckedShipL1: bool
CardWreckedShipBoss: bool
CardLowerNorfairL1: bool
CardLowerNorfairBoss: bool
def CanBlockLasers(self): return self.shield >= 3
Sword: bool
MasterSword: bool
Bow: bool
Hookshot: bool
Mushroom: bool
Powder: bool
Firerod: bool
Icerod: bool
Bombos: bool
Ether: bool
Quake: bool
Lamp: bool
Hammer: bool
Shovel: bool
Flute: bool
Book: bool
Bottle: bool
Somaria: bool
Byrna: bool
Cape: bool
Mirror: bool
Boots: bool
Glove: bool
Mitt: bool
Flippers: bool
MoonPearl: bool
HalfMagic: bool
Grapple: bool
Charge: bool
Ice: bool
Wave: bool
Plasma: bool
Varia: bool
Gravity: bool
Morph: bool
Bombs: bool
SpringBall: bool
ScrewAttack: bool
HiJump: bool
SpaceJump: bool
SpeedBooster: bool
Missile: bool
Super: bool
PowerBomb: bool
TwoPowerBombs: bool
ETank: int
ReserveTank: int
shield: int
itemMapping = [
ItemType.BigKeyEP,
ItemType.BigKeyDP,
ItemType.BigKeyTH,
ItemType.BigKeyPD,
ItemType.BigKeySP,
ItemType.BigKeySW,
ItemType.BigKeyTT,
ItemType.BigKeyIP,
ItemType.BigKeyMM,
ItemType.BigKeyTR,
ItemType.BigKeyGT,
ItemType.KeyHC,
ItemType.KeyDP,
ItemType.KeyTH,
ItemType.KeySP,
ItemType.KeyTT,
ItemType.CardCrateriaL1,
ItemType.CardCrateriaL2,
ItemType.CardCrateriaBoss,
ItemType.CardBrinstarL1,
ItemType.CardBrinstarL2,
ItemType.CardBrinstarBoss,
ItemType.CardNorfairL1,
ItemType.CardNorfairL2,
ItemType.CardNorfairBoss,
ItemType.CardMaridiaL1,
ItemType.CardMaridiaL2,
ItemType.CardMaridiaBoss,
ItemType.CardWreckedShipL1,
ItemType.CardWreckedShipBoss,
ItemType.CardLowerNorfairL1,
ItemType.CardLowerNorfairBoss,
ItemType.Bow,
ItemType.Hookshot,
ItemType.Mushroom,
ItemType.Powder,
ItemType.Firerod,
ItemType.Icerod,
ItemType.Bombos,
ItemType.Ether,
ItemType.Quake,
ItemType.Lamp,
ItemType.Hammer,
ItemType.Shovel,
ItemType.Flute,
ItemType.Book,
ItemType.Bottle,
ItemType.Somaria,
ItemType.Byrna,
ItemType.Cape,
ItemType.Mirror,
ItemType.Boots,
ItemType.Flippers,
ItemType.MoonPearl,
ItemType.HalfMagic,
ItemType.Grapple,
ItemType.Charge,
ItemType.Ice,
ItemType.Wave,
ItemType.Plasma,
ItemType.Varia,
ItemType.Gravity,
ItemType.Morph,
ItemType.Bombs,
ItemType.SpringBall,
ItemType.ScrewAttack,
ItemType.HiJump,
ItemType.SpaceJump,
ItemType.SpeedBooster,
ItemType.Missile,
ItemType.Super,
]
def __init__(self, items):
for item in Progression.itemMapping:
setattr(self, item.name, False)
self.KeyCT = 0
self.KeyPD = 0
self.KeySW = 0
self.KeyIP = 0
self.KeyMM = 0
self.KeyTR = 0
self.KeyGT = 0
self.ETank = 0
self.ReserveTank = 0
self.shield = 0
self.MasterSword = False
self.Sword = False
self.Mitt = False
self.Glove = False
self.TwoPowerBombs = False
self.PowerBomb = False
self.Add(items)
def Add(self, items:List[Item]):
for item in items:
found = item.Type in Progression.itemMapping
if found:
setattr(self, item.Type.name, True)
continue
if (item.Type == ItemType.KeyCT):
self.KeyCT += 1
elif (item.Type == ItemType.KeyPD):
self.KeyPD += 1
elif (item.Type == ItemType.KeySW):
self.KeySW += 1
elif (item.Type == ItemType.KeyIP):
self.KeyIP += 1
elif (item.Type == ItemType.KeyMM):
self.KeyMM += 1
elif (item.Type == ItemType.KeyTR):
self.KeyTR += 1
elif (item.Type == ItemType.KeyGT):
self.KeyGT += 1
elif (item.Type == ItemType.ETank):
self.ETank += 1
elif (item.Type == ItemType.ReserveTank):
self.ReserveTank += 1
elif (item.Type == ItemType.KeyPD):
self.shield += 1
elif (item.Type == ItemType.ProgressiveSword):
self.MasterSword = self.Sword
self.Sword = True
elif (item.Type == ItemType.ProgressiveGlove):
self.Mitt = self.Glove
self.Glove = True
elif (item.Type == ItemType.PowerBomb):
self.TwoPowerBombs = self.PowerBomb
self.PowerBomb = True
def Remove(self, items:List[Item]):
for item in items:
found = item.Type in Progression.itemMapping
if found:
setattr(self, item.Type.name, False)
continue
if (item.Type == ItemType.KeyCT):
self.KeyCT -= 1
elif (item.Type == ItemType.KeyPD):
self.KeyPD -= 1
elif (item.Type == ItemType.KeySW):
self.KeySW -= 1
elif (item.Type == ItemType.KeyIP):
self.KeyIP -= 1
elif (item.Type == ItemType.KeyMM):
self.KeyMM -= 1
elif (item.Type == ItemType.KeyTR):
self.KeyTR -= 1
elif (item.Type == ItemType.KeyGT):
self.KeyGT -= 1
elif (item.Type == ItemType.ETank):
self.ETank -= 1
elif (item.Type == ItemType.ReserveTank):
self.ReserveTank -= 1
elif (item.Type == ItemType.KeyPD):
self.shield -= 1
elif (item.Type == ItemType.ProgressiveSword):
self.Sword = self.MasterSword
self.MasterSword = False
elif (item.Type == ItemType.ProgressiveGlove):
self.Glove = self.Mitt
self.Mitt = False
elif (item.Type == ItemType.PowerBomb):
self.PowerBomb = self.TwoPowerBombs
self.TwoPowerBombs = False
def CanLiftLight(self): return self.Glove
def CanLiftHeavy(self): return self.Mitt
def CanLightTorches(self): return self.Firerod or self.Lamp
def CanMeltFreezors(self): return self.Firerod or self.Bombos and self.Sword
def CanExtendMagic(self, bars:int = 2): return (2 if self.HalfMagic else 1) * (2 if self.Bottle else 1) >= bars
def CanKillManyEnemies(self):
return self.Sword or self.Hammer or self.Bow or self.Firerod or \
self.Somaria or self.Byrna and self.CanExtendMagic()
def CanAccessDeathMountainPortal(self):
return (self.CanDestroyBombWalls() or self.SpeedBooster) and self.Super and self.Morph
def CanAccessDarkWorldPortal(self, config: Config):
if (config.SMLogic == SMLogic.Normal):
return self.CardMaridiaL1 and self.CardMaridiaL2 and self.CanUsePowerBombs() and self.Super and self.Gravity and self.SpeedBooster
else:
return self.CardMaridiaL1 and self.CardMaridiaL2 and self.CanUsePowerBombs() and self.Super and \
(self.Charge or self.Super and self.Missile) and \
(self.Gravity or self.HiJump and self.Ice and self.Grapple) and \
(self.Ice or self.Gravity and self.SpeedBooster)
def CanAccessMiseryMirePortal(self, config: Config):
if (config.SMLogic == SMLogic.Normal):
return (self.CardNorfairL2 or (self.SpeedBooster and self.Wave)) and self.Varia and self.Super and (self.Gravity and self.SpaceJump) and self.CanUsePowerBombs()
else:
return (self.CardNorfairL2 or self.SpeedBooster) and self.Varia and self.Super and \
(self.CanFly() or self.HiJump or self.SpeedBooster or self.CanSpringBallJump() or self.Ice) \
and (self.Gravity or self.HiJump) and self.CanUsePowerBombs()
def CanIbj(self):
return self.Morph and self.Bombs
def CanFly(self):
return self.SpaceJump or self.CanIbj()
def CanUsePowerBombs(self):
return self.Morph and self.PowerBomb
def CanPassBombPassages(self):
return self.Morph and (self.Bombs or self.PowerBomb)
def CanDestroyBombWalls(self):
return self.CanPassBombPassages() or self.ScrewAttack
def CanSpringBallJump(self):
return self.Morph and self.SpringBall
def CanHellRun(self):
return self.Varia or self.HasEnergyReserves(5)
def HasEnergyReserves(self, amount: int):
return (self.ETank + self.ReserveTank) >= amount
def CanOpenRedDoors(self):
return self.Missile or self.Super
def CanAccessNorfairUpperPortal(self):
return self.Flute or self.CanLiftLight() and self.Lamp
def CanAccessNorfairLowerPortal(self):
return self.Flute and self.CanLiftHeavy()
def CanAccessMaridiaPortal(self, world):
import worlds.smz3.TotalSMZ3.Region
if (world.Config.SMLogic == SMLogic.Normal):
return self.MoonPearl and self.Flippers and \
self.Gravity and self.Morph and \
(world.CanAquire(self, worlds.smz3.TotalSMZ3.Region.RewardType.Agahnim) or self.Hammer and self.CanLiftLight() or self.CanLiftHeavy())
else:
return self.MoonPearl and self.Flippers and \
(self.CanSpringBallJump() or self.HiJump or self.Gravity) and self.Morph and \
(world.CanAquire(self, worlds.smz3.TotalSMZ3.Region.RewardType.Agahnim) or self.Hammer and self.CanLiftLight() or self.CanLiftHeavy())
# Start of AP integration
items_start_id = 84000
lookup_id_to_name = { items_start_id + enum.value : enum.name for enum in ItemType }
lookup_name_to_id = { item_name : item_id for item_id, item_name in lookup_id_to_name.items() }

View File

@ -0,0 +1,112 @@
from enum import Enum
from typing import List, Callable
from worlds.smz3.TotalSMZ3.Item import Progression
import worlds.smz3.TotalSMZ3.Region as Region
import worlds.smz3.TotalSMZ3.World as World
class LocationType(Enum):
Regular = 0
HeraStandingKey = 1
Pedestal = 2
Ether = 3
Bombos = 4
NotInDungeon = 5
Visible = 6
Chozo = 7
Hidden = 8
# delegate bool Requirement(Progression items);
# delegate bool Verification(Item item, Progression items);
class Location:
Id: int
Name: str
Type: LocationType
Address: int
Region: Region
def Weight(self): return self.weight if self.weight != None else self.Region.Weight
canAccess: Callable = lambda items: True
alwaysAllow: Callable = lambda item, items: True
allow: Callable = lambda item, items: True
weight: int
def ItemIs(self, type, world: World):
item = self.APLocation.item.item if self.APLocation.item is not None and self.APLocation.item.game == "SMZ3" else None
return item.Is(type, world) if item != None else False
def ItemIsNot(self, type, world: World): return not self.ItemIs(type, world)
def __init__(self, region: Region, id: int, address: int, type: LocationType, name: str, access: Callable = lambda items : True):
self.Region = region
self.Id = id
self.Name = name
self.Type = type
self.Item = None
self.Address = address
self.canAccess = access
self.alwaysAllow = lambda item, items: False
self.allow = lambda item, items: True
def Weighted(self, weight: int):
self.weight = weight
return self
def AlwaysAllow(self, allow: Callable):
self.alwaysAllow = allow
return self
def Allow(self, allow: Callable):
self.allow = allow
return self
def Available(self, items: Progression):
return self.Region.CanEnter(items) and self.canAccess(items)
def CanFill(self, item, items: Progression):
oldItem = self.Item
self.Item = item
fillable = self.alwaysAllow(item, items) or (self.Region.CanFill(item) and self.allow(item, items) and self.Available(items))
self.Item = oldItem
return fillable
@staticmethod
def Get(locations, name: str):
loc = next((l for l in locations if l.Name == name), None)
if (loc == None):
raise Exception(f"Could not find location name {name}")
return loc
@staticmethod
def Empty(locations):
return [l for l in locations if l.Item == None]
@staticmethod
def Filled(locations):
return [l for l in locations if l.Item != None]
@staticmethod
def AvailableWithinWorld(locations, items):
result = []
worldList = []
[worldList.append(l.Region.World) for l in locations if l.Region.World not in worldList]
for world in worldList:
result += Location.AvailableGlobal([l for l in locations if l.Region.World == world], [i for i in items if i.World == world])
return result
@staticmethod
def AvailableGlobal(locations, items):
progression = Progression(items)
return [l for l in locations if l.Available(progression)]
@staticmethod
def CanFillWithinWorld(locations, item, items):
itemWorldProgression = Progression([i for i in items if i.World == item.World].append(item))
worldList = []
[worldList.append(l.Region.World) for l in locations if l.Region.World not in worldList]
worldProgression = {world.Id : Progression([i for i in items if i.World == world]) for world in worldList}
return [l for l in locations if l.CanFill(item, worldProgression[l.Region.World.Id] and next(ll for ll in item.World.Locations if ll.Id == l.Id).Available(itemWorldProgression))]
# Start of AP integration
locations_start_id = 85000

View File

@ -0,0 +1,809 @@
from enum import Enum
from logging import exception
from typing import Any, Callable, List, Sequence
import random
import typing
from BaseClasses import Location
from worlds.smz3.TotalSMZ3.Item import Item, ItemType
from worlds.smz3.TotalSMZ3.Location import LocationType
from worlds.smz3.TotalSMZ3.Region import IMedallionAccess, IReward, RewardType, SMRegion, Z3Region
from worlds.smz3.TotalSMZ3.Regions.Zelda.EasternPalace import EasternPalace
from worlds.smz3.TotalSMZ3.Regions.Zelda.DesertPalace import DesertPalace
from worlds.smz3.TotalSMZ3.Regions.Zelda.TowerOfHera import TowerOfHera
from worlds.smz3.TotalSMZ3.Regions.Zelda.PalaceOfDarkness import PalaceOfDarkness
from worlds.smz3.TotalSMZ3.Regions.Zelda.SwampPalace import SwampPalace
from worlds.smz3.TotalSMZ3.Regions.Zelda.SkullWoods import SkullWoods
from worlds.smz3.TotalSMZ3.Regions.Zelda.ThievesTown import ThievesTown
from worlds.smz3.TotalSMZ3.Regions.Zelda.IcePalace import IcePalace
from worlds.smz3.TotalSMZ3.Regions.Zelda.MiseryMire import MiseryMire
from worlds.smz3.TotalSMZ3.Regions.Zelda.TurtleRock import TurtleRock
from worlds.smz3.TotalSMZ3.Regions.Zelda.GanonsTower import GanonsTower
from worlds.smz3.TotalSMZ3.Text.StringTable import StringTable
from worlds.smz3.TotalSMZ3.World import World
from worlds.smz3.TotalSMZ3.Config import Config, GameMode, GanonInvincible
from worlds.smz3.TotalSMZ3.Text.Texts import Texts
from worlds.smz3.TotalSMZ3.Text.Dialog import Dialog
class KeycardPlaque:
Level1 = 0xe0
Level2 = 0xe1
Boss = 0xe2
Null = 0x00
class KeycardDoors:
Left = 0xd414
Right = 0xd41a
Up = 0xd420
Down = 0xd426
BossLeft = 0xc842
BossRight = 0xc848
class KeycardEvents:
CrateriaLevel1 = 0x0000
CrateriaLevel2 = 0x0100
CrateriaBoss = 0x0200
BrinstarLevel1 = 0x0300
BrinstarLevel2 = 0x0400
BrinstarBoss = 0x0500
NorfairLevel1 = 0x0600
NorfairLevel2 = 0x0700
NorfairBoss = 0x0800
MaridiaLevel1 = 0x0900
MaridiaLevel2 = 0x0a00
MaridiaBoss = 0x0b00
WreckedShipLevel1 = 0x0c00
WreckedShipBoss = 0x0d00
LowerNorfairLevel1 = 0x0e00
LowerNorfairBoss = 0x0f00
class DropPrize(Enum):
Heart = 0xD8
Green = 0xD9
Blue = 0xDA
Red = 0xDB
Bomb1 = 0xDC
Bomb4 = 0xDD
Bomb8 = 0xDE
Magic = 0xDF
FullMagic = 0xE0
Arrow5 = 0xE1
Arrow10 = 0xE2
Fairy = 0xE3
class Patch:
Major = 0
Minor = 1
allWorlds: List[World]
myWorld: World
seedGuid: str
seed: int
rnd: random.Random
patches: Sequence[Any]
stringTable: StringTable
silversWorldID: int
def __init__(self, myWorld: World, allWorlds: List[World], seedGuid: str, seed: int, rnd: random.Random, playerNames: List[str], silversWorldID: int):
self.myWorld = myWorld
self.allWorlds = allWorlds
self.seedGuid = seedGuid
self.seed = seed
self.rnd = rnd
self.playerNames = playerNames
self.playerIDToNames = {id:name for name, id in playerNames.items()}
self.silversWorldID = silversWorldID
def Create(self, config: Config):
self.stringTable = StringTable()
self.patches = []
self.title = ""
self.WriteMedallions()
self.WriteRewards()
self.WriteDungeonMusic(config.Keysanity)
self.WriteDiggingGameRng()
self.WritePrizeShuffle()
self.WriteRemoveEquipmentFromUncle( self.myWorld.GetLocation("Link's Uncle").APLocation.item.item if
self.myWorld.GetLocation("Link's Uncle").APLocation.item.game == "SMZ3" else
Item(ItemType.Something))
self.WriteGanonInvicible(config.GanonInvincible)
self.WriteRngBlock()
self.WriteSaveAndQuitFromBossRoom()
self.WriteWorldOnAgahnimDeath()
self.WriteTexts(config)
self.WriteSMLocations([loc for region in self.myWorld.Regions for loc in region.Locations if isinstance(region, SMRegion)])
self.WriteZ3Locations([loc for region in self.myWorld.Regions for loc in region.Locations if isinstance(region, Z3Region)])
self.WriteStringTable()
self.WriteSMKeyCardDoors()
self.WriteZ3KeysanityFlags()
self.WritePlayerNames()
self.WriteSeedData()
self.WriteGameTitle()
self.WriteCommonFlags()
return {patch[0]:patch[1] for patch in self.patches}
def WriteMedallions(self):
turtleRock = next(region for region in self.myWorld.Regions if isinstance(region, TurtleRock))
miseryMire = next(region for region in self.myWorld.Regions if isinstance(region, MiseryMire))
turtleRockAddresses = [0x308023, 0xD020, 0xD0FF, 0xD1DE ]
miseryMireAddresses = [ 0x308022, 0xCFF2, 0xD0D1, 0xD1B0 ]
if turtleRock.Medallion == ItemType.Bombos:
turtleRockValues = [0x00, 0x51, 0x10, 0x00]
elif turtleRock.Medallion == ItemType.Ether:
turtleRockValues = [0x01, 0x51, 0x18, 0x00]
elif turtleRock.Medallion == ItemType.Quake:
turtleRockValues = [0x02, 0x14, 0xEF, 0xC4]
else:
raise exception(f"Tried using {turtleRock.Medallion} in place of Turtle Rock medallion")
if miseryMire.Medallion == ItemType.Bombos:
miseryMireValues = [0x00, 0x51, 0x00, 0x00]
elif miseryMire.Medallion == ItemType.Ether:
miseryMireValues = [0x01, 0x13, 0x9F, 0xF1]
elif miseryMire.Medallion == ItemType.Quake:
miseryMireValues = [0x02, 0x51, 0x08, 0x00]
else:
raise exception(f"Tried using {miseryMire.Medallion} in place of Misery Mire medallion")
self.patches += [(Snes(addr), [value]) for addr, value in zip(turtleRockAddresses, turtleRockValues)]
self.patches += [(Snes(addr), [value]) for addr, value in zip(miseryMireAddresses, miseryMireValues)]
def WriteRewards(self):
crystalsBlue = [ 1, 2, 3, 4, 7 ]
self.rnd.shuffle(crystalsBlue)
crystalsRed = [ 5, 6 ]
self.rnd.shuffle(crystalsRed)
crystalRewards = crystalsBlue + crystalsRed
pendantsGreen = [ 1 ]
pendantsBlueRed = [ 2, 3 ]
self.rnd.shuffle(pendantsBlueRed)
pendantRewards = pendantsGreen + pendantsBlueRed
regions = [region for region in self.myWorld.Regions if isinstance(region, IReward)]
crystalRegions = [region for region in regions if region.Reward == RewardType.CrystalBlue] + [region for region in regions if region.Reward == RewardType.CrystalRed]
pendantRegions = [region for region in regions if region.Reward == RewardType.PendantGreen] + [region for region in regions if region.Reward == RewardType.PendantNonGreen]
self.patches += self.RewardPatches(crystalRegions, crystalRewards, self.CrystalValues)
self.patches += self.RewardPatches(pendantRegions, pendantRewards, self.PendantValues)
def RewardPatches(self, regions: List[IReward], rewards: List[int], rewardValues: Callable):
addresses = [self.RewardAddresses(region) for region in regions]
values = [rewardValues(reward) for reward in rewards]
associations = zip(addresses, values)
return [(Snes(i), [b]) for association in associations for i,b in zip(association[0], association[1])]
def RewardAddresses(self, region: IReward):
regionType = {
EasternPalace : [ 0x2A09D, 0xABEF8, 0xABEF9, 0x308052, 0x30807C, 0x1C6FE ],
DesertPalace : [ 0x2A09E, 0xABF1C, 0xABF1D, 0x308053, 0x308078, 0x1C6FF ],
TowerOfHera : [ 0x2A0A5, 0xABF0A, 0xABF0B, 0x30805A, 0x30807A, 0x1C706 ],
PalaceOfDarkness : [ 0x2A0A1, 0xABF00, 0xABF01, 0x308056, 0x30807D, 0x1C702 ],
SwampPalace : [ 0x2A0A0, 0xABF6C, 0xABF6D, 0x308055, 0x308071, 0x1C701 ],
SkullWoods : [ 0x2A0A3, 0xABF12, 0xABF13, 0x308058, 0x30807B, 0x1C704 ],
ThievesTown : [ 0x2A0A6, 0xABF36, 0xABF37, 0x30805B, 0x308077, 0x1C707 ],
IcePalace : [ 0x2A0A4, 0xABF5A, 0xABF5B, 0x308059, 0x308073, 0x1C705 ],
MiseryMire : [ 0x2A0A2, 0xABF48, 0xABF49, 0x308057, 0x308075, 0x1C703 ],
TurtleRock : [ 0x2A0A7, 0xABF24, 0xABF25, 0x30805C, 0x308079, 0x1C708 ]
}
result = regionType.get(type(region), None)
if result is None:
raise exception(f"Region {region} should not be a dungeon reward region")
else:
return result
def CrystalValues(self, crystal: int):
crystalMap = {
1 : [ 0x02, 0x34, 0x64, 0x40, 0x7F, 0x06 ],
2 : [ 0x10, 0x34, 0x64, 0x40, 0x79, 0x06 ],
3 : [ 0x40, 0x34, 0x64, 0x40, 0x6C, 0x06 ],
4 : [ 0x20, 0x34, 0x64, 0x40, 0x6D, 0x06 ],
5 : [ 0x04, 0x32, 0x64, 0x40, 0x6E, 0x06 ],
6 : [ 0x01, 0x32, 0x64, 0x40, 0x6F, 0x06 ],
7 : [ 0x08, 0x34, 0x64, 0x40, 0x7C, 0x06 ],
}
result = crystalMap.get(crystal, None)
if result is None:
raise exception(f"Tried using {crystal} as a crystal number")
else:
return result
def PendantValues(self, pendant: int):
pendantMap = {
1 : [ 0x04, 0x38, 0x62, 0x00, 0x69, 0x01 ],
2 : [ 0x01, 0x32, 0x60, 0x00, 0x69, 0x03 ],
3 : [ 0x02, 0x34, 0x60, 0x00, 0x69, 0x02 ],
}
result = pendantMap.get(pendant, None)
if result is None:
raise exception(f"Tried using {pendant} as a pendant number")
else:
return result
def WriteSMLocations(self, locations: List[Location]):
def GetSMItemPLM(location:Location):
itemMap = {
ItemType.ETank : 0xEED7,
ItemType.Missile : 0xEEDB,
ItemType.Super : 0xEEDF,
ItemType.PowerBomb : 0xEEE3,
ItemType.Bombs : 0xEEE7,
ItemType.Charge : 0xEEEB,
ItemType.Ice : 0xEEEF,
ItemType.HiJump : 0xEEF3,
ItemType.SpeedBooster : 0xEEF7,
ItemType.Wave : 0xEEFB,
ItemType.Spazer : 0xEEFF,
ItemType.SpringBall : 0xEF03,
ItemType.Varia : 0xEF07,
ItemType.Plasma : 0xEF13,
ItemType.Grapple : 0xEF17,
ItemType.Morph : 0xEF23,
ItemType.ReserveTank : 0xEF27,
ItemType.Gravity : 0xEF0B,
ItemType.XRay : 0xEF0F,
ItemType.SpaceJump : 0xEF1B,
ItemType.ScrewAttack : 0xEF1F
}
plmId = 0xEFE0 if self.myWorld.Config.GameMode == GameMode.Multiworld else \
itemMap.get(location.APLocation.item.item.Type, 0xEFE0)
if (plmId == 0xEFE0):
plmId += 4 if location.Type == LocationType.Chozo else 8 if location.Type == LocationType.Hidden else 0
else:
plmId += 0x54 if location.Type == LocationType.Chozo else 0xA8 if location.Type == LocationType.Hidden else 0
return plmId
for location in locations:
if (self.myWorld.Config.GameMode == GameMode.Multiworld):
self.patches.append((Snes(location.Address), getWordArray(GetSMItemPLM(location))))
self.patches.append(self.ItemTablePatch(location, self.GetZ3ItemId(location)))
else:
plmId = GetSMItemPLM(location)
self.patches.append((Snes(location.Address), getWordArray(plmId)))
if (plmId >= 0xEFE0):
self.patches.append((Snes(location.Address + 5), [self.GetZ3ItemId(location)]))
def WriteZ3Locations(self, locations: List[Location]):
for location in locations:
if (location.Type == LocationType.HeraStandingKey):
self.patches.append((Snes(0x9E3BB), [0xE4] if location.APLocation.item.game == "SMZ3" and location.APLocation.item.item.Type == ItemType.KeyTH else [0xEB]))
elif (location.Type in [LocationType.Pedestal, LocationType.Ether, LocationType.Bombos]):
text = Texts.ItemTextbox(location.APLocation.item.item if location.APLocation.item.game == "SMZ3" else Item(ItemType.Something))
dialog = Dialog.Simple(text)
if (location.Type == LocationType.Pedestal):
self.stringTable.SetPedestalText(text)
self.patches.append((Snes(0x308300), dialog))
elif (location.Type == LocationType.Ether):
self.stringTable.SetEtherText(text)
self.patches.append((Snes(0x308F00), dialog))
elif (location.Type == LocationType.Bombos):
self.stringTable.SetBombosText(text)
self.patches.append((Snes(0x309000), dialog))
if (self.myWorld.Config.GameMode == GameMode.Multiworld):
self.patches.append((Snes(location.Address), [(location.Id - 256)]))
self.patches.append(self.ItemTablePatch(location, self.GetZ3ItemId(location)))
else:
self.patches.append((Snes(location.Address), [self.GetZ3ItemId(location)]))
def GetZ3ItemId(self, location: Location):
if (location.APLocation.item.game == "SMZ3"):
item = location.APLocation.item.item
itemDungeon = None
if item.IsKey():
itemDungeon = ItemType.Key if (not item.World.Config.Keysanity or item.Type != ItemType.KeyHC) else ItemType.KeyHC
elif item.IsBigKey():
itemDungeon = ItemType.BigKey
elif item.IsMap():
itemDungeon = ItemType.Map if (not item.World.Config.Keysanity or item.Type != ItemType.MapHC) else ItemType.MapHC
elif item.IsCompass():
itemDungeon = ItemType.Compass
value = item.Type if location.Type == LocationType.NotInDungeon or \
not (item.IsDungeonItem() and location.Region.IsRegionItem(item) and item.World == self.myWorld) else itemDungeon
return value.value
else:
return ItemType.Something.value
def ItemTablePatch(self, location: Location, itemId: int):
itemtype = 0 if location.APLocation.item.player == location.Region.world.Id else 1
owner = location.APLocation.item.player
return (0x386000 + (location.Id * 8), getWordArray(itemtype) + getWordArray(itemId) + getWordArray(owner))
def WriteDungeonMusic(self, keysanity: bool):
if (not keysanity):
regions = [region for region in self.myWorld.Regions if isinstance(region, IReward)]
music = []
pendantRegions = [region for region in regions if region.Reward in [RewardType.PendantGreen, RewardType.PendantNonGreen]]
crystalRegions = [region for region in regions if region.Reward in [RewardType.CrystalBlue, RewardType.CrystalRed]]
regions = pendantRegions + crystalRegions
music = [
0x11, 0x11, 0x11, 0x16, 0x16,
0x16, 0x16, 0x16, 0x16, 0x16,
]
self.patches += self.MusicPatches(regions, music)
#IEnumerable<byte> RandomDungeonMusic() {
# while (true) yield return rnd.Next(2) == 0 ? (byte)0x11 : (byte)0x16;
#}
def MusicPatches(self, regions: List[IReward], music: List[int]):
addresses = [self.MusicAddresses(region) for region in regions]
associations = zip(addresses, music)
return [(Snes(i), [association[1]]) for association in associations for i in association[0]]
def MusicAddresses(self, region: IReward):
regionMap = {
EasternPalace : [ 0x2D59A ],
DesertPalace : [ 0x2D59B, 0x2D59C, 0x2D59D, 0x2D59E ],
TowerOfHera : [ 0x2D5C5, 0x2907A, 0x28B8C ],
PalaceOfDarkness : [ 0x2D5B8 ],
SwampPalace : [ 0x2D5B7 ],
SkullWoods : [ 0x2D5BA, 0x2D5BB, 0x2D5BC, 0x2D5BD, 0x2D608, 0x2D609, 0x2D60A, 0x2D60B ],
ThievesTown : [ 0x2D5C6 ],
IcePalace : [ 0x2D5BF ],
MiseryMire : [ 0x2D5B9 ],
TurtleRock : [ 0x2D5C7, 0x2D5A7, 0x2D5AA, 0x2D5AB ],
}
result = regionMap.get(type(region), None)
if result is None:
raise exception(f"Region {region} should not be a dungeon music region")
else:
return result
def WritePrizeShuffle(self):
prizePackItems = 56
treePullItems = 3
bytes = []
drop = 0
final = 0
pool = [
DropPrize.Heart, DropPrize.Heart, DropPrize.Heart, DropPrize.Heart, DropPrize.Green, DropPrize.Heart, DropPrize.Heart, DropPrize.Green, #// pack 1
DropPrize.Blue, DropPrize.Green, DropPrize.Blue, DropPrize.Red, DropPrize.Blue, DropPrize.Green, DropPrize.Blue, DropPrize.Blue, #// pack 2
DropPrize.FullMagic, DropPrize.Magic, DropPrize.Magic, DropPrize.Blue, DropPrize.FullMagic, DropPrize.Magic, DropPrize.Heart, DropPrize.Magic, #// pack 3
DropPrize.Bomb1, DropPrize.Bomb1, DropPrize.Bomb1, DropPrize.Bomb4, DropPrize.Bomb1, DropPrize.Bomb1, DropPrize.Bomb8, DropPrize.Bomb1, #// pack 4
DropPrize.Arrow5, DropPrize.Heart, DropPrize.Arrow5, DropPrize.Arrow10, DropPrize.Arrow5, DropPrize.Heart, DropPrize.Arrow5, DropPrize.Arrow10,#// pack 5
DropPrize.Magic, DropPrize.Green, DropPrize.Heart, DropPrize.Arrow5, DropPrize.Magic, DropPrize.Bomb1, DropPrize.Green, DropPrize.Heart, #// pack 6
DropPrize.Heart, DropPrize.Fairy, DropPrize.FullMagic, DropPrize.Red, DropPrize.Bomb8, DropPrize.Heart, DropPrize.Red, DropPrize.Arrow10, #// pack 7
DropPrize.Green, DropPrize.Blue, DropPrize.Red,#// from pull trees
DropPrize.Green, DropPrize.Red,#// from prize crab
DropPrize.Green, #// stunned prize
DropPrize.Red,#// saved fish prize
]
prizes = pool
self.rnd.shuffle(prizes)
#/* prize pack drop order */
(bytes, prizes) = SplitOff(prizes, prizePackItems)
self.patches.append((Snes(0x6FA78), [byte.value for byte in bytes]))
#/* tree pull prizes */
(bytes, prizes) = SplitOff(prizes, treePullItems)
self.patches.append((Snes(0x1DFBD4), [byte.value for byte in bytes]))
#/* crab prizes */
(drop, final, prizes) = (prizes[0], prizes[1], prizes[2:])
self.patches.append((Snes(0x6A9C8), [ drop.value ]))
self.patches.append((Snes(0x6A9C4), [ final.value ]))
#/* stun prize */
(drop, prizes) = (prizes[0], prizes[1:])
self.patches.append((Snes(0x6F993), [ drop.value ]))
#/* fish prize */
drop = prizes[0]
self.patches.append((Snes(0x1D82CC), [ drop.value ]))
self.patches += self.EnemyPrizePackDistribution()
#/* Pack drop chance */
#/* Normal difficulty is 50%. 0 => 100%, 1 => 50%, 3 => 25% */
nrPacks = 7
probability = 1
self.patches.append((Snes(0x6FA62), [probability] * nrPacks))
def EnemyPrizePackDistribution(self):
(prizePacks, duplicatePacks) = self.EnemyPrizePacks()
n = sum(len(x[1]) for x in prizePacks)
randomization = self.PrizePackRandomization(n, 1)
patches = []
for prizepack in prizePacks:
(packs, randomization) = SplitOff(randomization, len(prizepack[1]))
patches.append((prizepack[0], [(b | p) for b,p in zip(prizepack[1], packs)]))
duplicates = [(d[1], p[1])
for d in duplicatePacks
for p in patches
if p[0] == d[0]]
patches += duplicates
return [(Snes(x[0]), x[1]) for x in patches]
#/* Guarantees at least s of each prize pack, over a total of n packs.
#* In each iteration, from the product n * m, use the guaranteed number
#* at k, where k is the "row" (integer division by m), when k falls
#* within the list boundary. Otherwise use the "column" (modulo by m)
#* as the random element.
#*/
def PrizePackRandomization(self, n: int, s: int):
m = 7
g = list(range(0, m)) * s
def randomization(n: int):
result = []
n = m * n
while (n > 0):
r = self.rnd.randrange(0, n)
k = r // m
result.append(g[k] if k < len(g) else r % m)
if (k < len(g)): del g[k]
n -= m
return result
return [(x + 1) for x in randomization(n)]
#/* Todo: Deadrock turns into $8F Blob when powdered, but those "onion blobs" always drop prize pack 1. */
def EnemyPrizePacks(self):
offset = 0xDB632
patches = [
#/* sprite_prep */
(0x6888D, [ 0x00 ]), #// Keese DW
(0x688A8, [ 0x00 ]), #// Rope
(0x68967, [ 0x00, 0x00 ]), #// Crow/Dacto
(0x69125, [ 0x00, 0x00 ]), #// Red/Blue Hardhat Bettle
#/* sprite properties */
(offset+0x01, [ 0x90 ]), #// Vulture
(offset+0x08, [ 0x00 ]), #// Octorok (One Way)
(offset+0x0A, [ 0x00 ]), #// Octorok (Four Way)
(offset+0x0D, [ 0x80, 0x90 ]), #// Buzzblob, Snapdragon
(offset+0x11, [ 0x90, 0x90, 0x00 ]), #// Hinox, Moblin, Mini Helmasaur
(offset+0x18, [ 0x90, 0x90 ]), #// Mini Moldorm, Poe/Hyu
(offset+0x20, [ 0x00 ]), #// Sluggula
(offset+0x22, [ 0x80, 0x00, 0x00 ]), #// Ropa, Red Bari, Blue Bari
#// Blue Soldier/Tarus, Green Soldier, Red Spear Soldier
#// Blue Assault Soldier, Red Assault Spear Soldier/Tarus
#// Blue Archer, Green Archer
#// Red Javelin Soldier, Red Bush Javelin Soldier
#// Red Bomb Soldiers, Green Soldier Recruits,
#// Geldman, Toppo
(offset+0x41, [ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x10, 0x90, 0x90, 0x80 ]),
(offset+0x4F, [ 0x80 ]), #// Popo 2
(offset+0x51, [ 0x80 ]), #// Armos
(offset+0x55, [ 0x00, 0x00 ]), #// Ku, Zora
(offset+0x58, [ 0x90 ]), #// Crab
(offset+0x64, [ 0x80 ]), #// Devalant (Shooter)
(offset+0x6A, [ 0x90, 0x90 ]), #// Ball N' Chain Trooper, Cannon Soldier
(offset+0x6D, [ 0x80, 0x80 ]), #// Rat/Buzz, (Stal)Rope
(offset+0x71, [ 0x80 ]), #// Leever
(offset+0x7C, [ 0x90 ]), #// Initially Floating Stal
(offset+0x81, [ 0xC0 ]), #// Hover
#// Green Eyegore/Mimic, Red Eyegore/Mimic
#// Detached Stalfos Body, Kodongo
(offset+0x83, [ 0x10, 0x10, 0x10, 0x00 ]),
(offset+0x8B, [ 0x10 ]), #// Gibdo
(offset+0x8E, [ 0x00, 0x00 ]), #// Terrorpin, Blob
(offset+0x91, [ 0x10 ]), #// Stalfos Knight
(offset+0x99, [ 0x10 ]), #// Pengator
(offset+0x9B, [ 0x10 ]), #// Wizzrobe
#// Blue Zazak, Red Zazak, Stalfos
#// Green Zirro, Blue Zirro, Pikit
(offset+0xA5, [ 0x10, 0x10, 0x10, 0x80, 0x80, 0x80 ]),
(offset+0xC7, [ 0x10 ]), #// Hokku-Bokku
(offset+0xC9, [ 0x10 ]), #// Tektite
(offset+0xD0, [ 0x10 ]), #// Lynel
(offset+0xD3, [ 0x00 ]), #// Stal
]
duplicates = [
#/* Popo2 -> Popo. Popo is not used in vanilla Z3, but we duplicate from Popo2 just to be sure */
(offset + 0x4F, offset + 0x4E),
]
return (patches, duplicates)
def WriteTexts(self, config: Config):
regions = [region for region in self.myWorld.Regions if isinstance(region, IReward)]
greenPendantDungeon = [region for region in regions if region.Reward == RewardType.PendantGreen][0]
redCrystalDungeons = [region for region in regions if region.Reward == RewardType.CrystalRed]
sahasrahla = Texts.SahasrahlaReveal(greenPendantDungeon)
self.patches.append((Snes(0x308A00), Dialog.Simple(sahasrahla)))
self.stringTable.SetSahasrahlaRevealText(sahasrahla)
bombShop = Texts.BombShopReveal(redCrystalDungeons)
self.patches.append((Snes(0x308E00), Dialog.Simple(bombShop)))
self.stringTable.SetBombShopRevealText(bombShop)
blind = Texts.Blind(self.rnd)
self.patches.append((Snes(0x308800), Dialog.Simple(blind)))
self.stringTable.SetBlindText(blind)
tavernMan = Texts.TavernMan(self.rnd)
self.patches.append((Snes(0x308C00), Dialog.Simple(tavernMan)))
self.stringTable.SetTavernManText(tavernMan)
ganon = Texts.GanonFirstPhase(self.rnd)
self.patches.append((Snes(0x308600), Dialog.Simple(ganon)))
self.stringTable.SetGanonFirstPhaseText(ganon)
#// Todo: Verify these two are correct if ganon invincible patch is ever added
#// ganon_fall_in_alt in v30
ganonFirstPhaseInvincible = "You think you\nare ready to\nface me?\n\nI will not die\n\nunless you\ncomplete your\ngoals. Dingus!"
self.patches.append((Snes(0x309100), Dialog.Simple(ganonFirstPhaseInvincible)))
#// ganon_phase_3_alt in v30
ganonThirdPhaseInvincible = "Got wax in\nyour ears?\nI cannot die!"
self.patches.append((Snes(0x309200), Dialog.Simple(ganonThirdPhaseInvincible)))
#// ---
silversLocation = [loc for world in self.allWorlds for loc in world.Locations if loc.ItemIs(ItemType.SilverArrows, self.myWorld)]
if len(silversLocation) == 0:
silvers = Texts.GanonThirdPhaseMulti(None, self.myWorld, self.silversWorldID, self.playerIDToNames[self.silversWorldID])
else:
silvers = Texts.GanonThirdPhaseMulti(silversLocation[0].Region, self.myWorld) if config.GameMode == GameMode.Multiworld else \
Texts.GanonThirdPhaseSingle(silversLocation[0].Region)
self.patches.append((Snes(0x308700), Dialog.Simple(silvers)))
self.stringTable.SetGanonThirdPhaseText(silvers)
triforceRoom = Texts.TriforceRoom(self.rnd)
self.patches.append((Snes(0x308400), Dialog.Simple(triforceRoom)))
self.stringTable.SetTriforceRoomText(triforceRoom)
def WriteStringTable(self):
#// Todo: v12, base table in asm, use move instructions in seed patch
self.patches.append((Snes(0x1C8000), self.stringTable.GetPaddedBytes()))
def WritePlayerNames(self):
self.patches += [(0x385000 + (0 * 16), self.PlayerNameBytes("Archipelago"))]
self.patches += [(0x385000 + (id * 16), self.PlayerNameBytes(name)) for name, id in self.playerNames.items()]
def PlayerNameBytes(self, name: str):
name = (name[:16] if len(name) > 16 else name).center(16)
return bytearray(name, 'utf8')
def WriteSeedData(self):
configField = \
((1 if self.myWorld.Config.Race else 0) << 15) | \
((1 if self.myWorld.Config.Keysanity else 0) << 13) | \
((1 if self.myWorld.Config.GameMode == Config.GameMode.Multiworld else 0) << 12) | \
(self.myWorld.Config.Z3Logic.value << 10) | \
(self.myWorld.Config.SMLogic.value << 8) | \
(Patch.Major << 4) | \
(Patch.Minor << 0)
self.patches.append((Snes(0x80FF50), getWordArray(self.myWorld.Id)))
self.patches.append((Snes(0x80FF52), getWordArray(configField)))
self.patches.append((Snes(0x80FF54), getDoubleWordArray(self.seed)))
#/* Reserve the rest of the space for future use */
self.patches.append((Snes(0x80FF58), [0x00] * 8))
self.patches.append((Snes(0x80FF60), bytearray(self.seedGuid, 'utf8')))
self.patches.append((Snes(0x80FF80), bytearray(self.myWorld.Guid, 'utf8')))
def WriteCommonFlags(self):
#/* Common Combo Configuration flags at [asm]/config.asm */
if (self.myWorld.Config.GameMode == GameMode.Multiworld):
self.patches.append((Snes(0xF47000), getWordArray(0x0001)))
if (self.myWorld.Config.Keysanity):
self.patches.append((Snes(0xF47006), getWordArray(0x0001)))
def WriteGameTitle(self):
z3Glitch = "N" if self.myWorld.Config.Z3Logic == Config.Z3Logic.Nmg else \
"O" if self.myWorld.Config.Z3Logic == Config.Z3Logic.Owg else \
"C"
smGlitch = "N" if self.myWorld.Config.SMLogic == Config.SMLogic.Normal else \
"H" if self.myWorld.Config.SMLogic == Config.SMLogic.Hard else \
"X"
self.title = f"ZSM{Patch.Major}{Patch.Minor}{z3Glitch}{smGlitch}{self.myWorld.Id}{self.seed:08x}".ljust(21)[:21]
self.patches.append((Snes(0x00FFC0), bytearray(self.title, 'utf8')))
self.patches.append((Snes(0x80FFC0), bytearray(self.title, 'utf8')))
def WriteZ3KeysanityFlags(self):
if (self.myWorld.Config.Keysanity):
self.patches.append((Snes(0x40003B), [ 1 ])) #// MapMode #$00 = Always On (default) - #$01 = Require Map Item
self.patches.append((Snes(0x400045), [ 0x0f ])) #// display ----dcba a: Small Keys, b: Big Key, c: Map, d: Compass
def WriteSMKeyCardDoors(self):
if (not self.myWorld.Config.Keysanity):
return
plaquePLm = 0xd410
doorList = [
#// RoomId Door Facing yyxx Keycard Event Type Plaque type yyxx, Address (if 0 a dynamic PLM is created)
#// Crateria
[ 0x91F8, KeycardDoors.Right, 0x2601, KeycardEvents.CrateriaLevel1, KeycardPlaque.Level1, 0x2400, 0x0000 ], #// Crateria - Landing Site - Door to gauntlet
[ 0x91F8, KeycardDoors.Left, 0x168E, KeycardEvents.CrateriaLevel1, KeycardPlaque.Level1, 0x148F, 0x801E ], #// Crateria - Landing Site - Door to landing site PB
[ 0x948C, KeycardDoors.Left, 0x062E, KeycardEvents.CrateriaLevel2, KeycardPlaque.Level2, 0x042F, 0x8222 ], #// Crateria - Before Moat - Door to moat (overwrite PB door)
[ 0x99BD, KeycardDoors.Left, 0x660E, KeycardEvents.CrateriaBoss, KeycardPlaque.Boss, 0x640F, 0x8470 ], #// Crateria - Before G4 - Door to G4
[ 0x9879, KeycardDoors.Left, 0x062E, KeycardEvents.CrateriaBoss, KeycardPlaque.Boss, 0x042F, 0x8420 ], #// Crateria - Before BT - Door to Bomb Torizo
#// Brinstar
[ 0x9F11, KeycardDoors.Left, 0x060E, KeycardEvents.BrinstarLevel1, KeycardPlaque.Level1, 0x040F, 0x8784 ], #// Brinstar - Blue Brinstar - Door to ceiling e-tank room
[ 0x9AD9, KeycardDoors.Right, 0xA601, KeycardEvents.BrinstarLevel2, KeycardPlaque.Level2, 0xA400, 0x0000 ], #// Brinstar - Green Brinstar - Door to etecoon area
[ 0x9D9C, KeycardDoors.Down, 0x0336, KeycardEvents.BrinstarBoss, KeycardPlaque.Boss, 0x0234, 0x863A ], #// Brinstar - Pink Brinstar - Door to spore spawn
[ 0xA130, KeycardDoors.Left, 0x161E, KeycardEvents.BrinstarLevel2, KeycardPlaque.Level2, 0x141F, 0x881C ], #// Brinstar - Pink Brinstar - Door to wave gate e-tank
[ 0xA0A4, KeycardDoors.Left, 0x062E, KeycardEvents.BrinstarLevel2, KeycardPlaque.Level2, 0x042F, 0x0000 ], #// Brinstar - Pink Brinstar - Door to spore spawn super
[ 0xA56B, KeycardDoors.Left, 0x161E, KeycardEvents.BrinstarBoss, KeycardPlaque.Boss, 0x141F, 0x8A1A ], #// Brinstar - Before Kraid - Door to Kraid
#// Upper Norfair
[ 0xA7DE, KeycardDoors.Right, 0x3601, KeycardEvents.NorfairLevel1, KeycardPlaque.Level1, 0x3400, 0x8B00 ], #// Norfair - Business Centre - Door towards Ice
[ 0xA923, KeycardDoors.Right, 0x0601, KeycardEvents.NorfairLevel1, KeycardPlaque.Level1, 0x0400, 0x0000 ], #// Norfair - Pre-Crocomire - Door towards Ice
[ 0xA788, KeycardDoors.Left, 0x162E, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x142F, 0x8AEA ], #// Norfair - Lava Missile Room - Door towards Bubble Mountain
[ 0xAF72, KeycardDoors.Left, 0x061E, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x041F, 0x0000 ], #// Norfair - After frog speedway - Door to Bubble Mountain
[ 0xAEDF, KeycardDoors.Down, 0x0206, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x0204, 0x0000 ], #// Norfair - Below bubble mountain - Door to Bubble Mountain
[ 0xAD5E, KeycardDoors.Right, 0x0601, KeycardEvents.NorfairLevel2, KeycardPlaque.Level2, 0x0400, 0x0000 ], #// Norfair - LN Escape - Door to Bubble Mountain
[ 0xA923, KeycardDoors.Up, 0x2DC6, KeycardEvents.NorfairBoss, KeycardPlaque.Boss, 0x2EC4, 0x8B96 ], #// Norfair - Pre-Crocomire - Door to Crocomire
#// Lower Norfair
[ 0xB4AD, KeycardDoors.Left, 0x160E, KeycardEvents.LowerNorfairLevel1, KeycardPlaque.Level1, 0x140F, 0x0000 ], #// Lower Norfair - WRITG - Door to Amphitheatre
[ 0xAD5E, KeycardDoors.Left, 0x065E, KeycardEvents.LowerNorfairLevel1, KeycardPlaque.Level1, 0x045F, 0x0000 ], #// Lower Norfair - Exit - Door to "Reverse LN Entry"
[ 0xB37A, KeycardDoors.Right, 0x0601, KeycardEvents.LowerNorfairBoss, KeycardPlaque.Boss, 0x0400, 0x8EA6 ], #// Lower Norfair - Pre-Ridley - Door to Ridley
#// Maridia
[ 0xD0B9, KeycardDoors.Left, 0x065E, KeycardEvents.MaridiaLevel1, KeycardPlaque.Level1, 0x045F, 0x0000 ], #// Maridia - Mt. Everest - Door to Pink Maridia
[ 0xD5A7, KeycardDoors.Right, 0x1601, KeycardEvents.MaridiaLevel1, KeycardPlaque.Level1, 0x1400, 0x0000 ], #// Maridia - Aqueduct - Door towards Beach
[ 0xD617, KeycardDoors.Left, 0x063E, KeycardEvents.MaridiaLevel2, KeycardPlaque.Level2, 0x043F, 0x0000 ], #// Maridia - Pre-Botwoon - Door to Botwoon
[ 0xD913, KeycardDoors.Right, 0x2601, KeycardEvents.MaridiaLevel2, KeycardPlaque.Level2, 0x2400, 0x0000 ], #// Maridia - Pre-Colloseum - Door to post-botwoon
[ 0xD78F, KeycardDoors.Right, 0x2601, KeycardEvents.MaridiaBoss, KeycardPlaque.Boss, 0x2400, 0xC73B ], #// Maridia - Precious Room - Door to Draygon
[ 0xDA2B, KeycardDoors.BossLeft, 0x164E, 0x00f0, KeycardPlaque.Null, 0x144F, 0x0000 ], #// Maridia - Change Cac Alley Door to Boss Door (prevents key breaking)
#// Wrecked Ship
[ 0x93FE, KeycardDoors.Left, 0x167E, KeycardEvents.WreckedShipLevel1, KeycardPlaque.Level1, 0x147F, 0x0000 ], #// Wrecked Ship - Outside Wrecked Ship West - Door to Reserve Tank Check
[ 0x968F, KeycardDoors.Left, 0x060E, KeycardEvents.WreckedShipLevel1, KeycardPlaque.Level1, 0x040F, 0x0000 ], #// Wrecked Ship - Outside Wrecked Ship West - Door to Bowling Alley
[ 0xCE40, KeycardDoors.Left, 0x060E, KeycardEvents.WreckedShipLevel1, KeycardPlaque.Level1, 0x040F, 0x0000 ], #// Wrecked Ship - Gravity Suit - Door to Bowling Alley
[ 0xCC6F, KeycardDoors.Left, 0x064E, KeycardEvents.WreckedShipBoss, KeycardPlaque.Boss, 0x044F, 0xC29D ], #// Wrecked Ship - Pre-Phantoon - Door to Phantoon
]
doorId = 0x0000
plmTablePos = 0xf800
for door in doorList:
doorArgs = doorId | door[3] if door[4] != KeycardPlaque.Null else door[3]
if (door[6] == 0):
#// Write dynamic door
doorData = []
for x in door[0:3]:
doorData += getWordArray(x)
doorData += getWordArray(doorArgs)
self.patches.append((Snes(0x8f0000 + plmTablePos), doorData))
plmTablePos += 0x08
else:
#// Overwrite existing door
doorData = []
for x in door[1:3]:
doorData += getWordArray(x)
doorData += getWordArray(doorArgs)
self.patches.append((Snes(0x8f0000 + door[6]), doorData))
if((door[3] == KeycardEvents.BrinstarBoss and door[0] != 0x9D9C) or door[3] == KeycardEvents.LowerNorfairBoss or door[3] == KeycardEvents.MaridiaBoss or door[3] == KeycardEvents.WreckedShipBoss):
#// Overwrite the extra parts of the Gadora with a PLM that just deletes itself
self.patches.append((Snes(0x8f0000 + door[6] + 0x06), [ 0x2F, 0xB6, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xB6, 0x00, 0x00, 0x00, 0x00 ]))
#// Plaque data
if (door[4] != KeycardPlaque.Null):
plaqueData = getWordArray(door[0]) + getWordArray(plaquePLm) + getWordArray(door[5]) + getWordArray(door[4])
self.patches.append((Snes(0x8f0000 + plmTablePos), plaqueData))
plmTablePos += 0x08
doorId += 1
self.patches.append((Snes(0x8f0000 + plmTablePos), [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]))
def WriteDiggingGameRng(self):
digs = (self.rnd.randrange(30) + 1)
self.patches.append((Snes(0x308020), [ digs ]))
self.patches.append((Snes(0x1DFD95), [ digs ]))
#// Removes Sword/Shield from Uncle by moving the tiles for
#// sword/shield to his head and replaces them with his head.
def WriteRemoveEquipmentFromUncle(self, item: Item):
if (item.Type != ItemType.ProgressiveSword):
self.patches += [
(Snes(0xDD263), [ 0x00, 0x00, 0xF6, 0xFF, 0x00, 0x0E ]),
(Snes(0xDD26B), [ 0x00, 0x00, 0xF6, 0xFF, 0x00, 0x0E ]),
(Snes(0xDD293), [ 0x00, 0x00, 0xF6, 0xFF, 0x00, 0x0E ]),
(Snes(0xDD29B), [ 0x00, 0x00, 0xF7, 0xFF, 0x00, 0x0E ]),
(Snes(0xDD2B3), [ 0x00, 0x00, 0xF6, 0xFF, 0x02, 0x0E ]),
(Snes(0xDD2BB), [ 0x00, 0x00, 0xF6, 0xFF, 0x02, 0x0E ]),
(Snes(0xDD2E3), [ 0x00, 0x00, 0xF7, 0xFF, 0x02, 0x0E ]),
(Snes(0xDD2EB), [ 0x00, 0x00, 0xF7, 0xFF, 0x02, 0x0E ]),
(Snes(0xDD31B), [ 0x00, 0x00, 0xE4, 0xFF, 0x08, 0x0E ]),
(Snes(0xDD323), [ 0x00, 0x00, 0xE4, 0xFF, 0x08, 0x0E ]),
]
if (item.Type != ItemType.ProgressiveShield):
self.patches += [
(Snes(0xDD253), [ 0x00, 0x00, 0xF6, 0xFF, 0x00, 0x0E ]),
(Snes(0xDD25B), [ 0x00, 0x00, 0xF6, 0xFF, 0x00, 0x0E ]),
(Snes(0xDD283), [ 0x00, 0x00, 0xF6, 0xFF, 0x00, 0x0E ]),
(Snes(0xDD28B), [ 0x00, 0x00, 0xF7, 0xFF, 0x00, 0x0E ]),
(Snes(0xDD2CB), [ 0x00, 0x00, 0xF6, 0xFF, 0x02, 0x0E ]),
(Snes(0xDD2FB), [ 0x00, 0x00, 0xF7, 0xFF, 0x02, 0x0E ]),
(Snes(0xDD313), [ 0x00, 0x00, 0xE4, 0xFF, 0x08, 0x0E ]),
]
def WriteGanonInvicible(self, invincible: GanonInvincible):
#/* Defaults to $00 (never) at [asm]/z3/randomizer/tables.asm */
invincibleMap = {
GanonInvincible.Never : 0x00,
GanonInvincible.Always : 0x01,
GanonInvincible.BeforeAllDungeons : 0x02,
GanonInvincible.BeforeCrystals : 0x03,
}
value = invincibleMap.get(invincible, None)
if (value is None):
raise exception(f"Unknown Ganon invincible value {invincible}")
else:
self.patches.append((Snes(0x30803E), [value]))
def WriteRngBlock(self):
#/* Repoint RNG Block */
self.patches.append((0x420000, [self.rnd.randrange(0, 0x100) for x in range(0, 1024)]))
def WriteSaveAndQuitFromBossRoom(self):
#/* Defaults to $00 at [asm]/z3/randomizer/tables.asm */
self.patches.append((Snes(0x308042), [ 0x01 ]))
def WriteWorldOnAgahnimDeath(self):
pass
#/* Defaults to $01 at [asm]/z3/randomizer/tables.asm */
#// Todo: Z3r major glitches disables this, reconsider extending or dropping with glitched logic later.
#//patches.Add((Snes(0x3080A3), new byte[] { 0x01 }));
def Snes(addr: int):
#/* Redirect hi bank $30 access into ExHiRom lo bank $40 */
if (addr & 0xFF8000) == 0x308000:
addr = 0x400000 | (addr & 0x7FFF)
else: #/* General case, add ExHi offset for banks < $80, and collapse mirroring */
addr = (0x400000 if addr < 0x800000 else 0)| (addr & 0x3FFFFF)
if (addr > 0x600000):
raise Exception(f"Unmapped pc address target ${addr:x}")
return addr
def getWord(w):
return (w & 0x00FF, (w & 0xFF00) >> 8)
def getWordArray(w):
return [w & 0x00FF, (w & 0xFF00) >> 8]
def getDoubleWordArray(w):
return [w & 0x000000FF, (w & 0x0000FF00) >> 8, (w & 0x00FF0000) >> 16, (w & 0xFF000000) >> 24]
"""
byte[] UintBytes(int value) => BitConverter.GetBytes((uint)value);
byte[] UshortBytes(int value) => BitConverter.GetBytes((ushort)value);
byte[] AsAscii(string text) => Encoding.ASCII.GetBytes(text);
}
}
"""
def SplitOff(source: List[Any], count: int):
head = source[:count]
tail = source[count:]
return (head, tail)

View File

@ -0,0 +1,63 @@
from enum import Enum
from typing import Dict, List
from worlds.smz3.TotalSMZ3.Config import *
from worlds.smz3.TotalSMZ3.Item import Item, ItemType
class RewardType(Enum):
Null = 0
Agahnim = 1
PendantGreen = 2
PendantNonGreen = 3
CrystalBlue = 4
CrystalRed = 5
GoldenFourBoss = 6
class IReward:
Reward: RewardType
def CanComplete(self, items):
pass
class IMedallionAccess:
Medallion: object
class Region:
import worlds.smz3.TotalSMZ3.Location as Location
Name: str
Area: str
Locations: List[Location.Location]
Weight: int = 0
Config: Config
locationLookup: Dict[str, Location.Location]
def GetLocation(self, name: str):
return self.locationLookup[name]
def __init__(self, world, config: Config):
self.Config = config
self.world = world
self.locationLookup = {}
self.RegionItems = []
def GenerateLocationLookup(self):
self.locationLookup = {loc.Name:loc for loc in self.Locations}
def IsRegionItem(self, item):
return item.Type in self.RegionItems
def CanFill(self, item):
return self.Config.Keysanity or not item.IsDungeonItem() or self.IsRegionItem(item)
def CanEnter(self, items):
return True
class SMRegion(Region):
Logic: SMLogic = Config.SMLogic
def __init__(self, world, config: Config):
super().__init__(world, config)
class Z3Region(Region):
def __init__(self, world, config: Config):
super().__init__(world, config)

View File

@ -0,0 +1,26 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
class Blue(SMRegion):
Name = "Brinstar Blue"
Area = "Brinstar"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 26, 0x8F86EC, LocationType.Visible, "Morphing Ball"),
Location(self, 27, 0x8F874C, LocationType.Visible, "Power Bomb (blue Brinstar)",
lambda items: items.CanUsePowerBombs()),
Location(self, 28, 0x8F8798, LocationType.Visible, "Missile (blue Brinstar middle)",
lambda items: items.CardBrinstarL1 and items.Morph),
Location(self, 29, 0x8F879E, LocationType.Hidden, "Energy Tank, Brinstar Ceiling",
lambda items: items.CardBrinstarL1 and (items.CanFly() or items.HiJump or items.SpeedBooster or items.Ice) if self.Logic == SMLogic.Normal else \
lambda items: items.CardBrinstarL1),
Location(self, 34, 0x8F8802, LocationType.Chozo, "Missile (blue Brinstar bottom)",
lambda items: items.Morph),
Location(self, 36, 0x8F8836, LocationType.Visible, "Missile (blue Brinstar top)",
lambda items: items.CardBrinstarL1 and items.CanUsePowerBombs()),
Location(self, 37, 0x8F883C, LocationType.Hidden, "Missile (blue Brinstar behind missile)",
lambda items: items.CardBrinstarL1 and items.CanUsePowerBombs())
]

View File

@ -0,0 +1,37 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class Green(SMRegion):
Name = "Brinstar Green"
Area = "Brinstar"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Weight = -6
self.Locations = [
Location(self, 13, 0x8F84AC, LocationType.Chozo, "Power Bomb (green Brinstar bottom)",
lambda items: items.CardBrinstarL2 and items.CanUsePowerBombs()),
Location(self, 15, 0x8F8518, LocationType.Visible, "Missile (green Brinstar below super missile)",
lambda items: items.CanPassBombPassages() and items.CanOpenRedDoors()),
Location(self, 16, 0x8F851E, LocationType.Visible, "Super Missile (green Brinstar top)",
lambda items: items.CanOpenRedDoors() and items.SpeedBooster if self.Logic == SMLogic.Normal else \
lambda items: items.CanOpenRedDoors() and (items.Morph or items.SpeedBooster)),
Location(self, 17, 0x8F852C, LocationType.Chozo, "Reserve Tank, Brinstar",
lambda items: items.CanOpenRedDoors() and items.SpeedBooster if self.Logic == SMLogic.Normal else \
lambda items: items.CanOpenRedDoors() and (items.Morph or items.SpeedBooster)),
Location(self, 18, 0x8F8532, LocationType.Hidden, "Missile (green Brinstar behind missile)",
lambda items: items.SpeedBooster and items.CanPassBombPassages() and items.CanOpenRedDoors() if self.Logic == SMLogic.Normal else \
lambda items: (items.CanPassBombPassages() or items.Morph and items.ScrewAttack) and items.CanOpenRedDoors()),
Location(self, 19, 0x8F8538, LocationType.Visible, "Missile (green Brinstar behind reserve tank)",
lambda items: items.SpeedBooster and items.CanOpenRedDoors() and items.Morph if self.Logic == SMLogic.Normal else \
lambda items: items.CanOpenRedDoors() and items.Morph),
Location(self, 30, 0x8F87C2, LocationType.Visible, "Energy Tank, Etecoons",
lambda items: items.CardBrinstarL2 and items.CanUsePowerBombs()),
Location(self, 31, 0x8F87D0, LocationType.Visible, "Super Missile (green Brinstar bottom)",
lambda items: items.CardBrinstarL2 and items.CanUsePowerBombs() and items.Super)
]
def CanEnter(self, items: Progression):
return items.CanDestroyBombWalls() or items.SpeedBooster

View File

@ -0,0 +1,25 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion, IReward, RewardType
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class Kraid(SMRegion, IReward):
Name = "Brinstar Kraid"
Area = "Brinstar"
Reward = RewardType.GoldenFourBoss
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 43, 0x8F899C, LocationType.Hidden, "Energy Tank, Kraid", lambda items: items.CardBrinstarBoss),
Location(self, 48, 0x8F8ACA, LocationType.Chozo, "Varia Suit", lambda items: items.CardBrinstarBoss),
Location(self, 44, 0x8F89EC, LocationType.Hidden, "Missile (Kraid)", lambda items: items.CanUsePowerBombs())
]
def CanEnter(self, items:Progression):
return (items.CanDestroyBombWalls() or items.SpeedBooster or items.CanAccessNorfairUpperPortal()) and \
items.Super and items.CanPassBombPassages()
def CanComplete(self, items:Progression):
return self.GetLocation("Varia Suit").Available(items)

View File

@ -0,0 +1,44 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class Pink(SMRegion):
Name = "Brinstar Pink"
Area = "Brinstar"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Weight = -4
self.Locations = [
Location(self, 14, 0x8F84E4, LocationType.Chozo, "Super Missile (pink Brinstar)",
lambda items: items.CardBrinstarBoss and items.CanPassBombPassages() and items.Super if self.Logic == SMLogic.Normal else \
lambda items: (items.CardBrinstarBoss or items.CardBrinstarL2) and items.CanPassBombPassages() and items.Super),
Location(self, 21, 0x8F8608, LocationType.Visible, "Missile (pink Brinstar top)"),
Location(self, 22, 0x8F860E, LocationType.Visible, "Missile (pink Brinstar bottom)"),
Location(self, 23, 0x8F8614, LocationType.Chozo, "Charge Beam",
lambda items: items.CanPassBombPassages()),
Location(self, 24, 0x8F865C, LocationType.Visible, "Power Bomb (pink Brinstar)",
lambda items: items.CanUsePowerBombs() and items.Super and items.HasEnergyReserves(1) if self.Logic == SMLogic.Normal else \
lambda items: items.CanUsePowerBombs() and items.Super),
Location(self, 25, 0x8F8676, LocationType.Visible, "Missile (green Brinstar pipe)",
lambda items: items.Morph and (items.PowerBomb or items.Super or items.CanAccessNorfairUpperPortal())),
Location(self, 33, 0x8F87FA, LocationType.Visible, "Energy Tank, Waterway",
lambda items: items.CanUsePowerBombs() and items.CanOpenRedDoors() and items.SpeedBooster and (items.HasEnergyReserves(1) or items.Gravity)),
Location(self, 35, 0x8F8824, LocationType.Visible, "Energy Tank, Brinstar Gate",
lambda items: items.CardBrinstarL2 and items.CanUsePowerBombs() and items.Wave and items.HasEnergyReserves(1) if self.Logic == SMLogic.Normal else \
lambda items: items.CardBrinstarL2 and items.CanUsePowerBombs() and (items.Wave or items.Super))
]
def CanEnter(self, items: Progression):
if self.Logic == SMLogic.Normal:
return items.CanOpenRedDoors() and (items.CanDestroyBombWalls() or items.SpeedBooster) or \
items.CanUsePowerBombs() or \
items.CanAccessNorfairUpperPortal() and items.Morph and items.Wave and \
(items.Ice or items.HiJump or items.SpaceJump)
else:
return items.CanOpenRedDoors() and (items.CanDestroyBombWalls() or items.SpeedBooster) or \
items.CanUsePowerBombs() or \
items.CanAccessNorfairUpperPortal() and items.Morph and (items.CanOpenRedDoors() or items.Wave) and \
(items.Ice or items.HiJump or items.CanSpringBallJump() or items.CanFly())

View File

@ -0,0 +1,36 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class Red(SMRegion):
Name = "Brinstar Red"
Area = "Brinstar"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 38, 0x8F8876, LocationType.Chozo, "X-Ray Scope",
lambda items: items.CanUsePowerBombs() and items.CanOpenRedDoors() and (items.Grapple or items.SpaceJump) if self.Logic == SMLogic.Normal else \
lambda items: items.CanUsePowerBombs() and items.CanOpenRedDoors() and (
items.Grapple or items.SpaceJump or
(items.CanIbj() or items.HiJump and items.SpeedBooster or items.CanSpringBallJump()) and
(items.Varia and items.HasEnergyReserves(3) or items.HasEnergyReserves(5)))),
Location(self, 39, 0x8F88CA, LocationType.Visible, "Power Bomb (red Brinstar sidehopper room)",
lambda items: items.CanUsePowerBombs() and items.Super),
Location(self, 40, 0x8F890E, LocationType.Chozo, "Power Bomb (red Brinstar spike room)",
lambda items: (items.CanUsePowerBombs() or items.Ice) and items.Super if self.Logic == SMLogic.Normal else \
lambda items: items.Super),
Location(self, 41, 0x8F8914, LocationType.Visible, "Missile (red Brinstar spike room)",
lambda items: items.CanUsePowerBombs() and items.Super),
Location(self, 42, 0x8F896E, LocationType.Chozo, "Spazer",
lambda items: items.CanPassBombPassages() and items.Super)
]
def CanEnter(self, items: Progression):
if self.Logic == SMLogic.Normal:
return (items.CanDestroyBombWalls() or items.SpeedBooster) and items.Super and items.Morph or \
items.CanAccessNorfairUpperPortal() and (items.Ice or items.HiJump or items.SpaceJump)
else:
return (items.CanDestroyBombWalls() or items.SpeedBooster) and items.Super and items.Morph or \
items.CanAccessNorfairUpperPortal() and (items.Ice or items.CanSpringBallJump() or items.HiJump or items.CanFly())

View File

@ -0,0 +1,23 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
class Central(SMRegion):
Name = "Crateria Central"
Area = "Crateria"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 0, 0x8F81CC, LocationType.Visible, "Power Bomb (Crateria surface)",
lambda items: (items.CardCrateriaL1 if config.Keysanity else items.CanUsePowerBombs()) and (items.SpeedBooster or items.CanFly())),
Location(self, 12, 0x8F8486, LocationType.Visible, "Missile (Crateria middle)",
lambda items: items.CanPassBombPassages()),
Location(self, 6, 0x8F83EE, LocationType.Visible, "Missile (Crateria bottom)",
lambda items: items.CanDestroyBombWalls()),
Location(self, 11, 0x8F8478, LocationType.Visible, "Super Missile (Crateria)",
lambda items: items.CanUsePowerBombs() and items.HasEnergyReserves(2) and items.SpeedBooster),
Location(self, 7, 0x8F8404, LocationType.Chozo, "Bombs",
lambda items: (items.CardCrateriaBoss if config.Keysanity else items.CanOpenRedDoors()) and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \
lambda items: (items.CardCrateriaBoss if config.Keysanity else items.CanOpenRedDoors()) and items.Morph)
]

View File

@ -0,0 +1,59 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class East(SMRegion):
Name = "Crateria East"
Area = "Crateria"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 1, 0x8F81E8, LocationType.Visible, "Missile (outside Wrecked Ship bottom)",
lambda items: items.Morph and (
items.SpeedBooster or items.Grapple or items.SpaceJump or
items.Gravity and (items.CanIbj() or items.HiJump) or
self.world.CanEnter("Wrecked Ship", items)) if self.Logic == SMLogic.Normal else \
lambda items: items.Morph),
Location(self, 2, 0x8F81EE, LocationType.Hidden, "Missile (outside Wrecked Ship top)",
lambda items: self.world.CanEnter("Wrecked Ship", items) and (not self.Config.Keysanity or items.CardWreckedShipBoss) and items.CanPassBombPassages()),
Location(self, 3, 0x8F81F4, LocationType.Visible, "Missile (outside Wrecked Ship middle)",
lambda items: self.world.CanEnter("Wrecked Ship", items) and (not self.Config.Keysanity or items.CardWreckedShipBoss) and items.CanPassBombPassages()),
Location(self, 4, 0x8F8248, LocationType.Visible, "Missile (Crateria moat)",
lambda items: True)
]
def CanEnter(self, items:Progression):
if self.Logic == SMLogic.Normal:
# /* Ship -> Moat */
return (items.CardCrateriaL2 if self.Config.Keysanity else items.CanUsePowerBombs()) and items.Super or (
# /* UN Portal -> Red Tower -> Moat
items.CardCrateriaL2 if self.Config.Keysanity else items.CanUsePowerBombs()) and items.CanAccessNorfairUpperPortal() and (
items.Ice or items.HiJump or items.SpaceJump) or (
# /*Through Maridia From Portal*/
items.CanAccessMaridiaPortal(self.world)) and items.Gravity and items.Super and (
# /* Oasis -> Forgotten Highway */
items.CardMaridiaL2 and items.CanDestroyBombWalls() or (
# /* Draygon -> Cactus Alley -> Forgotten Highway */
self.world.GetLocation("Space Jump").Available(items))) or (
# /*Through Maridia from Pipe*/
items.CanUsePowerBombs()) and items.Super and items.Gravity
else:
# /* Ship -> Moat */
return (items.CardCrateriaL2 if self.Config.Keysanity else items.CanUsePowerBombs()) and items.Super or (
# /* UN Portal -> Red Tower -> Moat */
items.CardCrateriaL2 if self.Config.Keysanity else items.CanUsePowerBombs()) and items.CanAccessNorfairUpperPortal() and (
items.Ice or items.HiJump or items.CanFly() or items.CanSpringBallJump()) or (
# /*Through Maridia From Portal*/
items.CanAccessMaridiaPortal(self.world)) and (
# /* Oasis -> Forgotten Highway */
items.CardMaridiaL2 and items.Super and (
items.HiJump and items.CanPassBombPassages() or
items.Gravity and items.CanDestroyBombWalls()
) or
# /* Draygon -> Cactus Alley -> Forgotten Highway */
items.Gravity and self.world.GetLocation("Space Jump").Available(items)) or (
# /*Through Maridia from Pipe*/
items.CanUsePowerBombs()) and items.Super and (items.Gravity or items.HiJump and (items.Ice or items.CanSpringBallJump())
and items.Grapple and items.CardMaridiaL1)

View File

@ -0,0 +1,38 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class West(SMRegion):
Name = "Crateria West"
Area = "Crateria"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 8, 0x8F8432, LocationType.Visible, "Energy Tank, Terminator"),
Location(self, 5, 0x8F8264, LocationType.Visible, "Energy Tank, Gauntlet",
lambda items: self.CanEnterAndLeaveGauntlet(items) and items.HasEnergyReserves(1) if self.Logic == SMLogic.Normal else \
lambda items: self.CanEnterAndLeaveGauntlet(items)),
Location(self, 9, 0x8F8464, LocationType.Visible, "Missile (Crateria gauntlet right)",
lambda items: self.CanEnterAndLeaveGauntlet(items) and items.CanPassBombPassages() and items.HasEnergyReserves(2) if self.Logic == SMLogic.Normal else \
lambda items: self.CanEnterAndLeaveGauntlet(items) and items.CanPassBombPassages()),
Location(self, 10, 0x8F846A, LocationType.Visible, "Missile (Crateria gauntlet left)",
lambda items: self.CanEnterAndLeaveGauntlet(items) and items.CanPassBombPassages() and items.HasEnergyReserves(2) if self.Logic == SMLogic.Normal else \
lambda items: self.CanEnterAndLeaveGauntlet(items) and items.CanPassBombPassages())
]
def CanEnter(self, items: Progression):
return items.CanDestroyBombWalls() or items.SpeedBooster
def CanEnterAndLeaveGauntlet(self, items: Progression):
if self.Logic == SMLogic.Normal:
return items.CardCrateriaL1 and items.Morph and (items.CanFly() or items.SpeedBooster) and (
items.CanIbj() or
items.CanUsePowerBombs() and items.TwoPowerBombs or
items.ScrewAttack)
else:
return items.CardCrateriaL1 and (
items.Morph and (items.Bombs or items.TwoPowerBombs) or
items.ScrewAttack or
items.SpeedBooster and items.CanUsePowerBombs() and items.HasEnergyReserves(2))

View File

@ -0,0 +1,110 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion, IReward, RewardType
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class Inner(SMRegion, IReward):
Name = "Maridia Inner"
Area = "Maridia"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Reward = RewardType.GoldenFourBoss
self.Locations = [
Location(self, 140, 0x8FC4AF, LocationType.Visible, "Super Missile (yellow Maridia)",
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() and
(items.Gravity or items.Ice or items.HiJump and items.CanSpringBallJump())),
Location(self, 141, 0x8FC4B5, LocationType.Visible, "Missile (yellow Maridia super missile)",
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() and
(items.Gravity or items.Ice or items.HiJump and items.CanSpringBallJump())),
Location(self, 142, 0x8FC533, LocationType.Visible, "Missile (yellow Maridia false wall)",
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \
lambda items: items.CardMaridiaL1 and items.CanPassBombPassages() and
(items.Gravity or items.Ice or items.HiJump and items.CanSpringBallJump())),
Location(self, 143, 0x8FC559, LocationType.Chozo, "Plasma Beam",
lambda items: self.CanDefeatDraygon(items) and (items.ScrewAttack or items.Plasma) and (items.HiJump or items.CanFly()) if self.Logic == SMLogic.Normal else \
lambda items: self.CanDefeatDraygon(items) and
(items.Charge and items.HasEnergyReserves(3) or items.ScrewAttack or items.Plasma or items.SpeedBooster) and
(items.HiJump or items.CanSpringBallJump() or items.CanFly() or items.SpeedBooster)),
Location(self, 144, 0x8FC5DD, LocationType.Visible, "Missile (left Maridia sand pit room)",
lambda items: self.CanReachAqueduct(items) and items.Super and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Super and
(items.HiJump and (items.SpaceJump or items.CanSpringBallJump()) or items.Gravity)),
Location(self, 145, 0x8FC5E3, LocationType.Chozo, "Reserve Tank, Maridia",
lambda items: self.CanReachAqueduct(items) and items.Super and items.CanPassBombPassages() if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Super and
(items.HiJump and (items.SpaceJump or items.CanSpringBallJump()) or items.Gravity)),
Location(self, 146, 0x8FC5EB, LocationType.Visible, "Missile (right Maridia sand pit room)",
lambda items: self.CanReachAqueduct(items) and items.Super) if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Super and (items.HiJump or items.Gravity),
Location(self, 147, 0x8FC5F1, LocationType.Visible, "Power Bomb (right Maridia sand pit room)",
lambda items: self.CanReachAqueduct(items) and items.Super) if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Super and (items.HiJump and items.CanSpringBallJump() or items.Gravity),
Location(self, 148, 0x8FC603, LocationType.Visible, "Missile (pink Maridia)",
lambda items: self.CanReachAqueduct(items) and items.SpeedBooster if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Gravity),
Location(self, 149, 0x8FC609, LocationType.Visible, "Super Missile (pink Maridia)",
lambda items: self.CanReachAqueduct(items) and items.SpeedBooster if self.Logic == SMLogic.Normal else \
lambda items: self.CanReachAqueduct(items) and items.Gravity),
Location(self, 150, 0x8FC6E5, LocationType.Chozo, "Spring Ball",
lambda items: items.Super and items.Grapple and items.CanUsePowerBombs() and (items.SpaceJump or items.HiJump) if self.Logic == SMLogic.Normal else \
lambda items: items.Super and items.Grapple and items.CanUsePowerBombs() and (
items.Gravity and (items.CanFly() or items.HiJump) or
items.Ice and items.HiJump and items.CanSpringBallJump() and items.SpaceJump)),
Location(self, 151, 0x8FC74D, LocationType.Hidden, "Missile (Draygon)",
lambda items:
items.CardMaridiaL1 and items.CardMaridiaL2 and self.CanDefeatBotwoon(items) or
items.CanAccessMaridiaPortal(self.world) if self.Logic == SMLogic.Normal else \
lambda items: (
items.CardMaridiaL1 and items.CardMaridiaL2 and self.CanDefeatBotwoon(items) or
items.CanAccessMaridiaPortal(self.world)
) and items.Gravity),
Location(self, 152, 0x8FC755, LocationType.Visible, "Energy Tank, Botwoon",
lambda items:
items.CardMaridiaL1 and items.CardMaridiaL2 and self.CanDefeatBotwoon(items) or
items.CanAccessMaridiaPortal(self.world) and items.CardMaridiaL2),
Location(self, 154, 0x8FC7A7, LocationType.Chozo, "Space Jump",
lambda items: self.CanDefeatDraygon(items))
]
def CanReachAqueduct(self, items: Progression):
if self.Logic == SMLogic.Normal:
return items.CardMaridiaL1 and (items.CanFly() or items.SpeedBooster or items.Grapple) \
or items.CardMaridiaL2 and items.CanAccessMaridiaPortal(self.world)
else:
return items.CardMaridiaL1 and (items.Gravity or items.HiJump and (items.Ice or items.CanSpringBallJump()) and items.Grapple) \
or items.CardMaridiaL2 and items.CanAccessMaridiaPortal(self.world)
def CanDefeatDraygon(self, items: Progression):
if self.Logic == SMLogic.Normal:
return (items.CardMaridiaL1 and items.CardMaridiaL2 and self.CanDefeatBotwoon(items) or
items.CanAccessMaridiaPortal(self.world)
) and items.CardMaridiaBoss and items.Gravity and (items.SpeedBooster and items.HiJump or items.CanFly())
else:
return (items.CardMaridiaL1 and items.CardMaridiaL2 and self.CanDefeatBotwoon(items) or
items.CanAccessMaridiaPortal(self.world)
) and items.CardMaridiaBoss and items.Gravity
def CanDefeatBotwoon(self, items: Progression):
if self.Logic == SMLogic.Normal:
return items.SpeedBooster or items.CanAccessMaridiaPortal(self.world)
else:
return items.Ice or items.SpeedBooster and items.Gravity or items.CanAccessMaridiaPortal(self.world)
def CanEnter(self, items: Progression):
if self.Logic == SMLogic.Normal:
return items.Gravity and (
self.world.CanEnter("Norfair Upper West", items) and items.Super and items.CanUsePowerBombs() and
(items.CanFly() or items.SpeedBooster or items.Grapple) or
items.CanAccessMaridiaPortal(self.world))
else:
return items.Super and self.world.CanEnter("Norfair Upper West", items) and items.CanUsePowerBombs() and (
items.Gravity or items.HiJump and (items.Ice or items.CanSpringBallJump()) and items.Grapple) or \
items.CanAccessMaridiaPortal(self.world)
def CanComplete(self, items: Progression):
return self.GetLocation("Space Jump").Available(items)

View File

@ -0,0 +1,37 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class Outer(SMRegion):
Name = "Maridia Outer"
Area = "Maridia"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 136, 0x8FC437, LocationType.Visible, "Missile (green Maridia shinespark)",
lambda items: items.SpeedBooster if self.Logic == SMLogic.Normal else \
lambda items: items.Gravity and items.SpeedBooster),
Location(self, 137, 0x8FC43D, LocationType.Visible, "Super Missile (green Maridia)"),
Location(self, 138, 0x8FC47D, LocationType.Visible, "Energy Tank, Mama turtle",
lambda items: items.CanOpenRedDoors() and (items.CanFly() or items.SpeedBooster or items.Grapple) if self.Logic == SMLogic.Normal else \
lambda items: items.CanOpenRedDoors() and (
items.CanFly() or items.SpeedBooster or items.Grapple or
items.CanSpringBallJump() and (items.Gravity or items.HiJump))),
Location(self, 139, 0x8FC483, LocationType.Hidden, "Missile (green Maridia tatori)",
lambda items: items.CanOpenRedDoors())
]
def CanEnter(self, items:Progression):
if self.Logic == SMLogic.Normal:
return items.Gravity and (
self.world.CanEnter("Norfair Upper West", items) and items.CanUsePowerBombs() or
items.CanAccessMaridiaPortal(self.world) and items.CardMaridiaL1 and items.CardMaridiaL2 and (items.CanPassBombPassages() or items.ScrewAttack))
else:
return self.world.CanEnter("Norfair Upper West", items) and items.CanUsePowerBombs() and (
items.Gravity or items.HiJump and (items.CanSpringBallJump() or items.Ice)) or (
items.CanAccessMaridiaPortal(self.world)) and items.CardMaridiaL1 and items.CardMaridiaL2 and (
items.CanPassBombPassages() or
items.Gravity and items.ScrewAttack or
items.Super and (items.Gravity or items.HiJump and (items.CanSpringBallJump() or items.Ice)))

View File

@ -0,0 +1,60 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion, IReward, RewardType
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class East(SMRegion, IReward):
Name = "Norfair Lower East"
Area = "Norfair Lower"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Reward = RewardType.GoldenFourBoss
self.Locations = [
Location(self, 74, 0x8F8FCA, LocationType.Visible, "Missile (lower Norfair above fire flea room)",
lambda items: self.CanExit(items)),
Location(self, 75, 0x8F8FD2, LocationType.Visible, "Power Bomb (lower Norfair above fire flea room)",
lambda items: self.CanExit(items)) if self.Logic == SMLogic.Normal else \
lambda items: self.CanExit(items) and items.CanPassBombPassages(),
Location(self, 76, 0x8F90C0, LocationType.Visible, "Power Bomb (Power Bombs of shame)",
lambda items: self.CanExit(items) and items.CanUsePowerBombs()),
Location(self, 77, 0x8F9100, LocationType.Visible, "Missile (lower Norfair near Wave Beam)",
lambda items: self.CanExit(items)) if self.Logic == SMLogic.Normal else \
lambda items: self.CanExit(items) and items.Morph and items.CanDestroyBombWalls(),
Location(self, 78, 0x8F9108, LocationType.Hidden, "Energy Tank, Ridley",
lambda items: self.CanExit(items) and items.CardLowerNorfairBoss and items.CanUsePowerBombs() and items.Super),
Location(self, 80, 0x8F9184, LocationType.Visible, "Energy Tank, Firefleas",
lambda items: self.CanExit(items))
]
def CanExit(self, items:Progression):
if self.Logic == SMLogic.Normal:
# /*Bubble Mountain*/
return items.CardNorfairL2 or (
# /* Volcano Room and Blue Gate */
items.Gravity) and items.Wave and (
# /*Spikey Acid Snakes and Croc Escape*/
items.Grapple or items.SpaceJump)
else:
# /*Vanilla LN Escape*/
return (items.Morph and (items.CardNorfairL2 or # /*Bubble Mountain*/
(items.Missile or items.Super or items.Wave) and # /* Blue Gate */
(items.SpeedBooster or items.CanFly() or items.Grapple or items.HiJump and
(items.CanSpringBallJump() or items.Ice))) or # /*Frog Speedway or Croc Escape*/
# /*Reverse Amphitheater*/
items.HasEnergyReserves(5))
def CanEnter(self, items:Progression):
if self.Logic == SMLogic.Normal:
return items.Varia and items.CardLowerNorfairL1 and (
self.world.CanEnter("Norfair Upper East", items) and items.CanUsePowerBombs() and items.SpaceJump and items.Gravity or
items.CanAccessNorfairLowerPortal() and items.CanDestroyBombWalls() and items.Super and items.CanUsePowerBombs() and items.CanFly())
else:
return items.Varia and items.CardLowerNorfairL1 and (
self.world.CanEnter("Norfair Upper East", items) and items.CanUsePowerBombs() and (items.HiJump or items.Gravity) or
items.CanAccessNorfairLowerPortal() and items.CanDestroyBombWalls() and items.Super and (items.CanFly() or items.CanSpringBallJump() or items.SpeedBooster)) and (
items.CanFly() or items.HiJump or items.CanSpringBallJump() or items.Ice and items.Charge) and (
items.CanPassBombPassages() or items.ScrewAttack and items.SpaceJump)
def CanComplete(self, items:Progression):
return self.GetLocation("Energy Tank, Ridley").Available(items)

View File

@ -0,0 +1,67 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class West(SMRegion):
Name = "Norfair Lower West"
Area = "Norfair Lower"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 70, 0x8F8E6E, LocationType.Visible, "Missile (Gold Torizo)",
lambda items: items.CanUsePowerBombs() and items.SpaceJump and items.Super if self.Logic == SMLogic.Normal else \
lambda items: items.CanUsePowerBombs() and items.SpaceJump and items.Varia and (
items.HiJump or items.Gravity or
items.CanAccessNorfairLowerPortal() and (items.CanFly() or items.CanSpringBallJump() or items.SpeedBooster) and items.Super)),
Location(self, 71, 0x8F8E74, LocationType.Hidden, "Super Missile (Gold Torizo)",
lambda items: items.CanDestroyBombWalls() and (items.Super or items.Charge) and
(items.CanAccessNorfairLowerPortal() or items.SpaceJump and items.CanUsePowerBombs()) if self.Logic == SMLogic.Normal else \
lambda items: items.CanDestroyBombWalls() and items.Varia and (items.Super or items.Charge)),
Location(self, 79, 0x8F9110, LocationType.Chozo, "Screw Attack",
lambda items: items.CanDestroyBombWalls() and (items.SpaceJump and items.CanUsePowerBombs() or items.CanAccessNorfairLowerPortal()) if self.Logic == SMLogic.Normal else \
lambda items: items.CanDestroyBombWalls() and (items.Varia or items.CanAccessNorfairLowerPortal())),
Location(self, 73, 0x8F8F30, LocationType.Visible, "Missile (Mickey Mouse room)",
lambda items: items.CanFly() and items.Morph and items.Super and (
# /*Exit to Upper Norfair*/
(items.CardLowerNorfairL1 or
# /*Vanilla or Reverse Lava Dive*/
items.Gravity) and
# /*Bubble Mountain*/
items.CardNorfairL2 or
# /* Volcano Room and Blue Gate */
items.Gravity and items.Wave and
# /*Spikey Acid Snakes and Croc Escape*/
(items.Grapple or items.SpaceJump) or
# /*Exit via GT fight and Portal*/
(items.CanUsePowerBombs() and items.SpaceJump and (items.Super or items.Charge))) if self.Logic == SMLogic.Normal else \
lambda items:
items.Morph and items.Varia and items.Super and ((items.CanFly() or items.CanSpringBallJump() or
items.SpeedBooster and (items.HiJump and items.CanUsePowerBombs() or items.Charge and items.Ice)) and
# /*Exit to Upper Norfair*/
(items.CardNorfairL2 or (items.SpeedBooster or items.CanFly() or items.Grapple or items.HiJump and
(items.CanSpringBallJump() or items.Ice))) or
# /*Return to Portal*/
items.CanUsePowerBombs()))
]
# // Todo: account for Croc Speedway once Norfair Upper East also do so, otherwise it would be inconsistent to do so here
def CanEnter(self, items:Progression):
if self.Logic == SMLogic.Normal:
return items.Varia and (
self.world.CanEnter("Norfair Upper East", items) and items.CanUsePowerBombs() and items.SpaceJump and items.Gravity and (
# /* Trivial case, Bubble Mountain access */
items.CardNorfairL2 or
# /* Frog Speedway -> UN Farming Room gate */
items.SpeedBooster and items.Wave
) or
items.CanAccessNorfairLowerPortal() and items.CanDestroyBombWalls()
)
else:
return self.world.CanEnter("Norfair Upper East", items) and items.CanUsePowerBombs() and items.Varia and (items.HiJump or items.Gravity) and (
# /* Trivial case, Bubble Mountain access */
items.CardNorfairL2 or
# /* Frog Speedway -> UN Farming Room gate */
items.SpeedBooster and (items.Missile or items.Super or items.Wave) # /* Blue Gate */
) or items.CanAccessNorfairLowerPortal() and items.CanDestroyBombWalls()

View File

@ -0,0 +1,69 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class Crocomire(SMRegion):
Name = "Norfair Upper Crocomire"
Area = "Norfair Upper"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 52, 0x8F8BA4, LocationType.Visible, "Energy Tank, Crocomire",
lambda items: self.CanAccessCrocomire(items) and (items.HasEnergyReserves(1) or items.SpaceJump or items.Grapple) if self.Logic == SMLogic.Normal else \
lambda items: self.CanAccessCrocomire(items)),
Location(self, 54, 0x8F8BC0, LocationType.Visible, "Missile (above Crocomire)",
lambda items: items.CanFly() or items.Grapple or items.HiJump and items.SpeedBooster if self.Logic == SMLogic.Normal else \
lambda items: (items.CanFly() or items.Grapple or items.HiJump and
(items.SpeedBooster or items.CanSpringBallJump() or items.Varia and items.Ice)) and items.CanHellRun()),
Location(self, 57, 0x8F8C04, LocationType.Visible, "Power Bomb (Crocomire)",
lambda items: self.CanAccessCrocomire(items) and (items.CanFly() or items.HiJump or items.Grapple) if self.Logic == SMLogic.Normal else \
lambda items: self.CanAccessCrocomire(items)),
Location(self, 58, 0x8F8C14, LocationType.Visible, "Missile (below Crocomire)",
lambda items: self.CanAccessCrocomire(items) and items.Morph),
Location(self, 59, 0x8F8C2A, LocationType.Visible, "Missile (Grappling Beam)",
lambda items: self.CanAccessCrocomire(items) and items.Morph and (items.CanFly() or items.SpeedBooster and items.CanUsePowerBombs()) if self.Logic == SMLogic.Normal else \
lambda items: self.CanAccessCrocomire(items) and (items.SpeedBooster or items.Morph and (items.CanFly() or items.Grapple))),
Location(self, 60, 0x8F8C36, LocationType.Chozo, "Grappling Beam",
lambda items: self.CanAccessCrocomire(items) and items.Morph and (items.CanFly() or items.SpeedBooster and items.CanUsePowerBombs()) if self.Logic == SMLogic.Normal else \
lambda items: self.CanAccessCrocomire(items) and (items.SpaceJump or items.Morph or items.Grapple or
items.HiJump and items.SpeedBooster))
]
def CanAccessCrocomire(self, items:Progression):
return items.CardNorfairBoss if self.Config.Keysanity else items.Super
def CanEnter(self, items:Progression):
if self.Logic == SMLogic.Normal:
return ((items.CanDestroyBombWalls() or items.SpeedBooster) and items.Super and items.Morph or items.CanAccessNorfairUpperPortal()) and (
items.Varia) and (
# /* Ice Beam -> Croc Speedway */
(items.CardNorfairL1 if self.Config.Keysanity else items.Super) and items.CanUsePowerBombs() and items.SpeedBooster or
# /* Frog Speedway */
items.SpeedBooster and items.Wave or
# /* Cathedral -> through the floor or Vulcano */
items.CanOpenRedDoors() and (items.CardNorfairL2 if self.Config.Keysanity else items.Super) and
(items.CanFly() or items.HiJump or items.SpeedBooster) and
(items.CanPassBombPassages() or items.Gravity and items.Morph) and items.Wave
or
# /* Reverse Lava Dive */
items.CanAccessNorfairLowerPortal() and items.ScrewAttack and items.SpaceJump and items.Super and
items.Gravity and items.Wave and (items.CardNorfairL2 or items.Morph))
else:
return ((items.CanDestroyBombWalls() or items.SpeedBooster) and items.Super and items.Morph or items.CanAccessNorfairUpperPortal()) and (
# /* Ice Beam -> Croc Speedway */
(items.CardNorfairL1 if self.Config.Keysanity else items.Super) and items.CanUsePowerBombs() and
items.SpeedBooster and (items.HasEnergyReserves(3) or items.Varia) or
# /* Frog Speedway */
items.SpeedBooster and (items.HasEnergyReserves(2) or items.Varia) and
(items.Missile or items.Super or items.Wave) or ( # /* Blue Gate */
# /* Cathedral -> through the floor or Vulcano */
items.CanHellRun()) and items.CanOpenRedDoors() and (items.CardNorfairL2 if self.Config.Keysanity else items.Super) and
(items.CanFly() or items.HiJump or items.SpeedBooster or items.CanSpringBallJump() or items.Varia and items.Ice) and
(items.CanPassBombPassages() or items.Varia and items.Morph) and
(items.Missile or items.Super or items.Wave) # /* Blue Gate */
) or (
# /* Reverse Lava Dive */
items.CanAccessNorfairLowerPortal()) and items.ScrewAttack and items.SpaceJump and items.Varia and items.Super and (
items.HasEnergyReserves(2)) and (items.CardNorfairL2 or items.Morph)

View File

@ -0,0 +1,94 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class East(SMRegion):
Name = "Norfair Upper East"
Area = "Norfair Upper"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 61, 0x8F8C3E, LocationType.Chozo, "Reserve Tank, Norfair",
lambda items: items.CardNorfairL2 and items.Morph and (
items.CanFly() or
items.Grapple and (items.SpeedBooster or items.CanPassBombPassages()) or
items.HiJump or items.Ice
) if self.Logic == SMLogic.Normal else \
lambda items: items.CardNorfairL2 and items.Morph and items.Super),
Location(self, 62, 0x8F8C44, LocationType.Hidden, "Missile (Norfair Reserve Tank)",
lambda items: items.CardNorfairL2 and items.Morph and (
items.CanFly() or
items.Grapple and (items.SpeedBooster or items.CanPassBombPassages()) or
items.HiJump or items.Ice
) if self.Logic == SMLogic.Normal else \
lambda items: items.CardNorfairL2 and items.Morph and items.Super),
Location(self, 63, 0x8F8C52, LocationType.Visible, "Missile (bubble Norfair green door)",
lambda items: items.CardNorfairL2 and (
items.CanFly() or
items.Grapple and items.Morph and (items.SpeedBooster or items.CanPassBombPassages()) or
items.HiJump or items.Ice
) if self.Logic == SMLogic.Normal else \
lambda items: items.CardNorfairL2 and items.Super),
Location(self, 64, 0x8F8C66, LocationType.Visible, "Missile (bubble Norfair)",
lambda items: items.CardNorfairL2),
Location(self, 65, 0x8F8C74, LocationType.Hidden, "Missile (Speed Booster)",
lambda items: items.CardNorfairL2 and (
items.CanFly() or
items.Morph and (items.SpeedBooster or items.CanPassBombPassages()) or
items.HiJump or items.Ice
) if self.Logic == SMLogic.Normal else \
lambda items: items.CardNorfairL2 and items.Super),
Location(self, 66, 0x8F8C82, LocationType.Chozo, "Speed Booster",
lambda items: items.CardNorfairL2 and (
items.CanFly() or
items.Morph and (items.SpeedBooster or items.CanPassBombPassages()) or
items.HiJump or items.Ice
) if self.Logic == SMLogic.Normal else \
lambda items: items.CardNorfairL2 and items.Super),
Location(self, 67, 0x8F8CBC, LocationType.Visible, "Missile (Wave Beam)",
lambda items: items.CardNorfairL2 and (
items.CanFly() or
items.Morph and (items.SpeedBooster or items.CanPassBombPassages()) or
items.HiJump or items.Ice
) or
items.SpeedBooster and items.Wave and items.Morph and items.Super if self.Logic == SMLogic.Normal else \
lambda items: items.CardNorfairL2 or items.Varia),
Location(self, 68, 0x8F8CCA, LocationType.Chozo, "Wave Beam",
lambda items: items.Morph and (
items.CardNorfairL2 and (
items.CanFly() or
items.Morph and (items.SpeedBooster or items.CanPassBombPassages()) or
items.HiJump or items.Ice
) or
items.SpeedBooster and items.Wave and items.Morph and items.Super
) if self.Logic == SMLogic.Normal else \
lambda items: items.CanOpenRedDoors() and (items.CardNorfairL2 or items.Varia) and
(items.Morph or items.Grapple or items.HiJump and items.Varia or items.SpaceJump))
]
# // Todo: Super is not actually needed for Frog Speedway, but changing this will affect locations
# // Todo: Ice Beam -> Croc Speedway is not considered
def CanEnter(self, items:Progression):
if self.Logic == SMLogic.Normal:
return ((items.CanDestroyBombWalls() or items.SpeedBooster) and items.Super and items.Morph or
items.CanAccessNorfairUpperPortal()
) and items.Varia and items.Super and (
# /* Cathedral */
items.CanOpenRedDoors() and (items.CardNorfairL2 if self.Config.Keysanity else items.Super) and
(items.CanFly() or items.HiJump or items.SpeedBooster) or
# /* Frog Speedway */
items.SpeedBooster and (items.CardNorfairL2 or items.Wave) and items.CanUsePowerBombs()
)
else:
return ((items.CanDestroyBombWalls() or items.SpeedBooster) and items.Super and items.Morph or
items.CanAccessNorfairUpperPortal()) and (
items.CanHellRun()) and (
# /* Cathedral */
items.CanOpenRedDoors() and (items.CardNorfairL2 if self.Config.Keysanity else items.Super) and (
items.CanFly() or items.HiJump or items.SpeedBooster or
items.CanSpringBallJump() or items.Varia and items.Ice
) or
# /* Frog Speedway */
items.SpeedBooster and (items.CardNorfairL2 or items.Missile or items.Super or items.Wave) and items.CanUsePowerBombs())

View File

@ -0,0 +1,44 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class West(SMRegion):
Name = "Norfair Upper West"
Area = "Norfair Upper"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 49, 0x8F8AE4, LocationType.Hidden, "Missile (lava room)",
lambda items: items.Varia and (
items.CanOpenRedDoors() and (items.CanFly() or items.HiJump or items.SpeedBooster) or
self.world.CanEnter("Norfair Upper East", items) and items.CardNorfairL2
) and items.Morph if self.Logic == SMLogic.Normal else \
lambda items: items.CanHellRun() and (
items.CanOpenRedDoors() and (
items.CanFly() or items.HiJump or items.SpeedBooster or
items.CanSpringBallJump() or items.Varia and items.Ice
) or
self.world.CanEnter("Norfair Upper East", items) and items.CardNorfairL2
) and items.Morph),
Location(self, 50, 0x8F8B24, LocationType.Chozo, "Ice Beam",
lambda items: (items.CardNorfairL1 if config.Keysanity else items.Super) and items.CanPassBombPassages() and items.Varia and items.SpeedBooster if self.Logic == SMLogic.Normal else \
lambda items: (items.CardNorfairL1 if config.Keysanity else items.Super) and items.Morph and (items.Varia or items.HasEnergyReserves(3))),
Location(self, 51, 0x8F8B46, LocationType.Hidden, "Missile (below Ice Beam)",
lambda items: (items.CardNorfairL1 if config.Keysanity else items.Super) and items.CanUsePowerBombs() and items.Varia and items.SpeedBooster if self.Logic == SMLogic.Normal else \
lambda items:
(items.CardNorfairL1 if config.Keysanity else items.Super) and items.CanUsePowerBombs() and (items.Varia or items.HasEnergyReserves(3)) or
(items.Missile or items.Super or items.Wave) and items.Varia and items.SpeedBooster and # /* Blue Gate */
# /* Access to Croc's room to get spark */
(items.CardNorfairBoss if config.Keysanity else items.Super) and items.CardNorfairL1),
Location(self, 53, 0x8F8BAC, LocationType.Chozo, "Hi-Jump Boots",
lambda items: items.CanOpenRedDoors() and items.CanPassBombPassages()),
Location(self, 55, 0x8F8BE6, LocationType.Visible, "Missile (Hi-Jump Boots)",
lambda items: items.CanOpenRedDoors() and items.Morph),
Location(self, 56, 0x8F8BEC, LocationType.Visible, "Energy Tank (Hi-Jump Boots)",
lambda items: items.CanOpenRedDoors())
]
def CanEnter(self, items:Progression):
return (items.CanDestroyBombWalls() or items.SpeedBooster) and items.Super and items.Morph or items.CanAccessNorfairUpperPortal()

View File

@ -0,0 +1,77 @@
from worlds.smz3.TotalSMZ3.Region import SMRegion, IReward, RewardType
from worlds.smz3.TotalSMZ3.Config import Config, SMLogic
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class WreckedShip(SMRegion, IReward):
Name = "Wrecked Ship"
Area = "Wrecked Ship"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Reward = RewardType.GoldenFourBoss
self.Locations = [
Location(self, 128, 0x8FC265, LocationType.Visible, "Missile (Wrecked Ship middle)",
lambda items: items.CanPassBombPassages()),
Location(self, 129, 0x8FC2E9, LocationType.Chozo, "Reserve Tank, Wrecked Ship",
lambda items: self.CanUnlockShip(items) and items.CardWreckedShipL1 and items.SpeedBooster and items.CanUsePowerBombs() and
(items.Grapple or items.SpaceJump or items.Varia and items.HasEnergyReserves(2) or items.HasEnergyReserves(3)) if self.Logic == SMLogic.Normal else \
lambda items: self.CanUnlockShip(items) and items.CardWreckedShipL1 and items.CanUsePowerBombs() and items.SpeedBooster and
(items.Varia or items.HasEnergyReserves(2))),
Location(self, 130, 0x8FC2EF, LocationType.Visible, "Missile (Gravity Suit)",
lambda items: self.CanUnlockShip(items) and items.CardWreckedShipL1 and
(items.Grapple or items.SpaceJump or items.Varia and items.HasEnergyReserves(2) or items.HasEnergyReserves(3)) if self.Logic == SMLogic.Normal else \
lambda items: self.CanUnlockShip(items) and items.CardWreckedShipL1 and (items.Varia or items.HasEnergyReserves(1))),
Location(self, 131, 0x8FC319, LocationType.Visible, "Missile (Wrecked Ship top)",
lambda items: self.CanUnlockShip(items)),
Location(self, 132, 0x8FC337, LocationType.Visible, "Energy Tank, Wrecked Ship",
lambda items: self.CanUnlockShip(items) and
(items.HiJump or items.SpaceJump or items.SpeedBooster or items.Gravity) if self.Logic == SMLogic.Normal else \
lambda items: self.CanUnlockShip(items) and (items.Bombs or items.PowerBomb or items.CanSpringBallJump() or
items.HiJump or items.SpaceJump or items.SpeedBooster or items.Gravity)),
Location(self, 133, 0x8FC357, LocationType.Visible, "Super Missile (Wrecked Ship left)",
lambda items: self.CanUnlockShip(items)),
Location(self, 134, 0x8FC365, LocationType.Visible, "Right Super, Wrecked Ship",
lambda items: self.CanUnlockShip(items)),
Location(self, 135, 0x8FC36D, LocationType.Chozo, "Gravity Suit",
lambda items: self.CanUnlockShip(items) and items.CardWreckedShipL1 and
(items.Grapple or items.SpaceJump or items.Varia and items.HasEnergyReserves(2) or items.HasEnergyReserves(3)) if self.Logic == SMLogic.Normal else \
lambda items: self.CanUnlockShip(items) and items.CardWreckedShipL1 and (items.Varia or items.HasEnergyReserves(1)))
]
def CanUnlockShip(self, items:Progression):
return items.CardWreckedShipBoss and items.CanPassBombPassages()
def CanEnter(self, items:Progression):
if self.Logic == SMLogic.Normal:
return items.Super and (
# /* Over the Moat */
(items.CardCrateriaL2 if self.Config.Keysanity else items.CanUsePowerBombs()) and (
items.SpeedBooster or items.Grapple or items.SpaceJump or
items.Gravity and (items.CanIbj() or items.HiJump)
) or
# /* Through Maridia -> Forgotten Highway */
items.CanUsePowerBombs() and items.Gravity or
# /* From Maridia portal -> Forgotten Highway */
items.CanAccessMaridiaPortal(self.world) and items.Gravity and (
items.CanDestroyBombWalls() and items.CardMaridiaL2 or
self.world.GetLocation("Space Jump").Available(items)))
else:
return items.Super and (
# /* Over the Moat */
(items.CardCrateriaL2 if self.Config.Keysanity else items.CanUsePowerBombs()) or
# /* Through Maridia -> Forgotten Highway */
items.CanUsePowerBombs() and (
items.Gravity or
# /* Climb Mt. Everest */
items.HiJump and (items.Ice or items.CanSpringBallJump()) and items.Grapple and items.CardMaridiaL1
) or
# /* From Maridia portal -> Forgotten Highway */
items.CanAccessMaridiaPortal(self.world) and (
items.HiJump and items.CanPassBombPassages() and items.CardMaridiaL2 or
items.Gravity and (
items.CanDestroyBombWalls() and items.CardMaridiaL2 or
self.world.GetLocation("Space Jump").Available(items))))
def CanComplete(self, items:Progression):
return self.CanEnter(items) and self.CanUnlockShip(items)

View File

@ -0,0 +1,25 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType, IReward
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression, ItemType
class CastleTower(Z3Region, IReward):
Name = "Castle Tower"
Area = "Castle Tower"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Reward = RewardType.Agahnim
self.RegionItems = [ItemType.KeyCT]
self.Locations = [
Location(self, 256+101, 0x1EAB5, LocationType.Regular, "Castle Tower - Foyer"),
Location(self, 256+102, 0x1EAB2, LocationType.Regular, "Castle Tower - Dark Maze",
lambda items: items.Lamp and items.KeyCT >= 1)
]
def CanEnter(self, items: Progression):
return items.CanKillManyEnemies() and (items.Cape or items.MasterSword)
def CanComplete(self, items: Progression):
return self.CanEnter(items) and items.Lamp and items.KeyCT >= 2 and items.Sword

View File

@ -0,0 +1,28 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class East(Z3Region):
Name = "Dark World Death Mountain East"
Area = "Dark World"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 256+65, 0x1EB51, LocationType.Regular, "Hookshot Cave - Top Right",
lambda items: items.MoonPearl and items.Hookshot),
Location(self, 256+66, 0x1EB54, LocationType.Regular, "Hookshot Cave - Top Left",
lambda items: items.MoonPearl and items.Hookshot),
Location(self, 256+67, 0x1EB57, LocationType.Regular, "Hookshot Cave - Bottom Left",
lambda items: items.MoonPearl and items.Hookshot),
Location(self, 256+68, 0x1EB5A, LocationType.Regular, "Hookshot Cave - Bottom Right",
lambda items: items.MoonPearl and (items.Hookshot or items.Boots)),
Location(self, 256+69, 0x1EA7C, LocationType.Regular, "Superbunny Cave - Top",
lambda items: items.MoonPearl),
Location(self, 256+70, 0x1EA7F, LocationType.Regular, "Superbunny Cave - Bottom",
lambda items: items.MoonPearl)
]
def CanEnter(self, items: Progression):
return items.CanLiftHeavy() and self.world.CanEnter("Light World Death Mountain East", items)

View File

@ -0,0 +1,16 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
class West(Z3Region):
Name = "Dark World Death Mountain West"
Area = "Dark World"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 256+64, 0x1EA8B, LocationType.Regular, "Spike Cave",
lambda items: items.MoonPearl and items.Hammer and items.CanLiftLight() and
(items.CanExtendMagic() and items.Cape or items.Byrna) and
self.world.CanEnter("Light World Death Mountain West", items))
]

View File

@ -0,0 +1,20 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class Mire(Z3Region):
Name = "Dark World Mire"
Area = "Dark World"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 256+89, 0x1EA73, LocationType.Regular, "Mire Shed - Left",
lambda items: items.MoonPearl),
Location(self, 256+90, 0x1EA76, LocationType.Regular, "Mire Shed - Right",
lambda items: items.MoonPearl)
]
def CanEnter(self, items: Progression):
return items.Flute and items.CanLiftHeavy() or items.CanAccessMiseryMirePortal(Config)

View File

@ -0,0 +1,28 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class NorthEast(Z3Region):
Name = "Dark World North East"
Area = "Dark World"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 256+78, 0x1DE185, LocationType.Regular, "Catfish",
lambda items: items.MoonPearl and items.CanLiftLight()),
Location(self, 256+79, 0x308147, LocationType.Regular, "Pyramid"),
Location(self, 256+80, 0x1E980, LocationType.Regular, "Pyramid Fairy - Left",
lambda items: self.world.CanAquireAll(items, RewardType.CrystalRed) and items.MoonPearl and self.world.CanEnter("Dark World South", items) and
(items.Hammer or items.Mirror and self.world.CanAquire(items, RewardType.Agahnim))),
Location(self, 256+81, 0x1E983, LocationType.Regular, "Pyramid Fairy - Right",
lambda items: self.world.CanAquireAll(items, RewardType.CrystalRed) and items.MoonPearl and self.world.CanEnter("Dark World South", items) and
(items.Hammer or items.Mirror and self.world.CanAquire(items, RewardType.Agahnim)))
]
def CanEnter(self, items: Progression):
return self.world.CanAquire(items, RewardType.Agahnim) or items.MoonPearl and (
items.Hammer and items.CanLiftLight() or
items.CanLiftHeavy() and items.Flippers or
items.CanAccessDarkWorldPortal(self.Config) and items.Flippers)

View File

@ -0,0 +1,32 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class NorthWest(Z3Region):
Name = "Dark World North West"
Area = "Dark World"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 256+71, 0x308146, LocationType.Regular, "Bumper Cave",
lambda items: items.CanLiftLight() and items.Cape),
Location(self, 256+72, 0x1EDA8, LocationType.Regular, "Chest Game"),
Location(self, 256+73, 0x1E9EF, LocationType.Regular, "C-Shaped House"),
Location(self, 256+74, 0x1E9EC, LocationType.Regular, "Brewery"),
Location(self, 256+75, 0x308006, LocationType.Regular, "Hammer Pegs",
lambda items: items.CanLiftHeavy() and items.Hammer),
Location(self, 256+76, 0x30802A, LocationType.Regular, "Blacksmith",
lambda items: items.CanLiftHeavy()),
Location(self, 256+77, 0x6BD68, LocationType.Regular, "Purple Chest",
lambda items: items.CanLiftHeavy())
]
def CanEnter(self, items: Progression):
return items.MoonPearl and ((
self.world.CanAquire(items, RewardType.Agahnim) or
items.CanAccessDarkWorldPortal(self.Config) and items.Flippers
) and items.Hookshot and (items.Flippers or items.CanLiftLight() or items.Hammer) or
items.Hammer and items.CanLiftLight() or
items.CanLiftHeavy())

View File

@ -0,0 +1,28 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class South(Z3Region):
Name = "Dark World South"
Area = "Dark World"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 256+82, 0x308148, LocationType.Regular, "Digging Game"),
Location(self, 256+83, 0x6B0C7, LocationType.Regular, "Stumpy"),
Location(self, 256+84, 0x1EB1E, LocationType.Regular, "Hype Cave - Top"),
Location(self, 256+85, 0x1EB21, LocationType.Regular, "Hype Cave - Middle Right"),
Location(self, 256+86, 0x1EB24, LocationType.Regular, "Hype Cave - Middle Left"),
Location(self, 256+87, 0x1EB27, LocationType.Regular, "Hype Cave - Bottom"),
Location(self, 256+88, 0x308011, LocationType.Regular, "Hype Cave - NPC")
]
def CanEnter(self, items: Progression):
return items.MoonPearl and ((
self.world.CanAquire(items, RewardType.Agahnim) or
items.CanAccessDarkWorldPortal(self.Config) and items.Flippers
) and (items.Hammer or items.Hookshot and (items.Flippers or items.CanLiftLight())) or
items.Hammer and items.CanLiftLight() or
items.CanLiftHeavy())

View File

@ -0,0 +1,42 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType, IReward
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import ItemType, Progression
class DesertPalace(Z3Region, IReward):
Name = "Desert Palace"
Area = "Desert Palace"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Reward = RewardType.Null
self.RegionItems = [ ItemType.KeyDP, ItemType.BigKeyDP, ItemType.MapDP, ItemType.CompassDP ]
self.Locations = [
Location(self, 256+109, 0x1E98F, LocationType.Regular, "Desert Palace - Big Chest",
lambda items: items.BigKeyDP),
Location(self, 256+110, 0x308160, LocationType.Regular, "Desert Palace - Torch",
lambda items: items.Boots),
Location(self, 256+111, 0x1E9B6, LocationType.Regular, "Desert Palace - Map Chest"),
Location(self, 256+112, 0x1E9C2, LocationType.Regular, "Desert Palace - Big Key Chest",
lambda items: items.KeyDP),
Location(self, 256+113, 0x1E9CB, LocationType.Regular, "Desert Palace - Compass Chest",
lambda items: items.KeyDP),
Location(self, 256+114, 0x308151, LocationType.Regular, "Desert Palace - Lanmolas",
lambda items: (
items.CanLiftLight() or
items.CanAccessMiseryMirePortal(self.Config) and items.Mirror
) and items.BigKeyDP and items.KeyDP and items.CanLightTorches() and self.CanBeatBoss(items))
]
def CanBeatBoss(self, items: Progression):
return items.Sword or items.Hammer or items.Bow or \
items.Firerod or items.Icerod or \
items.Byrna or items.Somaria
def CanEnter(self, items: Progression):
return items.Book or \
items.Mirror and items.CanLiftHeavy() and items.Flute or \
items.CanAccessMiseryMirePortal(self.Config) and items.Mirror
def CanComplete(self, items: Progression):
return self.GetLocation("Desert Palace - Lanmolas").Available(items)

View File

@ -0,0 +1,27 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType, IReward
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression, ItemType
class EasternPalace(Z3Region, IReward):
Name = "Eastern Palace"
Area = "Eastern Palace"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Reward = RewardType.Null
self.RegionItems = [ ItemType.BigKeyEP, ItemType.MapEP, ItemType.CompassEP ]
self.Locations = [
Location(self, 256+103, 0x1E9B3, LocationType.Regular, "Eastern Palace - Cannonball Chest"),
Location(self, 256+104, 0x1E9F5, LocationType.Regular, "Eastern Palace - Map Chest"),
Location(self, 256+105, 0x1E977, LocationType.Regular, "Eastern Palace - Compass Chest"),
Location(self, 256+106, 0x1E97D, LocationType.Regular, "Eastern Palace - Big Chest",
lambda items: items.BigKeyEP),
Location(self, 256+107, 0x1E9B9, LocationType.Regular, "Eastern Palace - Big Key Chest",
lambda items: items.Lamp),
Location(self, 256+108, 0x308150, LocationType.Regular, "Eastern Palace - Armos Knights",
lambda items: items.BigKeyEP and items.Bow and items.Lamp)
]
def CanComplete(self, items: Progression):
return self.GetLocation("Eastern Palace - Armos Knights").Available(items)

View File

@ -0,0 +1,146 @@
from typing import List
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType
from worlds.smz3.TotalSMZ3.Config import Config, GameMode, KeyShuffle
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Item, Progression, ItemType
class GanonsTower(Z3Region):
Name = "Ganon's Tower"
Area = "Ganon's Tower"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Reward = RewardType.Null
self.RegionItems = [ ItemType.KeyGT, ItemType.BigKeyGT, ItemType.MapGT , ItemType.CompassGT]
self.Locations = [
Location(self, 256+189, 0x308161, LocationType.Regular, "Ganon's Tower - Bob's Torch",
lambda items: items.Boots),
Location(self, 256+190, 0x1EAB8, LocationType.Regular, "Ganon's Tower - DMs Room - Top Left",
lambda items: items.Hammer and items.Hookshot),
Location(self, 256+191, 0x1EABB, LocationType.Regular, "Ganon's Tower - DMs Room - Top Right",
lambda items: items.Hammer and items.Hookshot),
Location(self, 256+192, 0x1EABE, LocationType.Regular, "Ganon's Tower - DMs Room - Bottom Left",
lambda items: items.Hammer and items.Hookshot),
Location(self, 256+193, 0x1EAC1, LocationType.Regular, "Ganon's Tower - DMs Room - Bottom Right",
lambda items: items.Hammer and items.Hookshot),
Location(self, 256+194, 0x1EAD3, LocationType.Regular, "Ganon's Tower - Map Chest",
lambda items: items.Hammer and (items.Hookshot or items.Boots) and items.KeyGT >=
(3 if any(self.GetLocation("Ganon's Tower - Map Chest").ItemIs(type, self.world) for type in [ItemType.BigKeyGT, ItemType.KeyGT]) else 4))
.AlwaysAllow(lambda item, items: item.Is(ItemType.KeyGT, self.world) and items.KeyGT >= 3),
Location(self, 256+195, 0x1EAD0, LocationType.Regular, "Ganon's Tower - Firesnake Room",
lambda items: items.Hammer and items.Hookshot and items.KeyGT >= (2 if any(l.ItemIs(ItemType.BigKeyGT, self.world) for l in [
self.GetLocation("Ganon's Tower - Randomizer Room - Top Right"),
self.GetLocation("Ganon's Tower - Randomizer Room - Top Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right")
]) or self.GetLocation("Ganon's Tower - Firesnake Room").ItemIs(ItemType.KeyGT, self.world) else 3)),
Location(self, 256+196, 0x1EAC4, LocationType.Regular, "Ganon's Tower - Randomizer Room - Top Left",
lambda items: self.LeftSide(items, [
self.GetLocation("Ganon's Tower - Randomizer Room - Top Right"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right")
])),
Location(self, 256+197, 0x1EAC7, LocationType.Regular, "Ganon's Tower - Randomizer Room - Top Right",
lambda items: self.LeftSide(items, [
self.GetLocation("Ganon's Tower - Randomizer Room - Top Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right")
])),
Location(self, 256+198, 0x1EACA, LocationType.Regular, "Ganon's Tower - Randomizer Room - Bottom Left",
lambda items: self.LeftSide(items, [
self.GetLocation("Ganon's Tower - Randomizer Room - Top Right"),
self.GetLocation("Ganon's Tower - Randomizer Room - Top Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Right")
])),
Location(self, 256+199, 0x1EACD, LocationType.Regular, "Ganon's Tower - Randomizer Room - Bottom Right",
lambda items: self.LeftSide(items, [
self.GetLocation("Ganon's Tower - Randomizer Room - Top Right"),
self.GetLocation("Ganon's Tower - Randomizer Room - Top Left"),
self.GetLocation("Ganon's Tower - Randomizer Room - Bottom Left")
])),
Location(self, 256+200, 0x1EAD9, LocationType.Regular, "Ganon's Tower - Hope Room - Left"),
Location(self, 256+201, 0x1EADC, LocationType.Regular, "Ganon's Tower - Hope Room - Right"),
Location(self, 256+202, 0x1EAE2, LocationType.Regular, "Ganon's Tower - Tile Room",
lambda items: items.Somaria),
Location(self, 256+203, 0x1EAE5, LocationType.Regular, "Ganon's Tower - Compass Room - Top Left",
lambda items: self.RightSide(items, [
self.GetLocation("Ganon's Tower - Compass Room - Top Right"),
self.GetLocation("Ganon's Tower - Compass Room - Bottom Left"),
self.GetLocation("Ganon's Tower - Compass Room - Bottom Right")
])),
Location(self, 256+204, 0x1EAE8, LocationType.Regular, "Ganon's Tower - Compass Room - Top Right",
lambda items: self.RightSide(items, [
self.GetLocation("Ganon's Tower - Compass Room - Top Left"),
self.GetLocation("Ganon's Tower - Compass Room - Bottom Left"),
self.GetLocation("Ganon's Tower - Compass Room - Bottom Right")
])),
Location(self, 256+205, 0x1EAEB, LocationType.Regular, "Ganon's Tower - Compass Room - Bottom Left",
lambda items: self.RightSide(items, [
self.GetLocation("Ganon's Tower - Compass Room - Top Right"),
self.GetLocation("Ganon's Tower - Compass Room - Top Left"),
self.GetLocation("Ganon's Tower - Compass Room - Bottom Right")
])),
Location(self, 256+206, 0x1EAEE, LocationType.Regular, "Ganon's Tower - Compass Room - Bottom Right",
lambda items: self.RightSide(items, [
self.GetLocation("Ganon's Tower - Compass Room - Top Right"),
self.GetLocation("Ganon's Tower - Compass Room - Top Left"),
self.GetLocation("Ganon's Tower - Compass Room - Bottom Left")
])),
Location(self, 256+207, 0x1EADF, LocationType.Regular, "Ganon's Tower - Bob's Chest",
lambda items: items.KeyGT >= 3 and (
items.Hammer and items.Hookshot or
items.Somaria and items.Firerod)),
Location(self, 256+208, 0x1EAD6, LocationType.Regular, "Ganon's Tower - Big Chest",
lambda items: items.BigKeyGT and items.KeyGT >= 3 and (
items.Hammer and items.Hookshot or
items.Somaria and items.Firerod))
.Allow(lambda item, items: item.IsNot(ItemType.BigKeyGT, self.world)),
Location(self, 256+209, 0x1EAF1, LocationType.Regular, "Ganon's Tower - Big Key Chest", self.BigKeyRoom),
Location(self, 256+210, 0x1EAF4, LocationType.Regular, "Ganon's Tower - Big Key Room - Left", self.BigKeyRoom),
Location(self, 256+211, 0x1EAF7, LocationType.Regular, "Ganon's Tower - Big Key Room - Right", self.BigKeyRoom),
Location(self, 256+212, 0x1EAFD, LocationType.Regular, "Ganon's Tower - Mini Helmasaur Room - Left", self.TowerAscend)
.Allow(lambda item, items: item.IsNot(ItemType.BigKeyGT, self.world)),
Location(self, 256+213, 0x1EB00, LocationType.Regular, "Ganon's Tower - Mini Helmasaur Room - Right", self.TowerAscend)
.Allow(lambda item, items: item.IsNot(ItemType.BigKeyGT, self.world)),
Location(self, 256+214, 0x1EB03, LocationType.Regular, "Ganon's Tower - Pre-Moldorm Chest", self.TowerAscend)
.Allow(lambda item, items: item.IsNot(ItemType.BigKeyGT, self.world)),
Location(self, 256+215, 0x1EB06, LocationType.Regular, "Ganon's Tower - Moldorm Chest",
lambda items: items.BigKeyGT and items.KeyGT >= 4 and
items.Bow and items.CanLightTorches() and
self.CanBeatMoldorm(items) and items.Hookshot)
.Allow(lambda item, items: all(item.IsNot(type, self.world) for type in [ ItemType.KeyGT, ItemType.BigKeyGT ]))
]
def LeftSide(self, items: Progression, locations: List[Location]):
return items.Hammer and items.Hookshot and items.KeyGT >= (3 if any(l.ItemIs(ItemType.BigKeyGT, self.world) for l in locations) else 4)
def RightSide(self, items: Progression, locations: List[Location]):
return items.Somaria and items.Firerod and items.KeyGT >= (3 if any(l.ItemIs(ItemType.BigKeyGT, self.world) for l in locations) else 4)
def BigKeyRoom(self, items: Progression):
return items.KeyGT >= 3 and self.CanBeatArmos(items) \
and (items.Hammer and items.Hookshot or items.Firerod and items.Somaria)
def TowerAscend(self, items: Progression):
return items.BigKeyGT and items.KeyGT >= 3 and items.Bow and items.CanLightTorches()
def CanBeatArmos(self, items: Progression):
return items.Sword or items.Hammer or items.Bow or \
items.CanExtendMagic(2) and (items.Somaria or items.Byrna) or \
items.CanExtendMagic(4) and (items.Firerod or items.Icerod)
def CanBeatMoldorm(self, items: Progression):
return items.Sword or items.Hammer
def CanEnter(self, items: Progression):
return items.MoonPearl and self.world.CanEnter("Dark World Death Mountain East", items) and \
self.world.CanAquireAll(items, RewardType.CrystalBlue, RewardType.CrystalRed, RewardType.GoldenFourBoss)
def CanFill(self, item: Item):
if (self.Config.GameMode == GameMode.Multiworld):
if (item.World != self.world or item.Progression):
return False
if (self.Config.KeyShuffle == KeyShuffle.Keysanity and not ((item.Type == ItemType.BigKeyGT or item.Type == ItemType.KeyGT) and item.World == self.world) and (item.IsKey() or item.IsBigKey() or item.IsKeycard())):
return False
return super().CanFill(item)

View File

@ -0,0 +1,34 @@
from typing import List
from worlds.smz3.TotalSMZ3.Region import Z3Region
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import ItemType
class HyruleCastle(Z3Region):
Name = "Hyrule Castle"
Area = "Hyrule Castle"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.RegionItems = [ ItemType.KeyHC, ItemType.MapHC]
sphereOne = -10
self.Locations = [
Location(self, 256+91, 0x1EA79, LocationType.Regular, "Sanctuary").Weighted(sphereOne),
Location(self, 256+92, 0x1EB5D, LocationType.Regular, "Sewers - Secret Room - Left",
lambda items: items.CanLiftLight() or items.Lamp and items.KeyHC),
Location(self, 256+93, 0x1EB60, LocationType.Regular, "Sewers - Secret Room - Middle",
lambda items: items.CanLiftLight() or items.Lamp and items.KeyHC),
Location(self, 256+94, 0x1EB63, LocationType.Regular, "Sewers - Secret Room - Right",
lambda items: items.CanLiftLight() or items.Lamp and items.KeyHC),
Location(self, 256+95, 0x1E96E, LocationType.Regular, "Sewers - Dark Cross",
lambda items: items.Lamp),
Location(self, 256+96, 0x1EB0C, LocationType.Regular, "Hyrule Castle - Map Chest").Weighted(sphereOne),
Location(self, 256+97, 0x1E974, LocationType.Regular, "Hyrule Castle - Boomerang Chest",
lambda items: items.KeyHC).Weighted(sphereOne),
Location(self, 256+98, 0x1EB09, LocationType.Regular, "Hyrule Castle - Zelda's Cell",
lambda items: items.KeyHC).Weighted(sphereOne),
Location(self, 256+99, 0x5DF45, LocationType.NotInDungeon, "Link's Uncle")
.Allow(lambda item, items: self.Config.Keysanity or not item.IsDungeonItem()).Weighted(sphereOne),
Location(self, 256+100, 0x1E971, LocationType.NotInDungeon, "Secret Passage")
.Allow(lambda item, items: self.Config.Keysanity or not item.IsDungeonItem()).Weighted(sphereOne),
]

View File

@ -0,0 +1,52 @@
from typing import List
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType, IReward
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression, ItemType
class IcePalace(Z3Region, IReward):
Name = "Ice Palace"
Area = "Ice Palace"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.RegionItems = [ ItemType.KeyIP, ItemType.BigKeyIP, ItemType.MapIP, ItemType.CompassIP]
self.Reward = RewardType.Null
self.Locations = [
Location(self, 256+161, 0x1E9D4, LocationType.Regular, "Ice Palace - Compass Chest"),
Location(self, 256+162, 0x1E9E0, LocationType.Regular, "Ice Palace - Spike Room",
lambda items: items.Hookshot or items.KeyIP >= 1 and self.CanNotWasteKeysBeforeAccessible(items, [
self.GetLocation("Ice Palace - Map Chest"),
self.GetLocation("Ice Palace - Big Key Chest")
])),
Location(self, 256+163, 0x1E9DD, LocationType.Regular, "Ice Palace - Map Chest",
lambda items: items.Hammer and items.CanLiftLight() and (
items.Hookshot or items.KeyIP >= 1 and self.CanNotWasteKeysBeforeAccessible(items, [
self.GetLocation("Ice Palace - Spike Room"),
self.GetLocation("Ice Palace - Big Key Chest")
])
)),
Location(self, 256+164, 0x1E9A4, LocationType.Regular, "Ice Palace - Big Key Chest",
lambda items: items.Hammer and items.CanLiftLight() and (
items.Hookshot or items.KeyIP >= 1 and self.CanNotWasteKeysBeforeAccessible(items, [
self.GetLocation("Ice Palace - Spike Room"),
self.GetLocation("Ice Palace - Map Chest")
])
)),
Location(self, 256+165, 0x1E9E3, LocationType.Regular, "Ice Palace - Iced T Room"),
Location(self, 256+166, 0x1E995, LocationType.Regular, "Ice Palace - Freezor Chest"),
Location(self, 256+167, 0x1E9AA, LocationType.Regular, "Ice Palace - Big Chest",
lambda items: items.BigKeyIP),
Location(self, 256+168, 0x308157, LocationType.Regular, "Ice Palace - Kholdstare",
lambda items: items.BigKeyIP and items.Hammer and items.CanLiftLight() and
items.KeyIP >= (1 if items.Somaria else 2))
]
def CanNotWasteKeysBeforeAccessible(self, items: Progression, locations: List[Location]):
return not items.BigKeyIP or any(l.ItemIs(ItemType.BigKeyIP, self.world) for l in locations)
def CanEnter(self, items: Progression):
return items.MoonPearl and items.Flippers and items.CanLiftHeavy() and items.CanMeltFreezors()
def CanComplete(self, items: Progression):
return self.GetLocation("Ice Palace - Kholdstare").Available(items)

View File

@ -0,0 +1,30 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class East(Z3Region):
Name = "Light World Death Mountain East"
Area = "Death Mountain"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 256+4, 0x308141, LocationType.Regular, "Floating Island",
lambda items: items.Mirror and items.MoonPearl and items.CanLiftHeavy()),
Location(self, 256+5, 0x1E9BF, LocationType.Regular, "Spiral Cave"),
Location(self, 256+6, 0x1EB39, LocationType.Regular, "Paradox Cave Upper - Left"),
Location(self, 256+7, 0x1EB3C, LocationType.Regular, "Paradox Cave Upper - Right"),
Location(self, 256+8, 0x1EB2A, LocationType.Regular, "Paradox Cave Lower - Far Left"),
Location(self, 256+9, 0x1EB2D, LocationType.Regular, "Paradox Cave Lower - Left"),
Location(self, 256+10, 0x1EB36, LocationType.Regular, "Paradox Cave Lower - Middle"),
Location(self, 256+11, 0x1EB30, LocationType.Regular, "Paradox Cave Lower - Right"),
Location(self, 256+12, 0x1EB33, LocationType.Regular, "Paradox Cave Lower - Far Right"),
Location(self, 256+13, 0x1E9C5, LocationType.Regular, "Mimic Cave",
lambda items: items.Mirror and items.KeyTR >= 2 and self.world.CanEnter("Turtle Rock", items))
]
def CanEnter(self, items: Progression):
return self.world.CanEnter("Light World Death Mountain West", items) and (
items.Hammer and items.Mirror or
items.Hookshot)

View File

@ -0,0 +1,23 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression
class West(Z3Region):
Name = "Light World Death Mountain West"
Area = "Death Mountain"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.Locations = [
Location(self, 256+0, 0x308016, LocationType.Ether, "Ether Tablet",
lambda items: items.Book and items.MasterSword and (items.Mirror or items.Hammer and items.Hookshot)),
Location(self, 256+1, 0x308140, LocationType.Regular, "Spectacle Rock",
lambda items: items.Mirror),
Location(self, 256+2, 0x308002, LocationType.Regular, "Spectacle Rock Cave"),
Location(self, 256+3, 0x1EE9FA, LocationType.Regular, "Old Man",
lambda items: items.Lamp)
]
def CanEnter(self, items: Progression):
return items.Flute or items.CanLiftLight() and items.Lamp or items.CanAccessDeathMountainPortal()

View File

@ -0,0 +1,28 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
class NorthEast(Z3Region):
Name = "Light World North East"
Area = "Light World"
def __init__(self, world, config: Config):
super().__init__(world, config)
sphereOne = -10
self.Locations = [
Location(self, 256+36, 0x1DE1C3, LocationType.Regular, "King Zora",
lambda items: items.CanLiftLight() or items.Flippers),
Location(self, 256+37, 0x308149, LocationType.Regular, "Zora's Ledge",
lambda items: items.Flippers),
Location(self, 256+254, 0x1E9B0, LocationType.Regular, "Waterfall Fairy - Left",
lambda items: items.Flippers),
Location(self, 256+39, 0x1E9D1, LocationType.Regular, "Waterfall Fairy - Right",
lambda items: items.Flippers),
Location(self, 256+40, 0x308014, LocationType.Regular, "Potion Shop",
lambda items: items.Mushroom),
Location(self, 256+41, 0x1EA82, LocationType.Regular, "Sahasrahla's Hut - Left").Weighted(sphereOne),
Location(self, 256+42, 0x1EA85, LocationType.Regular, "Sahasrahla's Hut - Middle").Weighted(sphereOne),
Location(self, 256+43, 0x1EA88, LocationType.Regular, "Sahasrahla's Hut - Right").Weighted(sphereOne),
Location(self, 256+44, 0x5F1FC, LocationType.Regular, "Sahasrahla",
lambda items: self.world.CanAquire(items, RewardType.PendantGreen))
]

View File

@ -0,0 +1,44 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
class NorthWest(Z3Region):
Name = "Light World North West"
Area = "Light World"
def __init__(self, world, config: Config):
super().__init__(world, config)
sphereOne = -14
self.Locations = [
Location(self, 256+14, 0x589B0, LocationType.Pedestal, "Master Sword Pedestal",
lambda items: self.world.CanAquireAll(items, RewardType.PendantGreen, RewardType.PendantNonGreen)),
Location(self, 256+15, 0x308013, LocationType.Regular, "Mushroom").Weighted(sphereOne),
Location(self, 256+16, 0x308000, LocationType.Regular, "Lost Woods Hideout").Weighted(sphereOne),
Location(self, 256+17, 0x308001, LocationType.Regular, "Lumberjack Tree",
lambda items: self.world.CanAquire(items, RewardType.Agahnim) and items.Boots),
Location(self, 256+18, 0x1EB3F, LocationType.Regular, "Pegasus Rocks",
lambda items: items.Boots),
Location(self, 256+19, 0x308004, LocationType.Regular, "Graveyard Ledge",
lambda items: items.Mirror and items.MoonPearl and self.world.CanEnter("Dark World North West", items)),
Location(self, 256+20, 0x1E97A, LocationType.Regular, "King's Tomb",
lambda items: items.Boots and (
items.CanLiftHeavy() or
items.Mirror and items.MoonPearl and self.world.CanEnter("Dark World North West", items))),
Location(self, 256+21, 0x1EA8E, LocationType.Regular, "Kakariko Well - Top").Weighted(sphereOne),
Location(self, 256+22, 0x1EA91, LocationType.Regular, "Kakariko Well - Left").Weighted(sphereOne),
Location(self, 256+23, 0x1EA94, LocationType.Regular, "Kakariko Well - Middle").Weighted(sphereOne),
Location(self, 256+24, 0x1EA97, LocationType.Regular, "Kakariko Well - Right").Weighted(sphereOne),
Location(self, 256+25, 0x1EA9A, LocationType.Regular, "Kakariko Well - Bottom").Weighted(sphereOne),
Location(self, 256+26, 0x1EB0F, LocationType.Regular, "Blind's Hideout - Top").Weighted(sphereOne),
Location(self, 256+27, 0x1EB18, LocationType.Regular, "Blind's Hideout - Far Left").Weighted(sphereOne),
Location(self, 256+28, 0x1EB12, LocationType.Regular, "Blind's Hideout - Left").Weighted(sphereOne),
Location(self, 256+29, 0x1EB15, LocationType.Regular, "Blind's Hideout - Right").Weighted(sphereOne),
Location(self, 256+30, 0x1EB1B, LocationType.Regular, "Blind's Hideout - Far Right").Weighted(sphereOne),
Location(self, 256+31, 0x5EB18, LocationType.Regular, "Bottle Merchant").Weighted(sphereOne),
Location(self, 256+250, 0x1E9E9, LocationType.Regular, "Chicken House").Weighted(sphereOne),
Location(self, 256+33, 0x6B9CF, LocationType.Regular, "Sick Kid",
lambda items: items.Bottle),
Location(self, 256+34, 0x1E9CE, LocationType.Regular, "Kakariko Tavern").Weighted(sphereOne),
Location(self, 256+35, 0x308015, LocationType.Regular, "Magic Bat",
lambda items: items.Powder and (items.Hammer or items.MoonPearl and items.Mirror and items.CanLiftHeavy()))
]

View File

@ -0,0 +1,45 @@
from worlds.smz3.TotalSMZ3.Region import Z3Region
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
class South(Z3Region):
Name = "Light World South"
Area = "Light World"
def __init__(self, world, config: Config):
super().__init__(world, config)
sphereOne = -10
self.Locations = [
Location(self, 256+45, 0x308142, LocationType.Regular, "Maze Race").Weighted(sphereOne),
Location(self, 256+240, 0x308012, LocationType.Regular, "Library",
lambda items: items.Boots),
Location(self, 256+241, 0x30814A, LocationType.Regular, "Flute Spot",
lambda items: items.Shovel),
Location(self, 256+242, 0x308003, LocationType.Regular, "South of Grove",
lambda items: items.Mirror and self.world.CanEnter("Dark World South", items)),
Location(self, 256+243, 0x1E9BC, LocationType.Regular, "Link's House").Weighted(sphereOne),
Location(self, 256+244, 0x1E9F2, LocationType.Regular, "Aginah's Cave").Weighted(sphereOne),
Location(self, 256+51, 0x1EB42, LocationType.Regular, "Mini Moldorm Cave - Far Left").Weighted(sphereOne),
Location(self, 256+52, 0x1EB45, LocationType.Regular, "Mini Moldorm Cave - Left").Weighted(sphereOne),
Location(self, 256+53, 0x308010, LocationType.Regular, "Mini Moldorm Cave - NPC").Weighted(sphereOne),
Location(self, 256+54, 0x1EB48, LocationType.Regular, "Mini Moldorm Cave - Right").Weighted(sphereOne),
Location(self, 256+251, 0x1EB4B, LocationType.Regular, "Mini Moldorm Cave - Far Right").Weighted(sphereOne),
Location(self, 256+252, 0x308143, LocationType.Regular, "Desert Ledge",
lambda items: self.world.CanEnter("Desert Palace", items)),
Location(self, 256+253, 0x308005, LocationType.Regular, "Checkerboard Cave",
lambda items: items.Mirror and (
items.Flute and items.CanLiftHeavy() or
items.CanAccessMiseryMirePortal(Config)
) and items.CanLiftLight()),
Location(self, 256+58, 0x308017, LocationType.Bombos, "Bombos Tablet",
lambda items: items.Book and items.MasterSword and items.Mirror and self.world.CanEnter("Dark World South", items)),
Location(self, 256+59, 0x1E98C, LocationType.Regular, "Floodgate Chest").Weighted(sphereOne),
Location(self, 256+60, 0x308145, LocationType.Regular, "Sunken Treasure").Weighted(sphereOne),
Location(self, 256+61, 0x308144, LocationType.Regular, "Lake Hylia Island",
lambda items: items.Flippers and items.MoonPearl and items.Mirror and (
self.world.CanEnter("Dark World South", items) or
self.world.CanEnter("Dark World North East", items))),
Location(self, 256+62, 0x6BE7D, LocationType.Regular, "Hobo",
lambda items: items.Flippers),
Location(self, 256+63, 0x1EB4E, LocationType.Regular, "Ice Rod Cave").Weighted(sphereOne)
]

View File

@ -0,0 +1,43 @@
from typing import List
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType, IReward, IMedallionAccess
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression, ItemType
class MiseryMire(Z3Region, IReward, IMedallionAccess):
Name = "Misery Mire"
Area = "Misery Mire"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.RegionItems = [ ItemType.KeyMM, ItemType.BigKeyMM, ItemType.MapMM, ItemType.CompassMM]
self.Reward = RewardType.Null
self.Medallion = ItemType.Nothing
self.Locations = [
Location(self, 256+169, 0x1EA5E, LocationType.Regular, "Misery Mire - Main Lobby",
lambda items: items.BigKeyMM or items.KeyMM >= 1),
Location(self, 256+170, 0x1EA6A, LocationType.Regular, "Misery Mire - Map Chest",
lambda items: items.BigKeyMM or items.KeyMM >= 1),
Location(self, 256+171, 0x1EA61, LocationType.Regular, "Misery Mire - Bridge Chest"),
Location(self, 256+172, 0x1E9DA, LocationType.Regular, "Misery Mire - Spike Chest"),
Location(self, 256+173, 0x1EA64, LocationType.Regular, "Misery Mire - Compass Chest",
lambda items: items.CanLightTorches() and
items.KeyMM >= (2 if self.GetLocation("Misery Mire - Big Key Chest").ItemIs(ItemType.BigKeyMM, self.world) else 3)),
Location(self, 256+174, 0x1EA6D, LocationType.Regular, "Misery Mire - Big Key Chest",
lambda items: items.CanLightTorches() and
items.KeyMM >= (2 if self.GetLocation("Misery Mire - Compass Chest").ItemIs(ItemType.BigKeyMM, self.world) else 3)),
Location(self, 256+175, 0x1EA67, LocationType.Regular, "Misery Mire - Big Chest",
lambda items: items.BigKeyMM),
Location(self, 256+176, 0x308158, LocationType.Regular, "Misery Mire - Vitreous",
lambda items: items.BigKeyMM and items.Lamp and items.Somaria)
]
# // Need "CanKillManyEnemies" if implementing swordless
def CanEnter(self, items: Progression):
return (items.Bombos if self.Medallion == ItemType.Bombos else (
items.Ether if self.Medallion == ItemType.Ether else items.Quake)) and items.Sword and \
items.MoonPearl and (items.Boots or items.Hookshot) and \
self.world.CanEnter("Dark World Mire", items)
def CanComplete(self, items: Progression):
return self.GetLocation("Misery Mire - Vitreous").Available(items)

View File

@ -0,0 +1,54 @@
from typing import List
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType, IReward
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression, ItemType
class PalaceOfDarkness(Z3Region, IReward):
Name = "Palace of Darkness"
Area = "Dark Palace"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.RegionItems = [ ItemType.KeyPD, ItemType.BigKeyPD, ItemType.MapPD, ItemType.CompassPD]
self.Reward = RewardType.Null
self.Locations = [
Location(self, 256+121, 0x1EA5B, LocationType.Regular, "Palace of Darkness - Shooter Room"),
Location(self, 256+122, 0x1EA37, LocationType.Regular, "Palace of Darkness - Big Key Chest",
lambda items: items.KeyPD >= (1 if self.GetLocation("Palace of Darkness - Big Key Chest").ItemIs(ItemType.KeyPD, self.world) else
6 if (items.Hammer and items.Bow and items.Lamp) or config.Keysanity else 5))
.AlwaysAllow(lambda item, items: item.Is(ItemType.KeyPD, self.world) and items.KeyPD >= 5),
Location(self, 256+123, 0x1EA49, LocationType.Regular, "Palace of Darkness - Stalfos Basement",
lambda items: items.KeyPD >= 1 or items.Bow and items.Hammer),
Location(self, 256+124, 0x1EA3D, LocationType.Regular, "Palace of Darkness - The Arena - Bridge",
lambda items: items.KeyPD >= 1 or items.Bow and items.Hammer),
Location(self, 256+125, 0x1EA3A, LocationType.Regular, "Palace of Darkness - The Arena - Ledge",
lambda items: items.Bow),
Location(self, 256+126, 0x1EA52, LocationType.Regular, "Palace of Darkness - Map Chest",
lambda items: items.Bow),
Location(self, 256+127, 0x1EA43, LocationType.Regular, "Palace of Darkness - Compass Chest",
lambda items: items.KeyPD >= (4 if (items.Hammer and items.Bow and items.Lamp) or config.Keysanity else 3)),
Location(self, 256+128, 0x1EA46, LocationType.Regular, "Palace of Darkness - Harmless Hellway",
lambda items: items.KeyPD >= (4 if (items.Hammer and items.Bow and items.Lamp) or config.Keysanity else 3 if
self.GetLocation("Palace of Darkness - Harmless Hellway").ItemIs(ItemType.KeyPD, self.world) else
6 if (items.Hammer and items.Bow and items.Lamp) or config.Keysanity else 5))
.AlwaysAllow(lambda item, items: item.Is(ItemType.KeyPD, self.world) and items.KeyPD >= 5),
Location(self, 256+129, 0x1EA4C, LocationType.Regular, "Palace of Darkness - Dark Basement - Left",
lambda items: items.Lamp and items.KeyPD >= (4 if (items.Hammer and items.Bow) or config.Keysanity else 3)),
Location(self, 256+130, 0x1EA4F, LocationType.Regular, "Palace of Darkness - Dark Basement - Right",
lambda items: items.Lamp and items.KeyPD >= (4 if (items.Hammer and items.Bow) or config.Keysanity else 3)),
Location(self, 256+131, 0x1EA55, LocationType.Regular, "Palace of Darkness - Dark Maze - Top",
lambda items: items.Lamp and items.KeyPD >= (6 if (items.Hammer and items.Bow) or config.Keysanity else 5)),
Location(self, 256+132, 0x1EA58, LocationType.Regular, "Palace of Darkness - Dark Maze - Bottom",
lambda items: items.Lamp and items.KeyPD >= (6 if(items.Hammer and items.Bow) or config.Keysanity else 5)),
Location(self, 256+133, 0x1EA40, LocationType.Regular, "Palace of Darkness - Big Chest",
lambda items: items.BigKeyPD and items.Lamp and items.KeyPD >= (6 if (items.Hammer and items.Bow) or config.Keysanity else 5)),
Location(self, 256+134, 0x308153, LocationType.Regular, "Palace of Darkness - Helmasaur King",
lambda items: items.Lamp and items.Hammer and items.Bow and items.BigKeyPD and items.KeyPD >= 6),
]
def CanEnter(self, items: Progression):
return items.MoonPearl and self.world.CanEnter("Dark World North East", items)
def CanComplete(self, items: Progression):
return self.GetLocation("Palace of Darkness - Helmasaur King").Available(items)

View File

@ -0,0 +1,35 @@
from typing import List
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType, IReward
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression, ItemType
class SkullWoods(Z3Region, IReward):
Name = "Skull Woods"
Area = "Skull Woods"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.RegionItems = [ ItemType.KeySW, ItemType.BigKeySW, ItemType.MapSW, ItemType.CompassSW]
self.Reward = RewardType.Null
self.Locations = [
Location(self, 256+145, 0x1E9A1, LocationType.Regular, "Skull Woods - Pot Prison"),
Location(self, 256+146, 0x1E992, LocationType.Regular, "Skull Woods - Compass Chest"),
Location(self, 256+147, 0x1E998, LocationType.Regular, "Skull Woods - Big Chest",
lambda items: items.BigKeySW)
.AlwaysAllow(lambda item, items: item.Is(ItemType.BigKeySW, self.world)),
Location(self, 256+148, 0x1E99B, LocationType.Regular, "Skull Woods - Map Chest"),
Location(self, 256+149, 0x1E9C8, LocationType.Regular, "Skull Woods - Pinball Room")
.Allow(lambda item, items: item.Is(ItemType.KeySW, self.world)),
Location(self, 256+150, 0x1E99E, LocationType.Regular, "Skull Woods - Big Key Chest"),
Location(self, 256+151, 0x1E9FE, LocationType.Regular, "Skull Woods - Bridge Room",
lambda items: items.Firerod),
Location(self, 256+152, 0x308155, LocationType.Regular, "Skull Woods - Mothula",
lambda items: items.Firerod and items.Sword and items.KeySW >= 3),
]
def CanEnter(self, items: Progression):
return items.MoonPearl and self.world.CanEnter("Dark World North West", items)
def CanComplete(self, items: Progression):
return self.GetLocation("Skull Woods - Mothula").Available(items)

View File

@ -0,0 +1,43 @@
from typing import List
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType, IReward
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression, ItemType
class SwampPalace(Z3Region, IReward):
Name = "Swamp Palace"
Area = "Swamp Palace"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.RegionItems = [ ItemType.KeySP, ItemType.BigKeySP, ItemType.MapSP, ItemType.CompassSP]
self.Reward = RewardType.Null
self.Locations = [
Location(self, 256+135, 0x1EA9D, LocationType.Regular, "Swamp Palace - Entrance")
.Allow(lambda item, items: self.Config.Keysanity or item.Is(ItemType.KeySP, self.world)),
Location(self, 256+136, 0x1E986, LocationType.Regular, "Swamp Palace - Map Chest",
lambda items: items.KeySP),
Location(self, 256+137, 0x1E989, LocationType.Regular, "Swamp Palace - Big Chest",
lambda items: items.BigKeySP and items.KeySP and items.Hammer)
.AlwaysAllow(lambda item, items: item.Is(ItemType.BigKeySP, self.world)),
Location(self, 256+138, 0x1EAA0, LocationType.Regular, "Swamp Palace - Compass Chest",
lambda items: items.KeySP and items.Hammer),
Location(self, 256+139, 0x1EAA3, LocationType.Regular, "Swamp Palace - West Chest",
lambda items: items.KeySP and items.Hammer),
Location(self, 256+140, 0x1EAA6, LocationType.Regular, "Swamp Palace - Big Key Chest",
lambda items: items.KeySP and items.Hammer),
Location(self, 256+141, 0x1EAA9, LocationType.Regular, "Swamp Palace - Flooded Room - Left",
lambda items: items.KeySP and items.Hammer and items.Hookshot),
Location(self, 256+142, 0x1EAAC, LocationType.Regular, "Swamp Palace - Flooded Room - Right",
lambda items: items.KeySP and items.Hammer and items.Hookshot),
Location(self, 256+143, 0x1EAAF, LocationType.Regular, "Swamp Palace - Waterfall Room",
lambda items: items.KeySP and items.Hammer and items.Hookshot),
Location(self, 256+144, 0x308154, LocationType.Regular, "Swamp Palace - Arrghus",
lambda items: items.KeySP and items.Hammer and items.Hookshot)
]
def CanEnter(self, items: Progression):
return items.MoonPearl and items.Mirror and items.Flippers and self.world.CanEnter("Dark World South", items)
def CanComplete(self, items: Progression):
return self.GetLocation("Swamp Palace - Arrghus").Available(items)

View File

@ -0,0 +1,39 @@
from typing import List
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType, IReward
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression, ItemType
class ThievesTown(Z3Region, IReward):
Name = "Thieves' Town"
Area = "Thieves' Town"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.RegionItems = [ ItemType.KeyTT, ItemType.BigKeyTT, ItemType.MapTT, ItemType.CompassTT]
self.Reward = RewardType.Null
self.Locations = [
Location(self, 256+153, 0x1EA01, LocationType.Regular, "Thieves' Town - Map Chest"),
Location(self, 256+154, 0x1EA0A, LocationType.Regular, "Thieves' Town - Ambush Chest"),
Location(self, 256+155, 0x1EA07, LocationType.Regular, "Thieves' Town - Compass Chest"),
Location(self, 256+156, 0x1EA04, LocationType.Regular, "Thieves' Town - Big Key Chest"),
Location(self, 256+157, 0x1EA0D, LocationType.Regular, "Thieves' Town - Attic",
lambda items: items.BigKeyTT and items.KeyTT),
Location(self, 256+158, 0x1EA13, LocationType.Regular, "Thieves' Town - Blind's Cell",
lambda items: items.BigKeyTT),
Location(self, 256+159, 0x1EA10, LocationType.Regular, "Thieves' Town - Big Chest",
lambda items: items.BigKeyTT and items.Hammer and
(self.GetLocation("Thieves' Town - Big Chest").ItemIs(ItemType.KeyTT, self.world) or items.KeyTT))
.AlwaysAllow(lambda item, items: item.Is(ItemType.KeyTT, self.world) and items.Hammer),
Location(self, 256+160, 0x308156, LocationType.Regular, "Thieves' Town - Blind",
lambda items: items.BigKeyTT and items.KeyTT and self.CanBeatBoss(items)),
]
def CanBeatBoss(self, items: Progression):
return items.Sword or items.Hammer or items.Somaria or items.Byrna
def CanEnter(self, items: Progression):
return items.MoonPearl and self.world.CanEnter("Dark World North West", items)
def CanComplete(self, items: Progression):
return self.GetLocation("Thieves' Town - Blind").Available(items)

View File

@ -0,0 +1,36 @@
from typing import List
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType, IReward
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression, ItemType
class TowerOfHera(Z3Region, IReward):
Name = "Tower of Hera"
Area = "Tower of Hera"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.RegionItems = [ ItemType.KeyTH, ItemType.BigKeyTH, ItemType.MapTH, ItemType.CompassTH]
self.Reward = RewardType.Null
self.Locations = [
Location(self, 256+115, 0x308162, LocationType.HeraStandingKey, "Tower of Hera - Basement Cage"),
Location(self, 256+116, 0x1E9AD, LocationType.Regular, "Tower of Hera - Map Chest"),
Location(self, 256+117, 0x1E9E6, LocationType.Regular, "Tower of Hera - Big Key Chest",
lambda items: items.KeyTH and items.CanLightTorches())
.AlwaysAllow(lambda item, items: item.Is(ItemType.KeyTH, self.world)),
Location(self, 256+118, 0x1E9FB, LocationType.Regular, "Tower of Hera - Compass Chest",
lambda items: items.BigKeyTH),
Location(self, 256+119, 0x1E9F8, LocationType.Regular, "Tower of Hera - Big Chest",
lambda items: items.BigKeyTH),
Location(self, 256+120, 0x308152, LocationType.Regular, "Tower of Hera - Moldorm",
lambda items: items.BigKeyTH and self.CanBeatBoss(items)),
]
def CanBeatBoss(self, items: Progression):
return items.Sword or items.Hammer
def CanEnter(self, items: Progression):
return (items.Mirror or items.Hookshot and items.Hammer) and self.world.CanEnter("Light World Death Mountain West", items)
def CanComplete(self, items: Progression):
return self.GetLocation("Tower of Hera - Moldorm").Available(items)

View File

@ -0,0 +1,55 @@
from typing import List
from worlds.smz3.TotalSMZ3.Region import Z3Region, RewardType, IReward, IMedallionAccess
from worlds.smz3.TotalSMZ3.Config import Config
from worlds.smz3.TotalSMZ3.Location import Location, LocationType
from worlds.smz3.TotalSMZ3.Item import Progression, ItemType
class TurtleRock(Z3Region, IReward, IMedallionAccess):
Name = "Turtle Rock"
Area = "Turtle Rock"
def __init__(self, world, config: Config):
super().__init__(world, config)
self.RegionItems = [ ItemType.KeyTR, ItemType.BigKeyTR, ItemType.MapTR, ItemType.CompassTR]
self.Reward = RewardType.Null
self.Medallion = ItemType.Nothing
self.Locations = [
Location(self, 256+177, 0x1EA22, LocationType.Regular, "Turtle Rock - Compass Chest"),
Location(self, 256+178, 0x1EA1C, LocationType.Regular, "Turtle Rock - Roller Room - Left",
lambda items: items.Firerod),
Location(self, 256+179, 0x1EA1F, LocationType.Regular, "Turtle Rock - Roller Room - Right",
lambda items: items.Firerod),
Location(self, 256+180, 0x1EA16, LocationType.Regular, "Turtle Rock - Chain Chomps",
lambda items: items.KeyTR >= 1),
Location(self, 256+181, 0x1EA25, LocationType.Regular, "Turtle Rock - Big Key Chest",
lambda items: items.KeyTR >=
(2 if not self.Config.Keysanity or self.GetLocation("Turtle Rock - Big Key Chest").ItemIs(ItemType.BigKeyTR, self.world) else
3 if self.GetLocation("Turtle Rock - Big Key Chest").ItemIs(ItemType.KeyTR, self.world) else 4))
.AlwaysAllow(lambda item, items: item.Is(ItemType.KeyTR, self.world) and items.KeyTR >= 3),
Location(self, 256+182, 0x1EA19, LocationType.Regular, "Turtle Rock - Big Chest",
lambda items: items.BigKeyTR and items.KeyTR >= 2)
.Allow(lambda item, items: item.IsNot(ItemType.BigKeyTR, self.world)),
Location(self, 256+183, 0x1EA34, LocationType.Regular, "Turtle Rock - Crystaroller Room",
lambda items: items.BigKeyTR and items.KeyTR >= 2),
Location(self, 256+184, 0x1EA28, LocationType.Regular, "Turtle Rock - Eye Bridge - Top Right", self.LaserBridge),
Location(self, 256+185, 0x1EA2B, LocationType.Regular, "Turtle Rock - Eye Bridge - Top Left", self.LaserBridge),
Location(self, 256+186, 0x1EA2E, LocationType.Regular, "Turtle Rock - Eye Bridge - Bottom Right", self.LaserBridge),
Location(self, 256+187, 0x1EA31, LocationType.Regular, "Turtle Rock - Eye Bridge - Bottom Left", self.LaserBridge),
Location(self, 256+188, 0x308159, LocationType.Regular, "Turtle Rock - Trinexx",
lambda items: items.BigKeyTR and items.KeyTR >= 4 and items.Lamp and self.CanBeatBoss(items)),
]
def LaserBridge(self, items: Progression):
return items.BigKeyTR and items.KeyTR >= 3 and items.Lamp and (items.Cape or items.Byrna or items.CanBlockLasers)
def CanBeatBoss(self, items: Progression):
return items.Firerod and items.Icerod
def CanEnter(self, items: Progression):
return (items.Bombos if self.Medallion == ItemType.Bombos else (
items.Ether if self.Medallion == ItemType.Ether else items.Quake)) and items.Sword and \
items.MoonPearl and items.CanLiftHeavy() and items.Hammer and items.Somaria and \
self.world.CanEnter("Light World Death Mountain East", items)
def CanComplete(self, items: Progression):
return self.GetLocation("Turtle Rock - Trinexx").Available(items)

View File

@ -0,0 +1,323 @@
import re
class Dialog:
command = re.compile(r"^\{[^}]*\}")
invalid = re.compile(r"(?<!^)\{[^}]*\}(?!$)", re.MULTILINE)
digit = re.compile(r"\d")
uppercaseLetter = re.compile(r"[A-Z]")
lowercaseLetter = re.compile(r"[a-z]")
@staticmethod
def Simple(text: str):
maxBytes = 256
wrap = 19
bytes = []
lines = text.split('\n')
lineIndex = 0
for line in lines:
bytes.append(0x74 if 0 else 0x75 if 1 else 0x76)
letters = line[:wrap] if len(line) > wrap else line
for letter in letters:
write = Dialog.LetterToBytes(letter)
if (write[0] == 0xFD):
bytes += write
else:
for b in write:
bytes += [ 0x00, b ]
lineIndex += 1
if (lineIndex % 3 == 0 and lineIndex < len(lines)):
bytes.append(0x7E)
if (lineIndex >= 3 and lineIndex < len(lines)):
bytes.append(0x73)
bytes.append(0x7F)
if (len(bytes) > maxBytes):
return bytes[:maxBytes - 1].append(0x7F)
return bytes
@staticmethod
def Compiled(text: str, pause = True):
maxBytes = 2046
wrap = 19
if (Dialog.invalid.match(text)):
raise Exception("Dialog commands must be placed on separate lines", text)
padOut = False
bytes = [ 0xFB ]
lines = Dialog.Wordwrap(text, wrap)
lineCount = len([l for l in lines if not Dialog.command.match(l)])
lineIndex = 0
for line in lines:
match = Dialog.command.match(line)
if (match is not None):
if (match.string == "{NOTEXT}"):
return [ 0xFB, 0xFE, 0x6E, 0x00, 0xFE, 0x6B, 0x04 ]
if (match.string == "{INTRO}"):
padOut = True
bytesMap = {
"{SPEED0}" : [ 0xFC, 0x00 ],
"{SPEED2}" : [ 0xFC, 0x02 ],
"{SPEED6}" : [ 0xFC, 0x06 ],
"{PAUSE1}" : [ 0xFE, 0x78, 0x01 ],
"{PAUSE3}" : [ 0xFE, 0x78, 0x03 ],
"{PAUSE5}" : [ 0xFE, 0x78, 0x05 ],
"{PAUSE7}" : [ 0xFE, 0x78, 0x07 ],
"{PAUSE9}" : [ 0xFE, 0x78, 0x09 ],
"{INPUT}" : [ 0xFA ],
"{CHOICE}" : [ 0xFE, 0x68 ],
"{ITEMSELECT}" : [ 0xFE, 0x69 ],
"{CHOICE2}" : [ 0xFE, 0x71 ],
"{CHOICE3}" : [ 0xFE, 0x72 ],
"{C:GREEN}" : [ 0xFE, 0x77, 0x07 ],
"{C:YELLOW}" : [ 0xFE, 0x77, 0x02 ],
"{HARP}" : [ 0xFE, 0x79, 0x2D ],
"{MENU}" : [ 0xFE, 0x6D, 0x00 ],
"{BOTTOM}" : [ 0xFE, 0x6D, 0x01 ],
"{NOBORDER}" : [ 0xFE, 0x6B, 0x02 ],
"{CHANGEPIC}" : [ 0xFE, 0x67, 0xFE, 0x67 ],
"{CHANGEMUSIC}" : [ 0xFE, 0x67 ],
"{INTRO}" : [ 0xFE, 0x6E, 0x00, 0xFE, 0x77, 0x07, 0xFC, 0x03, 0xFE, 0x6B, 0x02, 0xFE, 0x67 ],
"{IBOX}" : [ 0xFE, 0x6B, 0x02, 0xFE, 0x77, 0x07, 0xFC, 0x03, 0xF7 ],
}
result = bytesMap.get(match.string, None)
if (result is None):
raise Exception(f"Dialog text contained unknown command {match.string}", text)
else:
bytes += result
if (len(bytes) > maxBytes):
raise Exception("Command overflowed maximum byte length", text)
continue
if (lineIndex == 1):
bytes.append(0xF8); #// row 2
elif (lineIndex >= 3 and lineIndex < lineCount):
bytes.append(0xF6); #// scroll
elif (lineIndex >= 2):
bytes.append(0xF9); #// row 3
#// The first box needs to fill the full width with spaces as the palette is loaded weird.
letters = line + (" " * wrap) if padOut and lineIndex < 3 else line
for letter in letters:
bytes += Dialog.LetterToBytes(letter)
lineIndex += 1
if (pause and lineIndex % 3 == 0 and lineIndex < lineCount):
bytes.append(0xFA) #// wait for input
return bytes[:maxBytes]
@staticmethod
def Wordwrap(text: str, width: int):
result = []
for line in text.split('\n'):
line = line.rstrip()
if (len(line) <= width):
result.append(line)
else:
words = line.split(' ')
lines = [ "" ]
for word in words:
line = lines.pop()
if (len(line) + len(word) <= width):
line = f"{line}{word} "
else:
if (len(line) > 0):
lines.append(line)
line = word
while (len(line) > width):
lines.append(line[:width])
line = line[width:]
line = f"{line} "
lines.append(line)
#lines.reverse()
result += [l.strip() for l in lines]
return result
@staticmethod
def LetterToBytes(c: str):
if Dialog.digit.match(c): return [(ord(c) - ord('0') + 0xA0) ]
elif Dialog.uppercaseLetter.match(c): return [ (ord(c) - ord('A') + 0xAA) ]
elif Dialog.lowercaseLetter.match(c): return [ (ord(c) - ord('a') + 0x30) ]
else:
value = Dialog.letters.get(c, None)
return value if value else [ 0xFF ]
#region letter bytes lookup
letters = {
' ' : [ 0x4F ],
'?' : [ 0xC6 ],
'!' : [ 0xC7 ],
',' : [ 0xC8 ],
'-' : [ 0xC9 ],
'' : [ 0xCC ],
'.' : [ 0xCD ],
'~' : [ 0xCE ],
'' : [ 0xCE ],
'\'' : [ 0xD8 ],
'' : [ 0xD8 ],
'"' : [ 0xD8 ],
':' : [ 0x4A ],
'@' : [ 0x4B ],
'#' : [ 0x4C ],
'¤' : [ 0x4D, 0x4E ], #// Morphing ball
'_' : [ 0xFF ], #// Full width space
'£' : [ 0xFE, 0x6A ], #// link's name compressed
'>' : [ 0xD2, 0xD3 ], #// link face
'%' : [ 0xDD ], #// Hylian Bird
'^' : [ 0xDE ], #// Hylian Ankh
'=' : [ 0xDF ], #// Hylian Wavy lines
'' : [ 0xE0 ],
'' : [ 0xE1 ],
'' : [ 0xE2 ],
'' : [ 0xE3 ],
'' : [ 0xE4 ], #// cursor
'¼' : [ 0xE5, 0xE7 ], #// 1/4 heart
'½' : [ 0xE6, 0xE7 ], #// 1/2 heart
'¾' : [ 0xE8, 0xE9 ], #// 3/4 heart
'' : [ 0xEA, 0xEB ], #// full heart
'' : [ 0xFE, 0x6C, 0x00 ], #// var 0
'' : [ 0xFE, 0x6C, 0x01 ], #// var 1
'' : [ 0xFE, 0x6C, 0x02 ], #// var 2
'' : [ 0xFE, 0x6C, 0x03 ], #// var 3
'' : [ 0x00 ],
'' : [ 0x01 ],
'' : [ 0x02 ],
'' : [ 0x03 ],
'' : [ 0x04 ],
'' : [ 0x05 ],
'' : [ 0x06 ],
'' : [ 0x07 ],
'' : [ 0x08 ],
'' : [ 0x09 ],
'' : [ 0x0A ],
'' : [ 0x0B ],
'' : [ 0x0C ],
'' : [ 0x0D ],
'' : [ 0x0E ],
'' : [ 0x0F ],
'' : [ 0x10 ],
'' : [ 0x11 ],
'' : [ 0x12 ],
'' : [ 0x13 ],
'' : [ 0x14 ],
'' : [ 0x15 ],
'' : [ 0x16 ],
'' : [ 0x17 ],
'' : [ 0x18 ],
'' : [ 0x19 ],
'' : [ 0x1A ],
'' : [ 0x1B ],
'' : [ 0x1C ],
'' : [ 0x1D ],
'' : [ 0x1E ],
'' : [ 0x1F ],
'' : [ 0x20 ],
'' : [ 0x21 ],
'' : [ 0x22 ],
'' : [ 0x23 ],
'' : [ 0x24 ],
'' : [ 0x25 ],
'' : [ 0x26 ],
'' : [ 0x27 ],
'' : [ 0x28 ],
'' : [ 0x29 ],
'' : [ 0x2A ],
'' : [ 0x2B ],
'' : [ 0x2C ],
'' : [ 0x2D ],
'' : [ 0x2E ],
'' : [ 0x2F ],
'' : [ 0x50 ],
'' : [ 0x51 ],
'' : [ 0x52 ],
'' : [ 0x53 ],
'' : [ 0x54 ],
'' : [ 0x55 ],
'' : [ 0x56 ],
'' : [ 0x57 ],
'' : [ 0x58 ],
'' : [ 0x59 ],
'' : [ 0x5A ],
'' : [ 0x5B ],
'' : [ 0x5C ],
'' : [ 0x5D ],
'' : [ 0x5E ],
'' : [ 0x5F ],
'' : [ 0x60 ],
'' : [ 0x61 ],
'' : [ 0x62 ],
'' : [ 0x63 ],
'' : [ 0x64 ],
'' : [ 0x65 ],
'' : [ 0x66 ],
'' : [ 0x67 ],
'' : [ 0x68 ],
'' : [ 0x69 ],
'' : [ 0x6A ],
'' : [ 0x6B ],
'' : [ 0x6C ],
'' : [ 0x6D ],
'' : [ 0x6E ],
'' : [ 0x6F ],
'' : [ 0x70 ],
'' : [ 0x71 ],
'' : [ 0x72 ],
'' : [ 0x73 ],
'' : [ 0x74 ],
'' : [ 0x75 ],
'' : [ 0x76 ],
'' : [ 0x77 ],
'' : [ 0x78 ],
'' : [ 0x79 ],
'' : [ 0x7A ],
'' : [ 0x7B ],
'' : [ 0x7C ],
'' : [ 0x7D ],
'' : [ 0x7E ],
'' : [ 0x80 ],
'' : [ 0x81 ],
'' : [ 0x82 ],
'' : [ 0x83 ],
'' : [ 0x84 ],
'' : [ 0x85 ],
'' : [ 0x86 ],
'' : [ 0x87 ],
'' : [ 0x88 ],
'' : [ 0x89 ],
'' : [ 0x8A ],
'' : [ 0x8B ],
'' : [ 0x8C ],
'' : [ 0x8D ],
'' : [ 0x8E ],
'' : [ 0x8F ],
'' : [ 0x90 ],
'' : [ 0x91 ],
'' : [ 0x92 ],
'' : [ 0x93 ],
'' : [ 0x94 ],
'' : [ 0x95 ],
'' : [ 0x96 ],
'' : [ 0x97 ],
'' : [ 0x98 ],
'' : [ 0x99 ],
'' : [ 0x9A ],
'' : [ 0x9B ],
'' : [ 0x9C ],
'' : [ 0x9D ],
'' : [ 0x9E ],
'' : [ 0x9F ],
}

View File

@ -0,0 +1,87 @@
I hate insect
puns, they
really bug me.
---
I haven't seen
the eye doctor
in years.
---
I don't see
you having a
bright future
---
Are you doing
a blind run
of this game?
---
Pizza joke? No
I think it's a
bit too cheesy
---
A novice skier
often jumps to
contusions.
---
The beach?
I'm not shore
I can make it.
---
Rental agents
offer quarters
for dollars.
---
I got my tires
fixed for a
flat rate.
---
New lightbulb
invented?
Enlighten me.
---
A baker's job
is a piece of
cake.
---
My optometrist
said I have
vision!
---
When you're a
baker, don't
loaf around
---
Mire requires
ether quake,
or bombos
---
Broken pencils
are pointless.
---
The food they
serve guards
lasts sentries
---
Being crushed
by big objects
is depressing.
---
A tap dancer's
routine runs
hot and cold.
---
A weeknight
is a tiny
nobleman
---
The chimney
sweep wore a
soot and tye.
---
Gardeners like
to spring into
action.
---
Bad at nuclear
physics. I Got
no fission.
---

View File

@ -0,0 +1,189 @@
Start your day
smiling with a
delicious
wholegrain
breakfast
created for
your
incredible
insides.
---
You drove
away my other
self, Agahnim
two times…
But, I won't
give you the
Triforce.
I'll defeat
you!
---
Impa says that
the mark on
your hand
means that you
are the hero
chosen to
awaken Zelda.
your blood can
resurrect me.
---
Don't stand,
don't stand so
Don't stand so
close to me
Don't stand so
close to me
back off buddy
---
So ya
Thought ya
Might like to
go to the show
To feel the
warm thrill of
confusion
That space
cadet glow.
---
Like other
pulmonate land
gastropods,
the majority
of land slugs
have two pairs
of 'feelers'
or tentacles
on their head.
---
If you were a
burrito, what
kind of a
burrito would
you be?
Me, I fancy I
would be a
spicy barbacoa
burrito.
---
I am your
father's
brother's
nephew's
cousin's
former
roommate. What
does that make
us, you ask?
---
I'll be more
eager about
encouraging
thinking
outside the
box when there
is evidence of
any thinking
inside it.
---
If we're not
meant to have
midnight
snacks, then
why is there
a light in the
fridge?
---
I feel like we
keep ending up
here.
Don't you?
It's like
deja vu
all over again
---
Did you know?
The biggest
and heaviest
cheese ever
produced
weighed
57,518 pounds
and was 32
feet long.
---
Now there was
a time, When
you loved me
so. I couldn't
do wrong,
And now you
need to know.
So How you
like me now?
---
Did you know?
Nutrition
experts
recommend that
at least half
of our daily
grains come
from whole
grain products
---
The Hemiptera
or true bugs
are an order
of insects
covering 50k
to 80k species
like aphids,
cicadas, and
shield bugs.
---
Thanks for
dropping in,
the first
passengers
in a hot
air balloon.
were a duck,
a sheep,
and a rooster.
---
You think you
are so smart?
I bet you
didn't know
You can't hum
while holding
your nose
closed.
---
Grumble,
grumble…
grumble,
grumble…
Seriously you
were supposed
to bring food
---
Join me hero,
and I shall
make your face
the greatest
in the dark
world!
Or else you
will die!
---

View File

@ -0,0 +1,385 @@
SahasrahlaReveal: |-
Want something
for free? Go
earn the green
pendant in
<dungeon>
and I'll give
you something.
BombShopReveal: |-
Bring me the
crystals from
<first>
and
<second>
so I can make
a big bomb!
GanonSilversReveal:
single:
local: |-
Did you find
the arrows in
my tower?
remote: |-
Did you find
the arrows in
<region>
multi:
local: |-
Seek the
arrows in
this world
remote: |-
Seek the sage
<player>
for the arrows
Items:
BigKeyEP: |-
The big key
of the east
BigKeyDP: |-
Sand spills
out of this
big key
BigKeyTH: |-
The big key
to moldorm's
heart
BigKeyPD: |-
Hammeryump
with this
big key
BigKeySP: |-
The big key
to the swamp
BigKeySW: |-
The big key
of the dark
forest
BigKeyTT: |-
The big key
of rogues
BigKeyIP: |-
A frozen
big key
rests here
BigKeyMM: |-
The big key
to Vitreous
BigKeyTR: |-
The big key
of terrapins
BigKeyGT: |-
The big key
of evil's bane
KeyHC: |-
The key to
the castle
KeyCT: |-
Agahanim
halfway
unlocked
KeyDP: |-
Sand spills
out of this
small key
KeyTH: |-
The key
to moldorm's
basement
KeyPD: |-
A small key
that steals
light
KeySP: |-
Access to
the swamp
is granted
KeySW: |-
The small key
of the dark
forest
KeyTT: |-
The small key
of rogues
KeyIP: |-
A frozen
small key
rests here
KeyMM: |-
The small key
to Vitreous
KeyTR: |-
The small key
of terrapins
KeyGT: |-
The small key
of evil's bane
Map: |-
You can now
find your way
home!
Compass: |-
Now you know
where the boss
hides!
ProgressiveTunic: |-
Time for a
change of
clothes?
ProgressiveShield: |-
Have a better
defense in
front of you
ProgressiveSword: |-
A better copy
of your sword
for your time
Bow: |-
You have
chosen the
archer class.
SilverArrows: |-
Do you fancy
silver tipped
arrows?
BlueBoomerang: |-
No matter what
you do, blue
returns to you
RedBoomerang: |-
No matter what
you do, red
returns to you
Hookshot: |-
BOING!!!
BOING!!!
BOING!!!
Mushroom: |-
I'm a fun guy!
I'm a funghi!
Powder: |-
You can turn
anti-faeries
into faeries
Firerod: |-
I'm the hot
rod. I make
things burn!
Icerod: |-
I'm the cold
rod. I make
things freeze!
Bombos: |-
Burn, baby,
burn! Fear my
ring of fire!
Ether: |-
This magic
coin freezes
everything!
Quake: |-
Maxing out the
Richter scale
is what I do!
Lamp: |-
Baby, baby,
baby.
Light my way!
Hammer: |-
Stop!
Hammer time!
Shovel: |-
Can
You
Dig it?
Flute: |-
Save the duck
and fly to
freedom!
Bugnet: |-
Let's catch
some bees and
faeries!
Book: |-
This is a
paradox?!
Bottle: |-
Now you can
store potions
and stuff!
BottleWithRedPotion: |-
You see red
goo in a
bottle?
BottleWithGreenPotion: |-
You see green
goo in a
bottle?
BottleWithBluePotion: |-
You see blue
goo in a
bottle?
BottleWithBee: |-
Release me
so I can go
bzzzzz!
BottleWithFairy: |-
If you die
I will revive
you!
Somaria: |-
I make blocks
to hold down
switches!
Byrna: |-
Use this to
become
invincible!
Cape: |-
Wear this to
become
invisible!
Mirror: |-
Isn't your
reflection so
pretty?
Boots: |-
Gotta go fast!
ProgressiveGlove: |-
A way to lift
heavier things
Flippers: |-
Fancy a swim?
MoonPearl: |-2
Bunny Link
be
gone!
HalfMagic: |-
Your magic
power has been
doubled!
HeartPiece: |-
Just a little
piece of love!
HeartContainer: |-
Maximum health
increased!
Yeah!
ThreeBombs: |-
I make things
go triple
BOOM!!!
Arrow: |-
A lonely arrow
sits here.
TenArrows: |-
This will give
you ten shots
with your bow!
PocketRupees: |-
Just pocket
change. Move
right along.
CouchRupees: |-
Just couch
cash. Move
right along.
OneHundredRupees: |-
A rupee stash!
Hell yeah!
ThreeHundredRupees: |-
A rupee hoard!
Hell yeah!
BombUpgrade: |-
Increase bomb
storage, low
low price
ArrowUpgrade: |-
Increase arrow
storage, low
low price
Missile: |-
Some kind of
flying bomb?
Super: |-
A really big
flying bomb!
PowerBomb: |-
Big bada boom!
Grapple: |-
Some kind of
futuristic
hookshot?
XRay: |-
THIS LENS OF
TRUTH IS MADE
IN ZEBES!
ETank: |-
A heart from
the future?
ReserveTank: |-
A fairy from
the future?
Charge: |-
IM'A CHARGIN
MA LAZER!
Ice: |-
Some kind of
ice rod for
aliens?
Wave: |-
Trigonometry gun.
Spazer: |-
Even space
lasers can
be sucky.
Plasma: |-
Some kind of
fire rod for
aliens?
Varia: |-
Alien armor?
Gravity: |-
No more water
physics.
Morph: |-
Why can't
Metroid crawl?
Bombs: |-
Bombs from
the future.
SpringBall: |-
Bouncy bouncy
bouncy bouncy
bounce.
ScrewAttack: |-
U spin me right
round baby
right round
HiJump: |-
This would be
great if I
could jump.
SpaceJump: |-
I believe
I can fly.
SpeedBooster: |-
THE GREEN
BOOMERANG IS
THE FASTEST!
Keycard: |-
A key from
the future?
default: |-
Don't waste
your time!

View File

@ -0,0 +1,433 @@
# Numbers in comments refer to US text numbers
# Except for the first few entries, JP1.0 text numbers are smaller by 2
# The order of the dialog entries is significant
- set_cursor: [0xFB, 0xFC, 0x00, 0xF9, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xE4, 0xFE, 0x68]
- set_cursor2: [0xFB, 0xFC, 0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xE4, 0xFE, 0x68]
- game_over_menu: { NoPause: "{SPEED0}\nSave and Continue\nSave and Quit\nContinue" }
- var_test: { NoPause: "0= ᚋ, 1= ᚌ\n2= ᚍ, 3= ᚎ" }
- follower_no_enter: "Can't you take me some place nice."
- choice_1_3: [0xFB, 0xFC, 0x00, 0xF7, 0xE4, 0xF8, 0xFF, 0xF9, 0xFF, 0xFE, 0x71]
- choice_2_3: [0xFB, 0xFC, 0x00, 0xF7, 0xFF, 0xF8, 0xE4, 0xF9, 0xFF, 0xFE, 0x71]
- choice_3_3: [0xFB, 0xFC, 0x00, 0xF7, 0xFF, 0xF8, 0xFF, 0xF9, 0xE4, 0xFE, 0x71]
- choice_1_2: [0xFB, 0xFC, 0x00, 0xF7, 0xE4, 0xF8, 0xFF, 0xFE, 0x72]
- choice_2_2: [0xFB, 0xFC, 0x00, 0xF7, 0xFF, 0xF8, 0xE4, 0xFE, 0x72]
- uncle_leaving_text: "I'm just going out for a pack of smokes."
- uncle_dying_sewer: "I've fallen and I can't get up, take this."
# $10
- tutorial_guard_1: "Only adults should travel at night."
- tutorial_guard_2: "You can press X to see the Map."
- tutorial_guard_3: "Press the A button to lift things by you."
- tutorial_guard_4: "When you has a sword, press B to slash it."
- tutorial_guard_5: "このメッセージはニホンゴでそのまま" # on purpose
- tutorial_guard_6: "Are we really still reading these?"
- tutorial_guard_7: "Jeeze! There really are a lot of things."
- priest_sanctuary_before_leave: "Go be a hero!"
- sanctuary_enter: "YAY!\nYou saved Zelda!"
- zelda_sanctuary_story: "Do you want to hear me say this again?\n{HARP}\n ≥ No\n _Yes\n{CHOICE}"
- priest_sanctuary_before_pendants: "Go'on and get them pendants so you can beat up Agahnim."
- priest_sanctuary_after_pendants_before_master_sword: "Kudos! But seriously, you should be getting the master sword, not having a kegger in here."
- priest_sanctuary_dying: "They took her to the castle! Take your sword and save her!"
- zelda_save_sewers: "You saved me!"
- priest_info: "So, I'm the dude that will protect Zelda. Don't worry, I got this covered."
- zelda_sanctuary_before_leave: "Be careful!"
- telepathic_intro: "{NOBORDER}\n{SPEED6}\nHey, come find me and help me!"
# $20
- telepathic_reminder: "{NOBORDER}\n{SPEED6}\nI'm in the castle basement."
- zelda_go_to_throne: "Go north to the throne."
- zelda_push_throne: "Let's push it from the left!"
- zelda_switch_room_pull: "Pull this lever using A."
- zelda_save_lets_go: "Let's get out of here!"
- zelda_save_repeat: "I like talking, do you?\n ≥ No\n _Yes\n{CHOICE}"
- zelda_before_pendants: "You need to find all the pendants…\n\n\nNumpty."
- zelda_after_pendants_before_master_sword: "Very pretty pendants, but really you should be getting that sword in the forest!"
- telepathic_zelda_right_after_master_sword: "{NOBORDER}\n{SPEED6}\nHi £,\nHave you been thinking about me?\narrrrrgghh…\n… … …"
- zelda_sewers: "Just a little further to the Sanctuary."
- zelda_switch_room: "The Sanctuary!\n\nPull my finger"
- kakariko_sahasrahla_wife: "Heya, £!\nLong time no see.\nYou want a master sword?\n\nWell good luck with that."
- kakariko_sahasrahla_wife_sword_story: "It occurs to me that I like toast and jam, but cheese and crackers is better.\nYou like?\n__≥ Cheese\n___ Jam\n{CHOICE}"
- kakariko_sahasrahla_wife_closing: "Anywho, I have things to do. You see those 2 ovens?\n\nYeah, 2!\nWho has 2 ovens nowadays?!"
- kakariko_sahasrahla_after_master_sword: "Cool sword!\n\n\n…\n\n\n…\n\n\nPlease save us"
- kakariko_alert_guards: "GUARDS! HELP!\nThe creeper\n£ is here!"
# $30
- sahasrahla_quest_have_pendants: "{BOTTOM}\nCool beans, but I think you should mosey on over to the lost woods."
- sahasrahla_quest_have_master_sword: "{BOTTOM}\nThat's a pretty sword, but I'm old, forgetful, and old. Why don't you go do all the hard work while I hang out in this hut."
- sahasrahla_quest_information: "{BOTTOM}\nSahasrahla, I am. You would do well to find the 3 pendants from the 3 dungeons in the Light World.\nUnderstand?\n ≥ Yes\n No\n{CHOICE}"
- sahasrahla_bring_courage: "{BOTTOM}\nWhile you're here, could you do me a solid and get the green pendant from that dungeon?\n{HARP}\nI'll give you a present if you do."
- sahasrahla_have_ice_rod: "{BOTTOM}\nLike, I sit here, and tell you what to do?\n\n\nAlright, go and find all the maidens, there are, like, maybe 7 of them. I dunno anymore. I'm old."
- telepathic_sahasrahla_beat_agahnim: "{NOBORDER}\n{SPEED6}\nNice, so you beat Agahnim. Now you must beat Ganon. Good Luck!"
- telepathic_sahasrahla_beat_agahnim_no_pearl: "{NOBORDER}\n{SPEED6}\nOh, also you forgot the Moon Pearl, dingus. Go back and find it!"
- sahasrahla_have_boots_no_icerod: "{BOTTOM}\nCave in South East has a cool item."
- sahasrahla_have_courage: "{BOTTOM}\nLook, you have the green pendant! I'll give you something. Go kill the other two bosses for more pendant fun!"
- sahasrahla_found: "{BOTTOM}\nYup!\n\nI'm the old man you are looking for. I'll keep it short and sweet: Go into that dungeon, then bring me the green pendant and talk to me again."
- sign_rain_north_of_links_house: "↑ Dying Uncle\n This way…"
- sign_north_of_links_house: "> Randomizer Don't read me, go beat Ganon!"
- sign_path_to_death_mountain: "Cave to lost, old man.\nGood luck."
- sign_lost_woods: "\n↑ Lost Woods"
- sign_zoras: "Danger!\nDeep water!\nZoras!"
- sign_outside_magic_shop: "Welcome to the Magic Shoppe"
# $40
- sign_death_mountain_cave_back: "Cave away from sky cabbages"
- sign_east_of_links_house: "↓ Lake Hylia\n\n Also, a shop"
- sign_south_of_lumberjacks: "← Kakariko\n Village"
- sign_east_of_desert: "← Desert\n\n It's hot."
- sign_east_of_sanctuary: "↑→ Potions!\n\nWish Waterfall"
- sign_east_of_castle: "→ East Palace\n\n← Castle"
- sign_north_of_lake: "\n Lake Hiriah"
- sign_desert_thief: "Don't talk to me or touch my sign!"
- sign_lumberjacks_house: "Lumberjacks, Inc.\nYou see 'em, we saw 'em."
- sign_north_kakariko: "↓ Kakariko\n Village"
- witch_bring_mushroom: "Double, double toil and trouble!\nBring me a mushroom!"
- witch_brewing_the_item: "This mushroom is busy brewing. Come back later."
- witch_assistant_no_bottle: "You got to give me the mushroom, Numpty."
- witch_assistant_no_empty_bottle: "Gotta use your stuff before you can get more."
- witch_assistant_informational: "Red is life\nGreen is magic\nBlue is both\nI'll heal you for free, though."
- witch_assistant_no_bottle_buying: "If only you had something to put that in, like a bottle…"
# $50
- potion_shop_no_empty_bottles: "Whoa, bucko!\nNo empty bottles."
- item_get_lamp: "Lamp! You can see in the dark, and light torches."
- item_get_boomerang: "Boomerang! Press START to select it."
- item_get_bow: "You're in bow mode now!"
- item_get_shovel: "This is my new mop. My friend George, he gave me this mop. It's a pretty good mop. It's not as good as my old mop. I miss my old mop. But it's still a good mop."
- item_get_magic_cape: "Finally! We get to play Invisible Man!"
- item_get_powder: "It's the powder. Let's cause some mischief!"
- item_get_flippers: "Splish! Splash! Let's go take a bath!"
- item_get_power_gloves: "Feel the power! You can now lift light rocks! Rock on!"
- item_get_pendant_courage: "We have the Pendant of Courage! How brave!"
- item_get_pendant_power: "We have the Pendant of Power! How robust!"
- item_get_pendant_wisdom: "We have the Pendant of Wisdom! How astute!"
- item_get_mushroom: "A Mushroom! Don't eat it. Find a witch."
- item_get_book: "It book! U R now litterit!"
- item_get_moonpearl: "I found a shiny marble! No more hops!"
- item_get_compass: "A compass! I can now find the boss."
# $60
- item_get_map: "Yo! You found a MAP! Press X to see it."
- item_get_ice_rod: "It's the Ice Rod! Freeze Ray time."
- item_get_fire_rod: "A Rod that shoots fire? Let's burn all the things!"
- item_get_ether: "We can chill out with this!"
- item_get_bombos: "Let's set everything on fire, and melt things!"
- item_get_quake: "Time to make the earth shake, rattle, and roll!"
- item_get_hammer: "STOP!\n\nHammer Time!"
- item_get_ocarina: "Finally! We can play the Song of Time!"
- item_get_cane_of_somaria: "Make blocks!\nThrow blocks!\nSplode Blocks!"
- item_get_hookshot: "BOING!!!\nBOING!!!\nSay no more…"
- item_get_bombs: "BOMBS! Use A to pick 'em up, throw 'em, get hurt!"
- item_get_bottle: "It's a terrarium. I hope we find a lizard!"
- item_get_big_key: "Yo! You got a Big Key!"
- item_get_titans_mitts: "So, like, you can now lift anything.\nANYTHING!"
- item_get_magic_mirror: "We could stare at this all day or, you know, beat Ganon…"
- item_get_fake_mastersword: "It's the Master Sword! …or not…\n\n FOOL!"
# $70
- post_item_get_mastersword: "{NOBORDER}\n{SPEED6}\n£, you got the sword!\n{CHANGEMUSIC}\nNow let's go beat up Agahnim!"
- item_get_red_potion: "Red goo to go! Nice!"
- item_get_green_potion: "Green goo to go! Nice!"
- item_get_blue_potion: "Blue goo to go! Nice!"
- item_get_bug_net: "Surprise Net! Let's catch stuff!"
- item_get_blue_mail: "Blue threads? Less damage activated!"
- item_get_red_mail: "You feel the power of the eggplant on your head."
- item_get_temperedsword: "Nice… I now have a craving for Cheetos."
- item_get_mirror_shield: "Pit would be proud!"
- item_get_cane_of_byrna: "It's the Blue Cane. You can now protect yourself with lag!"
- missing_big_key: "Something is missing…\nThe Big Key?"
- missing_magic: "Something is missing…\nMagic meter?"
- item_get_pegasus_boots: "Finally, it's bonking time!\nHold A to dash"
- talking_tree_info_start: "Whoa! I can talk again!"
- talking_tree_info_1: "Yank on the pitchfork in the center of town, ya heard it here."
- talking_tree_info_2: "Ganon is such a dingus, no one likes him, ya heard it here."
# $80
- talking_tree_info_3: "There is a portal near the Lost Woods, ya heard it here."
- talking_tree_info_4: "Use bombs to quickly kill the Hinox, ya heard it here."
- talking_tree_other: "I can breathe!"
- item_get_pendant_power_alt: "We have the Pendant of Power! How robust!"
- item_get_pendant_wisdom_alt: "We have the Pendant of Wisdom! How astute!"
- game_shooting_choice: "20 rupees.\n5 arrows.\nWin rupees!\nWant to play?\n ≥ Yes\n _No\n{CHOICE}"
- game_shooting_yes: "Let's do this!"
- game_shooting_no: "Where are you going? Straight up!"
- game_shooting_continue: "Keep playing?\n ≥ Yes\n _No\n{CHOICE}"
- pond_of_wishing: "-Wishing Pond-\n\n On Vacation"
- pond_item_select: "Pick something\nto throw in.\n{ITEMSELECT}"
- pond_item_test: "You toss this?\n ≥ Yup\n _Wrong\n{CHOICE}"
- pond_will_upgrade: "You're honest, so I'll give you a present."
- pond_item_test_no: "You sure?\n ≥ Oh yeah\n _Um\n{CHOICE}"
- pond_item_test_no_no: "Well, I don't want it, so take it back."
- pond_item_boomerang: "I don't much like you, so have this worse Boomerang."
# $90
- pond_item_shield: "I grant you the ability to block fireballs. Don't lose this to a Pikit!"
- pond_item_silvers: "So, wouldn't it be nice to kill Ganon? These should help in the final phase."
- pond_item_bottle_filled: "Bottle Filled!\nMoney Saved!"
- pond_item_sword: "Thank you for the sword, here is a stick of butter."
- pond_of_wishing_happiness: "Happiness up!\nYou are now\nᚌᚋ happy!"
- pond_of_wishing_choice: "Your wish?\n ≥More bombs\n _More arrows\n{CHOICE}"
- pond_of_wishing_bombs: "Woo-hoo!\nYou can now\ncarry ᚌᚋ bombs"
- pond_of_wishing_arrows: "Woo-hoo!\nYou can now\nhold ᚌᚋ arrows"
- pond_of_wishing_full_upgrades: "You have all I can give you, here are your rupees back."
- mountain_old_man_first: "Look out for holes, and monsters."
- mountain_old_man_deadend: "Oh, goody, hearts in jars! This place is creepy."
- mountain_old_man_turn_right: "Turn right. Let's get out of this place."
- mountain_old_man_lost_and_alone: "Hello. I can't see anything. Take me with you."
- mountain_old_man_drop_off: "Here's a thing to help you, good luck!"
- mountain_old_man_in_his_cave_pre_agahnim: "You need to beat the tower at the top of the mountain."
- mountain_old_man_in_his_cave: "You can find stuff in the tower at the top of this mountain.\nCome see me if you'd like to be healed."
# $A0
- mountain_old_man_in_his_cave_post_agahnim: "You should be heading to the castle… you have a portal there now.\nSay hi anytime you like."
- tavern_old_man_awake: "Life? Love? Happiness? The question you should really ask is: Was this generated by Stoops Alu or Stoops Jet?"
- tavern_old_man_unactivated_flute: "You should play that flute for the weathervane, cause reasons."
- tavern_old_man_know_tree_unactivated_flute: "You should play that flute for the weathervane, cause reasons."
- tavern_old_man_have_flute: "Life? Love? Happiness? The question you should really ask is: Was this generated by Stoops Alu or Stoops Jet?"
- chicken_hut_lady: "This is\nChristos' hut.\n\nHe's out, searching for a bow."
- running_man: "Hi, Do you\nknow Veetorp?\n\nYou really\nshould. And\nall the other great guys who made this possible.\nGo thank them.\n\n\nIf you can catch them…"
- game_race_sign: "Why are you reading this sign? Run!!!"
- sign_bumper_cave: "You need Cape, but not Hookshot"
- sign_catfish: "Toss rocks\nToss items\nToss cookies"
- sign_north_village_of_outcasts: "↑ Skull Woods\n\n↓ Steve's Town"
- sign_south_of_bumper_cave: "\n→ Karkats cave"
- sign_east_of_pyramid: "\n→ Dark Palace"
- sign_east_of_bomb_shop: "\n← Bomb Shoppe"
- sign_east_of_mire: "\n← Misery Mire\n no way in.\n no way out."
- sign_village_of_outcasts: "Have a Trulie Awesome Day!"
# $B0
- sign_before_wishing_pond: "waterfall\nup ahead\nmake wishes"
- sign_before_catfish_area: "→↑ Have you met Woeful Ike?"
- castle_wall_guard: "Looking for a Princess? Look downstairs."
- gate_guard: "No Lonks Allowed!"
- telepathic_tile_eastern_palace: "{NOBORDER}\nYou need a Bow to get past the red Eyegore. derpy"
- telepathic_tile_tower_of_hera_floor_4: "{NOBORDER}\nIf you find a shiny ball, you can be you in the Dark World."
- hylian_text_1: "%== %== %==\n ^ %==% ^\n%== ^%%^ ==^"
- mastersword_pedestal_translated: "A test of strength: If you have 3 pendants, I'm yours."
- telepathic_tile_spectacle_rock: "{NOBORDER}\nUse the Mirror, or the Hookshot and Hammer, to get to Tower of Hera!"
- telepathic_tile_swamp_entrance: "{NOBORDER}\nDrain the floodgate to raise the water here!"
- telepathic_tile_thieves_town_upstairs: "{NOBORDER}\nBlind hates bright light."
- telepathic_tile_misery_mire: "{NOBORDER}\nLighting 4 torches will open your way forward!"
- hylian_text_2: "%%^= %==%\n ^ =%^=\n==%= ^^%^"
- desert_entry_translated: "Kneel before this stone, and magic will move around you."
- telepathic_tile_under_ganon: "{NOBORDER}\nOnly Silver Arrows will finish off a blue Ganon, or really well timed spins in phase 4."
- telepathic_tile_palace_of_darkness: "{NOBORDER}\nThis is a funny looking Enemizer"
# $C0
- telepathic_tile_desert_bonk_torch_room: "{NOBORDER}\nThings can be knocked down, if you fancy yourself a dashing dude."
- telepathic_tile_castle_tower: "{NOBORDER}\nYou can reflect Agahnim's energy with Sword, Bug-net or Hammer."
- telepathic_tile_ice_large_room: "{NOBORDER}\nAll right, stop collaborate and listen\nIce is back with my brand new invention."
- telepathic_tile_turtle_rock: "{NOBORDER}\nYou Shall not pass… without the red cane."
- telepathic_tile_ice_entrace: "{NOBORDER}\nYou can use Fire Rod or Bombos to pass."
- telepathic_tile_ice_stalfos_knights_room: "{NOBORDER}\nKnock 'em down and then bomb them dead."
- telepathic_tile_tower_of_hera_entrance: "{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot."
- houlihan_room: "My name is Chris Houlihan, but I'm sure you only care about the money. Take it, it's not like I can stop you."
- caught_a_bee: "Caught a Bee\n ≥ Keep\n _Release\n{CHOICE}"
- caught_a_fairy: "Caught Fairy!\n ≥ Keep\n _Release\n{CHOICE}"
- no_empty_bottles: "Whoa, bucko!\nNo empty bottles."
- game_race_boy_time: "Your time was\nᚎᚍ min ᚌᚋ sec."
- game_race_girl: "You have 15 seconds,\nGo… Go… Go…"
- game_race_boy_success: "Nice!\nYou can have this trash!"
- game_race_boy_failure: "Too slow!\nI keep my\nprecious!"
- game_race_boy_already_won: "You already have your prize, dingus!"
# $D0
- game_race_boy_sneaky: "Thought you could sneak in, eh?"
- bottle_vendor_choice: "I gots bottles.\nYous gots 100 rupees?\n ≥ I want\n _No way!"
- bottle_vendor_get: "Nice! Hold it up son! Show the world what you got!"
- bottle_vendor_no: "Fine! I didn't want your money anyway."
- bottle_vendor_already_collected: "Dude! You already have it."
- bottle_vendor_bee: "Cool! A bee! Here's 100 rupees."
- bottle_vendor_fish: "Whoa! A fish! You walked this all the way here?"
- hobo_item_get_bottle: "You think life is rough? I guess you can take my last item. Except this tent. That's MY tent!"
- blacksmiths_what_you_want: "Nice of you to come back!\nWould you like us mess with your sword?\n ≥ Temper\n _It's fine\n{CHOICE}"
- blacksmiths_paywall: "It's 10 rupees\n ≥ Easy\n _Hang on…\n{CHOICE}"
- blacksmiths_extra_okay: "Are you sure you're sure?\n ≥ Ah, yup\n _Hang on…\n{CHOICE}"
- blacksmiths_tempered_already: "Whelp… We can't make this any better."
- blacksmiths_temper_no: "Oh, come by any time!"
- blacksmiths_bogart_sword: "We're going to have to take it to work on it."
- blacksmiths_get_sword: "Sword is done. Now, back to our bread!"
- blacksmiths_shop_before_saving: "I lost my friend. Help me find him!"
# $E0
- blacksmiths_shop_saving: "You found him! Colour me happy! Come back right away and we will bang on your sword."
- blacksmiths_collect_frog: "Ribbit! Ribbit! Let's find my partner. To the shop!"
- blacksmiths_still_working: "Something this precious takes time… Come back later."
- blacksmiths_saving_bows: "Thanks!\n\nThanks!"
- blacksmiths_hammer_anvil: "Dernt Take Er Jerbs!"
- dark_flute_boy_storytime: "Hi!\nI'm Stumpy!\nI've been chillin' in this world for a while now, but I miss my flute. If I gave you a shovel, would you go digging for it?\n ≥ Sure\n _Nahh\n{CHOICE}"
- dark_flute_boy_get_shovel: "Schaweet! Here you go. Happy digging!"
- dark_flute_boy_no_get_shovel: "Oh I see, not good enough for you… FINE!"
- dark_flute_boy_flute_not_found: "Still haven't found the item? Dig in the Light World around here, dingus!"
- dark_flute_boy_after_shovel_get: "So I gave you an item, and you're still here.\n\n\n\n\n\nI mean, we can sit here and stare at each other, if you like…\n\n\n\n\n\n\n\nFine, I guess you should just go."
- shop_fortune_teller_lw_hint_0: "{BOTTOM}\nBy the black cats, the book opens the desert"
- shop_fortune_teller_lw_hint_1: "{BOTTOM}\nBy the black cats, nothing doing"
- shop_fortune_teller_lw_hint_2: "{BOTTOM}\nBy the black cats, I'm cheap"
- shop_fortune_teller_lw_hint_3: "{BOTTOM}\nBy the black cats, am I cheap?"
- shop_fortune_teller_lw_hint_4: "{BOTTOM}\nBy the black cats, Zora lives at the end of the river"
- shop_fortune_teller_lw_hint_5: "{BOTTOM}\nBy the black cats, The Cape can pass through the barrier"
# $F0
- shop_fortune_teller_lw_hint_6: "{BOTTOM}\nBy the black cats, Spin, Hammer, or Net to hurt Agahnim"
- shop_fortune_teller_lw_hint_7: "{BOTTOM}\nBy the black cats, You can jump in the well by the blacksmiths"
- shop_fortune_teller_lw_no_rupees: "{BOTTOM}\nThe black cats are hungry, come back with rupees"
- shop_fortune_teller_lw: "{BOTTOM}\nWelcome to the Fortune Shoppe!\nFancy a read?\n ≥I must know\n _Negative\n{CHOICE}"
- shop_fortune_teller_lw_post_hint: "{BOTTOM}\nFor ᚋᚌ rupees\nIt is done.\nBe gone!"
- shop_fortune_teller_lw_no: "{BOTTOM}\nWell then, why did you even come in here?"
- shop_fortune_teller_lw_hint_8: "{BOTTOM}\nBy the black cats, why you do?"
- shop_fortune_teller_lw_hint_9: "{BOTTOM}\nBy the black cats, panda crackers"
- shop_fortune_teller_lw_hint_10: "{BOTTOM}\nBy the black cats, the missing blacksmith is south of the Village of Outcasts"
- shop_fortune_teller_lw_hint_11: "{BOTTOM}\nBy the black cats, open chests to get stuff"
- shop_fortune_teller_lw_hint_12: "{BOTTOM}\nBy the black cats, you can buy a new bomb at the Bomb Shoppe"
- shop_fortune_teller_lw_hint_13: "{BOTTOM}\nBy the black cats, big bombs blow up cracked walls in pyramids"
- shop_fortune_teller_lw_hint_14: "{BOTTOM}\nBy the black cats, you need all the crystals to open Ganon's Tower"
- shop_fortune_teller_lw_hint_15: "{BOTTOM}\nBy the black cats, Silver Arrows will defeat Ganon in his final phase"
- dark_sanctuary: "For 20 rupees I'll tell you something?\nHow about it?\n ≥ Yes\n _No\n{CHOICE}"
- dark_sanctuary_hint_0: "I once was a tea kettle, but then I moved up in the world, and now you can see me as this. Makes you wonder. What I could be next time."
# $100
- dark_sanctuary_no: "Then go away!"
- dark_sanctuary_hint_1: "There is a thief in the desert, he can open creepy chests that follow you. But now that we have that out of the way, do you like my hair? I've spent eons getting it this way."
- dark_sanctuary_yes: "With Crystals 5&6, you can find a great fairy in the pyramid.\n\nFlomp Flomp, Whizzle Whomp"
- dark_sanctuary_hint_2: "All I can say is that my life is pretty plain,\nI like watchin' the puddles gather rain,\nAnd all I can do is just pour some tea for two,\nAnd speak my point of view but it's not sane,\nIt's not sane"
- sick_kid_no_bottle: "{BOTTOM}\nI'm sick! Show me a bottle, get something!"
- sick_kid_trade: "{BOTTOM}\nCool Bottle! Here's something for you."
- sick_kid_post_trade: "{BOTTOM}\nLeave me alone\nI'm sick. You have my item."
- desert_thief_sitting: "………………………"
- desert_thief_following: "why……………"
- desert_thief_question: "I was a thief. I open purple chests!\nKeep secret?\n ≥ Sure thing\n Never!\n{CHOICE}"
- desert_thief_question_yes: "Cool, bring me any purple chests you find."
- desert_thief_after_item_get: "You tell anyone and I will give you such a pinch!"
- desert_thief_reassure: "Bring chests. It's a secret to everyone."
- hylian_text_3: "^^ ^%=^= =%=\n=%% =%%=^\n==%^= %=^^%"
- tablet_ether_book: "Can you make things fall out of the sky? With the Master Sword, you can!"
- tablet_bombos_book: "Can you make things fall out of the sky? With the Master Sword, you can!"
# $110
- magic_bat_wake: "You bum! I was sleeping! Where's my magic bolts?"
- magic_bat_give_half_magic: "How you like me now?"
- intro_main: { NoPause: "{INTRO}\n Episode III\n{PAUSE3}\n A Link to\n the Past\n{PAUSE3}\n Randomizer\n{PAUSE3}\nAfter mostly disregarding what happened in the first two games.\n{PAUSE3}\nLink awakens to his uncle leaving the house.\n{PAUSE3}\nHe just runs out the door,\n{PAUSE3}\ninto the rainy night.\n{PAUSE3}\n{CHANGEPIC}\nGanon has moved around all the items in Hyrule.\n{PAUSE7}\nYou will have to find all the items necessary to beat Ganon.\n{PAUSE7}\nThis is your chance to be a hero.\n{PAUSE3}\n{CHANGEPIC}\nYou must get the 7 crystals to beat Ganon.\n{PAUSE9}\n{CHANGEPIC}" }
- intro_throne_room: { NoPause: "{IBOX}\nLook at this Stalfos on the throne." }
- intro_zelda_cell: { NoPause: "{IBOX}\nIt is your time to shine!" }
- intro_agahnim: { NoPause: "{IBOX}\nAlso, you need to defeat this guy!" }
- pickup_purple_chest: "A curious box. Let's take it with us!"
- bomb_shop: "30 bombs for 100 rupees. Good deals all day!"
- bomb_shop_big_bomb: "30 bombs for 100 rupees, 100 rupees 1 BIG bomb. Good deals all day!"
- bomb_shop_big_bomb_buy: "Thanks!\nBoom goes the dynamite!"
- item_get_big_bomb: "YAY! press A to splode it!"
- kiki_second_extortion: "For 100 more, I'll open this place.\nHow about it?\n ≥ Open\n _Nah\n{CHOICE}"
- kiki_second_extortion_no: "Heh, good luck getting in."
- kiki_second_extortion_yes: "Yay! Rupees!\nOkay, let's do this!"
- kiki_first_extortion: "I'm Kiki. I like rupees, may I have 10?\nHow about it?\n ≥ Yes\n _No\n{CHOICE}"
- kiki_first_extortion_yes: "Nice. I'll tag along with you for a bit."
# $120
- kiki_first_extortion_no: "Pfft. I have no reason to hang. See ya!"
- kiki_leaving_screen: "No no no no no! We should play by my rules! Goodbye…"
- blind_in_the_cell: "You saved me!\nPlease get me out of here!"
- blind_by_the_light: "Aaaahhhh~!\nS-so bright~!"
- blind_not_that_way: "No! Don't go that way!"
- aginah_l1sword_no_book: "I once had a fish dinner. I still remember it to this day."
- aginah_l1sword_with_pendants: "Do you remember when I was young?\n\nI sure don't."
- aginah: "So, I've been living in this cave for years, and you think you can just come along and bomb open walls?"
- aginah_need_better_sword: "Once, I farted in this cave so bad all the jazz hands guys ran away and hid in the sand."
- aginah_have_better_sword: "Pandas are very vicious animals. Never forget…\n\n\n\n\nI never will…"
- catfish: "You woke me from my nap! Take this, and get out!"
- catfish_after_item: "I don't have anything else for you!\nTake this!"
- lumberjack_right: "One of us always lies."
- lumberjack_left: "One of us always tells the truth."
- lumberjack_left_post_agahnim: "One of us likes peanut butter."
- fighting_brothers_right: "I walled off my brother Leo\n\nWhat a dingus."
# $130
- fighting_brothers_right_opened: "Now I should probably talk to him…"
- fighting_brothers_left: "Did you come from my brothers room?\n\nAre we cool?"
- maiden_crystal_1: "{SPEED2}\n{BOTTOM}\n{NOBORDER}\nI have a pretty red dress.\n{SPEED2}\nJust thought I would tell you."
- maiden_crystal_2: "{SPEED2}\n{BOTTOM}\n{NOBORDER}\nI have a pretty blue dress.\n{SPEED2}\nJust thought I would tell you."
- maiden_crystal_3: "{SPEED2}\n{BOTTOM}\n{NOBORDER}\nI have a pretty gold dress.\n{SPEED2}\nJust thought I would tell you."
- maiden_crystal_4: "{SPEED2}\n{BOTTOM}\n{NOBORDER}\nI have a pretty redder dress.\n{SPEED2}\nJust thought I would tell you."
- maiden_crystal_5: "{SPEED2}\n{BOTTOM}\n{NOBORDER}\nI have a pretty green dress.\n{SPEED2}\nJust thought I would tell you."
- maiden_crystal_6: "{SPEED2}\n{BOTTOM}\n{NOBORDER}\nI have a pretty green dress.\n{SPEED2}\nJust thought I would tell you."
- maiden_crystal_7: "{SPEED2}\n{BOTTOM}\n{NOBORDER}\nIt's about friggin time.\n{SPEED2}\nDo you know how long I've been waiting?!"
- maiden_ending: "May the way of the hero lead to the Triforce"
- maiden_confirm_undersood: "{SPEED2}\n{BOTTOM}\n{NOBORDER}\nCapisce?\n ≥ Yes\n _No\n{CHOICE}"
- barrier_breaking: "What did the seven crystals say to Ganon's Tower?"
- maiden_crystal_7_again: "{SPEED2}\n{BOTTOM}\n{NOBORDER}\nIt's about friggin time.\n{SPEED2}\nDo you know how long I've been waiting?!"
- agahnim_zelda_teleport: "I am a magician, and this is my act. Watch as I make this girl disappear"
- agahnim_magic_running_away: "And now, the end is near\nAnd so I face the final curtain\nMy friend, I'll say it clear\nI'll state my case, of which I'm certain\nI've lived a life that's full\nI've traveled each and every highway\nBut more, much more than this\nI did it my way"
- agahnim_hide_and_seek_found: "Peek-a-boo!"
# $140
- agahnim_defeated: "Arrrgggghhh. Well you're coming with me!"
- agahnim_final_meeting: "You have done well to come this far. Now, die!"
# $142
- zora_meeting: "What do you want?\n ≥ Flippers\n _Nothin'\n{CHOICE}"
- zora_tells_cost: "Fine! But they aren't cheap. You got 500 rupees?\n ≥ Duh\n _Oh carp\n{CHOICE}"
- zora_get_flippers: "Here's some Flippers for you! Swim little fish, swim."
- zora_no_cash: "Fine!\nGo get some more money first."
- zora_no_buy_item: "Wah hoo! Well, whenever you want to see these gills, stop on by."
- kakariko_sahasrahla_grandson: "My grandpa is over in the East. I'm bad with directions. I'll mark your map. Best of luck!\n{HARP}"
- kakariko_sahasrahla_grandson_next: "Someday I'll be in a high school band!"
- dark_palace_tree_dude: "Did you know…\n\n\nA tree typically has many secondary branches supported clear of the ground by the trunk. This trunk typically contains woody tissue for strength, and vascular tissue to carry materials from one part of the tree to another."
- fairy_wishing_ponds: "\n-Wishing pond-\n\nThrow item in?\n ≥ Yesh\n _No\n{CHOICE}"
- fairy_wishing_ponds_no: "\n Stop it!"
- pond_of_wishing_no: "\n Fine then!"
- pond_of_wishing_return_item: "Okay. Here's your item back, cause I can't use it. I'm stuck in this fountain."
- pond_of_wishing_throw: "How many?\n ≥ᚌᚋ rupees\n _ᚎᚍ rupees\n{CHOICE}"
- pond_pre_item_silvers: "I like you, so here's a thing you can use to beat up Ganon."
# $150
- pond_of_wishing_great_luck: "\n is great luck"
- pond_of_wishing_good_luck: "\n is good luck"
- pond_of_wishing_meh_luck: "\n is meh luck"
# Repurposed to no items in Randomizer
- pond_of_wishing_bad_luck: "Why you come in here and pretend like you have something this fountain wants? Come back with bottles!"
- pond_of_wishing_fortune: "by the way, your fortune,"
- item_get_14_heart: "3 more to go\n ¼\nYay!"
- item_get_24_heart: "2 more to go\n ½\nWhee!"
- item_get_34_heart: "1 more to go\n ¾\nGood job!"
- item_get_whole_heart: "You got a whole ♥!!\nGo you!"
- item_get_sanc_heart: "You got a whole ♥!\nGo you!"
- fairy_fountain_refill: "Well done, lettuce have a cup of tea…"
- death_mountain_bullied_no_pearl: "I wrote a word. Just one. On a stone and threw it into the ocean. It was my word. It was what would save me. I hope someday someone finds that word and brings it to me. The word is the beginning of my song."
- death_mountain_bullied_with_pearl: "I wrote a song. Just one. On a guitar and threw it into the sky. It was my song. It could tame beasts and free minds. It flitters on the wind and lurks in our minds. It is the song of nature, of humanity, of dreams and dreamers."
- death_mountain_bully_no_pearl: "Add garlic, ginger and apple and cook for 2 minutes. Add carrots, potatoes, garam masala and curry powder and stir well. Add tomato paste, stir well and slowly add red wine and bring to a boil. Add sugar, soy sauce and water, stir and bring to a boil again."
- death_mountain_bully_with_pearl: "I think I forgot how to smile…"
- shop_darkworld_enter: "It's dangerous outside, buy my crap for safety."
# $160
- game_chest_village_of_outcasts: "Pay 30 rupees, open 2 chests. Are you lucky?\nSo, Play game?\n ≥ Play\n _Never!\n{CHOICE}"
- game_chest_no_cash: "So, like, you need 30 rupees.\nSilly!"
- game_chest_not_played: "You want to play a game?\nTalk to me."
- game_chest_played: "You've opened the chests!\nTime to go."
- game_chest_village_of_outcasts_play: "Alright, brother!\nGo play!"
- shop_first_time: "Welcome to my shop! Select stuff with A.\nDO IT NOW!"
- shop_already_have: "So, like, you already have one of those."
- shop_buy_shield: "Thanks! Now you can block fire balls."
- shop_buy_red_potion: "Red goo, so good! It's like a fairy in a bottle, except you have to activate it yourself."
- shop_buy_arrows: "Arrows! Cause you were too lazy to look under some pots!"
- shop_buy_bombs: "You bought bombs. What, couldn't find any under bushes?"
- shop_buy_bee: "He's my best friend. Please take care of him, and never lose him."
- shop_buy_heart: "You really just bought this?"
- shop_first_no_bottle_buy: "Why does no one own bottles? Go find one first!"
- shop_buy_no_space: "You are carrying to much crap, go use some of it first!"
- ganon_fall_in: "You drove\naway my other\nself, Agahnim,\ntwo times…\nBut, I won't\ngive you the\nTriforce.\nI'll defeat\nyou!"
# $170
- ganon_phase_3: "Can you beat\nmy darkness\ntechnique?"
- lost_woods_thief: "Have you seen Andy?\n\nHe was out looking for our prized Ether medallion.\nI wonder when he will be back?"
- blinds_hut_dude: "I'm just some dude. This is Blind's hut."
- end_triforce: "{SPEED2}\n{MENU}\n{NOBORDER}\n G G"
# $174
- toppi_fallen: "Ouch!\n\nYou Jerk!"
- kakariko_tavern_fisherman: "Don't argue\nwith a frozen\nDeadrock.\nHe'll never\nchange his\nposition!"
- thief_money: "It's a secret to everyone."
- thief_desert_rupee_cave: "So you, like, busted down my door, and are being a jerk by talking to me? Normally I would be angry and make you pay for it, but I bet you're just going to break all my pots and steal my 50 rupees."
- thief_ice_rupee_cave: "I'm a rupee pot farmer. One day I will take over the world with my skillz. Have you met my brother in the desert? He's way richer than I am."
- telepathic_tile_south_east_darkworld_cave: "~~ Dev cave ~~\n No farming\n required"
# $17A
- cukeman: "Did you hear that Veetorp beat ajneb174 in a 1 on 1 race at AGDQ?"
- cukeman_2: "You found Shabadoo, huh?\nNiiiiice."
- potion_shop_no_cash: "Yo! I'm not running a charity here."
- kakariko_powdered_chicken: "Smallhacker…\n\n\nWas hiding, you found me!\n\n\nOkay, you can leave now."
- game_chest_south_of_kakariko: "Pay 20 rupees, open 1 chest. Are you lucky?\nSo, Play game?\n ≥ Play\n _Never!\n{CHOICE}"
- game_chest_play_yes: "Good luck then"
# $180
- game_chest_play_no: "Well fine, I didn't want your rupees."
- game_chest_lost_woods: "Pay 100 rupees, open 1 chest. Are you lucky?\nSo, Play game?\n ≥ Play\n Never!\n{CHOICE}"
- kakariko_flophouse_man_no_flippers: "I sure do have a lot of beds.\n\nZora is a cheapskate and will try to sell you his trash for 500 rupees…"
- kakariko_flophouse_man: "I sure do have a lot of beds.\n\nDid you know if you played the flute in the center of town things could happen?"
- menu_start_2: { NoPause: "{MENU}\n{SPEED0}\n≥£'s House\n_Sanctuary\n{CHOICE3}" }
- menu_start_3: { NoPause: "{MENU}\n{SPEED0}\n≥£'s House\n_Sanctuary\n_Mountain Cave\n{CHOICE2}" }
- menu_pause: { NoPause: "{SPEED0}\n≥Continue Game\n_Save and Quit\n{CHOICE3}" }
- game_digging_choice: "Have 80 Rupees? Want to play digging game?\n ≥Yes\n _No\n{CHOICE}"
- game_digging_start: "Okay, use the shovel with Y!"
- game_digging_no_cash: "Shovel rental is 80 rupees.\nI have all day"
- game_digging_end_time: "Time's up!\nTime for you to go."
- game_digging_come_back_later: "Come back later, I have to bury things."
- game_digging_no_follower: "Something is following you. I don't like."
- menu_start_4: { NoPause: "{MENU}\n{SPEED0}\n≥£'s House\n_Mountain Cave\n{CHOICE3}" }
- ganon_fall_in_alt: "You think you\nare ready to\nface me?\n\nI will not die\n\nunless you\ncomplete your\ngoals. Dingus!"
- ganon_phase_3_alt: "Got wax in your ears? I cannot die!"
# $190
- sign_east_death_mountain_bridge: "How did you get up here?"
- fish_money: "It's a secret to everyone."
- end_pad_data: ""

View File

@ -0,0 +1,354 @@
What do you
call a blind
dinosaur?
Adoyouthink-
hesaurus
---
A blind man
walks into
a bar.
And a table.
And a chair.
---
What do ducks
like to eat?
Quackers!
---
How do you
set up a party
in space?
You planet!
---
I'm glad I
know sign
language,
it's pretty
handy.
---
What did Zelda
say to Link at
a secure door?
TRIFORCE!
---
I am on a
seafood diet.
Every time
I see food,
I eat it.
---
I've decided
to sell my
vacuum.
It was just
gathering
dust.
---
Whats the best
time to go to
the dentist?
Tooth-hurtie!
---
Why can't a
bike stand on
its own?
It's two-tired!
---
If you haven't
found Quake
yet…
it's not your
fault.
---
Why is Peter
Pan always
flying?
Because he
Neverlands!
---
I once told a
joke to Armos.
But he
remained
stone-faced!
---
Lanmola was
late to our
dinner party.
He just came
for the desert
---
Moldorm is
such a
prankster.
And I fall for
it every time!
---
Helmasaur is
throwing a
party.
I hope it's
a masquerade!
---
I'd like to
know Arrghus
better.
But he won't
come out of
his shell!
---
Mothula didn't
have much fun
at the party.
He's immune to
spiked punch!
---
Don't set me
up with that
chick from
Steve's Town.
I'm not
interested in
a Blind date!
---
Kholdstare is
afraid to go
to the circus.
Hungry kids
thought he was
cotton candy!
---
I asked who
Vitreous' best
friends are.
He said,
'Me, Myself,
and Eye!'
---
Trinexx can be
a hothead or
he can be an
ice guy. In
the end, he's
a solid
individual!
---
Bari thought I
had moved out
of town.
He was shocked
to see me!
---
I can only get
Weetabix
around here.
I have to go
to Steve's
Town for Count
Chocula!
---
Don't argue
with a frozen
Deadrock.
He'll never
change his
position!
---
I offered a
drink to a
self-loathing
Ghini.
He said he
didn't like
spirits!
---
I was supposed
to meet Gibdo
for lunch.
But he got
wrapped up in
something!
---
Goriya sure
has changed
in this game.
I hope he
comes back
around!
---
Hinox actually
wants to be a
lawyer.
Too bad he
bombed the
Bar exam!
---
I'm surprised
Moblin's tusks
are so gross.
He always has
his Trident
with him!
---
Dont tell
Stalfos Im
here.
He has a bone
to pick with
me!
---
I got
Wallmaster to
help me move
furniture.
He was really
handy!
---
Wizzrobe was
just here.
He always
vanishes right
before we get
the check!
---
I shouldn't
have picked up
Zora's tab.
That guy
drinks like
a fish!
---
I was sharing
a drink with
Poe.
For no reason,
he left in a
heartbeat!
---
Dont trust
horsemen on
Death Mountain
Theyre Lynel
the time!
---
Today's
special is
battered bat.
Got slapped
for offering a
lady a Keese!
---
Dont walk
under
propellered
pineapples.
You may end up
wearing
a pee hat!
---
My girlfriend
burrowed under
the sand.
So I decided
to Leever!
---
Geldman wants
to be a
Broadway star.
Hes always
practicing
Jazz Hands!
---
Octoballoon
must be mad
at me.
He blows up
at the sight
of me!
---
Toppo is a
total pothead.
He hates it
when you take
away his grass
---
I lost my
shield by
that house.
Why did they
put up a
Pikit fence?!
---
Know that fox
in Steves
Town?
Hell Pikku
pockets if you
aren't careful
---
Dash through
Dark World
bushes.
Youll see
Ganon is tryin
to Stal you!
---
Eyegore!
You gore!
We all gore
those jerks
with arrows!
---
I like my
whiskey neat.
Some prefer it
Octoroks!
---
I consoled
Freezor over a
cup of coffee.
His problems
just seemed to
melt away!
---
Magic droplets
of water dont
shut up.
They just
Kyameron!
---
I bought hot
wings for
Sluggula.
They gave him
explosive
diarrhea!
---
Hardhat Beetle
wont
Let It Be?
Tell it to Get
Back or give
it a Ticket to
Ride down
a hole!
---

View File

@ -0,0 +1,103 @@

G G
---
All your base
are belong
to us.
---
You have ended
the domination
of dr. wily
---
thanks for
playing!!!
---
You Win!
---
Thank you!
your quest
is over.
---
A winner
is
you!
---
WINNER!!
---
I'm sorry
but your
princess is in
another castle
---
success!
---
Whelp…
that just
happened
---
Oh hey…
it's you
---
Wheeeeee!!
---
Time for
another one?
---
and
scene
---
GOT EM!!
---
THE VALUUUE!!!
---
Cool seed,
right?
---
We did it!
---
Spam those
emotes in
wilds chat
---
O M G
---
Hello. Will
you be my
friend?
---
Beetorp
was
here!
---
The Wind Fish
will wake
soon. Hoot!
---
meow meow meow
meow meow meow
oh my god!
---
Ahhhhhhhhh
Ya ya yaaaah
Ya ya yaaah
---
.done
.comment lol
---
You get to
drink from
the firehose
---

View File

@ -0,0 +1,87 @@

from typing import Any, List
import copy
from worlds.smz3.TotalSMZ3.Text.Dialog import Dialog
from worlds.smz3.TotalSMZ3.Text.Texts import text_folder
from yaml import load, Loader
class StringTable:
@staticmethod
def ParseEntries(resource: str):
with open(resource, 'rb') as f:
yaml = str(f.read(), "utf-8")
content = load(yaml, Loader)
result = []
for entryValue in content:
(key, value) = next(iter(entryValue.items()))
if isinstance(value, List):
result.append((key, value))
elif isinstance(value, str):
result.append((key, Dialog.Compiled(value)))
elif isinstance(value, dict):
result.append((key, Dialog.Compiled(value["NoPause"], False)))
else: raise Exception(f"Did not expect an object of type {type(value)}")
return result
template = ParseEntries.__func__(text_folder + "/Scripts/StringTable.yaml")
def __init__(self):
self.entries = copy.deepcopy(StringTable.template)
def SetSahasrahlaRevealText(self, text: str):
self.SetText("sahasrahla_quest_information", text)
def SetBombShopRevealText(self, text: str):
self.SetText("bomb_shop", text)
def SetBlindText(self, text: str):
self.SetText("blind_by_the_light", text)
def SetTavernManText(self, text: str):
self.SetText("kakariko_tavern_fisherman", text)
def SetGanonFirstPhaseText(self, text: str):
self.SetText("ganon_fall_in", text)
def SetGanonThirdPhaseText(self, text: str):
self.SetText("ganon_phase_3", text)
def SetTriforceRoomText(self, text: str):
self.SetText("end_triforce", "{NOBORDER}\n" + text)
def SetPedestalText(self, text: str):
self.SetText("mastersword_pedestal_translated", text)
def SetEtherText(self, text: str):
self.SetText("tablet_ether_book", text)
def SetBombosText(self, text: str):
self.SetText("tablet_bombos_book", text)
def SetText(self, name: str, text: str):
count = 0
for key, value in self.entries:
if (key == name):
index = count
break
else:
count += 1
self.entries[index] = (name, Dialog.Compiled(text))
def GetPaddedBytes(self):
return self.GetBytes(True)
def GetBytes(self, pad = False):
maxBytes = 0x7355
data = []
for entry in self.entries:
data += entry[1]
if (len(data) > maxBytes):
raise Exception(f"String Table exceeds 0x{maxBytes:X} bytes")
if (pad and len(data) < maxBytes):
data += [0xFF] * (maxBytes - len(data))
return data

View File

@ -0,0 +1,97 @@
from typing import Any, List
from worlds.smz3.TotalSMZ3.Region import Region
from worlds.smz3.TotalSMZ3.Regions.Zelda.GanonsTower import GanonsTower
from worlds.smz3.TotalSMZ3.Item import Item, ItemType
from yaml import load, Loader
import random
import os
text_folder = os.path.dirname(__file__)
class Texts:
@staticmethod
def ParseYamlScripts(resource: str):
with open(resource, 'rb') as f:
yaml = str(f.read(), "utf-8")
return load(yaml, Loader)
@staticmethod
def ParseTextScript(resource: str):
with open(resource, 'r') as file:
return [text.rstrip('\n') for text in file.read().replace("\r", "").split("---\n") if text]
scripts: Any = ParseYamlScripts.__func__(text_folder + "/Scripts/General.yaml")
blind: List[str] = ParseTextScript.__func__(text_folder + "/Scripts/Blind.txt")
ganon: List[str] = ParseTextScript.__func__(text_folder + "/Scripts/Ganon.txt")
tavernMan: List[str] = ParseTextScript.__func__(text_folder + "/Scripts/TavernMan.txt")
triforceRoom: List[str] = ParseTextScript.__func__(text_folder + "/Scripts/TriforceRoom.txt")
@staticmethod
def SahasrahlaReveal(dungeon: Region):
text = Texts.scripts["SahasrahlaReveal"]
return text.replace("<dungeon>", dungeon.Area)
@staticmethod
def BombShopReveal(dungeons: List[Region]):
text = Texts.scripts["BombShopReveal"]
return text.replace("<first>", dungeons[0].Area).replace("<second>", dungeons[1].Area)
@staticmethod
def GanonThirdPhaseSingle(silvers: Region):
node = Texts.scripts["GanonSilversReveal"]["single"]
text = node["local" if isinstance(silvers, GanonsTower) else "remote"]
return text.replace("<region>", silvers.Area)
@staticmethod
def GanonThirdPhaseMulti(silvers: Region, myWorld: int, silversWorld: int = None, silversPlayer: str = None):
node = Texts.scripts["GanonSilversReveal"]["multi"]
if silvers is None:
if (silversWorld == myWorld.Id):
return node["local"]
player = silversPlayer
else:
if (silvers.world == myWorld):
return node["local"]
player = silvers.world.Player
player = player.rjust(7 + len(player) // 2)
text = node["remote"]
return text.replace("<player>", player)
@staticmethod
def ItemTextbox(item: Item):
nameMap = {
ItemType.BottleWithGoldBee : ItemType.BottleWithBee.name,
ItemType.HeartContainerRefill : ItemType.HeartContainer.name,
ItemType.OneRupee : "PocketRupees",
ItemType.FiveRupees : "PocketRupees",
ItemType.TwentyRupees : "CouchRupees",
ItemType.TwentyRupees2 : "CouchRupees",
ItemType.FiftyRupees : "CouchRupees",
ItemType.BombUpgrade5 : "BombUpgrade",
ItemType.BombUpgrade10 : "BombUpgrade",
ItemType.ArrowUpgrade5 : "ArrowUpgrade",
ItemType.ArrowUpgrade10 : "ArrowUpgrade",
item.Type : item.Type.name,
}
if item.IsMap(): name = "Map"
elif item.IsCompass(): name = "Compass"
elif item.IsKeycard(): name = "Keycard"
else: name = nameMap[item.Type]
items = Texts.scripts["Items"]
return items.get(name, None) or items["default"]
@staticmethod
def Blind(rnd: random): return Texts.RandomLine(rnd, Texts.blind)
@staticmethod
def TavernMan(rnd: random): return Texts.RandomLine(rnd, Texts.tavernMan)
@staticmethod
def GanonFirstPhase(rnd: random): return Texts.RandomLine(rnd, Texts.ganon)
@staticmethod
def TriforceRoom(rnd: random): return Texts.RandomLine(rnd, Texts.triforceRoom)
@staticmethod
def RandomLine(rnd: random, lines: List[str]): return lines[rnd.randrange(0, len(lines))]

View File

View File

@ -0,0 +1,166 @@
from typing import Dict, List
import random
import worlds.smz3.TotalSMZ3.Region as Region
import worlds.smz3.TotalSMZ3.Config as Config
import worlds.smz3.TotalSMZ3.Item as Item
import worlds.smz3.TotalSMZ3.Location as Location
from worlds.smz3.TotalSMZ3.Regions.Zelda.CastleTower import CastleTower
from worlds.smz3.TotalSMZ3.Regions.Zelda.EasternPalace import EasternPalace
from worlds.smz3.TotalSMZ3.Regions.Zelda.DesertPalace import DesertPalace
from worlds.smz3.TotalSMZ3.Regions.Zelda.TowerOfHera import TowerOfHera
from worlds.smz3.TotalSMZ3.Regions.Zelda.PalaceOfDarkness import PalaceOfDarkness
from worlds.smz3.TotalSMZ3.Regions.Zelda.SwampPalace import SwampPalace
from worlds.smz3.TotalSMZ3.Regions.Zelda.SkullWoods import SkullWoods
from worlds.smz3.TotalSMZ3.Regions.Zelda.ThievesTown import ThievesTown
from worlds.smz3.TotalSMZ3.Regions.Zelda.IcePalace import IcePalace
from worlds.smz3.TotalSMZ3.Regions.Zelda.MiseryMire import MiseryMire
from worlds.smz3.TotalSMZ3.Regions.Zelda.TurtleRock import TurtleRock
from worlds.smz3.TotalSMZ3.Regions.Zelda.GanonsTower import GanonsTower
from worlds.smz3.TotalSMZ3.Regions.Zelda.LightWorld.DeathMountain.West import West as LightWorldDeathMountainWest
from worlds.smz3.TotalSMZ3.Regions.Zelda.LightWorld.DeathMountain.East import East as LightWorldDeathMountainEast
from worlds.smz3.TotalSMZ3.Regions.Zelda.LightWorld.NorthWest import NorthWest as LightWorldNorthWest
from worlds.smz3.TotalSMZ3.Regions.Zelda.LightWorld.NorthEast import NorthEast as LightWorldNorthEast
from worlds.smz3.TotalSMZ3.Regions.Zelda.LightWorld.South import South as LightWorldSouth
from worlds.smz3.TotalSMZ3.Regions.Zelda.HyruleCastle import HyruleCastle
from worlds.smz3.TotalSMZ3.Regions.Zelda.DarkWorld.DeathMountain.West import West as DarkWorldDeathMountainWest
from worlds.smz3.TotalSMZ3.Regions.Zelda.DarkWorld.DeathMountain.East import East as DarkWorldDeathMountainEast
from worlds.smz3.TotalSMZ3.Regions.Zelda.DarkWorld.NorthWest import NorthWest as DarkWorldNorthWest
from worlds.smz3.TotalSMZ3.Regions.Zelda.DarkWorld.NorthEast import NorthEast as DarkWorldNorthEast
from worlds.smz3.TotalSMZ3.Regions.Zelda.DarkWorld.South import South as DarkWorldSouth
from worlds.smz3.TotalSMZ3.Regions.Zelda.DarkWorld.Mire import Mire as DarkWorldMire
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Crateria.Central import Central
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Crateria.West import West as CrateriaWest
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Crateria.East import East as CrateriaEast
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Brinstar.Blue import Blue
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Brinstar.Green import Green
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Brinstar.Kraid import Kraid
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Brinstar.Pink import Pink
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Brinstar.Red import Red
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Maridia.Outer import Outer
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.Maridia.Inner import Inner
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.NorfairUpper.West import West as NorfairUpperWest
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.NorfairUpper.East import East as NorfairUpperEast
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.NorfairUpper.Crocomire import Crocomire
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.NorfairLower.West import West as NorfairLowerWest
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.NorfairLower.East import East as NorfairLowerEast
from worlds.smz3.TotalSMZ3.Regions.SuperMetroid.WreckedShip import WreckedShip
class World:
Locations: List[Location.Location]
Regions: List[Region.Region]
Config: Config.Config
Player: str
Guid: str
Id: int
def Items(self):
return [l.Item for l in self.Locations if l.Item != None]
locationLookup: Dict[str, Location.Location]
regionLookup: Dict[str, Region.Region]
def GetLocation(self, name:str): return self.locationLookup[name]
def GetRegion(self, name:str): return self.regionLookup[name]
def __init__(self, config: Config, player: str, id: int, guid: str):
self.Config = config
self.Player = player
self.Id = id
self.Guid = guid
self.Regions = [
CastleTower(self, self.Config),
EasternPalace(self, self.Config),
DesertPalace(self, self.Config),
TowerOfHera(self, self.Config),
PalaceOfDarkness(self, self.Config),
SwampPalace(self, self.Config),
SkullWoods(self, self.Config),
ThievesTown(self, self.Config),
IcePalace(self, self.Config),
MiseryMire(self, self.Config),
TurtleRock(self, self.Config),
GanonsTower(self, self.Config),
LightWorldDeathMountainWest(self, self.Config),
LightWorldDeathMountainEast(self, self.Config),
LightWorldNorthWest(self, self.Config),
LightWorldNorthEast(self, self.Config),
LightWorldSouth(self, self.Config),
HyruleCastle(self, self.Config),
DarkWorldDeathMountainWest(self, self.Config),
DarkWorldDeathMountainEast(self, self.Config),
DarkWorldNorthWest(self, self.Config),
DarkWorldNorthEast(self, self.Config),
DarkWorldSouth(self, self.Config),
DarkWorldMire(self, self.Config),
Central(self, self.Config),
CrateriaWest(self, self.Config),
CrateriaEast(self, self.Config),
Blue(self, self.Config),
Green(self, self.Config),
Kraid(self, self.Config),
Pink(self, self.Config),
Red(self, self.Config),
Outer(self, self.Config),
Inner(self, self.Config),
NorfairUpperWest(self, self.Config),
NorfairUpperEast(self, self.Config),
Crocomire(self, self.Config),
NorfairLowerWest(self, self.Config),
NorfairLowerEast(self, self.Config),
WreckedShip(self, self.Config)
]
self.Locations = []
for r in self.Regions:
self.Locations = self.Locations + r.Locations
self.regionLookup = {r.Name:r for r in self.Regions}
self.locationLookup = {loc.Name:loc for loc in self.Locations}
for region in self.Regions:
region.GenerateLocationLookup()
def CanEnter(self, regionName: str, items: Item.Progression):
region = self.regionLookup[regionName]
if (region == None):
raise Exception(f"World.CanEnter: Invalid region name {regionName}", f'{regionName=}'.partition('=')[0])
return region.CanEnter(items)
def CanAquire(self, items: Item.Progression, reward: Region.RewardType):
return next(iter([region for region in self.Regions if isinstance(region, Region.IReward) and region.Reward == reward])).CanComplete(items)
def CanAquireAll(self, items: Item.Progression, *rewards: Region.RewardType):
for region in self.Regions:
if issubclass(type(region), Region.IReward):
if (region.Reward in rewards):
if not region.CanComplete(items):
return False
return True
# return all(region.CanComplete(items) for region in self.Regions if (isinstance(region, Region.IReward) and region.Reward in rewards))
def Setup(self, rnd: random):
self.SetMedallions(rnd)
self.SetRewards(rnd)
def SetMedallions(self, rnd: random):
medallionMap = {0: Item.ItemType.Bombos, 1: Item.ItemType.Ether, 2: Item.ItemType.Quake}
regionList = [region for region in self.Regions if isinstance(region, Region.IMedallionAccess)]
for region in regionList:
region.Medallion = medallionMap[rnd.randint(0, 2)]
def SetRewards(self, rnd: random):
rewards = [
Region.RewardType.PendantGreen, Region.RewardType.PendantNonGreen, Region.RewardType.PendantNonGreen, Region.RewardType.CrystalRed, Region.RewardType.CrystalRed,
Region.RewardType.CrystalBlue, Region.RewardType.CrystalBlue, Region.RewardType.CrystalBlue, Region.RewardType.CrystalBlue, Region.RewardType.CrystalBlue
]
rnd.shuffle(rewards)
regionList = [region for region in self.Regions if isinstance(region, Region.IReward) and region.Reward == Region.RewardType.Null]
for region in regionList:
region.Reward = rewards[0]
rewards.remove(region.Reward)

View File

438
worlds/smz3/__init__.py Normal file
View File

@ -0,0 +1,438 @@
import logging
import copy
import os
import random
import threading
import Patch
from typing import Dict, Set, TextIO
from BaseClasses import Region, Entrance, Location, MultiWorld, Item, RegionType, CollectionState
from worlds.generic.Rules import add_rule, set_rule
import worlds.smz3.TotalSMZ3.Item as TotalSMZ3Item
from worlds.smz3.TotalSMZ3.World import World as TotalSMZ3World
from worlds.smz3.TotalSMZ3.Config import Config, GameMode, GanonInvincible, Goal, KeyShuffle, MorphLocation, SMLogic, SwordLocation, Z3Logic
from worlds.smz3.TotalSMZ3.Location import LocationType, locations_start_id, Location as TotalSMZ3Location
from worlds.smz3.TotalSMZ3.Patch import Patch as TotalSMZ3Patch, getWord, getWordArray
from ..AutoWorld import World, AutoLogicRegister
from .Rom import get_base_rom_bytes
from .ips import IPS_Patch
from .Options import smz3_options
world_folder = os.path.dirname(__file__)
logger = logging.getLogger("SMZ3")
class SMCollectionState(metaclass=AutoLogicRegister):
def init_mixin(self, parent: MultiWorld):
# for unit tests where MultiWorld is instantiated before worlds
if hasattr(parent, "state"):
self.smz3state = {player: TotalSMZ3Item.Progression([]) for player in parent.get_game_players("SMZ3")}
else:
self.smz3state = {}
def copy_mixin(self, ret) -> CollectionState:
ret.smz3state = {player: copy.deepcopy(self.smz3state[player]) for player in self.world.get_game_players("SMZ3")}
return ret
class SMZ3World(World):
"""
A python port of Super Metroid & A Link To The Past Crossover Item Randomizer based on v11.2 of Total's SMZ3.
This is allowed as long as we keep features and logic as close as possible as the original.
"""
game: str = "SMZ3"
topology_present = False
data_version = 0
options = smz3_options
item_names: Set[str] = frozenset(TotalSMZ3Item.lookup_name_to_id)
location_names: Set[str]
item_name_to_id = TotalSMZ3Item.lookup_name_to_id
location_name_to_id: Dict[str, int] = {key : locations_start_id + value.Id for key, value in TotalSMZ3World(Config({}), "", 0, "").locationLookup.items()}
remote_items: bool = False
remote_start_inventory: bool = False
def __init__(self, world: MultiWorld, player: int):
self.rom_name_available_event = threading.Event()
self.locations = {}
self.unreachable = []
super().__init__(world, player)
def generate_early(self):
config = Config({})
config.GameMode = GameMode.Multiworld
config.Z3Logic = Z3Logic.Normal
config.SMLogic = SMLogic(self.world.sm_logic[self.player].value)
config.SwordLocation = SwordLocation(self.world.sword_location[self.player].value)
config.MorphLocation = MorphLocation(self.world.morph_location[self.player].value)
config.Goal = Goal.DefeatBoth
config.KeyShuffle = KeyShuffle(self.world.key_shuffle[self.player].value)
config.Keysanity = config.KeyShuffle != KeyShuffle.Null
config.GanonInvincible = GanonInvincible.BeforeCrystals
self.local_random = random.Random(self.world.random.randint(0, 1000))
self.smz3World = TotalSMZ3World(config, self.world.get_player_name(self.player), self.player, self.world.seed_name)
self.smz3DungeonItems = []
SMZ3World.location_names = frozenset(self.smz3World.locationLookup.keys())
self.world.state.smz3state[self.player] = TotalSMZ3Item.Progression([])
def generate_basic(self):
self.smz3World.Setup(self.world.random)
self.dungeon = TotalSMZ3Item.Item.CreateDungeonPool(self.smz3World)
self.dungeon.reverse()
self.progression = TotalSMZ3Item.Item.CreateProgressionPool(self.smz3World)
self.keyCardsItems = TotalSMZ3Item.Item.CreateKeycards(self.smz3World)
niceItems = TotalSMZ3Item.Item.CreateNicePool(self.smz3World)
junkItems = TotalSMZ3Item.Item.CreateJunkPool(self.smz3World)
allJunkItems = niceItems + junkItems
if (self.smz3World.Config.Keysanity):
progressionItems = self.progression + self.dungeon + self.keyCardsItems
else:
progressionItems = self.progression
for item in self.keyCardsItems:
self.world.push_precollected(SMZ3Item(item.Type.name, False, item.Type, self.item_name_to_id[item.Type.name], self.player, item))
itemPool = [SMZ3Item(item.Type.name, True, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in progressionItems] + \
[SMZ3Item(item.Type.name, False, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in allJunkItems]
self.smz3DungeonItems = [SMZ3Item(item.Type.name, True, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in self.dungeon]
self.world.itempool += itemPool
def set_rules(self):
# SM G4 is logically required to access Ganon's Tower in SMZ3
self.world.completion_condition[self.player] = lambda state: \
self.smz3World.GetRegion("Ganon's Tower").CanEnter(state.smz3state[self.player]) and \
self.smz3World.GetRegion("Ganon's Tower").TowerAscend(state.smz3state[self.player])
for region in self.smz3World.Regions:
entrance = self.world.get_entrance('Menu' + "->" + region.Name, self.player)
set_rule(entrance, lambda state, region=region: region.CanEnter(state.smz3state[self.player]))
for loc in region.Locations:
l = self.locations[loc.Name]
if self.world.accessibility[self.player] != 'locations':
l.always_allow = lambda state, item, loc=loc: \
item.game == "SMZ3" and \
loc.alwaysAllow(TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World), state.smz3state[self.player])
old_rule = l.item_rule
l.item_rule = lambda item, loc=loc, region=region: (\
item.game != "SMZ3" or \
loc.allow(TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World), None) and \
region.CanFill(TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World))) and old_rule(item)
set_rule(l, lambda state, loc=loc: loc.Available(state.smz3state[self.player]))
def create_regions(self):
self.create_locations(self.player)
startRegion = self.create_region(self.world, self.player, 'Menu')
self.world.regions.append(startRegion)
for region in self.smz3World.Regions:
currentRegion = self.create_region(self.world, self.player, region.Name, region.locationLookup.keys(), [region.Name + "->" + 'Menu'])
self.world.regions.append(currentRegion)
entrance = self.world.get_entrance(region.Name + "->" + 'Menu', self.player)
entrance.connect(startRegion)
exit = Entrance(self.player, 'Menu' + "->" + region.Name, startRegion)
startRegion.exits.append(exit)
exit.connect(currentRegion)
def apply_sm_custom_sprite(self):
itemSprites = ["off_world_prog_item.bin", "off_world_item.bin"]
itemSpritesAddress = [0xF800, 0xF900]
idx = 0
offworldSprites = {}
for fileName in itemSprites:
with open(world_folder + "/data/custom_sprite/" + fileName, 'rb') as stream:
buffer = bytearray(stream.read())
offworldSprites[0x04Eff2 + 10*((0x6B + 0x40) + idx)] = bytearray(getWordArray(itemSpritesAddress[idx])) + buffer[0:8]
offworldSprites[0x090000 + itemSpritesAddress[idx]] = buffer[8:264]
idx += 1
return offworldSprites
def convert_to_sm_item_name(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) = getWord(charMap.get(char, 0x3C4E))
data.append(w0)
data.append(w1)
return data
def convert_to_lttp_item_name(self, itemName):
return bytearray(itemName[:19].center(19, " ") , 'utf8') + bytearray(0)
def apply_item_names(self):
patch = {}
sm_remote_idx = 0
lttp_remote_idx = 0
for location in self.smz3World.Locations:
if self.world.worlds[location.APLocation.item.player].game != self.game:
if location.Type == LocationType.Visible or location.Type == LocationType.Chozo or location.Type == LocationType.Hidden:
patch[0x390000 + sm_remote_idx*64] = self.convert_to_sm_item_name(location.APLocation.item.name)
sm_remote_idx += 1
progressionItem = (0 if location.APLocation.item.advancement else 0x8000) + sm_remote_idx
patch[0x386000 + (location.Id * 8) + 6] = bytearray(getWordArray(progressionItem))
else:
patch[0x390000 + 100 * 64 + lttp_remote_idx * 20] = self.convert_to_lttp_item_name(location.APLocation.item.name)
lttp_remote_idx += 1
progressionItem = (0 if location.APLocation.item.advancement else 0x8000) + lttp_remote_idx
patch[0x386000 + (location.Id * 8) + 6] = bytearray(getWordArray(progressionItem))
return patch
def generate_output(self, output_directory: str):
try:
base_combined_rom = get_base_rom_bytes()
basepatch = IPS_Patch.load(world_folder + "/data/zsm.ips")
base_combined_rom = basepatch.apply(base_combined_rom)
patcher = TotalSMZ3Patch(self.smz3World,
[world.smz3World for key, world in self.world.worlds.items() if isinstance(world, SMZ3World)],
self.world.seed_name,
self.world.seed,
self.local_random,
self.world.world_name_lookup,
next(iter(loc.player for loc in self.world.get_locations() if loc.item == self.create_item("SilverArrows"))))
patches = patcher.Create(self.smz3World.Config)
patches.update(self.apply_sm_custom_sprite())
patches.update(self.apply_item_names())
for addr, bytes in patches.items():
offset = 0
for byte in bytes:
base_combined_rom[addr + offset] = byte
offset += 1
outfilebase = 'AP_' + self.world.seed_name
outfilepname = f'_P{self.player}'
outfilepname += f"_{self.world.player_name[self.player].replace(' ', '_')}" \
filename = os.path.join(output_directory, f'{outfilebase}{outfilepname}.sfc')
with open(filename, "wb") as binary_file:
binary_file.write(base_combined_rom)
Patch.create_patch_file(filename, player=self.player, player_name=self.world.player_name[self.player], game=Patch.GAME_SMZ3)
os.remove(filename)
self.rom_name = bytearray(patcher.title, 'utf8')
except:
raise
finally:
self.rom_name_available_event.set() # make sure threading continues and errors are collected
def modify_multidata(self, multidata: dict):
import base64
if (not self.smz3World.Config.Keysanity):
for item_name in self.keyCardsItems:
item_id = self.item_name_to_id.get(item_name.Type.name, None)
try:
multidata["precollected_items"][self.player].remove(item_id)
except ValueError as e:
logger.warning(f"Attempted to remove nonexistent item id {item_id} from smz3 precollected items ({item_name})")
# 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.smz3state[item.player].Add([TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World)])
if item.advancement:
state.prog_items[item.name, item.player] += 1
return True # indicate that a logical state change has occured
return False
def remove(self, state: CollectionState, item: Item) -> bool:
name = self.collect_item(state, item, True)
if name:
state.smz3state[item.player].Remove([TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World)])
state.prog_items[name, item.player] -= 1
if state.prog_items[name, item.player] < 1:
del (state.prog_items[name, item.player])
return True
return False
def create_item(self, name: str) -> Item:
return SMZ3Item(name, True, TotalSMZ3Item.ItemType[name], self.item_name_to_id[name], player = self.player)
def pre_fill(self):
from Fill import fill_restrictive
self.InitialFillInOwnWorld()
if (not self.smz3World.Config.Keysanity):
locations = [loc for loc in self.locations.values() if loc.item is None]
self.world.random.shuffle(locations)
all_state = self.world.get_all_state(False)
for item in self.smz3DungeonItems:
all_state.remove(item)
all_dungeonItems = self.smz3DungeonItems[:]
fill_restrictive(self.world, all_state, locations, all_dungeonItems, True, True)
# some small or big keys (those always_allow) can be unreachable in-game
# while logic still collects some of them (probably to simulate the player collecting pot keys in the logic), some others don't
# so we need to remove those exceptions as progression items
if self.world.accessibility[self.player] != 'locations':
exception_item = [TotalSMZ3Item.ItemType.BigKeySW, TotalSMZ3Item.ItemType.BigKeySP, TotalSMZ3Item.ItemType.KeyTH]
for item in self.smz3DungeonItems:
if item.item.Type in exception_item and item.location.always_allow(all_state, item) and not all_state.can_reach(item.location):
item.advancement = False
item.item.Progression = False
item.location.event = False
self.unreachable.append(item.location)
def get_pre_fill_items(self):
if (not self.smz3World.Config.Keysanity):
return self.smz3DungeonItems
else:
return []
def write_spoiler(self, spoiler_handle: TextIO):
self.world.spoiler.unreachables.update(self.unreachable)
def FillItemAtLocation(self, itemPool, itemType, location):
itemToPlace = TotalSMZ3Item.Item.Get(itemPool, itemType, self.smz3World)
if (itemToPlace == None):
raise Exception(f"Tried to place item {itemType} at {location.Name}, but there is no such item in the item pool")
else:
location.Item = itemToPlace
itemFromPool = next((i for i in self.world.itempool if i.player == self.player and i.name == itemToPlace.Type.name), None)
if itemFromPool is not None:
self.world.get_location(location.Name, self.player).place_locked_item(itemFromPool)
self.world.itempool.remove(itemFromPool)
else:
itemFromPool = next((i for i in self.smz3DungeonItems if i.player == self.player and i.name == itemToPlace.Type.name), None)
if itemFromPool is not None:
self.world.get_location(location.Name, self.player).place_locked_item(itemFromPool)
self.smz3DungeonItems.remove(itemFromPool)
itemPool.remove(itemToPlace)
def FrontFillItemInOwnWorld(self, itemPool, itemType):
item = TotalSMZ3Item.Item.Get(itemPool, itemType, self.smz3World)
location = next(iter(self.world.random.sample(TotalSMZ3Location.AvailableGlobal(TotalSMZ3Location.Empty(self.smz3World.Locations), self.smz3World.Items()), 1)), None)
if (location == None):
raise Exception(f"Tried to front fill {item.Name} in, but no location was available")
location.Item = item
itemFromPool = next((i for i in self.world.itempool if i.player == self.player and i.name == item.Type.name), None)
if itemFromPool is not None:
self.world.get_location(location.Name, self.player).place_locked_item(itemFromPool)
self.world.itempool.remove(itemFromPool)
itemPool.remove(item)
def InitialFillInOwnWorld(self):
self.FillItemAtLocation(self.dungeon, TotalSMZ3Item.ItemType.KeySW, self.smz3World.GetLocation("Skull Woods - Pinball Room"))
# /* Check Swords option and place as needed */
if self.smz3World.Config.SwordLocation == SwordLocation.Uncle:
self.FillItemAtLocation(self.progression, TotalSMZ3Item.ItemType.ProgressiveSword, self.smz3World.GetLocation("Link's Uncle"))
elif self.smz3World.Config.SwordLocation == SwordLocation.Early:
self.FrontFillItemInOwnWorld(self.progression, TotalSMZ3Item.ItemType.ProgressiveSword)
# /* Check Morph option and place as needed */
if self.smz3World.Config.MorphLocation == MorphLocation.Original:
self.FillItemAtLocation(self.progression, TotalSMZ3Item.ItemType.Morph, self.smz3World.GetLocation("Morphing Ball"))
elif self.smz3World.Config.MorphLocation == MorphLocation.Early:
self.FrontFillItemInOwnWorld(self.progression, TotalSMZ3Item.ItemType.Morph)
# /* We place a PB and Super in Sphere 1 to make sure the filler
# * doesn't start locking items behind this when there are a
# * high chance of the trash fill actually making them available */
self.FrontFillItemInOwnWorld(self.progression, TotalSMZ3Item.ItemType.Super)
self.FrontFillItemInOwnWorld(self.progression, TotalSMZ3Item.ItemType.PowerBomb)
def create_locations(self, player: int):
for name, id in SMZ3World.location_name_to_id.items():
newLoc = SMZ3Location(player, name, id)
self.locations[name] = newLoc
self.smz3World.locationLookup[name].APLocation = newLoc
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 SMZ3Location(Location):
game: str = "SMZ3"
def __init__(self, player: int, name: str, address=None, parent=None):
super(SMZ3Location, self).__init__(player, name, address, parent)
def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool:
oldItem = self.item
self.item = item
result = self.always_allow(state, item) or (self.item_rule(item) and (not check_access or self.can_reach(state)))
self.item = oldItem
return result
class SMZ3Item(Item):
game = "SMZ3"
def __init__(self, name, advancement, type, code, player: int = None, item = None):
self.type = type
self.item = item
super(SMZ3Item, self).__init__(name, advancement, code, player)

Binary file not shown.

Binary file not shown.

BIN
worlds/smz3/data/zsm.ips Normal file

Binary file not shown.

252
worlds/smz3/ips.py Normal file
View File

@ -0,0 +1,252 @@
import itertools
def range_union(ranges):
ret = []
for rg in sorted([[r.start, r.stop] for r in ranges]):
begin, end = rg[0], rg[-1]
if ret and ret[-1][1] > begin:
ret[-1][1] = max(ret[-1][1], end)
else:
ret.append([begin, end])
return [range(r[0], r[1]) for r in ret]
# adapted from ips-util for python 3.2 (https://pypi.org/project/ips-util/)
class IPS_Patch(object):
def __init__(self, patchDict=None):
self.records = []
self.truncate_length = None
self.max_size = 0
if patchDict is not None:
for addr, data in patchDict.items():
byteData = bytearray(data)
self.add_record(addr, byteData)
def toDict(self):
ret = {}
for record in self.records:
if 'rle_count' in record:
ret[record['address']] = [int.from_bytes(record['data'],'little')]*record['rle_count']
else:
ret[record['address']] = [int(b) for b in record['data']]
return ret
@staticmethod
def load(filename):
loaded_patch = IPS_Patch()
with open(filename, 'rb') as file:
header = file.read(5)
if header != b'PATCH':
raise Exception('Not a valid IPS patch file!')
while True:
address_bytes = file.read(3)
if address_bytes == b'EOF':
break
address = int.from_bytes(address_bytes, byteorder='big')
length = int.from_bytes(file.read(2), byteorder='big')
rle_count = 0
if length == 0:
rle_count = int.from_bytes(file.read(2), byteorder='big')
length = 1
data = file.read(length)
if rle_count > 0:
loaded_patch.add_rle_record(address, data, rle_count)
else:
loaded_patch.add_record(address, data)
truncate_bytes = file.read(3)
if len(truncate_bytes) == 3:
loaded_patch.set_truncate_length(int.from_bytes(truncate_bytes, byteorder='big'))
return loaded_patch
@staticmethod
def create(original_data, patched_data):
# The heuristics for optimizing a patch were chosen with reference to
# the source code of Flips: https://github.com/Alcaro/Flips
patch = IPS_Patch()
run_in_progress = False
current_run_start = 0
current_run_data = bytearray()
runs = []
if len(original_data) > len(patched_data):
patch.set_truncate_length(len(patched_data))
original_data = original_data[:len(patched_data)]
elif len(original_data) < len(patched_data):
original_data += bytes([0] * (len(patched_data) - len(original_data)))
if original_data[-1] == 0 and patched_data[-1] == 0:
patch.add_record(len(patched_data) - 1, bytes([0]))
for index, (original, patched) in enumerate(zip(original_data, patched_data)):
if not run_in_progress:
if original != patched:
run_in_progress = True
current_run_start = index
current_run_data = bytearray([patched])
else:
if original == patched:
runs.append((current_run_start, current_run_data))
run_in_progress = False
else:
current_run_data.append(patched)
if run_in_progress:
runs.append((current_run_start, current_run_data))
for start, data in runs:
if start == int.from_bytes(b'EOF', byteorder='big'):
start -= 1
data = bytes([patched_data[start - 1]]) + data
grouped_byte_data = list([
{'val': key, 'count': sum(1 for _ in group), 'is_last': False}
for key,group in itertools.groupby(data)
])
grouped_byte_data[-1]['is_last'] = True
record_in_progress = bytearray()
pos = start
for group in grouped_byte_data:
if len(record_in_progress) > 0:
# We don't want to interrupt a record in progress with a new header unless
# this group is longer than two complete headers.
if group['count'] > 13:
patch.add_record(pos, record_in_progress)
pos += len(record_in_progress)
record_in_progress = bytearray()
patch.add_rle_record(pos, bytes([group['val']]), group['count'])
pos += group['count']
else:
record_in_progress += bytes([group['val']] * group['count'])
elif (group['count'] > 3 and group['is_last']) or group['count'] > 8:
# We benefit from making this an RLE record if the length is at least 8,
# or the length is at least 3 and we know it to be the last part of this diff.
# Make sure not to overflow the maximum length. Split it up if necessary.
remaining_length = group['count']
while remaining_length > 0xffff:
patch.add_rle_record(pos, bytes([group['val']]), 0xffff)
remaining_length -= 0xffff
pos += 0xffff
patch.add_rle_record(pos, bytes([group['val']]), remaining_length)
pos += remaining_length
else:
# Just begin a new standard record.
record_in_progress += bytes([group['val']] * group['count'])
if len(record_in_progress) > 0xffff:
patch.add_record(pos, record_in_progress[:0xffff])
record_in_progress = record_in_progress[0xffff:]
pos += 0xffff
# Finalize any record still in progress.
if len(record_in_progress) > 0:
patch.add_record(pos, record_in_progress)
return patch
def add_record(self, address, data):
if address == int.from_bytes(b'EOF', byteorder='big'):
raise RuntimeError('Start address {0:x} is invalid in the IPS format. Please shift your starting address back by one byte to avoid it.'.format(address))
if address > 0xffffff:
raise RuntimeError('Start address {0:x} is too large for the IPS format. Addresses must fit into 3 bytes.'.format(address))
if len(data) > 0xffff:
raise RuntimeError('Record with length {0} is too large for the IPS format. Records must be less than 65536 bytes.'.format(len(data)))
if len(data) == 0: # ignore empty records
return
record = {'address': address, 'data': data, 'size':len(data)}
self.appendRecord(record)
def add_rle_record(self, address, data, count):
if address == int.from_bytes(b'EOF', byteorder='big'):
raise RuntimeError('Start address {0:x} is invalid in the IPS format. Please shift your starting address back by one byte to avoid it.'.format(address))
if address > 0xffffff:
raise RuntimeError('Start address {0:x} is too large for the IPS format. Addresses must fit into 3 bytes.'.format(address))
if count > 0xffff:
raise RuntimeError('RLE record with length {0} is too large for the IPS format. RLE records must be less than 65536 bytes.'.format(count))
if len(data) != 1:
raise RuntimeError('Data for RLE record must be exactly one byte! Received {0}.'.format(data))
record = {'address': address, 'data': data, 'rle_count': count, 'size': count}
self.appendRecord(record)
def appendRecord(self, record):
sz = record['address'] + record['size']
if sz > self.max_size:
self.max_size = sz
self.records.append(record)
def set_truncate_length(self, truncate_length):
self.truncate_length = truncate_length
def encode(self):
encoded_bytes = bytearray()
encoded_bytes += 'PATCH'.encode('ascii')
for record in self.records:
encoded_bytes += record['address'].to_bytes(3, byteorder='big')
if 'rle_count' in record:
encoded_bytes += (0).to_bytes(2, byteorder='big')
encoded_bytes += record['rle_count'].to_bytes(2, byteorder='big')
else:
encoded_bytes += len(record['data']).to_bytes(2, byteorder='big')
encoded_bytes += record['data']
encoded_bytes += 'EOF'.encode('ascii')
if self.truncate_length is not None:
encoded_bytes += self.truncate_length.to_bytes(3, byteorder='big')
return encoded_bytes
# save patch into IPS file
def save(self, path):
with open(path, 'wb') as ipsFile:
ipsFile.write(self.encode())
# applies patch on an existing bytearray
def apply(self, in_data):
out_data = bytearray(in_data)
for record in self.records:
if record['address'] >= len(out_data):
out_data += bytes([0] * (record['address'] - len(out_data) + 1))
if 'rle_count' in record:
out_data[record['address'] : record['address'] + record['rle_count']] = b''.join([record['data']] * record['rle_count'])
else:
out_data[record['address'] : record['address'] + len(record['data'])] = record['data']
if self.truncate_length is not None:
out_data = out_data[:self.truncate_length]
return out_data
# applies patch on an opened file
def applyFile(self, handle):
for record in self.records:
handle.seek(record['address'])
if 'rle_count' in record:
handle.write(bytearray(b'').join([record['data']]) * record['rle_count'])
else:
handle.write(record['data'])
# appends an IPS_Patch on top of this one
def append(self, patch):
if patch.truncate_length is not None and (self.truncate_length is None or patch.truncate_length > self.truncate_length):
self.set_truncate_length(patch.truncate_length)
for record in patch.records:
if record['size'] > 0: # ignore empty records
self.appendRecord(record)
# gets address ranges written to by this patch
def getRanges(self):
def getRange(record):
return range(record['address'], record['address']+record['size'])
return range_union([getRange(record) for record in self.records])