move ALTTP to its own world folder

This commit is contained in:
Fabian Dill 2020-10-24 05:38:56 +02:00
parent 4f8c737eec
commit 1d58f54101
36 changed files with 253 additions and 228 deletions

View File

@ -6,7 +6,7 @@ import textwrap
import sys import sys
from AdjusterMain import adjust from AdjusterMain import adjust
from Rom import get_sprite_from_name from worlds.alttp.Rom import get_sprite_from_name
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):

View File

@ -3,7 +3,7 @@ import time
import logging import logging
from Utils import output_path from Utils import output_path
from Rom import LocalRom, apply_rom_settings from worlds.alttp.Rom import LocalRom, apply_rom_settings
def adjust(args): def adjust(args):

View File

@ -5,16 +5,20 @@ from enum import Enum, unique
import logging import logging
import json import json
from collections import OrderedDict, Counter, deque from collections import OrderedDict, Counter, deque
from typing import Union, Optional, List, Set, Dict from typing import Union, Optional, List, Dict
import secrets import secrets
import random import random
from EntranceShuffle import door_addresses, indirect_connections import worlds.alttp
from worlds.alttp.EntranceShuffle import door_addresses, indirect_connections
from Utils import int16_as_bytes from Utils import int16_as_bytes
from Items import item_name_groups from worlds.alttp.Items import item_name_groups
class World(object): class World():
pass
class MultiWorld():
debug_types = False debug_types = False
player_names: list player_names: list
_region_cache: dict _region_cache: dict
@ -26,15 +30,7 @@ class World(object):
def __init__(self, players: int, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, def __init__(self, players: int, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer,
progressive, progressive,
goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints): goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints):
if self.debug_types:
import inspect
methods = inspect.getmembers(self, predicate=inspect.ismethod)
for name, method in methods:
if name.startswith("_debug_"):
setattr(self, name[7:], method)
logging.debug(f"Set {self}.{name[7:]} to {method}")
self.get_location = self._debug_get_location
self.random = random.Random() # world-local random state is saved in case of future use a self.random = random.Random() # world-local random state is saved in case of future use a
# persistently running program with multiple worlds rolling concurrently # persistently running program with multiple worlds rolling concurrently
self.players = players self.players = players
@ -131,6 +127,10 @@ class World(object):
set_player_attr('dark_room_logic', "lamp") set_player_attr('dark_room_logic', "lamp")
set_player_attr('restrict_dungeon_item_on_boss', False) set_player_attr('restrict_dungeon_item_on_boss', False)
self.worlds = []
#for i in range(players):
# self.worlds.append(worlds.alttp.ALTTPWorld({}, i))
def secure(self): def secure(self):
self.random = secrets.SystemRandom() self.random = secrets.SystemRandom()
@ -170,18 +170,6 @@ class World(object):
self._recache() self._recache()
return self._region_cache[player][regionname] return self._region_cache[player][regionname]
def _debug_get_region(self, regionname: str, player: int) -> Region:
if type(regionname) != str:
raise TypeError(f"expected str, got {type(regionname)} instead")
try:
return self._region_cache[player][regionname]
except KeyError:
for region in self.regions:
if region.name == regionname and region.player == player:
assert not region.world # this should only happen before initialization
self._region_cache[player][regionname] = region
return region
raise KeyError('No such region %s for player %d' % (regionname, player))
def get_entrance(self, entrance: str, player: int) -> Entrance: def get_entrance(self, entrance: str, player: int) -> Entrance:
try: try:
@ -190,19 +178,6 @@ class World(object):
self._recache() self._recache()
return self._entrance_cache[entrance, player] return self._entrance_cache[entrance, player]
def _debug_get_entrance(self, entrance: str, player: int) -> Entrance:
if type(entrance) != str:
raise TypeError(f"expected str, got {type(entrance)} instead")
try:
return self._entrance_cache[(entrance, player)]
except KeyError:
for region in self.regions:
for exit in region.exits:
if exit.name == entrance and exit.player == player:
self._entrance_cache[(entrance, player)] = exit
return exit
raise KeyError('No such entrance %s for player %d' % (entrance, player))
def get_location(self, location: str, player: int) -> Location: def get_location(self, location: str, player: int) -> Location:
try: try:
@ -211,19 +186,6 @@ class World(object):
self._recache() self._recache()
return self._location_cache[location, player] return self._location_cache[location, player]
def _debug_get_location(self, location: str, player: int) -> Location:
if type(location) != str:
raise TypeError(f"expected str, got {type(location)} instead")
try:
return self._location_cache[(location, player)]
except KeyError:
for region in self.regions:
for r_location in region.locations:
if r_location.name == location and r_location.player == player:
self._location_cache[(location, player)] = r_location
return r_location
raise KeyError('No such location %s for player %d' % (location, player))
def get_dungeon(self, dungeonname: str, player: int) -> Dungeon: def get_dungeon(self, dungeonname: str, player: int) -> Dungeon:
for dungeon in self.dungeons: for dungeon in self.dungeons:
@ -231,13 +193,6 @@ class World(object):
return dungeon return dungeon
raise KeyError('No such dungeon %s for player %d' % (dungeonname, player)) raise KeyError('No such dungeon %s for player %d' % (dungeonname, player))
def _debug_get_dungeon(self, dungeonname: str, player: int) -> Dungeon:
if type(dungeonname) != str:
raise TypeError(f"expected str, got {type(dungeonname)} instead")
for dungeon in self.dungeons:
if dungeon.name == dungeonname and dungeon.player == player:
return dungeon
raise KeyError('No such dungeon %s for player %d' % (dungeonname, player))
def get_all_state(self, keys=False) -> CollectionState: def get_all_state(self, keys=False) -> CollectionState:
ret = CollectionState(self) ret = CollectionState(self)
@ -291,7 +246,7 @@ class World(object):
if keys: if keys:
for p in range(1, self.players + 1): for p in range(1, self.players + 1):
from Items import ItemFactory from worlds.alttp.Items import ItemFactory
for item in ItemFactory( for item in ItemFactory(
['Small Key (Hyrule Castle)', 'Big Key (Eastern Palace)', 'Big Key (Desert Palace)', ['Small Key (Hyrule Castle)', 'Big Key (Eastern Palace)', 'Big Key (Desert Palace)',
'Small Key (Desert Palace)', 'Big Key (Tower of Hera)', 'Small Key (Tower of Hera)', 'Small Key (Desert Palace)', 'Big Key (Tower of Hera)', 'Small Key (Tower of Hera)',
@ -432,7 +387,7 @@ class World(object):
class CollectionState(object): class CollectionState(object):
def __init__(self, parent: World): def __init__(self, parent: MultiWorld):
self.prog_items = Counter() self.prog_items = Counter()
self.world = parent self.world = parent
self.reachable_regions = {player: set() for player in range(1, parent.players + 1)} self.reachable_regions = {player: set() for player in range(1, parent.players + 1)}
@ -1164,7 +1119,7 @@ class UpgradeShop(Shop):
class Spoiler(object): class Spoiler(object):
world: World world: MultiWorld
def __init__(self, world): def __init__(self, world):
self.world = world self.world = world

6
Gui.py
View File

@ -15,10 +15,10 @@ import ModuleUpdate
ModuleUpdate.update() ModuleUpdate.update()
from AdjusterMain import adjust from AdjusterMain import adjust
from EntranceRandomizer import parse_arguments from worlds.alttp.EntranceRandomizer import parse_arguments
from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
from Main import main, get_seed, __version__ as MWVersion from worlds.alttp.Main import main, get_seed, __version__ as MWVersion
from Rom import Sprite from worlds.alttp.Rom import Sprite
from Utils import is_bundled, local_path, output_path, open_file from Utils import is_bundled, local_path, output_path, open_file

View File

@ -33,7 +33,7 @@ from prompt_toolkit.patch_stdout import patch_stdout
from NetUtils import Endpoint from NetUtils import Endpoint
import WebUI import WebUI
import Regions from worlds.alttp import Regions
import Utils import Utils

View File

@ -24,8 +24,7 @@ import prompt_toolkit
from prompt_toolkit.patch_stdout import patch_stdout from prompt_toolkit.patch_stdout import patch_stdout
from fuzzywuzzy import process as fuzzy_process from fuzzywuzzy import process as fuzzy_process
import Items from worlds.alttp import Items, Regions
import Regions
import Utils import Utils
from Utils import get_item_name_from_id, get_location_name_from_address, \ from Utils import get_item_name_from_id, get_location_name_from_address, \
ReceivedItem, _version_tuple, restricted_loads ReceivedItem, _version_tuple, restricted_loads

View File

@ -11,11 +11,11 @@ import ModuleUpdate
ModuleUpdate.update() ModuleUpdate.update()
from Utils import parse_yaml from Utils import parse_yaml
from Rom import get_sprite_from_name from worlds.alttp.Rom import get_sprite_from_name
from EntranceRandomizer import parse_arguments from worlds.alttp.EntranceRandomizer import parse_arguments
from Main import main as ERmain from worlds.alttp.Main import main as ERmain
from Main import get_seed, seeddigits from worlds.alttp.Main import get_seed, seeddigits
from Items import item_name_groups, item_table from worlds.alttp.Items import item_name_groups, item_table
def mystery_argparse(): def mystery_argparse():

View File

@ -10,8 +10,9 @@ import sys
from typing import Tuple, Optional from typing import Tuple, Optional
import Utils import Utils
from Rom import JAP10HASH from worlds.alttp.Rom import JAP10HASH
current_patch_version = 1
def get_base_rom_path(file_name: str = "") -> str: def get_base_rom_path(file_name: str = "") -> str:
options = Utils.get_options() options = Utils.get_options()
@ -23,7 +24,7 @@ def get_base_rom_path(file_name: str = "") -> str:
def get_base_rom_bytes(file_name: str = "") -> bytes: def get_base_rom_bytes(file_name: str = "") -> bytes:
from Rom import read_rom from worlds.alttp.Rom import read_rom
base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
if not base_rom_bytes: if not base_rom_bytes:
file_name = get_base_rom_path(file_name) file_name = get_base_rom_path(file_name)
@ -42,6 +43,8 @@ def generate_yaml(patch: bytes, metadata: Optional[dict] = None) -> bytes:
patch = yaml.dump({"meta": metadata, patch = yaml.dump({"meta": metadata,
"patch": patch, "patch": patch,
"game": "alttp", "game": "alttp",
"compatible_version": 1, # minimum version of patch system expected for patching to be successful
"version": current_patch_version,
"base_checksum": JAP10HASH}) "base_checksum": JAP10HASH})
return patch.encode(encoding="utf-8-sig") return patch.encode(encoding="utf-8-sig")
@ -63,6 +66,8 @@ def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str
def create_rom_bytes(patch_file: str) -> Tuple[dict, str, bytearray]: def create_rom_bytes(patch_file: str) -> Tuple[dict, str, bytearray]:
data = Utils.parse_yaml(lzma.decompress(load_bytes(patch_file)).decode("utf-8-sig")) data = Utils.parse_yaml(lzma.decompress(load_bytes(patch_file)).decode("utf-8-sig"))
if data["compatible_version"] > current_patch_version:
raise RuntimeError("Patch file is incompatible with this patcher, likely an update is required.")
patched_data = bsdiff4.patch(get_base_rom_bytes(), data["patch"]) patched_data = bsdiff4.patch(get_base_rom_bytes(), data["patch"])
rom_hash = patched_data[int(0x7FC0):int(0x7FD5)] rom_hash = patched_data[int(0x7FC0):int(0x7FD5)]
data["meta"]["hash"] = "".join(chr(x) for x in rom_hash) data["meta"]["hash"] = "".join(chr(x) for x in rom_hash)

View File

@ -181,12 +181,12 @@ def get_options() -> dict:
def get_item_name_from_id(code): def get_item_name_from_id(code):
import Items from worlds.alttp import Items
return Items.lookup_id_to_name.get(code, f'Unknown item (ID:{code})') return Items.lookup_id_to_name.get(code, f'Unknown item (ID:{code})')
def get_location_name_from_address(address): def get_location_name_from_address(address):
import Regions from worlds.alttp import Regions
return Regions.lookup_id_to_name.get(address, f'Unknown location (ID:{address})') return Regions.lookup_id_to_name.get(address, f'Unknown location (ID:{address})')

View File

@ -6,9 +6,9 @@ import json
from flask import request, flash, redirect, url_for, session, render_template from flask import request, flash, redirect, url_for, session, render_template
from EntranceRandomizer import parse_arguments from worlds.alttp.EntranceRandomizer import parse_arguments
from Main import main as ERmain from worlds.alttp.Main import main as ERmain
from Main import get_seed, seeddigits from worlds.alttp.Main import get_seed, seeddigits
import pickle import pickle
from .models import * from .models import *

View File

@ -3,11 +3,9 @@ import collections
from flask import render_template from flask import render_template
from werkzeug.exceptions import abort from werkzeug.exceptions import abort
import datetime import datetime
import logging
from uuid import UUID from uuid import UUID
import Items from worlds.alttp import Items, Regions
import Regions
from WebHostLib import app, cache, Room from WebHostLib import app, cache, Room
from Utils import Hint from Utils import Hint

View File

@ -2,7 +2,7 @@ import argparse
import json import json
from os import listdir from os import listdir
from os.path import isfile, join from os.path import isfile, join
from Rom import Sprite from worlds.alttp.Rom import Sprite
from Gui import get_image_for_sprite from Gui import get_image_for_sprite
parser = argparse.ArgumentParser(description='Dump sprite data and .png files to a directory.') parser = argparse.ArgumentParser(description='Dump sprite data and .png files to a directory.')

View File

@ -1,7 +1,7 @@
import unittest import unittest
from BaseClasses import CollectionState from BaseClasses import CollectionState
from Items import ItemFactory from worlds.alttp.Items import ItemFactory
class TestBase(unittest.TestCase): class TestBase(unittest.TestCase):

View File

@ -1,17 +1,17 @@
import unittest import unittest
from BaseClasses import World, CollectionState from BaseClasses import MultiWorld, CollectionState
from Dungeons import create_dungeons, get_dungeon_item_pool from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool
from EntranceShuffle import mandatory_connections, connect_simple from worlds.alttp.EntranceShuffle import mandatory_connections, connect_simple
from ItemPool import difficulties, generate_itempool from worlds.alttp.ItemPool import difficulties, generate_itempool
from Items import ItemFactory from worlds.alttp.Items import ItemFactory
from Regions import create_regions, create_shops from worlds.alttp.Regions import create_regions, create_shops
from Rules import set_rules from worlds.alttp.Rules import set_rules
class TestDungeon(unittest.TestCase): class TestDungeon(unittest.TestCase):
def setUp(self): def setUp(self):
self.world = World(1, {1:'vanilla'}, {1:'noglitches'}, {1:'open'}, {1:'random'}, {1:'normal'}, {1:'normal'}, {1:False}, {1:'on'}, {1:'ganon'}, 'balanced', {1:'items'}, self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'noglitches'}, {1: 'open'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
True, {1:False}, False, None, {1:False}) True, {1:False}, False, None, {1:False})
self.starting_regions = [] # Where to start exploring self.starting_regions = [] # Where to start exploring
self.remove_exits = [] # Block dungeon exits self.remove_exits = [] # Block dungeon exits

View File

@ -1,17 +1,17 @@
from BaseClasses import World from BaseClasses import MultiWorld
from Dungeons import create_dungeons, get_dungeon_item_pool from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool
from EntranceShuffle import link_inverted_entrances from worlds.alttp.EntranceShuffle import link_inverted_entrances
from InvertedRegions import create_inverted_regions from worlds.alttp.InvertedRegions import create_inverted_regions
from ItemPool import generate_itempool, difficulties from worlds.alttp.ItemPool import generate_itempool, difficulties
from Items import ItemFactory from worlds.alttp.Items import ItemFactory
from Regions import mark_light_world_regions, create_shops from worlds.alttp.Regions import mark_light_world_regions, create_shops
from Rules import set_rules from worlds.alttp.Rules import set_rules
from test.TestBase import TestBase from test.TestBase import TestBase
class TestInverted(TestBase): class TestInverted(TestBase):
def setUp(self): def setUp(self):
self.world = World(1, {1:'vanilla'}, {1:'noglitches'}, {1:'inverted'}, {1:'random'}, {1:'normal'}, {1:'normal'}, {1:False}, {1:'on'}, {1:'ganon'}, 'balanced', {1:'items'}, self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'noglitches'}, {1: 'inverted'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
True, {1:False}, False, None, {1:False}) True, {1:False}, False, None, {1:False})
self.world.difficulty_requirements[1] = difficulties['normal'] self.world.difficulty_requirements[1] = difficulties['normal']
create_inverted_regions(self.world, 1) create_inverted_regions(self.world, 1)

View File

@ -1,18 +1,18 @@
import unittest import unittest
from BaseClasses import World from BaseClasses import MultiWorld
from Dungeons import create_dungeons from worlds.alttp.Dungeons import create_dungeons
from EntranceShuffle import connect_entrance, Inverted_LW_Entrances, Inverted_LW_Dungeon_Entrances, Inverted_LW_Single_Cave_Doors, Inverted_Old_Man_Entrances, Inverted_DW_Entrances, Inverted_DW_Dungeon_Entrances, Inverted_DW_Single_Cave_Doors, \ from worlds.alttp.EntranceShuffle import connect_entrance, Inverted_LW_Entrances, Inverted_LW_Dungeon_Entrances, Inverted_LW_Single_Cave_Doors, Inverted_Old_Man_Entrances, Inverted_DW_Entrances, Inverted_DW_Dungeon_Entrances, Inverted_DW_Single_Cave_Doors, \
Inverted_LW_Entrances_Must_Exit, Inverted_LW_Dungeon_Entrances_Must_Exit, Inverted_Bomb_Shop_Multi_Cave_Doors, Inverted_Bomb_Shop_Single_Cave_Doors, Blacksmith_Single_Cave_Doors, Inverted_Blacksmith_Multi_Cave_Doors Inverted_LW_Entrances_Must_Exit, Inverted_LW_Dungeon_Entrances_Must_Exit, Inverted_Bomb_Shop_Multi_Cave_Doors, Inverted_Bomb_Shop_Single_Cave_Doors, Blacksmith_Single_Cave_Doors, Inverted_Blacksmith_Multi_Cave_Doors
from InvertedRegions import create_inverted_regions from worlds.alttp.InvertedRegions import create_inverted_regions
from ItemPool import difficulties from worlds.alttp.ItemPool import difficulties
from Rules import set_inverted_big_bomb_rules from worlds.alttp.Rules import set_inverted_big_bomb_rules
class TestInvertedBombRules(unittest.TestCase): class TestInvertedBombRules(unittest.TestCase):
def setUp(self): def setUp(self):
self.world = World(1, {1:'vanilla'}, {1:'noglitches'}, {1:'inverted'}, {1:'random'}, {1:'normal'}, {1:'normal'}, {1:False}, {1:'on'}, {1:'ganon'}, 'balanced', {1:'items'}, self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'noglitches'}, {1: 'inverted'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
True, {1:False}, False, None, {1:False}) True, {1:False}, False, None, {1:False})
self.world.difficulty_requirements[1] = difficulties['normal'] self.world.difficulty_requirements[1] = difficulties['normal']
create_inverted_regions(self.world, 1) create_inverted_regions(self.world, 1)

View File

@ -1,17 +1,17 @@
from BaseClasses import World from BaseClasses import MultiWorld
from Dungeons import create_dungeons, get_dungeon_item_pool from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool
from EntranceShuffle import link_inverted_entrances from worlds.alttp.EntranceShuffle import link_inverted_entrances
from InvertedRegions import create_inverted_regions from worlds.alttp.InvertedRegions import create_inverted_regions
from ItemPool import generate_itempool, difficulties from worlds.alttp.ItemPool import generate_itempool, difficulties
from Items import ItemFactory from worlds.alttp.Items import ItemFactory
from Regions import mark_light_world_regions, create_shops from worlds.alttp.Regions import mark_light_world_regions, create_shops
from Rules import set_rules from worlds.alttp.Rules import set_rules
from test.TestBase import TestBase from test.TestBase import TestBase
class TestInvertedOWG(TestBase): class TestInvertedOWG(TestBase):
def setUp(self): def setUp(self):
self.world = World(1, {1:'vanilla'}, {1:'owglitches'}, {1:'inverted'}, {1:'random'}, {1:'normal'}, {1:'normal'}, {1:False}, {1:'on'}, {1:'ganon'}, 'balanced', {1:'items'}, self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'owglitches'}, {1: 'inverted'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
True, {1:False}, False, None, {1:False}) True, {1:False}, False, None, {1:False})
self.world.difficulty_requirements[1] = difficulties['normal'] self.world.difficulty_requirements[1] = difficulties['normal']
create_inverted_regions(self.world, 1) create_inverted_regions(self.world, 1)

