mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Hollow Knight integration
(prototype status)
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
__all__ = {"lookup_any_item_id_to_name",
|
||||
"lookup_any_location_id_to_name"}
|
||||
|
||||
from .alttp.Items import lookup_id_to_name as alttp
|
||||
from .hk.Items import lookup_id_to_name as hk
|
||||
lookup_any_item_id_to_name = {**alttp, **hk}
|
||||
|
||||
|
||||
from .alttp import Regions
|
||||
from .hk import Locations
|
||||
lookup_any_location_id_to_name = {**Regions.lookup_id_to_name, **Locations.lookup_id_to_name}
|
||||
|
@@ -1,15 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import copy
|
||||
import os
|
||||
import logging
|
||||
import textwrap
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from worlds.alttp.Main import main, get_seed
|
||||
from worlds.alttp.Rom import Sprite
|
||||
from Utils import is_bundled, close_console
|
||||
|
||||
|
||||
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
||||
@@ -359,6 +352,7 @@ def parse_arguments(argv, no_defaults=False):
|
||||
parser.add_argument('--names', default=defval(''))
|
||||
parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1))
|
||||
parser.add_argument('--outputpath')
|
||||
parser.add_argument('--game', default="A Link to the Past")
|
||||
parser.add_argument('--race', default=defval(False), action='store_true')
|
||||
parser.add_argument('--outputname')
|
||||
parser.add_argument('--create_diff', default=defval(False), action='store_true', help='''\
|
||||
@@ -412,7 +406,7 @@ def parse_arguments(argv, no_defaults=False):
|
||||
"plando_items", "plando_texts", "plando_connections", "er_seeds",
|
||||
'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
||||
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
|
||||
'restrict_dungeon_item_on_boss', 'reduceflashing',
|
||||
'restrict_dungeon_item_on_boss', 'reduceflashing', 'game',
|
||||
'hud_palettes', 'sword_palettes', 'shield_palettes', 'link_palettes']:
|
||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||
if player == 1:
|
||||
|
@@ -1,7 +1,8 @@
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
|
||||
from BaseClasses import Region, RegionType, Location
|
||||
from BaseClasses import Region, RegionType
|
||||
from worlds.alttp import ALttPLocation
|
||||
from worlds.alttp.Shops import TakeAny, total_shop_slots, set_up_shops, shuffle_shops
|
||||
from worlds.alttp.Bosses import place_bosses
|
||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
@@ -243,7 +244,7 @@ def generate_itempool(world, player: int):
|
||||
if world.goal[player] in ['triforcehunt', 'localtriforcehunt']:
|
||||
region = world.get_region('Light World', player)
|
||||
|
||||
loc = Location(player, "Murahdahla", parent=region)
|
||||
loc = ALttPLocation(player, "Murahdahla", parent=region)
|
||||
loc.access_rule = lambda state: state.has_triforce_pieces(state.world.treasure_hunt_count[player], player)
|
||||
|
||||
region.locations.append(loc)
|
||||
@@ -501,7 +502,7 @@ def create_dynamic_shop_locations(world, player):
|
||||
if item is None:
|
||||
continue
|
||||
if item['create_location']:
|
||||
loc = Location(player, "{} Slot {}".format(shop.region.name, i + 1), parent=shop.region)
|
||||
loc = ALttPLocation(player, "{} Slot {}".format(shop.region.name, i + 1), parent=shop.region)
|
||||
shop.region.locations.append(loc)
|
||||
world.dynamic_locations.append(loc)
|
||||
|
||||
@@ -515,7 +516,7 @@ def create_dynamic_shop_locations(world, player):
|
||||
|
||||
def fill_prizes(world, attempts=15):
|
||||
all_state = world.get_all_state(keys=True)
|
||||
for player in range(1, world.players + 1):
|
||||
for player in world.alttp_player_ids:
|
||||
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),
|
||||
|
@@ -16,7 +16,7 @@ def GetBeemizerItem(world, player, item):
|
||||
|
||||
|
||||
def ItemFactory(items, player):
|
||||
from BaseClasses import Item
|
||||
from worlds.alttp import ALttPItem
|
||||
ret = []
|
||||
singleton = False
|
||||
if isinstance(items, str):
|
||||
@@ -24,7 +24,7 @@ def ItemFactory(items, player):
|
||||
singleton = True
|
||||
for item in items:
|
||||
if item in item_table:
|
||||
ret.append(Item(item, *item_table[item], player))
|
||||
ret.append(ALttPItem(item, *item_table[item], player))
|
||||
else:
|
||||
raise Exception(f"Unknown item {item}")
|
||||
|
||||
@@ -200,7 +200,7 @@ item_table = {'Bow': (True, None, 0x0B, 'You have\nchosen the\narcher class.', '
|
||||
'Open Floodgate': (True, 'Event', None, None, None, None, None, None, None, None),
|
||||
}
|
||||
|
||||
lookup_id_to_name = {data[2]: name for name, data in item_table.items()}
|
||||
lookup_id_to_name = {data[2]: name for name, data in item_table.items() if data[2]}
|
||||
|
||||
hint_blacklist = {"Triforce"}
|
||||
|
||||
|
@@ -1,716 +0,0 @@
|
||||
from collections import OrderedDict
|
||||
import copy
|
||||
from itertools import zip_longest
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import zlib
|
||||
import concurrent.futures
|
||||
import pickle
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState, Item, Region, Location
|
||||
from worlds.alttp.Items import ItemFactory, item_table, item_name_groups
|
||||
from worlds.alttp.Regions import create_regions, mark_light_world_regions, \
|
||||
lookup_vanilla_location_to_entrance
|
||||
from worlds.alttp.InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||
from worlds.alttp.EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
|
||||
from worlds.alttp.Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, get_hash_string
|
||||
from worlds.alttp.Rules import set_rules
|
||||
from worlds.alttp.Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
||||
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
|
||||
from worlds.alttp.Shops import create_shops, ShopSlotFill, SHOP_ID_START, total_shop_slots, FillDisabledShopSlots
|
||||
from worlds.alttp.ItemPool import generate_itempool, difficulties, fill_prizes
|
||||
from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple
|
||||
import Patch
|
||||
|
||||
seeddigits = 20
|
||||
|
||||
|
||||
def get_seed(seed=None):
|
||||
if seed is None:
|
||||
random.seed(None)
|
||||
return random.randint(0, pow(10, seeddigits) - 1)
|
||||
return seed
|
||||
|
||||
|
||||
def main(args, seed=None):
|
||||
if args.outputpath:
|
||||
os.makedirs(args.outputpath, exist_ok=True)
|
||||
output_path.cached_path = args.outputpath
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
# initialize the world
|
||||
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.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints)
|
||||
|
||||
logger = logging.getLogger('')
|
||||
world.seed = get_seed(seed)
|
||||
if args.race:
|
||||
world.secure()
|
||||
else:
|
||||
world.random.seed(world.seed)
|
||||
|
||||
world.remote_items = args.remote_items.copy()
|
||||
world.mapshuffle = args.mapshuffle.copy()
|
||||
world.compassshuffle = args.compassshuffle.copy()
|
||||
world.keyshuffle = args.keyshuffle.copy()
|
||||
world.bigkeyshuffle = args.bigkeyshuffle.copy()
|
||||
world.crystals_needed_for_ganon = {
|
||||
player: world.random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(
|
||||
args.crystals_ganon[player]) for player in range(1, world.players + 1)}
|
||||
world.crystals_needed_for_gt = {
|
||||
player: world.random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player])
|
||||
for player in range(1, world.players + 1)}
|
||||
world.open_pyramid = args.open_pyramid.copy()
|
||||
world.boss_shuffle = args.shufflebosses.copy()
|
||||
world.enemy_shuffle = args.enemy_shuffle.copy()
|
||||
world.enemy_health = args.enemy_health.copy()
|
||||
world.enemy_damage = args.enemy_damage.copy()
|
||||
world.killable_thieves = args.killable_thieves.copy()
|
||||
world.bush_shuffle = args.bush_shuffle.copy()
|
||||
world.tile_shuffle = args.tile_shuffle.copy()
|
||||
world.beemizer = args.beemizer.copy()
|
||||
world.timer = args.timer.copy()
|
||||
world.countdown_start_time = args.countdown_start_time.copy()
|
||||
world.red_clock_time = args.red_clock_time.copy()
|
||||
world.blue_clock_time = args.blue_clock_time.copy()
|
||||
world.green_clock_time = args.green_clock_time.copy()
|
||||
world.shufflepots = args.shufflepots.copy()
|
||||
world.progressive = args.progressive.copy()
|
||||
world.dungeon_counters = args.dungeon_counters.copy()
|
||||
world.glitch_boots = args.glitch_boots.copy()
|
||||
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
||||
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
||||
world.shop_shuffle = args.shop_shuffle.copy()
|
||||
world.shop_shuffle_slots = args.shop_shuffle_slots.copy()
|
||||
world.progression_balancing = {player: not balance for player, balance in args.skip_progression_balancing.items()}
|
||||
world.shuffle_prizes = args.shuffle_prizes.copy()
|
||||
world.sprite_pool = args.sprite_pool.copy()
|
||||
world.dark_room_logic = args.dark_room_logic.copy()
|
||||
world.plando_items = args.plando_items.copy()
|
||||
world.plando_texts = args.plando_texts.copy()
|
||||
world.plando_connections = args.plando_connections.copy()
|
||||
world.er_seeds = args.er_seeds.copy()
|
||||
world.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy()
|
||||
world.required_medallions = args.required_medallions.copy()
|
||||
|
||||
world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)}
|
||||
|
||||
for player in range(1, world.players+1):
|
||||
world.er_seeds[player] = str(world.random.randint(0, 2 ** 64))
|
||||
|
||||
if "-" in world.shuffle[player]:
|
||||
shuffle, seed = world.shuffle[player].split("-")
|
||||
world.shuffle[player] = shuffle
|
||||
world.er_seeds[player] = seed
|
||||
|
||||
logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed)
|
||||
|
||||
parsed_names = parse_player_names(args.names, world.players, args.teams)
|
||||
world.teams = len(parsed_names)
|
||||
for i, team in enumerate(parsed_names, 1):
|
||||
if world.players > 1:
|
||||
logger.info('%s%s', 'Team%d: ' % i if world.teams > 1 else 'Players: ', ', '.join(team))
|
||||
for player, name in enumerate(team, 1):
|
||||
world.player_names[player].append(name)
|
||||
|
||||
logger.info('')
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
world.difficulty_requirements[player] = difficulties[world.difficulty[player]]
|
||||
|
||||
if world.open_pyramid[player] == 'goal':
|
||||
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'}
|
||||
elif world.open_pyramid[player] == 'auto':
|
||||
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} and \
|
||||
(world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull'} or not world.shuffle_ganon)
|
||||
else:
|
||||
world.open_pyramid[player] = {'on': True, 'off': False, 'yes': True, 'no': False}.get(world.open_pyramid[player], world.open_pyramid[player])
|
||||
|
||||
for tok in filter(None, args.startinventory[player].split(',')):
|
||||
item = ItemFactory(tok.strip(), player)
|
||||
if item:
|
||||
world.push_precollected(item)
|
||||
# item in item_table gets checked in mystery, but not CLI - so we double-check here
|
||||
world.local_items[player] = {item.strip() for item in args.local_items[player].split(',') if
|
||||
item.strip() in item_table}
|
||||
world.non_local_items[player] = {item.strip() for item in args.non_local_items[player].split(',') if
|
||||
item.strip() in item_table}
|
||||
|
||||
# enforce pre-defined local items.
|
||||
if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]:
|
||||
world.local_items[player].add('Triforce Piece')
|
||||
|
||||
# items can't be both local and non-local, prefer local
|
||||
world.non_local_items[player] -= world.local_items[player]
|
||||
|
||||
# dungeon items can't be in non-local if the appropriate dungeon item shuffle setting is not set.
|
||||
if not world.mapshuffle[player]:
|
||||
world.non_local_items[player] -= item_name_groups['Maps']
|
||||
|
||||
if not world.compassshuffle[player]:
|
||||
world.non_local_items[player] -= item_name_groups['Compasses']
|
||||
|
||||
if not world.keyshuffle[player]:
|
||||
world.non_local_items[player] -= item_name_groups['Small Keys']
|
||||
|
||||
if not world.bigkeyshuffle[player]:
|
||||
world.non_local_items[player] -= item_name_groups['Big Keys']
|
||||
|
||||
# Not possible to place pendants/crystals out side of boss prizes yet.
|
||||
world.non_local_items[player] -= item_name_groups['Pendants']
|
||||
world.non_local_items[player] -= item_name_groups['Crystals']
|
||||
|
||||
world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player], world.triforce_pieces_required[player])
|
||||
|
||||
if world.mode[player] != 'inverted':
|
||||
create_regions(world, player)
|
||||
else:
|
||||
create_inverted_regions(world, player)
|
||||
create_shops(world, player)
|
||||
create_dungeons(world, player)
|
||||
|
||||
logger.info('Shuffling the World about.')
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
if world.logic[player] not in ["noglitches", "minorglitches"] and world.shuffle[player] in \
|
||||
{"vanilla", "dungeonssimple", "dungeonsfull", "simple", "restricted", "full"}:
|
||||
world.fix_fake_world[player] = False
|
||||
|
||||
# seeded entrance shuffle
|
||||
old_random = world.random
|
||||
world.random = random.Random(world.er_seeds[player])
|
||||
|
||||
if world.mode[player] != 'inverted':
|
||||
link_entrances(world, player)
|
||||
mark_light_world_regions(world, player)
|
||||
else:
|
||||
link_inverted_entrances(world, player)
|
||||
mark_dark_world_regions(world, player)
|
||||
|
||||
world.random = old_random
|
||||
plando_connect(world, player)
|
||||
|
||||
logger.info('Generating Item Pool.')
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
generate_itempool(world, player)
|
||||
|
||||
logger.info('Calculating Access Rules.')
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
set_rules(world, player)
|
||||
|
||||
logger.info("Running Item Plando")
|
||||
|
||||
distribute_planned(world)
|
||||
|
||||
logger.info('Placing Dungeon Prizes.')
|
||||
|
||||
fill_prizes(world)
|
||||
|
||||
logger.info('Placing Dungeon Items.')
|
||||
|
||||
if args.algorithm in ['balanced', 'vt26'] or any(
|
||||
list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
|
||||
list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())):
|
||||
fill_dungeons_restrictive(world)
|
||||
else:
|
||||
fill_dungeons(world)
|
||||
|
||||
logger.info('Fill the world.')
|
||||
|
||||
if args.algorithm == 'flood':
|
||||
flood_items(world) # different algo, biased towards early game progress items
|
||||
elif args.algorithm == 'vt25':
|
||||
distribute_items_restrictive(world, False)
|
||||
elif args.algorithm == 'vt26':
|
||||
distribute_items_restrictive(world, True)
|
||||
elif args.algorithm == 'balanced':
|
||||
distribute_items_restrictive(world, True)
|
||||
|
||||
logger.info("Filling Shop Slots")
|
||||
|
||||
ShopSlotFill(world)
|
||||
|
||||
if world.players > 1:
|
||||
balance_multiworld_progression(world)
|
||||
|
||||
logger.info('Patching ROM.')
|
||||
|
||||
|
||||
|
||||
outfilebase = 'AP_%s' % (args.outputname if args.outputname else world.seed)
|
||||
|
||||
rom_names = []
|
||||
|
||||
def _gen_rom(team: int, player: int):
|
||||
use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player]
|
||||
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
|
||||
or world.shufflepots[player] or world.bush_shuffle[player]
|
||||
or world.killable_thieves[player])
|
||||
|
||||
rom = LocalRom(args.rom)
|
||||
|
||||
patch_rom(world, rom, player, team, use_enemizer)
|
||||
|
||||
if use_enemizer:
|
||||
patch_enemizer(world, team, player, rom, args.enemizercli)
|
||||
|
||||
if args.race:
|
||||
patch_race_rom(rom, world, player)
|
||||
|
||||
world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash)
|
||||
|
||||
palettes_options={}
|
||||
palettes_options['dungeon']=args.uw_palettes[player]
|
||||
palettes_options['overworld']=args.ow_palettes[player]
|
||||
palettes_options['hud']=args.hud_palettes[player]
|
||||
palettes_options['sword']=args.sword_palettes[player]
|
||||
palettes_options['shield']=args.shield_palettes[player]
|
||||
palettes_options['link']=args.link_palettes[player]
|
||||
|
||||
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
|
||||
args.fastmenu[player], args.disablemusic[player], args.sprite[player],
|
||||
palettes_options, world, player, True, reduceflashing=args.reduceflashing[player] if not args.race else True)
|
||||
|
||||
mcsb_name = ''
|
||||
if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player],
|
||||
world.bigkeyshuffle[player]]):
|
||||
mcsb_name = '-keysanity'
|
||||
elif [world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player],
|
||||
world.bigkeyshuffle[player]].count(True) == 1:
|
||||
mcsb_name = '-mapshuffle' if world.mapshuffle[player] else \
|
||||
'-compassshuffle' if world.compassshuffle[player] else \
|
||||
'-universal_keys' if world.keyshuffle[player] == "universal" else \
|
||||
'-keyshuffle' if world.keyshuffle[player] else '-bigkeyshuffle'
|
||||
elif any([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player],
|
||||
world.bigkeyshuffle[player]]):
|
||||
mcsb_name = '-%s%s%s%sshuffle' % (
|
||||
'M' if world.mapshuffle[player] else '', 'C' if world.compassshuffle[player] else '',
|
||||
'U' if world.keyshuffle[player] == "universal" else 'S' if world.keyshuffle[player] else '',
|
||||
'B' if world.bigkeyshuffle[player] else '')
|
||||
|
||||
outfilepname = f'_T{team + 1}' if world.teams > 1 else ''
|
||||
outfilepname += f'_P{player}'
|
||||
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" \
|
||||
if world.player_names[player][team] != 'Player%d' % player else ''
|
||||
outfilestuffs = {
|
||||
"logic": world.logic[player], # 0
|
||||
"difficulty": world.difficulty[player], # 1
|
||||
"item_functionality": world.item_functionality[player], # 2
|
||||
"mode": world.mode[player], # 3
|
||||
"goal": world.goal[player], # 4
|
||||
"timer": str(world.timer[player]), # 5
|
||||
"shuffle": world.shuffle[player], # 6
|
||||
"algorithm": world.algorithm, # 7
|
||||
"mscb": mcsb_name, # 8
|
||||
"retro": world.retro[player], # 9
|
||||
"progressive": world.progressive, # A
|
||||
"hints": 'True' if world.hints[player] else 'False' # B
|
||||
}
|
||||
# 0 1 2 3 4 5 6 7 8 9 A B
|
||||
outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (
|
||||
# 0 1 2 3 4 5 6 7 8 9 A B C
|
||||
# _noglitches_normal-normal-open-ganon-ohko_simple-balanced-keysanity-retro-prog_random-nohints
|
||||
# _noglitches_normal-normal-open-ganon _simple-balanced-keysanity-retro
|
||||
# _noglitches_normal-normal-open-ganon _simple-balanced-keysanity -prog_random
|
||||
# _noglitches_normal-normal-open-ganon _simple-balanced-keysanity -nohints
|
||||
outfilestuffs["logic"], # 0
|
||||
|
||||
outfilestuffs["difficulty"], # 1
|
||||
outfilestuffs["item_functionality"], # 2
|
||||
outfilestuffs["mode"], # 3
|
||||
outfilestuffs["goal"], # 4
|
||||
"" if outfilestuffs["timer"] in ['False', 'none', 'display'] else "-" + outfilestuffs["timer"], # 5
|
||||
|
||||
outfilestuffs["shuffle"], # 6
|
||||
outfilestuffs["algorithm"], # 7
|
||||
outfilestuffs["mscb"], # 8
|
||||
|
||||
"-retro" if outfilestuffs["retro"] == "True" else "", # 9
|
||||
"-prog_" + outfilestuffs["progressive"] if outfilestuffs["progressive"] in ['off', 'random'] else "", # A
|
||||
"-nohints" if not outfilestuffs["hints"] == "True" else "") # B
|
||||
) if not args.outputname else ''
|
||||
rompath = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')
|
||||
rom.write_to_file(rompath, hide_enemizer=True)
|
||||
if args.create_diff:
|
||||
Patch.create_patch_file(rompath)
|
||||
return player, team, bytes(rom.name)
|
||||
|
||||
pool = concurrent.futures.ThreadPoolExecutor()
|
||||
multidata_task = None
|
||||
check_accessibility_task = pool.submit(world.fulfills_accessibility)
|
||||
if not args.suppress_rom:
|
||||
|
||||
rom_futures = []
|
||||
|
||||
for team in range(world.teams):
|
||||
for player in range(1, world.players + 1):
|
||||
rom_futures.append(pool.submit(_gen_rom, team, player))
|
||||
|
||||
def get_entrance_to_region(region: Region):
|
||||
for entrance in region.entrances:
|
||||
if entrance.parent_region.type in (RegionType.DarkWorld, RegionType.LightWorld):
|
||||
return entrance
|
||||
for entrance in region.entrances: # BFS might be better here, trying DFS for now.
|
||||
return get_entrance_to_region(entrance.parent_region)
|
||||
|
||||
# collect ER hint info
|
||||
er_hint_data = {player: {} for player in range(1, world.players + 1) if world.shuffle[player] != "vanilla" or world.retro[player]}
|
||||
from worlds.alttp.Regions import RegionType
|
||||
for region in world.regions:
|
||||
if region.player in er_hint_data and region.locations:
|
||||
main_entrance = get_entrance_to_region(region)
|
||||
for location in region.locations:
|
||||
if type(location.address) == int: # skips events and crystals
|
||||
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
|
||||
er_hint_data[region.player][location.address] = main_entrance.name
|
||||
|
||||
ordered_areas = ('Light World', 'Dark World', 'Hyrule Castle', 'Agahnims Tower', 'Eastern Palace', 'Desert Palace',
|
||||
'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace',
|
||||
'Misery Mire', 'Turtle Rock', 'Ganons Tower', "Total")
|
||||
|
||||
checks_in_area = {player: {area: list() for area in ordered_areas}
|
||||
for player in range(1, world.players + 1)}
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
checks_in_area[player]["Total"] = 0
|
||||
|
||||
for location in [loc for loc in world.get_filled_locations() if type(loc.address) is int]:
|
||||
main_entrance = get_entrance_to_region(location.parent_region)
|
||||
if location.parent_region.dungeon:
|
||||
dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower',
|
||||
'Inverted Ganons Tower': 'Ganons Tower'}\
|
||||
.get(location.parent_region.dungeon.name, location.parent_region.dungeon.name)
|
||||
checks_in_area[location.player][dungeonname].append(location.address)
|
||||
elif main_entrance.parent_region.type == RegionType.LightWorld:
|
||||
checks_in_area[location.player]["Light World"].append(location.address)
|
||||
elif main_entrance.parent_region.type == RegionType.DarkWorld:
|
||||
checks_in_area[location.player]["Dark World"].append(location.address)
|
||||
checks_in_area[location.player]["Total"] += 1
|
||||
|
||||
oldmancaves = []
|
||||
takeanyregions = ["Old Man Sword Cave", "Take-Any #1", "Take-Any #2", "Take-Any #3", "Take-Any #4"]
|
||||
for index, take_any in enumerate(takeanyregions):
|
||||
for region in [world.get_region(take_any, player) for player in range(1, world.players + 1) if world.retro[player]]:
|
||||
item = ItemFactory(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'], region.player)
|
||||
player = region.player
|
||||
location_id = SHOP_ID_START + total_shop_slots + index
|
||||
|
||||
main_entrance = get_entrance_to_region(region)
|
||||
if main_entrance.parent_region.type == RegionType.LightWorld:
|
||||
checks_in_area[player]["Light World"].append(location_id)
|
||||
else:
|
||||
checks_in_area[player]["Dark World"].append(location_id)
|
||||
checks_in_area[player]["Total"] += 1
|
||||
|
||||
er_hint_data[player][location_id] = main_entrance.name
|
||||
oldmancaves.append(((location_id, player), (item.code, player)))
|
||||
|
||||
precollected_items = [[] for player in range(world.players)]
|
||||
for item in world.precollected_items:
|
||||
precollected_items[item.player - 1].append(item.code)
|
||||
|
||||
FillDisabledShopSlots(world)
|
||||
|
||||
def write_multidata(roms):
|
||||
import base64
|
||||
for future in roms:
|
||||
rom_name = future.result()
|
||||
rom_names.append(rom_name)
|
||||
minimum_versions = {"server": (0, 0, 1)}
|
||||
multidata = zlib.compress(pickle.dumps({"names": parsed_names,
|
||||
"roms": {base64.b64encode(rom_name).decode(): (team, slot) for
|
||||
slot, team, rom_name in rom_names},
|
||||
"remote_items": {player for player in range(1, world.players + 1) if
|
||||
world.remote_items[player]},
|
||||
"locations": {
|
||||
(location.address, location.player):
|
||||
(location.item.code, location.item.player)
|
||||
for location in world.get_filled_locations() if
|
||||
type(location.address) is int},
|
||||
"checks_in_area": checks_in_area,
|
||||
"server_options": get_options()["server_options"],
|
||||
"er_hint_data": er_hint_data,
|
||||
"precollected_items": precollected_items,
|
||||
"version": tuple(_version_tuple),
|
||||
"tags": ["AP"],
|
||||
"minimum_versions": minimum_versions,
|
||||
}), 9)
|
||||
|
||||
with open(output_path('%s.archipelago' % outfilebase), 'wb') as f:
|
||||
f.write(bytes([1])) # version of format
|
||||
f.write(multidata)
|
||||
|
||||
multidata_task = pool.submit(write_multidata, rom_futures)
|
||||
if not check_accessibility_task.result():
|
||||
if not world.can_beat_game():
|
||||
raise Exception("Game appears is unbeatable. Aborting.")
|
||||
else:
|
||||
logger.warning("Location Accessibility requirements not fulfilled.")
|
||||
if not args.skip_playthrough:
|
||||
logger.info('Calculating playthrough.')
|
||||
create_playthrough(world)
|
||||
if multidata_task:
|
||||
multidata_task.result() # retrieve exception if one exists
|
||||
pool.shutdown() # wait for all queued tasks to complete
|
||||
if args.create_spoiler: # needs spoiler.hashes to be filled, that depend on rom_futures being done
|
||||
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
||||
|
||||
logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start)
|
||||
return world
|
||||
|
||||
|
||||
|
||||
def copy_world(world):
|
||||
# ToDo: Not good yet
|
||||
ret = MultiWorld(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.item_functionality, 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.player_names = copy.deepcopy(world.player_names)
|
||||
ret.remote_items = world.remote_items.copy()
|
||||
ret.required_medallions = world.required_medallions.copy()
|
||||
ret.swamp_patch_required = world.swamp_patch_required.copy()
|
||||
ret.ganon_at_pyramid = world.ganon_at_pyramid.copy()
|
||||
ret.powder_patch_required = world.powder_patch_required.copy()
|
||||
ret.ganonstower_vanilla = world.ganonstower_vanilla.copy()
|
||||
ret.treasure_hunt_count = world.treasure_hunt_count.copy()
|
||||
ret.treasure_hunt_icon = world.treasure_hunt_icon.copy()
|
||||
ret.sewer_light_cone = world.sewer_light_cone.copy()
|
||||
ret.light_world_light_cone = world.light_world_light_cone
|
||||
ret.dark_world_light_cone = world.dark_world_light_cone
|
||||
ret.seed = world.seed
|
||||
ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge.copy()
|
||||
ret.can_access_trock_front = world.can_access_trock_front.copy()
|
||||
ret.can_access_trock_big_chest = world.can_access_trock_big_chest.copy()
|
||||
ret.can_access_trock_middle = world.can_access_trock_middle.copy()
|
||||
ret.can_take_damage = world.can_take_damage
|
||||
ret.difficulty_requirements = world.difficulty_requirements.copy()
|
||||
ret.fix_fake_world = world.fix_fake_world.copy()
|
||||
ret.mapshuffle = world.mapshuffle.copy()
|
||||
ret.compassshuffle = world.compassshuffle.copy()
|
||||
ret.keyshuffle = world.keyshuffle.copy()
|
||||
ret.bigkeyshuffle = world.bigkeyshuffle.copy()
|
||||
ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy()
|
||||
ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy()
|
||||
ret.open_pyramid = world.open_pyramid.copy()
|
||||
ret.boss_shuffle = world.boss_shuffle.copy()
|
||||
ret.enemy_shuffle = world.enemy_shuffle.copy()
|
||||
ret.enemy_health = world.enemy_health.copy()
|
||||
ret.enemy_damage = world.enemy_damage.copy()
|
||||
ret.beemizer = world.beemizer.copy()
|
||||
ret.timer = world.timer.copy()
|
||||
ret.shufflepots = world.shufflepots.copy()
|
||||
ret.shuffle_prizes = world.shuffle_prizes.copy()
|
||||
ret.shop_shuffle = world.shop_shuffle.copy()
|
||||
ret.shop_shuffle_slots = world.shop_shuffle_slots.copy()
|
||||
ret.dark_room_logic = world.dark_room_logic.copy()
|
||||
ret.restrict_dungeon_item_on_boss = world.restrict_dungeon_item_on_boss.copy()
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
if world.mode[player] != 'inverted':
|
||||
create_regions(ret, player)
|
||||
else:
|
||||
create_inverted_regions(ret, player)
|
||||
create_shops(ret, player)
|
||||
create_dungeons(ret, player)
|
||||
|
||||
copy_dynamic_regions_and_locations(world, ret)
|
||||
|
||||
# copy bosses
|
||||
for dungeon in world.dungeons:
|
||||
for level, boss in dungeon.bosses.items():
|
||||
ret.get_dungeon(dungeon.name, dungeon.player).bosses[level] = boss
|
||||
|
||||
for shop in world.shops:
|
||||
copied_shop = ret.get_region(shop.region.name, shop.region.player).shop
|
||||
copied_shop.inventory = copy.copy(shop.inventory)
|
||||
|
||||
# connect copied world
|
||||
for region in world.regions:
|
||||
copied_region = ret.get_region(region.name, region.player)
|
||||
copied_region.is_light_world = region.is_light_world
|
||||
copied_region.is_dark_world = region.is_dark_world
|
||||
for exit in copied_region.exits:
|
||||
old_connection = world.get_entrance(exit.name, exit.player).connected_region
|
||||
exit.connect(ret.get_region(old_connection.name, old_connection.player))
|
||||
|
||||
# fill locations
|
||||
for location in world.get_locations():
|
||||
if location.item is not None:
|
||||
item = Item(location.item.name, location.item.advancement, location.item.type, player = location.item.player)
|
||||
ret.get_location(location.name, location.player).item = item
|
||||
item.location = ret.get_location(location.name, location.player)
|
||||
item.world = ret
|
||||
if location.event:
|
||||
ret.get_location(location.name, location.player).event = True
|
||||
if location.locked:
|
||||
ret.get_location(location.name, location.player).locked = True
|
||||
|
||||
# copy remaining itempool. No item in itempool should have an assigned location
|
||||
for item in world.itempool:
|
||||
ret.itempool.append(Item(item.name, item.advancement, item.type, player = item.player))
|
||||
|
||||
for item in world.precollected_items:
|
||||
ret.push_precollected(ItemFactory(item.name, item.player))
|
||||
|
||||
# copy progress items in state
|
||||
ret.state.prog_items = world.state.prog_items.copy()
|
||||
ret.state.stale = {player: True for player in range(1, world.players + 1)}
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
set_rules(ret, player)
|
||||
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def copy_dynamic_regions_and_locations(world, ret):
|
||||
for region in world.dynamic_regions:
|
||||
new_reg = Region(region.name, region.type, region.hint_text, region.player)
|
||||
ret.regions.append(new_reg)
|
||||
ret.initialize_regions([new_reg])
|
||||
ret.dynamic_regions.append(new_reg)
|
||||
|
||||
# Note: ideally exits should be copied here, but the current use case (Take anys) do not require this
|
||||
|
||||
if region.shop:
|
||||
new_reg.shop = region.shop.__class__(new_reg, region.shop.room_id, region.shop.shopkeeper_config,
|
||||
region.shop.custom, region.shop.locked, region.shop.sram_offset)
|
||||
ret.shops.append(new_reg.shop)
|
||||
|
||||
for location in world.dynamic_locations:
|
||||
new_reg = ret.get_region(location.parent_region.name, location.parent_region.player)
|
||||
new_loc = Location(location.player, location.name, location.address, location.crystal, location.hint_text, new_reg)
|
||||
# todo: this is potentially dangerous. later refactor so we
|
||||
# can apply dynamic region rules on top of copied world like other rules
|
||||
new_loc.access_rule = location.access_rule
|
||||
new_loc.always_allow = location.always_allow
|
||||
new_loc.item_rule = location.item_rule
|
||||
new_reg.locations.append(new_loc)
|
||||
|
||||
ret.clear_location_cache()
|
||||
|
||||
|
||||
def create_playthrough(world):
|
||||
# create a copy as we will modify it
|
||||
old_world = world
|
||||
world = copy_world(world)
|
||||
|
||||
# get locations containing progress items
|
||||
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
|
||||
state_cache = [None]
|
||||
collection_spheres = []
|
||||
state = CollectionState(world)
|
||||
sphere_candidates = list(prog_locations)
|
||||
logging.debug('Building up collection spheres.')
|
||||
while sphere_candidates:
|
||||
state.sweep_for_events(key_only=True)
|
||||
|
||||
sphere = set()
|
||||
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
||||
for location in sphere_candidates:
|
||||
if state.can_reach(location):
|
||||
sphere.add(location)
|
||||
|
||||
for location in sphere:
|
||||
sphere_candidates.remove(location)
|
||||
state.collect(location.item, True, location)
|
||||
|
||||
collection_spheres.append(sphere)
|
||||
|
||||
state_cache.append(state.copy())
|
||||
|
||||
logging.debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere),
|
||||
len(prog_locations))
|
||||
if not sphere:
|
||||
logging.debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (
|
||||
location.item.name, location.item.player, location.name, location.player) for location in
|
||||
sphere_candidates])
|
||||
if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
|
||||
raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). '
|
||||
f'Something went terribly wrong here.')
|
||||
else:
|
||||
old_world.spoiler.unreachables = sphere_candidates.copy()
|
||||
break
|
||||
|
||||
# in the second phase, we cull each sphere such that the game is still beatable, reducing each range of influence to the bare minimum required inside it
|
||||
for num, sphere in reversed(tuple(enumerate(collection_spheres))):
|
||||
to_delete = set()
|
||||
for location in sphere:
|
||||
# we remove the item at location and check if game is still beatable
|
||||
logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player)
|
||||
old_item = location.item
|
||||
location.item = None
|
||||
if world.can_beat_game(state_cache[num]):
|
||||
to_delete.add(location)
|
||||
else:
|
||||
# still required, got to keep it around
|
||||
location.item = old_item
|
||||
|
||||
# cull entries in spheres for spoiler walkthrough at end
|
||||
sphere -= to_delete
|
||||
|
||||
# second phase, sphere 0
|
||||
for item in (i for i in world.precollected_items if i.advancement):
|
||||
logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', item.name, item.player)
|
||||
world.precollected_items.remove(item)
|
||||
world.state.remove(item)
|
||||
if not world.can_beat_game():
|
||||
world.push_precollected(item)
|
||||
|
||||
# we are now down to just the required progress items in collection_spheres. Unfortunately
|
||||
# the previous pruning stage could potentially have made certain items dependant on others
|
||||
# in the same or later sphere (because the location had 2 ways to access but the item originally
|
||||
# used to access it was deemed not required.) So we need to do one final sphere collection pass
|
||||
# to build up the correct spheres
|
||||
|
||||
required_locations = {item for sphere in collection_spheres for item in sphere}
|
||||
state = CollectionState(world)
|
||||
collection_spheres = []
|
||||
while required_locations:
|
||||
state.sweep_for_events(key_only=True)
|
||||
|
||||
sphere = set(filter(state.can_reach, required_locations))
|
||||
|
||||
for location in sphere:
|
||||
required_locations.remove(location)
|
||||
state.collect(location.item, True, location)
|
||||
|
||||
collection_spheres.append(sphere)
|
||||
|
||||
logging.getLogger('').debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(required_locations))
|
||||
if not sphere:
|
||||
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
||||
|
||||
def flist_to_iter(node):
|
||||
while node:
|
||||
value, node = node
|
||||
yield value
|
||||
|
||||
def get_path(state, region):
|
||||
reversed_path_as_flist = state.path.get(region, (region, None))
|
||||
string_path_flat = reversed(list(map(str, flist_to_iter(reversed_path_as_flist))))
|
||||
# Now we combine the flat string list into (region, exit) pairs
|
||||
pathsiter = iter(string_path_flat)
|
||||
pathpairs = zip_longest(pathsiter, pathsiter)
|
||||
return list(pathpairs)
|
||||
|
||||
old_world.spoiler.paths = dict()
|
||||
for player in range(1, world.players + 1):
|
||||
old_world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player})
|
||||
for path in dict(old_world.spoiler.paths).values():
|
||||
if any(exit == 'Pyramid Fairy' for (_, exit) in path):
|
||||
if world.mode[player] != 'inverted':
|
||||
old_world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player))
|
||||
else:
|
||||
old_world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player))
|
||||
|
||||
# we can finally output our playthrough
|
||||
old_world.spoiler.playthrough = {"0": sorted([str(item) for item in world.precollected_items if item.advancement])}
|
||||
|
||||
for i, sphere in enumerate(collection_spheres):
|
||||
old_world.spoiler.playthrough[str(i + 1)] = {str(location): str(location.item) for location in sorted(sphere)}
|
@@ -1,8 +1,8 @@
|
||||
import collections
|
||||
import typing
|
||||
|
||||
from BaseClasses import Region, Location, Entrance, RegionType
|
||||
|
||||
from BaseClasses import Region, Entrance, RegionType
|
||||
from worlds.alttp import ALttPLocation
|
||||
|
||||
|
||||
def create_regions(world, player):
|
||||
@@ -333,7 +333,7 @@ def _create_region(player: int, name: str, type: RegionType, hint: str, location
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
for location in locations:
|
||||
address, player_address, crystal, hint_text = location_table[location]
|
||||
ret.locations.append(Location(player, location, address, crystal, hint_text, ret, player_address))
|
||||
ret.locations.append(ALttPLocation(player, location, address, crystal, hint_text, ret, player_address))
|
||||
return ret
|
||||
|
||||
|
||||
|
@@ -16,7 +16,8 @@ import xxtea
|
||||
import concurrent.futures
|
||||
from typing import Optional
|
||||
|
||||
from BaseClasses import CollectionState, Region, Location
|
||||
from BaseClasses import CollectionState, Region
|
||||
from worlds.alttp import ALttPLocation
|
||||
from worlds.alttp.Shops import ShopType
|
||||
from worlds.alttp.Dungeons import dungeon_music_addresses
|
||||
from worlds.alttp.Regions import location_table, old_location_address_to_new_location_address
|
||||
@@ -700,18 +701,24 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
|
||||
itemid = location.item.code if location.item is not None else 0x5A
|
||||
|
||||
if location.item.game != "A Link to the Past":
|
||||
itemid = itemid
|
||||
|
||||
if not location.crystal:
|
||||
|
||||
if location.item is not None:
|
||||
if location.item.game != "A Link to the Past":
|
||||
itemid = 0x21
|
||||
# Keys in their native dungeon should use the orignal item code for keys
|
||||
if location.parent_region.dungeon:
|
||||
elif location.parent_region.dungeon:
|
||||
if location.parent_region.dungeon.is_dungeon_item(location.item):
|
||||
if location.item.bigkey:
|
||||
itemid = 0x32
|
||||
if location.item.smallkey:
|
||||
elif location.item.smallkey:
|
||||
itemid = 0x24
|
||||
if location.item.map:
|
||||
elif location.item.map:
|
||||
itemid = 0x33
|
||||
if location.item.compass:
|
||||
elif location.item.compass:
|
||||
itemid = 0x25
|
||||
if world.remote_items[player]:
|
||||
itemid = list(location_table.keys()).index(location.name) + 1
|
||||
@@ -1572,7 +1579,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
|
||||
# set rom name
|
||||
# 21 bytes
|
||||
from worlds.alttp.Main import __version__
|
||||
from Main import __version__
|
||||
# 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.extend([0] * (21 - len(rom.name)))
|
||||
@@ -2007,7 +2014,7 @@ def write_strings(rom, world, player, team):
|
||||
if dest.player != player:
|
||||
if ped_hint:
|
||||
hint += f" for {world.player_names[dest.player][team]}!"
|
||||
elif type(dest) in [Region, Location]:
|
||||
elif type(dest) in [Region, ALttPLocation]:
|
||||
hint += f" in {world.player_names[dest.player][team]}'s world"
|
||||
else:
|
||||
hint += f" for {world.player_names[dest.player][team]}"
|
||||
|
@@ -3,7 +3,7 @@ from enum import unique, Enum
|
||||
from typing import List, Union, Optional, Set, NamedTuple, Dict
|
||||
import logging
|
||||
|
||||
from BaseClasses import Location
|
||||
from worlds.alttp import ALttPLocation
|
||||
from worlds.alttp.EntranceShuffle import door_addresses
|
||||
from worlds.alttp.Items import item_name_groups, item_table, ItemFactory, trap_replaceable, GetBeemizerItem
|
||||
from Utils import int16_as_bytes
|
||||
@@ -130,8 +130,8 @@ shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
||||
|
||||
|
||||
def FillDisabledShopSlots(world):
|
||||
shop_slots: Set[Location] = {location for shop_locations in (shop.region.locations for shop in world.shops)
|
||||
for location in shop_locations if location.shop_slot and location.shop_slot_disabled}
|
||||
shop_slots: Set[ALttPLocation] = {location for shop_locations in (shop.region.locations for shop in world.shops)
|
||||
for location in shop_locations if location.shop_slot and location.shop_slot_disabled}
|
||||
for location in shop_slots:
|
||||
location.shop_slot_disabled = True
|
||||
slot_num = int(location.name[-1]) - 1
|
||||
@@ -141,8 +141,8 @@ def FillDisabledShopSlots(world):
|
||||
|
||||
|
||||
def ShopSlotFill(world):
|
||||
shop_slots: Set[Location] = {location for shop_locations in (shop.region.locations for shop in world.shops)
|
||||
for location in shop_locations if location.shop_slot}
|
||||
shop_slots: Set[ALttPLocation] = {location for shop_locations in (shop.region.locations for shop in world.shops)
|
||||
for location in shop_locations if location.shop_slot}
|
||||
removed = set()
|
||||
for location in shop_slots:
|
||||
slot_num = int(location.name[-1]) - 1
|
||||
@@ -282,8 +282,8 @@ def create_shops(world, player: int):
|
||||
shop.add_inventory(index, *item)
|
||||
if not locked and num_slots:
|
||||
slot_name = "{} Slot {}".format(region.name, index + 1)
|
||||
loc = Location(player, slot_name, address=shop_table_by_location[slot_name],
|
||||
parent=region, hint_text="for sale")
|
||||
loc = ALttPLocation(player, slot_name, address=shop_table_by_location[slot_name],
|
||||
parent=region, hint_text="for sale")
|
||||
loc.shop_slot = True
|
||||
loc.locked = True
|
||||
if single_purchase_slots.pop():
|
||||
|
@@ -1,110 +1,141 @@
|
||||
from typing import Optional
|
||||
|
||||
from BaseClasses import Location, Item
|
||||
from worlds.generic import World
|
||||
|
||||
|
||||
class ALTTPWorld(World):
|
||||
"""WIP"""
|
||||
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 = []
|
||||
#class ALTTPWorld(World):
|
||||
# """WIP"""
|
||||
# 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'}
|
||||
|
||||
|
||||
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
|
||||
class ALttPLocation(Location):
|
||||
game: str = "A Link to the Past"
|
||||
|
||||
def __init__(self, player: int, name: str = '', address=None, crystal: bool = False,
|
||||
hint_text: Optional[str] = None, parent=None,
|
||||
player_address=None):
|
||||
super(ALttPLocation, self).__init__(player, name, address, parent)
|
||||
self.crystal = crystal
|
||||
self.player_address = player_address
|
||||
self._hint_text: str = hint_text
|
||||
|
||||
|
||||
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
|
||||
class ALttPItem(Item):
|
||||
|
||||
@property
|
||||
def sewer_light_cone(self):
|
||||
return self.mode == "standard"
|
||||
game: str = "A Link to the Past"
|
||||
|
||||
@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'}
|
||||
def __init__(self, name='', advancement=False, type=None, code=None, pedestal_hint=None, pedestal_credit=None, sickkid_credit=None, zora_credit=None, witch_credit=None, fluteboy_credit=None, hint_text=None, player=None):
|
||||
super(ALttPItem, self).__init__(name, advancement, code, player)
|
||||
self.type = type
|
||||
self._pedestal_hint_text = pedestal_hint
|
||||
self.pedestal_credit_text = pedestal_credit
|
||||
self.sickkid_credit_text = sickkid_credit
|
||||
self.zora_credit_text = zora_credit
|
||||
self.magicshop_credit_text = witch_credit
|
||||
self.fluteboy_credit_text = fluteboy_credit
|
||||
self._hint_text = hint_text
|
325
worlds/hk/Items.py
Normal file
325
worlds/hk/Items.py
Normal file
@@ -0,0 +1,325 @@
|
||||
items = \
|
||||
{ 16777217: {'advancement': True, 'name': 'Lurien'},
|
||||
16777218: {'advancement': True, 'name': 'Monomon'},
|
||||
16777219: {'advancement': True, 'name': 'Herrah'},
|
||||
16777220: {'advancement': False, 'name': 'World_Sense'},
|
||||
16777221: {'advancement': True, 'name': 'Dreamer'},
|
||||
16777222: {'advancement': True, 'name': 'Mothwing_Cloak'},
|
||||
16777223: {'advancement': True, 'name': 'Mantis_Claw'},
|
||||
16777224: {'advancement': True, 'name': 'Crystal_Heart'},
|
||||
16777225: {'advancement': True, 'name': 'Monarch_Wings'},
|
||||
16777226: {'advancement': True, 'name': 'Shade_Cloak'},
|
||||
16777227: {'advancement': True, 'name': "Isma's_Tear"},
|
||||
16777228: {'advancement': True, 'name': 'Dream_Nail'},
|
||||
16777229: {'advancement': True, 'name': 'Dream_Gate'},
|
||||
16777230: {'advancement': True, 'name': 'Awoken_Dream_Nail'},
|
||||
16777231: {'advancement': True, 'name': 'Vengeful_Spirit'},
|
||||
16777232: {'advancement': True, 'name': 'Shade_Soul'},
|
||||
16777233: {'advancement': True, 'name': 'Desolate_Dive'},
|
||||
16777234: {'advancement': True, 'name': 'Descending_Dark'},
|
||||
16777235: {'advancement': True, 'name': 'Howling_Wraiths'},
|
||||
16777236: {'advancement': True, 'name': 'Abyss_Shriek'},
|
||||
16777237: {'advancement': True, 'name': 'Cyclone_Slash'},
|
||||
16777238: {'advancement': True, 'name': 'Dash_Slash'},
|
||||
16777239: {'advancement': True, 'name': 'Great_Slash'},
|
||||
16777240: {'advancement': True, 'name': 'Focus'},
|
||||
16777241: {'advancement': False, 'name': 'Gathering_Swarm'},
|
||||
16777242: {'advancement': False, 'name': 'Wayward_Compass'},
|
||||
16777243: {'advancement': False, 'name': 'Grubsong'},
|
||||
16777244: {'advancement': False, 'name': 'Stalwart_Shell'},
|
||||
16777245: {'advancement': False, 'name': 'Baldur_Shell'},
|
||||
16777246: {'advancement': False, 'name': 'Fury_of_the_Fallen'},
|
||||
16777247: {'advancement': False, 'name': 'Quick_Focus'},
|
||||
16777248: {'advancement': True, 'name': 'Lifeblood_Heart'},
|
||||
16777249: {'advancement': True, 'name': 'Lifeblood_Core'},
|
||||
16777250: {'advancement': False, 'name': "Defender's_Crest"},
|
||||
16777251: {'advancement': False, 'name': 'Flukenest'},
|
||||
16777252: {'advancement': False, 'name': 'Thorns_of_Agony'},
|
||||
16777253: {'advancement': True, 'name': 'Mark_of_Pride'},
|
||||
16777254: {'advancement': False, 'name': 'Steady_Body'},
|
||||
16777255: {'advancement': False, 'name': 'Heavy_Blow'},
|
||||
16777256: {'advancement': True, 'name': 'Sharp_Shadow'},
|
||||
16777257: {'advancement': True, 'name': 'Spore_Shroom'},
|
||||
16777258: {'advancement': False, 'name': 'Longnail'},
|
||||
16777259: {'advancement': False, 'name': 'Shaman_Stone'},
|
||||
16777260: {'advancement': False, 'name': 'Soul_Catcher'},
|
||||
16777261: {'advancement': False, 'name': 'Soul_Eater'},
|
||||
16777262: {'advancement': True, 'name': 'Glowing_Womb'},
|
||||
16777263: {'advancement': False, 'name': 'Fragile_Heart'},
|
||||
16777264: {'advancement': False, 'name': 'Fragile_Greed'},
|
||||
16777265: {'advancement': False, 'name': 'Fragile_Strength'},
|
||||
16777266: {'advancement': False, 'name': "Nailmaster's_Glory"},
|
||||
16777267: {'advancement': True, 'name': "Joni's_Blessing"},
|
||||
16777268: {'advancement': False, 'name': 'Shape_of_Unn'},
|
||||
16777269: {'advancement': False, 'name': 'Hiveblood'},
|
||||
16777270: {'advancement': False, 'name': 'Dream_Wielder'},
|
||||
16777271: {'advancement': True, 'name': 'Dashmaster'},
|
||||
16777272: {'advancement': False, 'name': 'Quick_Slash'},
|
||||
16777273: {'advancement': False, 'name': 'Spell_Twister'},
|
||||
16777274: {'advancement': False, 'name': 'Deep_Focus'},
|
||||
16777275: {'advancement': True, 'name': "Grubberfly's_Elegy"},
|
||||
16777276: {'advancement': True, 'name': 'Queen_Fragment'},
|
||||
16777277: {'advancement': True, 'name': 'King_Fragment'},
|
||||
16777278: {'advancement': True, 'name': 'Void_Heart'},
|
||||
16777279: {'advancement': True, 'name': 'Sprintmaster'},
|
||||
16777280: {'advancement': False, 'name': 'Dreamshield'},
|
||||
16777281: {'advancement': True, 'name': 'Weaversong'},
|
||||
16777282: {'advancement': True, 'name': 'Grimmchild'},
|
||||
16777283: {'advancement': True, 'name': 'City_Crest'},
|
||||
16777284: {'advancement': True, 'name': 'Lumafly_Lantern'},
|
||||
16777285: {'advancement': True, 'name': 'Tram_Pass'},
|
||||
16777286: {'advancement': True, 'name': 'Simple_Key-Sly'},
|
||||
16777287: {'advancement': True, 'name': 'Simple_Key-Basin'},
|
||||
16777288: {'advancement': True, 'name': 'Simple_Key-City'},
|
||||
16777289: {'advancement': True, 'name': 'Simple_Key-Lurker'},
|
||||
16777290: {'advancement': True, 'name': "Shopkeeper's_Key"},
|
||||
16777291: {'advancement': True, 'name': 'Elegant_Key'},
|
||||
16777292: {'advancement': True, 'name': 'Love_Key'},
|
||||
16777293: {'advancement': True, 'name': "King's_Brand"},
|
||||
16777294: {'advancement': False, 'name': 'Godtuner'},
|
||||
16777295: {'advancement': False, 'name': "Collector's_Map"},
|
||||
16777296: {'advancement': False, 'name': 'Mask_Shard-Sly1'},
|
||||
16777297: {'advancement': False, 'name': 'Mask_Shard-Sly2'},
|
||||
16777298: {'advancement': False, 'name': 'Mask_Shard-Sly3'},
|
||||
16777299: {'advancement': False, 'name': 'Mask_Shard-Sly4'},
|
||||
16777300: {'advancement': False, 'name': 'Mask_Shard-Seer'},
|
||||
16777301: {'advancement': False, 'name': 'Mask_Shard-5_Grubs'},
|
||||
16777302: {'advancement': False, 'name': 'Mask_Shard-Brooding_Mawlek'},
|
||||
16777303: {'advancement': False, 'name': 'Mask_Shard-Crossroads_Goam'},
|
||||
16777304: {'advancement': False, 'name': 'Mask_Shard-Stone_Sanctuary'},
|
||||
16777305: {'advancement': False, 'name': "Mask_Shard-Queen's_Station"},
|
||||
16777306: {'advancement': False, 'name': 'Mask_Shard-Deepnest'},
|
||||
16777307: {'advancement': False, 'name': 'Mask_Shard-Waterways'},
|
||||
16777308: {'advancement': False, 'name': 'Mask_Shard-Enraged_Guardian'},
|
||||
16777309: {'advancement': False, 'name': 'Mask_Shard-Hive'},
|
||||
16777310: {'advancement': False, 'name': 'Mask_Shard-Grey_Mourner'},
|
||||
16777311: {'advancement': False, 'name': 'Mask_Shard-Bretta'},
|
||||
16777312: {'advancement': False, 'name': 'Vessel_Fragment-Sly1'},
|
||||
16777313: {'advancement': False, 'name': 'Vessel_Fragment-Sly2'},
|
||||
16777314: {'advancement': False, 'name': 'Vessel_Fragment-Seer'},
|
||||
16777315: {'advancement': False, 'name': 'Vessel_Fragment-Greenpath'},
|
||||
16777316: {'advancement': False, 'name': 'Vessel_Fragment-City'},
|
||||
16777317: {'advancement': False, 'name': 'Vessel_Fragment-Crossroads'},
|
||||
16777318: {'advancement': False, 'name': 'Vessel_Fragment-Basin'},
|
||||
16777319: {'advancement': False, 'name': 'Vessel_Fragment-Deepnest'},
|
||||
16777320: {'advancement': False, 'name': 'Vessel_Fragment-Stag_Nest'},
|
||||
16777321: {'advancement': False, 'name': 'Charm_Notch-Shrumal_Ogres'},
|
||||
16777322: {'advancement': False, 'name': 'Charm_Notch-Fog_Canyon'},
|
||||
16777323: {'advancement': False, 'name': 'Charm_Notch-Colosseum'},
|
||||
16777324: {'advancement': False, 'name': 'Charm_Notch-Grimm'},
|
||||
16777325: {'advancement': False, 'name': 'Pale_Ore-Basin'},
|
||||
16777326: {'advancement': False, 'name': 'Pale_Ore-Crystal_Peak'},
|
||||
16777327: {'advancement': False, 'name': 'Pale_Ore-Nosk'},
|
||||
16777328: {'advancement': False, 'name': 'Pale_Ore-Seer'},
|
||||
16777329: {'advancement': False, 'name': 'Pale_Ore-Grubs'},
|
||||
16777330: {'advancement': False, 'name': 'Pale_Ore-Colosseum'},
|
||||
16777331: {'advancement': False, 'name': '200_Geo-False_Knight_Chest'},
|
||||
16777332: {'advancement': False, 'name': '380_Geo-Soul_Master_Chest'},
|
||||
16777333: {'advancement': False, 'name': '655_Geo-Watcher_Knights_Chest'},
|
||||
16777334: {'advancement': False, 'name': '85_Geo-Greenpath_Chest'},
|
||||
16777335: {'advancement': False, 'name': '620_Geo-Mantis_Lords_Chest'},
|
||||
16777336: {'advancement': False, 'name': '150_Geo-Resting_Grounds_Chest'},
|
||||
16777337: {'advancement': False, 'name': '80_Geo-Crystal_Peak_Chest'},
|
||||
16777338: {'advancement': False, 'name': '160_Geo-Weavers_Den_Chest'},
|
||||
16777339: {'advancement': False, 'name': '1_Geo'},
|
||||
16777340: {'advancement': False, 'name': 'Rancid_Egg-Sly'},
|
||||
16777341: {'advancement': False, 'name': 'Rancid_Egg-Grubs'},
|
||||
16777342: {'advancement': False, 'name': 'Rancid_Egg-Sheo'},
|
||||
16777343: {'advancement': False, 'name': 'Rancid_Egg-Fungal_Core'},
|
||||
16777344: {'advancement': False, 'name': "Rancid_Egg-Queen's_Gardens"},
|
||||
16777345: {'advancement': False, 'name': 'Rancid_Egg-Blue_Lake'},
|
||||
16777346: { 'advancement': False,
|
||||
'name': 'Rancid_Egg-Crystal_Peak_Dive_Entrance'},
|
||||
16777347: { 'advancement': False,
|
||||
'name': 'Rancid_Egg-Crystal_Peak_Dark_Room'},
|
||||
16777348: { 'advancement': False,
|
||||
'name': 'Rancid_Egg-Crystal_Peak_Tall_Room'},
|
||||
16777349: {'advancement': False, 'name': 'Rancid_Egg-City_of_Tears_Left'},
|
||||
16777350: { 'advancement': False,
|
||||
'name': 'Rancid_Egg-City_of_Tears_Pleasure_House'},
|
||||
16777351: {'advancement': False, 'name': "Rancid_Egg-Beast's_Den"},
|
||||
16777352: {'advancement': False, 'name': 'Rancid_Egg-Dark_Deepnest'},
|
||||
16777353: {'advancement': False, 'name': "Rancid_Egg-Weaver's_Den"},
|
||||
16777354: {'advancement': False, 'name': 'Rancid_Egg-Near_Quick_Slash'},
|
||||
16777355: {'advancement': False, 'name': "Rancid_Egg-Upper_Kingdom's_Edge"},
|
||||
16777356: {'advancement': False, 'name': 'Rancid_Egg-Waterways_East'},
|
||||
16777357: {'advancement': False, 'name': 'Rancid_Egg-Waterways_Main'},
|
||||
16777358: { 'advancement': False,
|
||||
'name': 'Rancid_Egg-Waterways_West_Bluggsac'},
|
||||
16777359: { 'advancement': False,
|
||||
'name': 'Rancid_Egg-Waterways_West_Pickup'},
|
||||
16777360: {'advancement': False, 'name': "Wanderer's_Journal-Cliffs"},
|
||||
16777361: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-Greenpath_Stag"},
|
||||
16777362: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-Greenpath_Lower"},
|
||||
16777363: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-Fungal_Wastes_Thorns_Gauntlet"},
|
||||
16777364: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-Above_Mantis_Village"},
|
||||
16777365: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-Crystal_Peak_Crawlers"},
|
||||
16777366: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-Resting_Grounds_Catacombs"},
|
||||
16777367: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-King's_Station"},
|
||||
16777368: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-Pleasure_House"},
|
||||
16777369: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-City_Storerooms"},
|
||||
16777370: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-Ancient_Basin"},
|
||||
16777371: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-Kingdom's_Edge_Entrance"},
|
||||
16777372: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-Kingdom's_Edge_Camp"},
|
||||
16777373: { 'advancement': False,
|
||||
'name': "Wanderer's_Journal-Kingdom's_Edge_Requires_Dive"},
|
||||
16777374: {'advancement': False, 'name': 'Hallownest_Seal-Crossroads_Well'},
|
||||
16777375: {'advancement': False, 'name': 'Hallownest_Seal-Grubs'},
|
||||
16777376: {'advancement': False, 'name': 'Hallownest_Seal-Greenpath'},
|
||||
16777377: {'advancement': False, 'name': 'Hallownest_Seal-Fog_Canyon_West'},
|
||||
16777378: {'advancement': False, 'name': 'Hallownest_Seal-Fog_Canyon_East'},
|
||||
16777379: {'advancement': False, 'name': "Hallownest_Seal-Queen's_Station"},
|
||||
16777380: { 'advancement': False,
|
||||
'name': 'Hallownest_Seal-Fungal_Wastes_Sporgs'},
|
||||
16777381: {'advancement': False, 'name': 'Hallownest_Seal-Mantis_Lords'},
|
||||
16777382: {'advancement': False, 'name': 'Hallownest_Seal-Seer'},
|
||||
16777383: { 'advancement': False,
|
||||
'name': 'Hallownest_Seal-Resting_Grounds_Catacombs'},
|
||||
16777384: {'advancement': False, 'name': "Hallownest_Seal-King's_Station"},
|
||||
16777385: {'advancement': False, 'name': 'Hallownest_Seal-City_Rafters'},
|
||||
16777386: {'advancement': False, 'name': 'Hallownest_Seal-Soul_Sanctum'},
|
||||
16777387: {'advancement': False, 'name': 'Hallownest_Seal-Watcher_Knight'},
|
||||
16777388: { 'advancement': False,
|
||||
'name': 'Hallownest_Seal-Deepnest_By_Mantis_Lords'},
|
||||
16777389: {'advancement': False, 'name': "Hallownest_Seal-Beast's_Den"},
|
||||
16777390: {'advancement': False, 'name': "Hallownest_Seal-Queen's_Gardens"},
|
||||
16777391: {'advancement': False, 'name': "King's_Idol-Grubs"},
|
||||
16777392: {'advancement': False, 'name': "King's_Idol-Cliffs"},
|
||||
16777393: {'advancement': False, 'name': "King's_Idol-Crystal_Peak"},
|
||||
16777394: {'advancement': False, 'name': "King's_Idol-Glade_of_Hope"},
|
||||
16777395: {'advancement': False, 'name': "King's_Idol-Dung_Defender"},
|
||||
16777396: {'advancement': False, 'name': "King's_Idol-Great_Hopper"},
|
||||
16777397: {'advancement': False, 'name': "King's_Idol-Pale_Lurker"},
|
||||
16777398: {'advancement': False, 'name': "King's_Idol-Deepnest"},
|
||||
16777399: {'advancement': False, 'name': 'Arcane_Egg-Seer'},
|
||||
16777400: {'advancement': False, 'name': 'Arcane_Egg-Lifeblood_Core'},
|
||||
16777401: {'advancement': False, 'name': 'Arcane_Egg-Shade_Cloak'},
|
||||
16777402: {'advancement': False, 'name': 'Arcane_Egg-Birthplace'},
|
||||
16777403: {'advancement': True, 'name': 'Whispering_Root-Crossroads'},
|
||||
16777404: {'advancement': True, 'name': 'Whispering_Root-Greenpath'},
|
||||
16777405: {'advancement': True, 'name': 'Whispering_Root-Leg_Eater'},
|
||||
16777406: {'advancement': True, 'name': 'Whispering_Root-Mantis_Village'},
|
||||
16777407: {'advancement': True, 'name': 'Whispering_Root-Deepnest'},
|
||||
16777408: {'advancement': True, 'name': 'Whispering_Root-Queens_Gardens'},
|
||||
16777409: {'advancement': True, 'name': 'Whispering_Root-Kingdoms_Edge'},
|
||||
16777410: {'advancement': True, 'name': 'Whispering_Root-Waterways'},
|
||||
16777411: {'advancement': True, 'name': 'Whispering_Root-City'},
|
||||
16777412: {'advancement': True, 'name': 'Whispering_Root-Resting_Grounds'},
|
||||
16777413: {'advancement': True, 'name': 'Whispering_Root-Spirits_Glade'},
|
||||
16777414: {'advancement': True, 'name': 'Whispering_Root-Crystal_Peak'},
|
||||
16777415: {'advancement': True, 'name': 'Whispering_Root-Howling_Cliffs'},
|
||||
16777416: {'advancement': True, 'name': 'Whispering_Root-Ancestral_Mound'},
|
||||
16777417: {'advancement': True, 'name': 'Whispering_Root-Hive'},
|
||||
16777418: {'advancement': True, 'name': 'Boss_Essence-Elder_Hu'},
|
||||
16777419: {'advancement': True, 'name': 'Boss_Essence-Xero'},
|
||||
16777420: {'advancement': True, 'name': 'Boss_Essence-Gorb'},
|
||||
16777421: {'advancement': True, 'name': 'Boss_Essence-Marmu'},
|
||||
16777422: {'advancement': True, 'name': 'Boss_Essence-No_Eyes'},
|
||||
16777423: {'advancement': True, 'name': 'Boss_Essence-Galien'},
|
||||
16777424: {'advancement': True, 'name': 'Boss_Essence-Markoth'},
|
||||
16777425: {'advancement': True, 'name': 'Boss_Essence-Failed_Champion'},
|
||||
16777426: {'advancement': True, 'name': 'Boss_Essence-Soul_Tyrant'},
|
||||
16777427: {'advancement': True, 'name': 'Boss_Essence-Lost_Kin'},
|
||||
16777428: {'advancement': True, 'name': 'Boss_Essence-White_Defender'},
|
||||
16777429: {'advancement': True, 'name': 'Boss_Essence-Grey_Prince_Zote'},
|
||||
16777430: {'advancement': True, 'name': 'Grub-Crossroads_Acid'},
|
||||
16777431: {'advancement': True, 'name': 'Grub-Crossroads_Center'},
|
||||
16777432: {'advancement': True, 'name': 'Grub-Crossroads_Stag'},
|
||||
16777433: {'advancement': True, 'name': 'Grub-Crossroads_Spike'},
|
||||
16777434: {'advancement': True, 'name': 'Grub-Crossroads_Guarded'},
|
||||
16777435: {'advancement': True, 'name': 'Grub-Greenpath_Cornifer'},
|
||||
16777436: {'advancement': True, 'name': 'Grub-Greenpath_Journal'},
|
||||
16777437: {'advancement': True, 'name': 'Grub-Greenpath_MMC'},
|
||||
16777438: {'advancement': True, 'name': 'Grub-Greenpath_Stag'},
|
||||
16777439: {'advancement': True, 'name': 'Grub-Fog_Canyon'},
|
||||
16777440: {'advancement': True, 'name': 'Grub-Fungal_Bouncy'},
|
||||
16777441: {'advancement': True, 'name': 'Grub-Fungal_Spore_Shroom'},
|
||||
16777442: {'advancement': True, 'name': 'Grub-Deepnest_Mimic'},
|
||||
16777443: {'advancement': True, 'name': 'Grub-Deepnest_Nosk'},
|
||||
16777444: {'advancement': True, 'name': 'Grub-Deepnest_Spike'},
|
||||
16777445: {'advancement': True, 'name': 'Grub-Dark_Deepnest'},
|
||||
16777446: {'advancement': True, 'name': "Grub-Beast's_Den"},
|
||||
16777447: {'advancement': True, 'name': "Grub-Kingdom's_Edge_Oro"},
|
||||
16777448: {'advancement': True, 'name': "Grub-Kingdom's_Edge_Camp"},
|
||||
16777449: {'advancement': True, 'name': 'Grub-Hive_External'},
|
||||
16777450: {'advancement': True, 'name': 'Grub-Hive_Internal'},
|
||||
16777451: {'advancement': True, 'name': 'Grub-Basin_Requires_Wings'},
|
||||
16777452: {'advancement': True, 'name': 'Grub-Basin_Requires_Dive'},
|
||||
16777453: {'advancement': True, 'name': 'Grub-Waterways_Main'},
|
||||
16777454: {'advancement': True, 'name': 'Grub-Waterways_East'},
|
||||
16777455: {'advancement': True, 'name': 'Grub-Waterways_Requires_Tram'},
|
||||
16777456: {'advancement': True, 'name': 'Grub-City_of_Tears_Left'},
|
||||
16777457: {'advancement': True, 'name': 'Grub-Soul_Sanctum'},
|
||||
16777458: {'advancement': True, 'name': "Grub-Watcher's_Spire"},
|
||||
16777459: {'advancement': True, 'name': 'Grub-City_of_Tears_Guarded'},
|
||||
16777460: {'advancement': True, 'name': "Grub-King's_Station"},
|
||||
16777461: {'advancement': True, 'name': 'Grub-Resting_Grounds'},
|
||||
16777462: {'advancement': True, 'name': 'Grub-Crystal_Peak_Below_Chest'},
|
||||
16777463: {'advancement': True, 'name': 'Grub-Crystallized_Mound'},
|
||||
16777464: {'advancement': True, 'name': 'Grub-Crystal_Peak_Spike'},
|
||||
16777465: {'advancement': True, 'name': 'Grub-Crystal_Peak_Mimic'},
|
||||
16777466: {'advancement': True, 'name': 'Grub-Crystal_Peak_Crushers'},
|
||||
16777467: {'advancement': True, 'name': 'Grub-Crystal_Heart'},
|
||||
16777468: {'advancement': True, 'name': 'Grub-Hallownest_Crown'},
|
||||
16777469: {'advancement': True, 'name': 'Grub-Howling_Cliffs'},
|
||||
16777470: {'advancement': True, 'name': "Grub-Queen's_Gardens_Stag"},
|
||||
16777471: {'advancement': True, 'name': "Grub-Queen's_Gardens_Marmu"},
|
||||
16777472: {'advancement': True, 'name': "Grub-Queen's_Gardens_Top"},
|
||||
16777473: {'advancement': True, 'name': 'Grub-Collector_1'},
|
||||
16777474: {'advancement': True, 'name': 'Grub-Collector_2'},
|
||||
16777475: {'advancement': True, 'name': 'Grub-Collector_3'},
|
||||
16777476: {'advancement': False, 'name': 'Crossroads_Map'},
|
||||
16777477: {'advancement': False, 'name': 'Greenpath_Map'},
|
||||
16777478: {'advancement': False, 'name': 'Fog_Canyon_Map'},
|
||||
16777479: {'advancement': False, 'name': 'Fungal_Wastes_Map'},
|
||||
16777480: {'advancement': False, 'name': 'Deepnest_Map-Upper'},
|
||||
16777481: { 'advancement': False,
|
||||
'name': 'Deepnest_Map-Right_[Gives_Quill]'},
|
||||
16777482: {'advancement': False, 'name': 'Ancient_Basin_Map'},
|
||||
16777483: {'advancement': False, 'name': "Kingdom's_Edge_Map"},
|
||||
16777484: {'advancement': False, 'name': 'City_of_Tears_Map'},
|
||||
16777485: {'advancement': False, 'name': 'Royal_Waterways_Map'},
|
||||
16777486: {'advancement': False, 'name': 'Howling_Cliffs_Map'},
|
||||
16777487: {'advancement': False, 'name': 'Crystal_Peak_Map'},
|
||||
16777488: {'advancement': False, 'name': "Queen's_Gardens_Map"},
|
||||
16777489: {'advancement': False, 'name': 'Resting_Grounds_Map'},
|
||||
16777490: {'advancement': True, 'name': 'Dirtmouth_Stag'},
|
||||
16777491: {'advancement': True, 'name': 'Crossroads_Stag'},
|
||||
16777492: {'advancement': True, 'name': 'Greenpath_Stag'},
|
||||
16777493: {'advancement': True, 'name': "Queen's_Station_Stag"},
|
||||
16777494: {'advancement': True, 'name': "Queen's_Gardens_Stag"},
|
||||
16777495: {'advancement': True, 'name': 'City_Storerooms_Stag'},
|
||||
16777496: {'advancement': True, 'name': "King's_Station_Stag"},
|
||||
16777497: {'advancement': True, 'name': 'Resting_Grounds_Stag'},
|
||||
16777498: {'advancement': True, 'name': 'Distant_Village_Stag'},
|
||||
16777499: {'advancement': True, 'name': 'Hidden_Station_Stag'},
|
||||
16777500: {'advancement': True, 'name': 'Stag_Nest_Stag'},
|
||||
16777501: {'advancement': False, 'name': "Lifeblood_Cocoon-King's_Pass"},
|
||||
16777502: { 'advancement': False,
|
||||
'name': 'Lifeblood_Cocoon-Ancestral_Mound'},
|
||||
16777503: {'advancement': False, 'name': 'Lifeblood_Cocoon-Greenpath'},
|
||||
16777504: { 'advancement': False,
|
||||
'name': 'Lifeblood_Cocoon-Fog_Canyon_West'},
|
||||
16777505: {'advancement': False, 'name': 'Lifeblood_Cocoon-Mantis_Village'},
|
||||
16777506: {'advancement': False, 'name': 'Lifeblood_Cocoon-Failed_Tramway'},
|
||||
16777507: {'advancement': False, 'name': 'Lifeblood_Cocoon-Galien'},
|
||||
16777508: {'advancement': False, 'name': "Lifeblood_Cocoon-Kingdom's_Edge"},
|
||||
16777509: {'advancement': False, 'name': 'Grubfather'},
|
||||
16777510: {'advancement': False, 'name': 'Seer'},
|
||||
16777511: {'advancement': False, 'name': 'Equipped'},
|
||||
16777512: {'advancement': False, 'name': 'Placeholder'}}
|
||||
|
||||
item_table = {data["name"]: item_id for item_id, data in items.items()}
|
||||
lookup_id_to_name = {item_id: data["name"] for item_id, data in items.items()}
|
1018
worlds/hk/Locations.py
Normal file
1018
worlds/hk/Locations.py
Normal file
File diff suppressed because it is too large
Load Diff
63
worlds/hk/__init__.py
Normal file
63
worlds/hk/__init__.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("Hollow Knight")
|
||||
|
||||
from .Locations import locations, lookup_name_to_id
|
||||
from .Items import items
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
||||
|
||||
|
||||
class HKLocation(Location):
|
||||
game: str = "Hollow Knight"
|
||||
|
||||
def __init__(self, player: int, name: str, address=None, parent=None):
|
||||
super(HKLocation, self).__init__(player, name, address, parent)
|
||||
|
||||
class HKItem(Item):
|
||||
def __init__(self, name, advancement, code, player: int = None):
|
||||
super(HKItem, self).__init__(name, advancement, code, player)
|
||||
|
||||
def gen_hollow(world: MultiWorld, player: int):
|
||||
logger.info("Doing buggy things.")
|
||||
gen_regions(world, player)
|
||||
link_regions(world, player)
|
||||
gen_items(world, player)
|
||||
world.clear_location_cache()
|
||||
world.clear_entrance_cache()
|
||||
|
||||
|
||||
def gen_regions(world: MultiWorld, player: int):
|
||||
world.regions += [
|
||||
create_region(world, player, 'Menu', None, ['Hollow Nest S&Q']),
|
||||
create_region(world, player, 'Hollow Nest', [location["name"] for location in locations.values()])
|
||||
]
|
||||
|
||||
|
||||
def link_regions(world: MultiWorld, player: int):
|
||||
world.get_entrance('Hollow Nest S&Q', player).connect(world.get_region('Hollow Nest', player))
|
||||
|
||||
|
||||
def gen_items(world: MultiWorld, player: int):
|
||||
pool = []
|
||||
for item_id, item_data in items.items():
|
||||
name = item_data["name"]
|
||||
item = HKItem(name, item_data["advancement"], item_id, player=player)
|
||||
item.game = "Hollow Knight"
|
||||
pool.append(item)
|
||||
world.itempool += pool
|
||||
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
ret = Region(name, None, name, player)
|
||||
ret.world = world
|
||||
if locations:
|
||||
for location in locations:
|
||||
loc_id = lookup_name_to_id[location]
|
||||
location = HKLocation(player, location, loc_id, ret)
|
||||
ret.locations.append(location)
|
||||
if exits:
|
||||
for exit in exits:
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
|
||||
return ret
|
Reference in New Issue
Block a user