parent
dcce53f8c8
commit
ff9b24e88e
215
BaseClasses.py
215
BaseClasses.py
|
@ -136,6 +136,7 @@ class MultiWorld():
|
|||
set_player_attr('plando_items', [])
|
||||
set_player_attr('plando_texts', {})
|
||||
set_player_attr('plando_connections', [])
|
||||
set_player_attr('game', "A Link to the Past")
|
||||
|
||||
self.worlds = []
|
||||
#for i in range(players):
|
||||
|
@ -148,6 +149,14 @@ class MultiWorld():
|
|||
def player_ids(self):
|
||||
yield from range(1, self.players + 1)
|
||||
|
||||
@property
|
||||
def alttp_player_ids(self):
|
||||
yield from (player for player in range(1, self.players + 1) if self.game[player] == "A Link to the Past")
|
||||
|
||||
@property
|
||||
def hk_player_ids(self):
|
||||
yield from (player for player in range(1, self.players + 1) if self.game[player] == "Hollow Knight")
|
||||
|
||||
def get_name_string_for_object(self, obj) -> str:
|
||||
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
|
||||
|
||||
|
@ -1012,7 +1021,9 @@ class Dungeon(object):
|
|||
def is_dungeon_item(self, item: Item) -> bool:
|
||||
return item.player == self.player and item.name in [dungeon_item.name for dungeon_item in self.all_items]
|
||||
|
||||
def __eq__(self, other: Item) -> bool:
|
||||
def __eq__(self, other: Dungeon) -> bool:
|
||||
if not other:
|
||||
return False
|
||||
return self.name == other.name and self.player == other.player
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -1031,29 +1042,25 @@ class Boss():
|
|||
def can_defeat(self, state) -> bool:
|
||||
return self.defeat_rule(state, self.player)
|
||||
|
||||
|
||||
class Location():
|
||||
shop_slot: bool = False
|
||||
shop_slot_disabled: bool = False
|
||||
event: bool = False
|
||||
locked: bool = False
|
||||
spot_type = 'Location'
|
||||
game: str = "Generic"
|
||||
crystal: bool = False
|
||||
|
||||
def __init__(self, player: int, name: str = '', address=None, crystal: bool = False,
|
||||
hint_text: Optional[str] = None, parent=None,
|
||||
player_address=None):
|
||||
def __init__(self, player: int, name: str = '', address:int = None, parent=None):
|
||||
self.name = name
|
||||
self.parent_region = parent
|
||||
self.item = None
|
||||
self.crystal = crystal
|
||||
self.address = address
|
||||
self.player_address = player_address
|
||||
self.hint_text: str = hint_text if hint_text else name
|
||||
self.parent_region = parent
|
||||
self.recursion_count = 0
|
||||
self.player = player
|
||||
self.item = None
|
||||
self.always_allow = lambda item, state: False
|
||||
self.access_rule = lambda state: True
|
||||
self.item_rule = lambda item: True
|
||||
self.player = player
|
||||
|
||||
def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool:
|
||||
return self.always_allow(state, item) or (self.parent_region.can_fill(item) and self.item_rule(item) and (not check_access or self.can_reach(state)))
|
||||
|
@ -1077,24 +1084,42 @@ class Location():
|
|||
def __lt__(self, other):
|
||||
return (self.player, self.name) < (other.player, other.name)
|
||||
|
||||
@property
|
||||
def hint_text(self):
|
||||
hint_text = getattr(self, "_hint_text", None)
|
||||
if not hint_text:
|
||||
return self.name
|
||||
|
||||
class Item(object):
|
||||
class Item():
|
||||
location: Optional[Location] = None
|
||||
world: Optional[World] = None
|
||||
game: str = "Generic"
|
||||
type: str = None
|
||||
pedestal_credit_text = "and the Unknown Item"
|
||||
sickkid_credit_text = None
|
||||
magicshop_credit_text = None
|
||||
zora_credit_text = None
|
||||
fluteboy_credit_text = None
|
||||
|
||||
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):
|
||||
def __init__(self, name: str, advancement:bool, code:int, player:int):
|
||||
self.name = name
|
||||
self.advancement = advancement
|
||||
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
|
||||
self.code = code
|
||||
self.player = player
|
||||
self.code = code
|
||||
|
||||
@property
|
||||
def hint_text(self):
|
||||
hint_text = getattr(self, "_hint_text", None)
|
||||
if not hint_text:
|
||||
return self.name
|
||||
return hint_text
|
||||
|
||||
@property
|
||||
def pedestal_hint_text(self):
|
||||
pedestal_hint_text = getattr(self, "_pedestal_hint_text", None)
|
||||
if not pedestal_hint_text:
|
||||
return self.name
|
||||
return pedestal_hint_text
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == other.name and self.player == other.player
|
||||
|
@ -1134,11 +1159,6 @@ class Item(object):
|
|||
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
||||
|
||||
|
||||
# have 6 address that need to be filled
|
||||
class Crystal(Item):
|
||||
pass
|
||||
|
||||
|
||||
class Spoiler(object):
|
||||
world: MultiWorld
|
||||
|
||||
|
@ -1224,7 +1244,7 @@ class Spoiler(object):
|
|||
shopdata['item_{}'.format(index)] += ", {} - {}".format(item['replacement'], item['replacement_price']) if item['replacement_price'] else item['replacement']
|
||||
self.shops.append(shopdata)
|
||||
|
||||
for player in range(1, self.world.players + 1):
|
||||
for player in self.world.alttp_player_ids:
|
||||
self.bosses[str(player)] = OrderedDict()
|
||||
self.bosses[str(player)]["Eastern Palace"] = self.world.get_dungeon("Eastern Palace", player).boss.name
|
||||
self.bosses[str(player)]["Desert Palace"] = self.world.get_dungeon("Desert Palace", player).boss.name
|
||||
|
@ -1252,8 +1272,8 @@ class Spoiler(object):
|
|||
if self.world.players == 1:
|
||||
self.bosses = self.bosses["1"]
|
||||
|
||||
from Utils import __version__ as ERVersion
|
||||
self.metadata = {'version': ERVersion,
|
||||
from Utils import __version__ as APVersion
|
||||
self.metadata = {'version': APVersion,
|
||||
'logic': self.world.logic,
|
||||
'dark_room_logic': self.world.dark_room_logic,
|
||||
'mode': self.world.mode,
|
||||
|
@ -1292,6 +1312,7 @@ class Spoiler(object):
|
|||
'shuffle_prizes': self.world.shuffle_prizes,
|
||||
'sprite_pool': self.world.sprite_pool,
|
||||
'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss,
|
||||
'game': self.world.game,
|
||||
'er_seeds': self.world.er_seeds
|
||||
}
|
||||
|
||||
|
@ -1331,74 +1352,80 @@ class Spoiler(object):
|
|||
for player in range(1, self.world.players + 1):
|
||||
if self.world.players > 1:
|
||||
outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player)))
|
||||
for team in range(self.world.teams):
|
||||
outfile.write('%s%s\n' % (
|
||||
f"Hash - {self.world.player_names[player][team]} (Team {team + 1}): " if self.world.teams > 1 else 'Hash: ',
|
||||
self.hashes[player, team]))
|
||||
outfile.write('Logic: %s\n' % self.metadata['logic'][player])
|
||||
outfile.write('Dark Room Logic: %s\n' % self.metadata['dark_room_logic'][player])
|
||||
outfile.write('Restricted Boss Drops: %s\n' %
|
||||
bool_to_text(self.metadata['restrict_dungeon_item_on_boss'][player]))
|
||||
outfile.write('Game: %s\n' % self.metadata['game'][player])
|
||||
if self.world.players > 1:
|
||||
outfile.write('Progression Balanced: %s\n' % (
|
||||
'Yes' if self.metadata['progression_balancing'][player] else 'No'))
|
||||
outfile.write('Mode: %s\n' % self.metadata['mode'][player])
|
||||
outfile.write('Retro: %s\n' %
|
||||
('Yes' if self.metadata['retro'][player] else 'No'))
|
||||
outfile.write('Swords: %s\n' % self.metadata['weapons'][player])
|
||||
outfile.write('Goal: %s\n' % self.metadata['goal'][player])
|
||||
if "triforce" in self.metadata["goal"][player]: # triforce hunt
|
||||
outfile.write("Pieces available for Triforce: %s\n" %
|
||||
self.metadata['triforce_pieces_available'][player])
|
||||
outfile.write("Pieces required for Triforce: %s\n" %
|
||||
self.metadata["triforce_pieces_required"][player])
|
||||
outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][player])
|
||||
outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player])
|
||||
outfile.write('Item Progression: %s\n' % self.metadata['progressive'][player])
|
||||
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player])
|
||||
if self.metadata['shuffle'][player] != "vanilla":
|
||||
outfile.write('Entrance Shuffle Seed %s\n' % self.metadata['er_seeds'][player])
|
||||
outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals'][player])
|
||||
outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals'][player])
|
||||
outfile.write('Pyramid hole pre-opened: %s\n' % (
|
||||
'Yes' if self.metadata['open_pyramid'][player] else 'No'))
|
||||
outfile.write('Accessibility: %s\n' % self.metadata['accessibility'][player])
|
||||
outfile.write('Map shuffle: %s\n' %
|
||||
('Yes' if self.metadata['mapshuffle'][player] else 'No'))
|
||||
outfile.write('Compass shuffle: %s\n' %
|
||||
('Yes' if self.metadata['compassshuffle'][player] else 'No'))
|
||||
outfile.write(
|
||||
'Small Key shuffle: %s\n' % (bool_to_text(self.metadata['keyshuffle'][player])))
|
||||
outfile.write('Big Key shuffle: %s\n' % (
|
||||
'Yes' if self.metadata['bigkeyshuffle'][player] else 'No'))
|
||||
outfile.write('Shop inventory shuffle: %s\n' %
|
||||
bool_to_text("i" in self.metadata["shop_shuffle"][player]))
|
||||
outfile.write('Shop price shuffle: %s\n' %
|
||||
bool_to_text("p" in self.metadata["shop_shuffle"][player]))
|
||||
outfile.write('Shop upgrade shuffle: %s\n' %
|
||||
bool_to_text("u" in self.metadata["shop_shuffle"][player]))
|
||||
outfile.write('New Shop inventory: %s\n' %
|
||||
bool_to_text("g" in self.metadata["shop_shuffle"][player] or
|
||||
"f" in self.metadata["shop_shuffle"][player]))
|
||||
outfile.write('Custom Potion Shop: %s\n' %
|
||||
bool_to_text("w" in self.metadata["shop_shuffle"][player]))
|
||||
outfile.write('Shop Slots: %s\n' %
|
||||
self.metadata["shop_shuffle_slots"][player])
|
||||
outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'][player])
|
||||
outfile.write(
|
||||
'Enemy shuffle: %s\n' % bool_to_text(self.metadata['enemy_shuffle'][player]))
|
||||
outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player])
|
||||
outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player])
|
||||
outfile.write(f'Killable thieves: {bool_to_text(self.metadata["killable_thieves"][player])}\n')
|
||||
outfile.write(f'Shuffled tiles: {bool_to_text(self.metadata["tile_shuffle"][player])}\n')
|
||||
outfile.write(f'Shuffled bushes: {bool_to_text(self.metadata["bush_shuffle"][player])}\n')
|
||||
outfile.write(
|
||||
'Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No'))
|
||||
outfile.write('Beemizer: %s\n' % self.metadata['beemizer'][player])
|
||||
outfile.write('Pot shuffle %s\n'
|
||||
% ('Yes' if self.metadata['shufflepots'][player] else 'No'))
|
||||
outfile.write('Prize shuffle %s\n' %
|
||||
self.metadata['shuffle_prizes'][player])
|
||||
if player in self.world.alttp_player_ids:
|
||||
for team in range(self.world.teams):
|
||||
outfile.write('%s%s\n' % (
|
||||
f"Hash - {self.world.player_names[player][team]} (Team {team + 1}): " if
|
||||
(player in self.world.alttp_player_ids and self.world.teams > 1) else 'Hash: ',
|
||||
self.hashes[player, team]))
|
||||
|
||||
outfile.write('Logic: %s\n' % self.metadata['logic'][player])
|
||||
outfile.write('Dark Room Logic: %s\n' % self.metadata['dark_room_logic'][player])
|
||||
outfile.write('Restricted Boss Drops: %s\n' %
|
||||
bool_to_text(self.metadata['restrict_dungeon_item_on_boss'][player]))
|
||||
|
||||
outfile.write('Mode: %s\n' % self.metadata['mode'][player])
|
||||
outfile.write('Retro: %s\n' %
|
||||
('Yes' if self.metadata['retro'][player] else 'No'))
|
||||
outfile.write('Swords: %s\n' % self.metadata['weapons'][player])
|
||||
outfile.write('Goal: %s\n' % self.metadata['goal'][player])
|
||||
if "triforce" in self.metadata["goal"][player]: # triforce hunt
|
||||
outfile.write("Pieces available for Triforce: %s\n" %
|
||||
self.metadata['triforce_pieces_available'][player])
|
||||
outfile.write("Pieces required for Triforce: %s\n" %
|
||||
self.metadata["triforce_pieces_required"][player])
|
||||
outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][player])
|
||||
outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player])
|
||||
outfile.write('Item Progression: %s\n' % self.metadata['progressive'][player])
|
||||
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player])
|
||||
if self.metadata['shuffle'][player] != "vanilla":
|
||||
outfile.write('Entrance Shuffle Seed %s\n' % self.metadata['er_seeds'][player])
|
||||
outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals'][player])
|
||||
outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals'][player])
|
||||
outfile.write('Pyramid hole pre-opened: %s\n' % (
|
||||
'Yes' if self.metadata['open_pyramid'][player] else 'No'))
|
||||
|
||||
outfile.write('Map shuffle: %s\n' %
|
||||
('Yes' if self.metadata['mapshuffle'][player] else 'No'))
|
||||
outfile.write('Compass shuffle: %s\n' %
|
||||
('Yes' if self.metadata['compassshuffle'][player] else 'No'))
|
||||
outfile.write(
|
||||
'Small Key shuffle: %s\n' % (bool_to_text(self.metadata['keyshuffle'][player])))
|
||||
outfile.write('Big Key shuffle: %s\n' % (
|
||||
'Yes' if self.metadata['bigkeyshuffle'][player] else 'No'))
|
||||
outfile.write('Shop inventory shuffle: %s\n' %
|
||||
bool_to_text("i" in self.metadata["shop_shuffle"][player]))
|
||||
outfile.write('Shop price shuffle: %s\n' %
|
||||
bool_to_text("p" in self.metadata["shop_shuffle"][player]))
|
||||
outfile.write('Shop upgrade shuffle: %s\n' %
|
||||
bool_to_text("u" in self.metadata["shop_shuffle"][player]))
|
||||
outfile.write('New Shop inventory: %s\n' %
|
||||
bool_to_text("g" in self.metadata["shop_shuffle"][player] or
|
||||
"f" in self.metadata["shop_shuffle"][player]))
|
||||
outfile.write('Custom Potion Shop: %s\n' %
|
||||
bool_to_text("w" in self.metadata["shop_shuffle"][player]))
|
||||
outfile.write('Shop Slots: %s\n' %
|
||||
self.metadata["shop_shuffle_slots"][player])
|
||||
outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'][player])
|
||||
outfile.write(
|
||||
'Enemy shuffle: %s\n' % bool_to_text(self.metadata['enemy_shuffle'][player]))
|
||||
outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player])
|
||||
outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player])
|
||||
outfile.write(f'Killable thieves: {bool_to_text(self.metadata["killable_thieves"][player])}\n')
|
||||
outfile.write(f'Shuffled tiles: {bool_to_text(self.metadata["tile_shuffle"][player])}\n')
|
||||
outfile.write(f'Shuffled bushes: {bool_to_text(self.metadata["bush_shuffle"][player])}\n')
|
||||
outfile.write(
|
||||
'Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No'))
|
||||
outfile.write('Beemizer: %s\n' % self.metadata['beemizer'][player])
|
||||
outfile.write('Pot shuffle %s\n'
|
||||
% ('Yes' if self.metadata['shufflepots'][player] else 'No'))
|
||||
outfile.write('Prize shuffle %s\n' %
|
||||
self.metadata['shuffle_prizes'][player])
|
||||
if self.entrances:
|
||||
outfile.write('\n\nEntrances:\n\n')
|
||||
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: '
|
||||
|
|
2
Gui.py
2
Gui.py
|
@ -17,7 +17,7 @@ ModuleUpdate.update()
|
|||
from worlds.alttp.AdjusterMain import adjust
|
||||
from worlds.alttp.EntranceRandomizer import parse_arguments
|
||||
from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
|
||||
from worlds.alttp.Main import main, get_seed, __version__ as MWVersion
|
||||
from Main import main, get_seed, __version__ as MWVersion
|
||||
from worlds.alttp.Rom import Sprite
|
||||
from Utils import local_path, output_path, open_file
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import zlib
|
|||
import concurrent.futures
|
||||
import pickle
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState, Item, Region, Location
|
||||
from BaseClasses import MultiWorld, CollectionState, Region
|
||||
from worlds.alttp import ALttPLocation, ALttPItem
|
||||
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
|
||||
|
@ -22,6 +23,7 @@ from Fill import distribute_items_restrictive, flood_items, balance_multiworld_p
|
|||
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
|
||||
from worlds.hk import *
|
||||
import Patch
|
||||
|
||||
seeddigits = 20
|
||||
|
@ -96,6 +98,7 @@ def main(args, seed=None):
|
|||
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.game = args.game.copy()
|
||||
|
||||
world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)}
|
||||
|
||||
|
@ -119,17 +122,7 @@ def main(args, seed=None):
|
|||
|
||||
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 player in world.player_ids:
|
||||
for tok in filter(None, args.startinventory[player].split(',')):
|
||||
item = ItemFactory(tok.strip(), player)
|
||||
if item:
|
||||
|
@ -164,6 +157,19 @@ def main(args, seed=None):
|
|||
world.non_local_items[player] -= item_name_groups['Pendants']
|
||||
world.non_local_items[player] -= item_name_groups['Crystals']
|
||||
|
||||
|
||||
for player in world.alttp_player_ids:
|
||||
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])
|
||||
|
||||
|
||||
world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player], world.triforce_pieces_required[player])
|
||||
|
||||
if world.mode[player] != 'inverted':
|
||||
|
@ -175,7 +181,7 @@ def main(args, seed=None):
|
|||
|
||||
logger.info('Shuffling the World about.')
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
for player in world.alttp_player_ids:
|
||||
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
|
||||
|
@ -196,14 +202,19 @@ def main(args, seed=None):
|
|||
|
||||
logger.info('Generating Item Pool.')
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
for player in world.alttp_player_ids:
|
||||
generate_itempool(world, player)
|
||||
|
||||
logger.info('Calculating Access Rules.')
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
for player in world.alttp_player_ids:
|
||||
set_rules(world, player)
|
||||
|
||||
logger.info("Doing Hollow Knight things")
|
||||
|
||||
for player in world.hk_player_ids:
|
||||
gen_hollow(world, player)
|
||||
|
||||
logger.info("Running Item Plando")
|
||||
|
||||
distribute_planned(world)
|
||||
|
@ -239,9 +250,7 @@ def main(args, seed=None):
|
|||
if world.players > 1:
|
||||
balance_multiworld_progression(world)
|
||||
|
||||
logger.info('Patching ROM.')
|
||||
|
||||
|
||||
logger.info('Generating output files.')
|
||||
|
||||
outfilebase = 'AP_%s' % (args.outputname if args.outputname else world.seed)
|
||||
|
||||
|
@ -349,7 +358,7 @@ def main(args, seed=None):
|
|||
rom_futures = []
|
||||
|
||||
for team in range(world.teams):
|
||||
for player in range(1, world.players + 1):
|
||||
for player in world.alttp_player_ids:
|
||||
rom_futures.append(pool.submit(_gen_rom, team, player))
|
||||
|
||||
def get_entrance_to_region(region: Region):
|
||||
|
@ -382,7 +391,9 @@ def main(args, seed=None):
|
|||
|
||||
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:
|
||||
if location.game == "Hollow Knight":
|
||||
checks_in_area[location.player]["Light World"].append(location.address)
|
||||
elif 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)
|
||||
|
@ -423,9 +434,15 @@ def main(args, seed=None):
|
|||
rom_name = future.result()
|
||||
rom_names.append(rom_name)
|
||||
minimum_versions = {"server": (0, 0, 1)}
|
||||
connect_names = {base64.b64encode(rom_name).decode(): (team, slot) for
|
||||
slot, team, rom_name in rom_names}
|
||||
|
||||
for i, team in enumerate(parsed_names):
|
||||
for player, name in enumerate(team, 1):
|
||||
if player in world.hk_player_ids:
|
||||
connect_names[name] = (i, player)
|
||||
multidata = zlib.compress(pickle.dumps({"names": parsed_names,
|
||||
"roms": {base64.b64encode(rom_name).decode(): (team, slot) for
|
||||
slot, team, rom_name in rom_names},
|
||||
"connect_names": connect_names,
|
||||
"remote_items": {player for player in range(1, world.players + 1) if
|
||||
world.remote_items[player]},
|
||||
"locations": {
|
||||
|
@ -509,8 +526,9 @@ def copy_world(world):
|
|||
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()
|
||||
ret.game = world.game.copy()
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
for player in world.alttp_player_ids:
|
||||
if world.mode[player] != 'inverted':
|
||||
create_regions(ret, player)
|
||||
else:
|
||||
|
@ -518,6 +536,9 @@ def copy_world(world):
|
|||
create_shops(ret, player)
|
||||
create_dungeons(ret, player)
|
||||
|
||||
for player in world.hk_player_ids:
|
||||
gen_regions(ret, player)
|
||||
|
||||
copy_dynamic_regions_and_locations(world, ret)
|
||||
|
||||
# copy bosses
|
||||
|
@ -541,7 +562,7 @@ def copy_world(world):
|
|||
# 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)
|
||||
item = ALttPItem(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
|
||||
|
@ -552,7 +573,7 @@ def copy_world(world):
|
|||
|
||||
# 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))
|
||||
ret.itempool.append(ALttPItem(item.name, item.advancement, item.type, player = item.player))
|
||||
|
||||
for item in world.precollected_items:
|
||||
ret.push_precollected(ItemFactory(item.name, item.player))
|
||||
|
@ -561,7 +582,7 @@ def copy_world(world):
|
|||
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):
|
||||
for player in world.alttp_player_ids:
|
||||
set_rules(ret, player)
|
||||
|
||||
|
||||
|
@ -584,7 +605,7 @@ def copy_dynamic_regions_and_locations(world, ret):
|
|||
|
||||
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)
|
||||
new_loc = ALttPLocation(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
|
||||
|
@ -702,12 +723,13 @@ def create_playthrough(world):
|
|||
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))
|
||||
if player in world.alttp_player_ids:
|
||||
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])}
|
|
@ -14,7 +14,7 @@ import shutil
|
|||
|
||||
from random import randrange
|
||||
|
||||
from Utils import get_item_name_from_id, get_location_name_from_address, ReceivedItem
|
||||
from Utils import get_item_name_from_id, get_location_name_from_address
|
||||
|
||||
exit_func = atexit.register(input, "Press enter to close.")
|
||||
|
||||
|
@ -30,8 +30,8 @@ from NetUtils import *
|
|||
import WebUI
|
||||
|
||||
from worlds.alttp import Regions, Shops
|
||||
from worlds.alttp import Items
|
||||
import Utils
|
||||
import Items
|
||||
|
||||
# logging note:
|
||||
# logging.* gets send to only the text console, logger.* gets send to the WebUI as well, if it's initialized.
|
||||
|
@ -44,8 +44,6 @@ def create_named_task(coro, *args, name=None):
|
|||
return asyncio.create_task(coro, *args, name=name)
|
||||
|
||||
|
||||
|
||||
|
||||
class Context():
|
||||
def __init__(self, snes_address, server_address, password, found_items, port: int):
|
||||
self.snes_address = snes_address
|
||||
|
@ -122,6 +120,9 @@ class Context():
|
|||
return
|
||||
await self.server.socket.send(dumps(msgs))
|
||||
|
||||
def consume_players_package(self, package:typing.List[tuple]):
|
||||
self.player_names = {slot: name for team, slot, name, orig_name in package if self.team == team}
|
||||
|
||||
|
||||
def color_item(item_id: int, green: bool = False) -> str:
|
||||
item_name = get_item_name_from_id(item_id)
|
||||
|
@ -819,26 +820,24 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
|
|||
version = ".".join(str(item) for item in version)
|
||||
|
||||
logger.info(f'Server protocol version: {version}')
|
||||
if "tags" in args:
|
||||
logger.info("Server protocol tags: " + ", ".join(args["tags"]))
|
||||
logger.info("Server protocol tags: " + ", ".join(args["tags"]))
|
||||
if args['password']:
|
||||
logger.info('Password required')
|
||||
if "forfeit_mode" in args: # could also be version > 2.2.1, but going with implicit content here
|
||||
logging.info(f"Forfeit setting: {args['forfeit_mode']}")
|
||||
logging.info(f"Remaining setting: {args['remaining_mode']}")
|
||||
logging.info(f"A !hint costs {args['hint_cost']} points and you get {args['location_check_points']}"
|
||||
f" for each location checked.")
|
||||
ctx.hint_cost = int(args['hint_cost'])
|
||||
ctx.check_points = int(args['location_check_points'])
|
||||
ctx.forfeit_mode = args['forfeit_mode']
|
||||
ctx.remaining_mode = args['remaining_mode']
|
||||
ctx.ui_node.send_game_info(ctx)
|
||||
logging.info(f"Forfeit setting: {args['forfeit_mode']}")
|
||||
logging.info(f"Remaining setting: {args['remaining_mode']}")
|
||||
logging.info(f"A !hint costs {args['hint_cost']} points and you get {args['location_check_points']}"
|
||||
f" for each location checked.")
|
||||
ctx.hint_cost = int(args['hint_cost'])
|
||||
ctx.check_points = int(args['location_check_points'])
|
||||
ctx.forfeit_mode = args['forfeit_mode']
|
||||
ctx.remaining_mode = args['remaining_mode']
|
||||
ctx.ui_node.send_game_info(ctx)
|
||||
if len(args['players']) < 1:
|
||||
logger.info('No player connected')
|
||||
else:
|
||||
args['players'].sort()
|
||||
current_team = -1
|
||||
logger.info('Connected players:')
|
||||
logger.info('Players:')
|
||||
for team, slot, name in args['players']:
|
||||
if team != current_team:
|
||||
logger.info(f' Team #{team + 1}')
|
||||
|
@ -848,7 +847,7 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
|
|||
|
||||
elif cmd == 'ConnectionRefused':
|
||||
errors = args["errors"]
|
||||
if 'InvalidRom' in errors:
|
||||
if 'InvalidSlot' in errors:
|
||||
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
|
||||
asyncio.create_task(ctx.snes_socket.close())
|
||||
raise Exception('Invalid ROM detected, '
|
||||
|
@ -871,7 +870,7 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
|
|||
Utils.persistent_store("servers", ctx.rom, ctx.server_address)
|
||||
ctx.team = args["team"]
|
||||
ctx.slot = args["slot"]
|
||||
ctx.player_names = {p: n for p, n in args["playernames"]}
|
||||
ctx.consume_players_package(args["players"])
|
||||
msgs = []
|
||||
if ctx.locations_checked:
|
||||
msgs.append(['LocationChecks',
|
||||
|
@ -904,7 +903,7 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
|
|||
if start_index == len(ctx.items_received):
|
||||
|
||||
for item in args['items']:
|
||||
ctx.items_received.append(ReceivedItem(*item))
|
||||
ctx.items_received.append(NetworkItem(*item))
|
||||
ctx.watcher_event.set()
|
||||
|
||||
elif cmd == 'LocationInfo':
|
||||
|
@ -917,13 +916,13 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
|
|||
ctx.locations_info[location] = (item, player)
|
||||
ctx.watcher_event.set()
|
||||
|
||||
elif cmd == 'ItemSent':
|
||||
found = ReceivedItem(*args["item"])
|
||||
elif cmd == 'ItemSent': # going away
|
||||
found = NetworkItem(*args["item"])
|
||||
receiving_player = args["receiver"]
|
||||
ctx.ui_node.notify_item_sent(ctx.player_names[found.player], ctx.player_names[receiving_player],
|
||||
get_item_name_from_id(found.item), get_location_name_from_address(found.location),
|
||||
found.player == ctx.slot, receiving_player == ctx.slot,
|
||||
get_item_name_from_id(item) in Items.progression_items)
|
||||
get_item_name_from_id(found.item) in Items.progression_items)
|
||||
item = color(get_item_name_from_id(found.item), 'cyan' if found.player != ctx.slot else 'green')
|
||||
found_player = color(ctx.player_names[found.player], 'yellow' if found.player != ctx.slot else 'magenta')
|
||||
receiving_player = color(ctx.player_names[receiving_player], 'yellow' if receiving_player != ctx.slot else 'magenta')
|
||||
|
@ -931,8 +930,8 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
|
|||
'%s sent %s to %s (%s)' % (found_player, item, receiving_player,
|
||||
color(get_location_name_from_address(found.location), 'blue_bg', 'white')))
|
||||
|
||||
elif cmd == 'ItemFound':
|
||||
found = ReceivedItem(*args["item"])
|
||||
elif cmd == 'ItemFound': # going away
|
||||
found = NetworkItem(*args["item"])
|
||||
ctx.ui_node.notify_item_found(ctx.player_names[found.player], get_item_name_from_id(found.item),
|
||||
get_location_name_from_address(found.location), found.player == ctx.slot,
|
||||
get_item_name_from_id(found.item) in Items.progression_items)
|
||||
|
@ -941,7 +940,7 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
|
|||
logging.info('%s found %s (%s)' % (player_sent, item, color(get_location_name_from_address(found.location),
|
||||
'blue_bg', 'white')))
|
||||
|
||||
elif cmd == 'Hint':
|
||||
elif cmd == 'Hint': # going away
|
||||
hints = [Utils.Hint(*hint) for hint in args["hints"]]
|
||||
for hint in hints:
|
||||
ctx.ui_node.send_hint(ctx.player_names[hint.finding_player], ctx.player_names[hint.receiving_player],
|
||||
|
@ -962,8 +961,10 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
|
|||
logging.info(text + (f". {color('(found)', 'green_bg', 'black')} " if hint.found else "."))
|
||||
|
||||
elif cmd == "RoomUpdate":
|
||||
if "playernames" in args:
|
||||
ctx.player_names = {p: n for p, n in args["playernames"]}
|
||||
if "players" in args:
|
||||
ctx.consume_players_package(args["players"])
|
||||
if "hint_points" in args:
|
||||
ctx.hint_points = args['hint_points']
|
||||
|
||||
elif cmd == 'Print':
|
||||
logger.info(args["text"])
|
||||
|
@ -971,9 +972,6 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
|
|||
elif cmd == 'PrintJSON':
|
||||
logger.info(ctx.jsontotextparser(args["data"]))
|
||||
|
||||
elif cmd == 'HintPointUpdate':
|
||||
ctx.hint_points = args['points']
|
||||
|
||||
elif cmd == 'InvalidArguments':
|
||||
logger.warning(f"Invalid Arguments: {args['text']}")
|
||||
|
||||
|
@ -1001,8 +999,8 @@ async def server_auth(ctx: Context, password_requested):
|
|||
ctx.auth = ctx.rom
|
||||
auth = base64.b64encode(ctx.rom).decode()
|
||||
await ctx.send_msgs([['Connect', {
|
||||
'password': ctx.password, 'rom': auth, 'version': Utils._version_tuple, 'tags': get_tags(ctx),
|
||||
'uuid': Utils.get_unique_identifier(), 'game': "ALTTP"
|
||||
'password': ctx.password, 'name': auth, 'version': Utils._version_tuple, 'tags': get_tags(ctx),
|
||||
'uuid': Utils.get_unique_identifier(), 'game': "A Link to the Past"
|
||||
}]])
|
||||
|
||||
|
||||
|
@ -1247,7 +1245,7 @@ async def track_locations(ctx: Context, roomid, roomdata):
|
|||
|
||||
async def send_finished_game(ctx: Context):
|
||||
try:
|
||||
await ctx.send_msgs([['StatusUpdate', {"status": CLIENT_GOAL}]])
|
||||
await ctx.send_msgs([['StatusUpdate', {"status": CLientStatus.CLIENT_GOAL}]])
|
||||
ctx.finished_game = True
|
||||
except Exception as ex:
|
||||
logger.exception(ex)
|
||||
|
|
|
@ -28,8 +28,8 @@ from fuzzywuzzy import process as fuzzy_process
|
|||
from worlds.alttp import Items, Regions
|
||||
import Utils
|
||||
from Utils import get_item_name_from_id, get_location_name_from_address, \
|
||||
ReceivedItem, _version_tuple, restricted_loads
|
||||
from NetUtils import Node, Endpoint, CLIENT_GOAL
|
||||
_version_tuple, restricted_loads
|
||||
from NetUtils import Node, Endpoint, CLientStatus, NetworkItem
|
||||
|
||||
colorama.init()
|
||||
console_names = frozenset(set(Items.item_table) | set(Items.item_name_groups) | set(Regions.lookup_name_to_id))
|
||||
|
@ -76,7 +76,7 @@ class Context(Node):
|
|||
self.save_filename = None
|
||||
self.saving = False
|
||||
self.player_names = {}
|
||||
self.rom_names = {}
|
||||
self.connect_names = {} # names of slots clients can connect to
|
||||
self.allow_forfeits = {}
|
||||
self.remote_items = set()
|
||||
self.locations = {}
|
||||
|
@ -140,7 +140,7 @@ class Context(Node):
|
|||
for player, name in enumerate(names, 1):
|
||||
self.player_names[(team, player)] = name
|
||||
|
||||
self.rom_names = decoded_obj['roms']
|
||||
self.connect_names = decoded_obj['connect_names']
|
||||
self.remote_items = decoded_obj['remote_items']
|
||||
self.locations = decoded_obj['locations']
|
||||
self.er_hint_data = {int(player): {int(address): name for address, name in loc_data.items()}
|
||||
|
@ -149,6 +149,9 @@ class Context(Node):
|
|||
server_options = decoded_obj.get("server_options", {})
|
||||
self._set_options(server_options)
|
||||
|
||||
def get_players_package(self):
|
||||
return [(t, p, self.get_aliased_name(t, p), n) for (t, p), n in self.player_names.items()]
|
||||
|
||||
def _set_options(self, server_options: dict):
|
||||
for key, value in server_options.items():
|
||||
data_type = self.simple_options.get(key, None)
|
||||
|
@ -225,7 +228,7 @@ class Context(Node):
|
|||
|
||||
def get_save(self) -> dict:
|
||||
d = {
|
||||
"rom_names": list(self.rom_names.items()),
|
||||
"rom_names": list(self.connect_names.items()),
|
||||
"received_items": tuple((k, v) for k, v in self.received_items.items()),
|
||||
"hints_used": tuple((key, value) for key, value in self.hints_used.items()),
|
||||
"hints": tuple(
|
||||
|
@ -246,15 +249,15 @@ class Context(Node):
|
|||
adjusted = {rom: (team, slot) for rom, (team, slot) in rom_names}
|
||||
except TypeError:
|
||||
adjusted = {tuple(rom): (team, slot) for (rom, (team, slot)) in rom_names} # old format, ponyorm friendly
|
||||
if self.rom_names != adjusted:
|
||||
if self.connect_names != adjusted:
|
||||
logging.warning('Save file mismatch, will start a new game')
|
||||
return
|
||||
else:
|
||||
if adjusted != self.rom_names:
|
||||
if adjusted != self.connect_names:
|
||||
logging.warning('Save file mismatch, will start a new game')
|
||||
return
|
||||
|
||||
received_items = {tuple(k): [ReceivedItem(*i) for i in v] for k, v in savedata["received_items"]}
|
||||
received_items = {tuple(k): [NetworkItem(*i) for i in v] for k, v in savedata["received_items"]}
|
||||
|
||||
self.received_items = received_items
|
||||
self.hints_used.update({tuple(key): value for key, value in savedata["hints_used"]})
|
||||
|
@ -330,7 +333,7 @@ class Context(Node):
|
|||
|
||||
# separated out, due to compatibilty between clients
|
||||
def notify_hints(ctx: Context, team: int, hints: typing.List[Utils.Hint]):
|
||||
cmd = dumps([["Hint", {"hints", hints}]])
|
||||
cmd = dumps([["Hint", {"hints" : hints}]])
|
||||
texts = [['PrintHTML', format_hint(ctx, team, hint)] for hint in hints]
|
||||
for _, text in texts:
|
||||
logging.info("Notice (Team #%d): %s" % (team + 1, text))
|
||||
|
@ -341,8 +344,7 @@ def notify_hints(ctx: Context, team: int, hints: typing.List[Utils.Hint]):
|
|||
|
||||
def update_aliases(ctx: Context, team: int, client: typing.Optional[Client] = None):
|
||||
cmd = dumps([["RoomUpdate",
|
||||
{"playernames": [(key[1], ctx.get_aliased_name(*key)) for key, value in ctx.player_names.items() if
|
||||
key[0] == team]}]])
|
||||
{"players": ctx.get_players_package()}]])
|
||||
if client is None:
|
||||
for client in ctx.endpoints:
|
||||
if client.team == team and client.auth:
|
||||
|
@ -367,6 +369,8 @@ async def server(websocket, path, ctx: Context):
|
|||
await ctx.disconnect(client)
|
||||
|
||||
|
||||
|
||||
|
||||
async def on_client_connected(ctx: Context, client: Client):
|
||||
await ctx.send_msgs(client, [['RoomInfo', {
|
||||
'password': ctx.password is not None,
|
||||
|
@ -419,7 +423,7 @@ async def countdown(ctx: Context, timer):
|
|||
async def missing(ctx: Context, client: Client, locations: list, checked_locations: list):
|
||||
await ctx.send_msgs(client, [['Missing', {
|
||||
'locations': dumps(locations),
|
||||
'checked_locations': json.dumps(checked_locations)
|
||||
'checked_locations': dumps(checked_locations)
|
||||
}]])
|
||||
|
||||
|
||||
|
@ -441,7 +445,7 @@ def get_players_string(ctx: Context):
|
|||
return f'{len(auth_clients)} players of {len(ctx.player_names)} connected ' + text[:-1]
|
||||
|
||||
|
||||
def get_received_items(ctx: Context, team: int, player: int) -> typing.List[ReceivedItem]:
|
||||
def get_received_items(ctx: Context, team: int, player: int) -> typing.List[NetworkItem]:
|
||||
return ctx.received_items.setdefault((team, player), [])
|
||||
|
||||
|
||||
|
@ -495,7 +499,7 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
|
|||
break
|
||||
|
||||
if not found:
|
||||
new_item = ReceivedItem(target_item, location, slot)
|
||||
new_item = NetworkItem(target_item, location, slot)
|
||||
recvd_items.append(new_item)
|
||||
if slot != target_player:
|
||||
ctx.broadcast_team(team,
|
||||
|
@ -511,14 +515,14 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
|
|||
if client.team == team and client.wants_item_notification:
|
||||
asyncio.create_task(
|
||||
ctx.send_msgs(client, [['ItemFound',
|
||||
{"item": ReceivedItem(target_item, location, slot)}]]))
|
||||
{"item": NetworkItem(target_item, location, slot)}]]))
|
||||
ctx.location_checks[team, slot] |= known_locations
|
||||
send_new_items(ctx)
|
||||
|
||||
if found_items:
|
||||
for client in ctx.endpoints:
|
||||
if client.team == team and client.slot == slot:
|
||||
asyncio.create_task(ctx.send_msgs(client, [["HintPointUpdate", {"points": get_client_points(ctx, client)}]]))
|
||||
asyncio.create_task(ctx.send_msgs(client, [["RoomUpdate", {"hint_points": get_client_points(ctx, client)}]]))
|
||||
ctx.save()
|
||||
|
||||
|
||||
|
@ -672,7 +676,8 @@ class CommandProcessor(metaclass=CommandMeta):
|
|||
self.output(f"Could not find command {raw}. Known commands: {', '.join(self.commands)}")
|
||||
|
||||
def _error_parsing_command(self, exception: Exception):
|
||||
self.output(str(exception))
|
||||
import traceback
|
||||
self.output(traceback.format_exc())
|
||||
|
||||
|
||||
class CommonCommandProcessor(CommandProcessor):
|
||||
|
@ -780,7 +785,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
"Sorry, client forfeiting has been disabled on this server. You can ask the server admin for a /forfeit")
|
||||
return False
|
||||
else: # is auto or goal
|
||||
if self.ctx.client_game_state[self.client.team, self.client.slot] == CLIENT_GOAL:
|
||||
if self.ctx.client_game_state[self.client.team, self.client.slot] == CLientStatus.CLIENT_GOAL:
|
||||
forfeit_player(self.ctx, self.client.team, self.client.slot)
|
||||
return True
|
||||
else:
|
||||
|
@ -807,7 +812,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
"Sorry, !remaining has been disabled on this server.")
|
||||
return False
|
||||
else: # is goal
|
||||
if self.ctx.client_game_state[self.client.team, self.client.slot] == CLIENT_GOAL:
|
||||
if self.ctx.client_game_state[self.client.team, self.client.slot] == CLientStatus.CLIENT_GOAL:
|
||||
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
|
||||
if remaining_item_ids:
|
||||
self.output("Remaining items: " + ", ".join(Items.lookup_id_to_name.get(item_id, "unknown item")
|
||||
|
@ -862,7 +867,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
if self.ctx.item_cheat:
|
||||
item_name, usable, response = get_intended_text(item_name, Items.item_table.keys())
|
||||
if usable:
|
||||
new_item = ReceivedItem(Items.item_table[item_name][2], -1, self.client.slot)
|
||||
new_item = NetworkItem(Items.item_table[item_name][2], -1, self.client.slot)
|
||||
get_received_items(self.ctx, self.client.team, self.client.slot).append(new_item)
|
||||
self.ctx.notify_all('Cheat console: sending "' + item_name + '" to ' + self.ctx.get_aliased_name(self.client.team, self.client.slot))
|
||||
send_new_items(self.ctx)
|
||||
|
@ -993,11 +998,11 @@ async def process_client_cmd(ctx: Context, client: Client, cmd: str, args: typin
|
|||
if ctx.password and args['password'] != ctx.password:
|
||||
errors.add('InvalidPassword')
|
||||
|
||||
if args['rom'] not in ctx.rom_names:
|
||||
logging.info((args["rom"], ctx.rom_names))
|
||||
errors.add('InvalidRom')
|
||||
if args['name'] not in ctx.connect_names:
|
||||
logging.info((args["name"], ctx.connect_names))
|
||||
errors.add('InvalidSlot')
|
||||
else:
|
||||
team, slot = ctx.rom_names[args['rom']]
|
||||
team, slot = ctx.connect_names[args['name']]
|
||||
# this can only ever be 0 or 1 elements
|
||||
clients = [c for c in ctx.endpoints if c.auth and c.slot == slot and c.team == team]
|
||||
if clients:
|
||||
|
@ -1031,14 +1036,14 @@ async def process_client_cmd(ctx: Context, client: Client, cmd: str, args: typin
|
|||
client.version = args['version']
|
||||
client.tags = args['tags']
|
||||
reply = [['Connected', {"team": client.team, "slot": client.slot,
|
||||
"playernames": [(p, ctx.get_aliased_name(t, p)) for (t, p), n in
|
||||
ctx.player_names.items() if t == client.team],
|
||||
"players": ctx.get_players_package(),
|
||||
"missing_checks": get_missing_checks(ctx, client),
|
||||
"items_checked": get_checked_checks(ctx, client)}]]
|
||||
items = get_received_items(ctx, client.team, client.slot)
|
||||
if items:
|
||||
reply.append(['ReceivedItems', {"index": 0, "items": tuplize_received_items(items)}])
|
||||
client.send_index = len(items)
|
||||
|
||||
await ctx.send_msgs(client, reply)
|
||||
await on_client_joined(ctx, client)
|
||||
|
||||
|
@ -1079,8 +1084,8 @@ async def process_client_cmd(ctx: Context, client: Client, cmd: str, args: typin
|
|||
|
||||
elif cmd == 'StatusUpdate':
|
||||
current = ctx.client_game_state[client.team, client.slot]
|
||||
if current != CLIENT_GOAL: # can't undo goal completion
|
||||
if args["status"] == CLIENT_GOAL:
|
||||
if current != CLientStatus.CLIENT_GOAL: # can't undo goal completion
|
||||
if args["status"] == CLientStatus.CLIENT_GOAL:
|
||||
finished_msg = f'{ctx.get_aliased_name(client.team, client.slot)} (Team #{client.team + 1}) has completed their goal.'
|
||||
ctx.notify_all(finished_msg)
|
||||
if "auto" in ctx.forfeit_mode:
|
||||
|
@ -1218,7 +1223,7 @@ class ServerCommandProcessor(CommonCommandProcessor):
|
|||
if usable:
|
||||
for client in self.ctx.endpoints:
|
||||
if client.name == seeked_player:
|
||||
new_item = ReceivedItem(Items.item_table[item][2], -1, client.slot)
|
||||
new_item = NetworkItem(Items.item_table[item][2], -1, client.slot)
|
||||
get_received_items(self.ctx, client.team, client.slot).append(new_item)
|
||||
self.ctx.notify_all('Cheat console: sending "' + item + '" to ' + self.ctx.get_aliased_name(client.team, client.slot))
|
||||
send_new_items(self.ctx)
|
||||
|
|
|
@ -14,8 +14,8 @@ ModuleUpdate.update()
|
|||
from Utils import parse_yaml
|
||||
from worlds.alttp.Rom import Sprite
|
||||
from worlds.alttp.EntranceRandomizer import parse_arguments
|
||||
from worlds.alttp.Main import main as ERmain
|
||||
from worlds.alttp.Main import get_seed, seeddigits
|
||||
from Main import main as ERmain
|
||||
from Main import get_seed, seeddigits
|
||||
from worlds.alttp.Items import item_name_groups, item_table
|
||||
from worlds.alttp import Bosses
|
||||
from worlds.alttp.Text import TextTable
|
||||
|
@ -360,6 +360,8 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
|||
if ret.name:
|
||||
ret.name = handle_name(ret.name)
|
||||
|
||||
ret.game = get_choice("game", weights, "A Link to the Past")
|
||||
|
||||
glitches_required = get_choice('glitches_required', weights)
|
||||
if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'minor_glitches']:
|
||||
logging.warning("Only NMG, OWG and No Logic supported")
|
||||
|
|
39
NetUtils.py
39
NetUtils.py
|
@ -2,10 +2,15 @@ from __future__ import annotations
|
|||
import asyncio
|
||||
import logging
|
||||
import typing
|
||||
import enum
|
||||
from json import loads, dumps
|
||||
|
||||
import websockets
|
||||
|
||||
class JSONMessagePart(typing.TypedDict):
|
||||
type: typing.Optional[str]
|
||||
color: typing.Optional[str]
|
||||
text: typing.Optional[str]
|
||||
|
||||
class Node:
|
||||
endpoints: typing.List
|
||||
|
@ -80,32 +85,32 @@ class JSONtoTextParser(metaclass=HandlerMeta):
|
|||
def __init__(self, ctx: "MultiClient.Context"):
|
||||
self.ctx = ctx
|
||||
|
||||
def __call__(self, input_object: typing.List[dict]) -> str:
|
||||
def __call__(self, input_object: typing.List[JSONMessagePart]) -> str:
|
||||
return "".join(self.handle_node(section) for section in input_object)
|
||||
|
||||
def handle_node(self, node: dict):
|
||||
def handle_node(self, node: JSONMessagePart):
|
||||
type = node.get("type", None)
|
||||
handler = self.handlers.get(type, self.handlers["text"])
|
||||
return handler(node)
|
||||
|
||||
def _handle_color(self, node: dict):
|
||||
def _handle_color(self, node: JSONMessagePart):
|
||||
if node["color"] in color_codes:
|
||||
return color_code(node["color"]) + self._handle_text(node) + color_code("reset")
|
||||
else:
|
||||
logging.warning(f"Unknown color in node {node}")
|
||||
return self._handle_text(node)
|
||||
|
||||
def _handle_text(self, node: dict):
|
||||
def _handle_text(self, node: JSONMessagePart):
|
||||
return node.get("text", "")
|
||||
|
||||
def _handle_player_id(self, node: dict):
|
||||
def _handle_player_id(self, node: JSONMessagePart):
|
||||
player = node["player"]
|
||||
node["color"] = 'yellow' if player != self.ctx.slot else 'magenta'
|
||||
node["text"] = self.ctx.player_names[player]
|
||||
return self._handle_color(node)
|
||||
|
||||
# for other teams, spectators etc.? Only useful if player isn't in the clientside mapping
|
||||
def _handle_player_name(self, node: dict):
|
||||
def _handle_player_name(self, node: JSONMessagePart):
|
||||
node["color"] = 'yellow'
|
||||
return self._handle_color(node)
|
||||
|
||||
|
@ -124,7 +129,21 @@ def color(text, *args):
|
|||
return color_code(*args) + text + color_code('reset')
|
||||
|
||||
|
||||
CLIENT_UNKNOWN = 0
|
||||
CLIENT_READY = 10
|
||||
CLIENT_PLAYING = 20
|
||||
CLIENT_GOAL = 30
|
||||
class CLientStatus(enum.IntEnum):
|
||||
CLIENT_UNKNOWN = 0
|
||||
# CLIENT_CONNECTED = 5 maybe?
|
||||
CLIENT_READY = 10
|
||||
CLIENT_PLAYING = 20
|
||||
CLIENT_GOAL = 30
|
||||
|
||||
class NetworkPlayer(typing.NamedTuple):
|
||||
team: int
|
||||
slot: int
|
||||
alias: str
|
||||
name: str
|
||||
|
||||
|
||||
class NetworkItem(typing.NamedTuple):
|
||||
item: int
|
||||
location: int
|
||||
player: int
|
19
Utils.py
19
Utils.py
|
@ -279,13 +279,13 @@ def get_options() -> dict:
|
|||
|
||||
|
||||
def get_item_name_from_id(code):
|
||||
from worlds.alttp import Items
|
||||
return Items.lookup_id_to_name.get(code, f'Unknown item (ID:{code})')
|
||||
from worlds import lookup_any_item_id_to_name
|
||||
return lookup_any_item_id_to_name.get(code, f'Unknown item (ID:{code})')
|
||||
|
||||
|
||||
def get_location_name_from_address(address):
|
||||
from worlds.alttp import Regions
|
||||
return Regions.lookup_id_to_name.get(address, f'Unknown location (ID:{address})')
|
||||
from worlds import lookup_any_location_id_to_name
|
||||
return lookup_any_location_id_to_name.get(address, f'Unknown location (ID:{address})')
|
||||
|
||||
|
||||
def persistent_store(category, key, value):
|
||||
|
@ -357,12 +357,6 @@ def get_adjuster_settings(romfile: str) -> typing.Tuple[str, bool]:
|
|||
return romfile, False
|
||||
|
||||
|
||||
class ReceivedItem(typing.NamedTuple):
|
||||
item: int
|
||||
location: int
|
||||
player: int
|
||||
|
||||
|
||||
def get_unique_identifier():
|
||||
uuid = persistent_load().get("client", {}).get("uuid", None)
|
||||
if uuid:
|
||||
|
@ -384,8 +378,9 @@ class RestrictedUnpickler(pickle.Unpickler):
|
|||
def find_class(self, module, name):
|
||||
if module == "builtins" and name in safe_builtins:
|
||||
return getattr(builtins, name)
|
||||
if module == "Utils" and name in {"ReceivedItem"}:
|
||||
return globals()[name]
|
||||
if module == "NetUtils" and name in {"NetworkItem"}:
|
||||
import NetUtils
|
||||
return getattr(NetUtils, name)
|
||||
# Forbid everything else.
|
||||
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
|
||||
(module, name))
|
||||
|
|
|
@ -5,8 +5,8 @@ import random
|
|||
from flask import request, flash, redirect, url_for, session, render_template
|
||||
|
||||
from worlds.alttp.EntranceRandomizer import parse_arguments
|
||||
from worlds.alttp.Main import main as ERmain
|
||||
from worlds.alttp.Main import get_seed, seeddigits
|
||||
from Main import main as ERmain
|
||||
from Main import get_seed, seeddigits
|
||||
import pickle
|
||||
|
||||
from .models import *
|
||||
|
|
|
@ -3,5 +3,5 @@ pony>=0.7.14
|
|||
waitress>=1.4.4
|
||||
flask-caching>=1.9.0
|
||||
Flask-Autoversion>=0.2.0
|
||||
Flask-Compress>=1.8.0
|
||||
Flask-Compress>=1.9.0
|
||||
Flask-Limiter>=1.4
|
||||
|
|
|
@ -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,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
|
|
@ -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()}
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
Loading…
Reference in New Issue