View File

@ -1,4 +1,4 @@
from ItemPool import difficulties from worlds.alttp.ItemPool import difficulties
from test.TestBase import TestBase from test.TestBase import TestBase
base_items = 41 base_items = 41

View File

@ -1,17 +1,17 @@
from BaseClasses import World from BaseClasses import MultiWorld
from Dungeons import create_dungeons, get_dungeon_item_pool from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool
from EntranceShuffle import link_entrances from worlds.alttp.EntranceShuffle import link_entrances
from InvertedRegions import mark_dark_world_regions from worlds.alttp.InvertedRegions import mark_dark_world_regions
from ItemPool import difficulties, generate_itempool from worlds.alttp.ItemPool import difficulties, generate_itempool
from Items import ItemFactory from worlds.alttp.Items import ItemFactory
from Regions import create_regions, create_shops from worlds.alttp.Regions import create_regions, create_shops
from Rules import set_rules from worlds.alttp.Rules import set_rules
from test.TestBase import TestBase from test.TestBase import TestBase
class TestVanillaOWG(TestBase): class TestVanillaOWG(TestBase):
def setUp(self): def setUp(self):
self.world = World(1, {1:'vanilla'}, {1:'owglitches'}, {1:'open'}, {1:'random'}, {1:'normal'}, {1:'normal'}, {1:False}, {1:'on'}, {1:'ganon'}, 'balanced', {1:'items'}, self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'owglitches'}, {1: 'open'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
True, {1:False}, False, None, {1:False}) True, {1:False}, False, None, {1:False})
self.world.difficulty_requirements[1] = difficulties['normal'] self.world.difficulty_requirements[1] = difficulties['normal']
create_regions(self.world, 1) create_regions(self.world, 1)

