LttP: move some LttP specific things more towards locations where they belong.
This commit is contained in:
parent
4bfeb77a3a
commit
299036ecca
|
@ -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
|
||||
|
||||
|
|
1
Fill.py
1
Fill.py
|
@ -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(
|
||||
|
|
|
@ -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))
|
||||
|
|
39
Patch.py
39
Patch.py
|
@ -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
|
||||
|
|
4
Utils.py
4
Utils.py
|
@ -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"}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue