LttP: move some LttP specific things more towards locations where they belong.

This commit is contained in:
Fabian Dill 2021-08-10 08:00:53 +02:00
parent 4bfeb77a3a
commit 299036ecca
8 changed files with 54 additions and 75 deletions

View File

@ -852,9 +852,8 @@ class Region(object):
def can_fill(self, item: Item):
inside_dungeon_item = item.locked_dungeon_item
sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Hyrule Castle)'
if sewer_hack or inside_dungeon_item:
return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player
if inside_dungeon_item:
return self.dungeon.is_dungeon_item(item) and item.player == self.player
return True

View File

@ -130,7 +130,6 @@ def distribute_items_restrictive(world: MultiWorld, gftower_trash=False, fill_lo
if world.mode[player] == 'standard' and world.keyshuffle[player] is True:
standard_keyshuffle_players.add(player)
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
if standard_keyshuffle_players:
progitempool.sort(

View File

@ -19,7 +19,7 @@ from tkinter import Tk, Frame, Label, StringVar, Entry, filedialog, messagebox,
from urllib.parse import urlparse
from urllib.request import urlopen
from worlds.alttp.Rom import Sprite, LocalRom, apply_rom_settings
from worlds.alttp.Rom import Sprite, LocalRom, apply_rom_settings, get_base_rom_bytes
from Utils import output_path, local_path, open_file
@ -222,7 +222,6 @@ def adjustGUI():
else:
messagebox.showinfo(title="Success", message=f"Rom patched successfully to {path}")
from Utils import persistent_store
from worlds.alttp.Rom import Sprite
if isinstance(guiargs.sprite, Sprite):
guiargs.sprite = guiargs.sprite.name
persistent_store("adjuster", "last_settings_3", guiargs)
@ -412,9 +411,8 @@ def get_rom_frame(parent=None):
def RomSelect():
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")])
import Patch
try:
Patch.get_base_rom_bytes(rom) # throws error on checksum fail
get_base_rom_bytes(rom) # throws error on checksum fail
except Exception as e:
logging.exception(e)
messagebox.showerror(title="Error while reading ROM", message=str(e))

View File

@ -2,7 +2,6 @@ import bsdiff4
import yaml
import os
import lzma
import hashlib
import threading
import concurrent.futures
import zipfile
@ -10,37 +9,13 @@ import sys
from typing import Tuple, Optional
import Utils
from worlds.alttp.Rom import JAP10HASH
current_patch_version = 2
def get_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 get_base_rom_bytes(file_name: str = "") -> bytes:
from worlds.alttp.Rom import read_rom
base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
if not base_rom_bytes:
file_name = get_base_rom_path(file_name)
base_rom_bytes = bytes(read_rom(open(file_name, "rb")))
basemd5 = hashlib.md5()
basemd5.update(base_rom_bytes)
if JAP10HASH != basemd5.hexdigest():
raise Exception('Supplied Base Rom does not match known MD5 for JAP(1.0) release. '
'Get the correct game and version, then dump it')
get_base_rom_bytes.base_rom_bytes = base_rom_bytes
return base_rom_bytes
def generate_yaml(patch: bytes, metadata: Optional[dict] = None) -> bytes:
from worlds.alttp.Rom import JAP10HASH
patch = yaml.dump({"meta": metadata,
"patch": patch,
"game": "A Link to the Past",
@ -52,6 +27,7 @@ def generate_yaml(patch: bytes, metadata: Optional[dict] = None) -> bytes:
def generate_patch(rom: bytes, metadata: Optional[dict] = None) -> bytes:
from worlds.alttp.Rom import get_base_rom_bytes
if metadata is None:
metadata = {}
patch = bsdiff4.diff(get_base_rom_bytes(), rom)
@ -71,6 +47,7 @@ def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str
def create_rom_bytes(patch_file: str, ignore_version: bool = False) -> Tuple[dict, str, bytearray]:
from worlds.alttp.Rom import get_base_rom_bytes
data = Utils.parse_yaml(lzma.decompress(load_bytes(patch_file)).decode("utf-8-sig"))
if not ignore_version and data["compatible_version"] > current_patch_version:
raise RuntimeError("Patch file is incompatible with this patcher, likely an update is required.")
@ -184,3 +161,11 @@ if __name__ == "__main__":
traceback.print_exc()
input("Press enter to close.")
def read_rom(stream, strip_header=True) -> bytearray:
"""Reads rom into bytearray and optionally strips off any smc header"""
buffer = bytearray(stream.read())
if strip_header and len(buffer) % 0x400 == 0x200:
return buffer[0x200:]
return buffer

View File

@ -286,9 +286,9 @@ def get_adjuster_settings(romfile: str) -> typing.Tuple[str, bool]:
if adjuster_settings:
import pprint
import Patch
from worlds.alttp.Rom import get_base_rom_path
adjuster_settings.rom = romfile
adjuster_settings.baserom = Patch.get_base_rom_path()
adjuster_settings.baserom = get_base_rom_path()
adjuster_settings.world = None
whitelist = {"music", "menuspeed", "heartbeep", "heartcolor", "ow_palettes", "quickswap",
"uw_palettes", "sprite"}

View File

@ -492,6 +492,7 @@ def set_up_take_anys(world, player):
world.initialize_regions()
def create_dynamic_shop_locations(world, player):
for shop in world.shops:
if shop.region.player == player:
@ -511,35 +512,7 @@ def create_dynamic_shop_locations(world, player):
loc.locked = True
def fill_prizes(world, attempts=15):
all_state = world.get_all_state(keys=True)
for player in world.get_game_players("A Link to the Past"):
crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'], player)
crystal_locations = [world.get_location('Turtle Rock - Prize', player), world.get_location('Eastern Palace - Prize', player), world.get_location('Desert Palace - Prize', player), world.get_location('Tower of Hera - Prize', player), world.get_location('Palace of Darkness - Prize', player),
world.get_location('Thieves\' Town - Prize', player), world.get_location('Skull Woods - Prize', player), world.get_location('Swamp Palace - Prize', player), world.get_location('Ice Palace - Prize', player),
world.get_location('Misery Mire - Prize', player)]
placed_prizes = {loc.item.name for loc in crystal_locations if loc.item}
unplaced_prizes = [crystal for crystal in crystals if crystal.name not in placed_prizes]
empty_crystal_locations = [loc for loc in crystal_locations if not loc.item]
for attempt in range(attempts):
try:
prizepool = unplaced_prizes.copy()
prize_locs = empty_crystal_locations.copy()
world.random.shuffle(prize_locs)
fill_restrictive(world, all_state, prize_locs, prizepool, True, lock=True)
except FillError as e:
logging.getLogger('').exception("Failed to place dungeon prizes (%s). Will retry %s more times", e,
attempts - attempt)
for location in empty_crystal_locations:
location.item = None
continue
break
else:
raise FillError('Unable to place dungeon prizes')
def get_pool_core(world, player: int):
progressive = world.progressive[player]
shuffle = world.shuffle[player]
difficulty = world.difficulty[player]
timer = world.timer[player]

View File

@ -1,4 +1,5 @@
import typing
import random
from Options import Choice, Range, Option, Toggle, DefaultOnToggle
@ -94,6 +95,7 @@ class Palette(Choice):
option_negative = 6
option_dizzy = 7
option_sick = 8
alias_random = 1
class OWPalette(Palette):
@ -136,6 +138,11 @@ class HeartColor(Choice):
option_green = 2
option_yellow = 3
@classmethod
def from_text(cls, text: str) -> Choice:
# remove when this becomes a base Choice feature
if text == "random":
return cls(random.randint(0, 3))
class QuickSwap(DefaultOnToggle):
displayname = "L/R Quickswapping"

View File

@ -1,5 +1,8 @@
from __future__ import annotations
import Utils
from Patch import read_rom
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '13a75c5dd28055fbcf8f69bd8161871d'
@ -31,7 +34,7 @@ from worlds.alttp.Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts
DeathMountain_texts, \
LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \
SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen
from Utils import local_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen
from worlds.alttp.Items import ItemFactory, item_table
from worlds.alttp.EntranceShuffle import door_addresses
import Patch
@ -168,14 +171,6 @@ class LocalRom(object):
self.write_int32(startaddress + (i * 4), value)
def read_rom(stream) -> bytearray:
"Reads rom into bytearray and strips off any smc header"
buffer = bytearray(stream.read())
if len(buffer) % 0x400 == 0x200:
buffer = buffer[0x200:]
return buffer
check_lock = threading.Lock()
@ -546,7 +541,6 @@ class Sprite():
self.valid = False
def get_vanilla_sprite_data(self):
from Patch import get_base_rom_path
file_name = get_base_rom_path()
base_rom_bytes = bytes(read_rom(open(file_name, "rb")))
Sprite.sprite = base_rom_bytes[0x80000:0x87000]
@ -2934,3 +2928,27 @@ hash_alphabet = [
"Lamp", "Hammer", "Shovel", "Flute", "Bug Net", "Book", "Bottle", "Potion", "Cane", "Cape", "Mirror", "Boots",
"Gloves", "Flippers", "Pearl", "Shield", "Tunic", "Heart", "Map", "Compass", "Key"
]
def get_base_rom_bytes(file_name: str = "") -> bytes:
base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
if not base_rom_bytes:
file_name = get_base_rom_path(file_name)
base_rom_bytes = bytes(read_rom(open(file_name, "rb")))
basemd5 = hashlib.md5()
basemd5.update(base_rom_bytes)
if JAP10HASH != basemd5.hexdigest():
raise Exception('Supplied Base Rom does not match known MD5 for JAP(1.0) release. '
'Get the correct game and version, then dump it')
get_base_rom_bytes.base_rom_bytes = base_rom_bytes
return base_rom_bytes
def get_base_rom_path(file_name: str = "") -> str:
options = Utils.get_options()
if not file_name:
file_name = options["lttp_options"]["rom_file"]
if not os.path.exists(file_name):
file_name = Utils.local_path(file_name)
return file_name