View File

@ -1,17 +1,17 @@
from BaseClasses import World from BaseClasses import MultiWorld
from Dungeons import create_dungeons, get_dungeon_item_pool from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool
from EntranceShuffle import link_entrances from worlds.alttp.EntranceShuffle import link_entrances
from InvertedRegions import mark_dark_world_regions from worlds.alttp.InvertedRegions import mark_dark_world_regions
from ItemPool import difficulties, generate_itempool from worlds.alttp.ItemPool import difficulties, generate_itempool
from Items import ItemFactory from worlds.alttp.Items import ItemFactory
from Regions import create_regions, create_shops from worlds.alttp.Regions import create_regions, create_shops
from Rules import set_rules from worlds.alttp.Rules import set_rules
from test.TestBase import TestBase from test.TestBase import TestBase
class TestVanilla(TestBase): class TestVanilla(TestBase):
def setUp(self): def setUp(self):
self.world = World(1, {1:'vanilla'}, {1:'noglitches'}, {1:'open'}, {1:'random'}, {1:'normal'}, {1:'normal'}, {1:False}, {1:'on'}, {1:'ganon'}, 'balanced', {1:'items'}, self.world = MultiWorld(1, {1: 'vanilla'}, {1: 'noglitches'}, {1: 'open'}, {1: 'random'}, {1: 'normal'}, {1: 'normal'}, {1:False}, {1: 'on'}, {1: 'ganon'}, 'balanced', {1: 'items'},
True, {1:False}, False, None, {1:False}) True, {1:False}, False, None, {1:False})
self.world.difficulty_requirements[1] = difficulties['normal'] self.world.difficulty_requirements[1] = difficulties['normal']
create_regions(self.world, 1) create_regions(self.world, 1)

0
worlds/__init__.py Normal file
View File

View File

@ -1,8 +1,8 @@
from BaseClasses import Dungeon from BaseClasses import Dungeon
from Bosses import BossFactory from worlds.alttp.Bosses import BossFactory
from Fill import fill_restrictive from Fill import fill_restrictive
from Items import ItemFactory from worlds.alttp.Items import ItemFactory
from Regions import lookup_boss_drops from worlds.alttp.Regions import lookup_boss_drops
def create_dungeons(world, player): def create_dungeons(world, player):

View File

@ -7,8 +7,8 @@ import textwrap
import shlex import shlex
import sys import sys
from Main import main, get_seed from worlds.alttp.Main import main, get_seed
from Rom import get_sprite_from_name from worlds.alttp.Rom import get_sprite_from_name
from Utils import is_bundled, close_console from Utils import is_bundled, close_console
@ -376,44 +376,3 @@ def parse_arguments(argv, no_defaults=False):
getattr(ret, name)[player] = value getattr(ret, name)[player] = value
return ret return ret
def start():
args = parse_arguments(None)
if is_bundled() and len(sys.argv) == 1:
# for the bundled builds, if we have no arguments, the user
# probably wants the gui. Users of the bundled build who want the command line
# interface shouuld specify at least one option, possibly setting a value to a
# default if they like all the defaults
from Gui import guiMain
close_console()
guiMain()
sys.exit(0)
# ToDo: Validate files further than mere existance
if not os.path.isfile(args.rom):
input(
'Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
sys.exit(1)
if any([sprite is not None and not os.path.isfile(sprite) and not get_sprite_from_name(sprite) for sprite in
args.sprite.values()]):
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
sys.exit(1)
# set up logger
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel]
logging.basicConfig(format='%(message)s', level=loglevel)
if args.gui:
from Gui import guiMain
guiMain(args)
elif args.count is not None:
seed = args.seed
for _ in range(args.count):
main(seed=seed, args=args)
seed = get_seed()
else:
main(seed=args.seed, args=args)
if __name__ == '__main__':
start()

View File

@ -1941,7 +1941,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player):
invalid_cave_connections = defaultdict(set) invalid_cave_connections = defaultdict(set)
if world.logic[player] in ['owglitches', 'nologic']: if world.logic[player] in ['owglitches', 'nologic']:
import OverworldGlitchRules from worlds.alttp import OverworldGlitchRules
for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.mode[player] == 'inverted'): for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.mode[player] == 'inverted'):
invalid_connections[entrance] = set() invalid_connections[entrance] = set()
if entrance in must_be_exits: if entrance in must_be_exits:

View File

@ -1,6 +1,6 @@
import collections import collections
from BaseClasses import RegionType from BaseClasses import RegionType
from Regions import create_lw_region, create_dw_region, create_cave_region, create_dungeon_region from worlds.alttp.Regions import create_lw_region, create_dw_region, create_cave_region, create_dungeon_region
def create_inverted_regions(world, player): def create_inverted_regions(world, player):

View File

@ -2,11 +2,11 @@ from collections import namedtuple
import logging import logging
from BaseClasses import Region, RegionType, ShopType, Location, TakeAny from BaseClasses import Region, RegionType, ShopType, Location, TakeAny
from Bosses import place_bosses from worlds.alttp.Bosses import place_bosses
from Dungeons import get_dungeon_item_pool from worlds.alttp.Dungeons import get_dungeon_item_pool
from EntranceShuffle import connect_entrance from worlds.alttp.EntranceShuffle import connect_entrance
from Fill import FillError, fill_restrictive from Fill import FillError, fill_restrictive
from Items import ItemFactory from worlds.alttp.Items import ItemFactory
# This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space. # This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
# Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided. # Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.

View File

@ -8,16 +8,16 @@ import time
import zlib import zlib
import concurrent.futures import concurrent.futures
from BaseClasses import World, CollectionState, Item, Region, Location, Shop from BaseClasses import MultiWorld, CollectionState, Item, Region, Location
from Items import ItemFactory from worlds.alttp.Items import ItemFactory
from Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance from worlds.alttp.Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance
from InvertedRegions import create_inverted_regions, mark_dark_world_regions from worlds.alttp.InvertedRegions import create_inverted_regions, mark_dark_world_regions
from EntranceShuffle import link_entrances, link_inverted_entrances from worlds.alttp.EntranceShuffle import link_entrances, link_inverted_entrances
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, get_hash_string from worlds.alttp.Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, get_hash_string
from Rules import set_rules from worlds.alttp.Rules import set_rules
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive from worlds.alttp.Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression
from ItemPool import generate_itempool, difficulties, fill_prizes from worlds.alttp.ItemPool import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple
import Patch import Patch
@ -39,7 +39,7 @@ def main(args, seed=None):
start = time.perf_counter() start = time.perf_counter()
# initialize the world # initialize the world
world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, world = MultiWorld(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty,
args.item_functionality, args.timer, args.progressive.copy(), args.goal, args.algorithm, args.item_functionality, args.timer, args.progressive.copy(), args.goal, args.algorithm,
args.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints) args.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints)
@ -278,7 +278,7 @@ def main(args, seed=None):
# collect ER hint info # collect ER hint info
er_hint_data = {player: {} for player in range(1, world.players + 1) if world.shuffle[player] != "vanilla"} er_hint_data = {player: {} for player in range(1, world.players + 1) if world.shuffle[player] != "vanilla"}
from Regions import RegionType from worlds.alttp.Regions import RegionType
for region in world.regions: for region in world.regions:
if region.player in er_hint_data and region.locations: if region.player in er_hint_data and region.locations:
main_entrance = get_entrance_to_region(region) main_entrance = get_entrance_to_region(region)
@ -333,7 +333,7 @@ def main(args, seed=None):
def copy_world(world): def copy_world(world):
# ToDo: Not good yet # ToDo: Not good yet
ret = World(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints) ret = MultiWorld(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints)
ret.teams = world.teams ret.teams = world.teams
ret.player_names = copy.deepcopy(world.player_names) ret.player_names = copy.deepcopy(world.player_names)
ret.remote_items = world.remote_items.copy() ret.remote_items = world.remote_items.copy()

View File

@ -15,17 +15,17 @@ import concurrent.futures
from typing import Optional from typing import Optional
from BaseClasses import CollectionState, ShopType, Region, Location from BaseClasses import CollectionState, ShopType, Region, Location
from Dungeons import dungeon_music_addresses from worlds.alttp.Dungeons import dungeon_music_addresses
from Regions import location_table from worlds.alttp.Regions import location_table
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from worlds.alttp.Text import MultiByteTextMapper, text_addresses, Credits, TextTable
from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts from worlds.alttp.Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, \ from worlds.alttp.Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, \
LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \ LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \
SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names 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_bundled from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_bundled
from Items import ItemFactory from worlds.alttp.Items import ItemFactory
from EntranceShuffle import door_addresses from worlds.alttp.EntranceShuffle import door_addresses
import Patch import Patch
try: try:
@ -455,7 +455,7 @@ class Sprite(object):
@staticmethod @staticmethod
def default_link_sprite(): def default_link_sprite():
return Sprite(local_path('data', 'default.zspr')) return Sprite(local_path('../../data', 'default.zspr'))
def decode8(self, pos): def decode8(self, pos):
arr = [[0 for _ in range(8)] for _ in range(8)] arr = [[0 for _ in range(8)] for _ in range(8)]
@ -1397,7 +1397,7 @@ def patch_rom(world, rom, player, team, enemized):
# set rom name # set rom name
# 21 bytes # 21 bytes
from Main import __version__ from worlds.alttp.Main import __version__
# TODO: Adjust Enemizer to accept AP and AD # TODO: Adjust Enemizer to accept AP and AD
rom.name = bytearray(f'BM{__version__.replace(".", "")[0:3]}_{team + 1}_{player}_{world.seed:09}\0', 'utf8')[:21] rom.name = bytearray(f'BM{__version__.replace(".", "")[0:3]}_{team + 1}_{player}_{world.seed:09}\0', 'utf8')[:21]
rom.name.extend([0] * (21 - len(rom.name))) rom.name.extend([0] * (21 - len(rom.name)))
@ -1544,7 +1544,7 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
"randomize_overworld": ow_palettes == 'random' "randomize_overworld": ow_palettes == 'random'
} }
if any(options.values()): if any(options.values()):
data_dir = local_path("data") if is_bundled() else None data_dir = local_path("../../data") if is_bundled() else None
offsets_array = build_offset_collections(options, data_dir) offsets_array = build_offset_collections(options, data_dir)
ColorF = z3pr.ColorF ColorF = z3pr.ColorF

View File

@ -1,10 +1,10 @@
import collections import collections
import logging import logging
import OverworldGlitchRules from worlds.alttp import OverworldGlitchRules
from BaseClasses import RegionType, World, Entrance from BaseClasses import RegionType, MultiWorld, Entrance
from Items import ItemFactory, progression_items, item_name_groups from worlds.alttp.Items import ItemFactory, progression_items, item_name_groups
from OverworldGlitchRules import overworld_glitches_rules, no_logic_rules from worlds.alttp.OverworldGlitchRules import overworld_glitches_rules, no_logic_rules
from Bosses import GanonDefeatRule from worlds.alttp.Bosses import GanonDefeatRule
def set_rules(world, player): def set_rules(world, player):
@ -124,7 +124,7 @@ def add_rule(spot, rule, combine='and'):
spot.access_rule = lambda state: rule(state) and old_rule(state) spot.access_rule = lambda state: rule(state) and old_rule(state)
def add_lamp_requirement(world: World, spot, player: int, has_accessible_torch: bool = False): def add_lamp_requirement(world: MultiWorld, spot, player: int, has_accessible_torch: bool = False):
if world.dark_room_logic[player] == "lamp": if world.dark_room_logic[player] == "lamp":
add_rule(spot, lambda state: state.has('Lamp', player)) add_rule(spot, lambda state: state.has('Lamp', player))
elif world.dark_room_logic[player] == "torches": # implicitly lamp as well elif world.dark_room_logic[player] == "torches": # implicitly lamp as well
@ -1371,7 +1371,7 @@ def set_inverted_big_bomb_rules(world, player):
raise Exception('No logic found for routing from %s to the pyramid.' % bombshop_entrance.name) raise Exception('No logic found for routing from %s to the pyramid.' % bombshop_entrance.name)
def set_bunny_rules(world: World, player: int, inverted: bool): def set_bunny_rules(world: MultiWorld, player: int, inverted: bool):
# regions for the exits of multi-entrance caves/drops that bunny cannot pass # regions for the exits of multi-entrance caves/drops that bunny cannot pass
# Note spiral cave and two brothers house are passable in superbunny state for glitch logic with extra requirements. # Note spiral cave and two brothers house are passable in superbunny state for glitch logic with extra requirements.

109
worlds/alttp/__init__.py Normal file
View File

@ -0,0 +1,109 @@
from BaseClasses import World
class ALTTPWorld(World):
def __init__(self, options, slot: int):
self._region_cache = {}
self.slot = slot
self.shuffle = shuffle
self.logic = logic
self.mode = mode
self.swords = swords
self.difficulty = difficulty
self.difficulty_adjustments = difficulty_adjustments
self.timer = timer
self.progressive = progressive
self.goal = goal
self.dungeons = []
self.regions = []
self.shops = []
self.itempool = []
self.seed = None
self.precollected_items = []
self.state = CollectionState(self)
self._cached_entrances = None
self._cached_locations = None
self._entrance_cache = {}
self._location_cache = {}
self.required_locations = []
self.light_world_light_cone = False
self.dark_world_light_cone = False
self.rupoor_cost = 10
self.aga_randomness = True
self.lock_aga_door_in_escape = False
self.save_and_quit_from_boss = True
self.accessibility = accessibility
self.shuffle_ganon = shuffle_ganon
self.fix_gtower_exit = self.shuffle_ganon
self.retro = retro
self.custom = custom
self.customitemarray: List[int] = customitemarray
self.hints = hints
self.dynamic_regions = []
self.dynamic_locations = []
self.remote_items = False
self.required_medallions = ['Ether', 'Quake']
self.swamp_patch_required = False
self.powder_patch_required = False
self.ganon_at_pyramid = True
self.ganonstower_vanilla = True
self.can_access_trock_eyebridge = None
self.can_access_trock_front = None
self.can_access_trock_big_chest = None
self.can_access_trock_middle = None
self.fix_fake_world = True
self.mapshuffle = False
self.compassshuffle = False
self.keyshuffle = False
self.bigkeyshuffle = False
self.difficulty_requirements = None
self.boss_shuffle = 'none'
self.enemy_shuffle = False
self.enemy_health = 'default'
self.enemy_damage = 'default'
self.killable_thieves = False
self.tile_shuffle = False
self.bush_shuffle = False
self.beemizer = 0
self.escape_assist = []
self.crystals_needed_for_ganon = 7
self.crystals_needed_for_gt = 7
self.open_pyramid = False
self.treasure_hunt_icon = 'Triforce Piece'
self.treasure_hunt_count = 0
self.clock_mode = False
self.can_take_damage = True
self.glitch_boots = True
self.progression_balancing = True
self.local_items = set()
self.triforce_pieces_available = 30
self.triforce_pieces_required = 20
self.shop_shuffle = 'off'
self.shuffle_prizes = "g"
self.sprite_pool = []
self.dark_room_logic = "lamp"
self.restrict_dungeon_item_on_boss = False
@property
def sewer_light_cone(self):
return self.mode == "standard"
@property
def fix_trock_doors(self):
return self.shuffle != 'vanilla' or self.mode == 'inverted'
@property
def fix_skullwoods_exit(self):
return self.shuffle not in {'vanilla', 'simple', 'restricted', 'dungeonssimple'}
@property
def fix_palaceofdarkness_exit(self):
return self.shuffle not in {'vanilla', 'simple', 'restricted', 'dungeonssimple'}
@property
def fix_trock_exit(self):
return self.shuffle not in {'vanilla', 'simple', 'restricted', 'dungeonssimple'}

View File