Multiple: Followed a rabbit hole of moving LttP Rom generation to AutoWorld
Generator: Re-allow names with spaces (and see what breaks) Generator: Removed teams (Note that teams are intended to move from a generation step feature to a server runtime feature, allowing dynamic creation of an already generated MW) LttP: All Rom Options are now on the new system LttP: palette option "random" is now called "good" LttP: Roms are now created as part of the general output file creation step LttP: disable Music is now Music, removing potential double negatives LttP & Factorio: Progressive option random is now grouped_random LttP: Enemy damage option random is now Enemy damage: chaos
This commit is contained in:
parent
01d88c362a
commit
ba3bb201cd
|
@ -13,7 +13,7 @@ import random
|
||||||
|
|
||||||
class MultiWorld():
|
class MultiWorld():
|
||||||
debug_types = False
|
debug_types = False
|
||||||
player_names: Dict[int, List[str]]
|
player_name: Dict[int, str]
|
||||||
_region_cache: Dict[int, Dict[str, Region]]
|
_region_cache: Dict[int, Dict[str, Region]]
|
||||||
difficulty_requirements: dict
|
difficulty_requirements: dict
|
||||||
required_medallions: dict
|
required_medallions: dict
|
||||||
|
@ -36,7 +36,6 @@ class MultiWorld():
|
||||||
def __init__(self, players: int):
|
def __init__(self, players: int):
|
||||||
self.random = random.Random() # world-local random state is saved for multiple generations running concurrently
|
self.random = random.Random() # world-local random state is saved for multiple generations running concurrently
|
||||||
self.players = players
|
self.players = players
|
||||||
self.teams = 1
|
|
||||||
self.glitch_triforce = False
|
self.glitch_triforce = False
|
||||||
self.algorithm = 'balanced'
|
self.algorithm = 'balanced'
|
||||||
self.dungeons = []
|
self.dungeons = []
|
||||||
|
@ -83,11 +82,9 @@ class MultiWorld():
|
||||||
set_player_attr('item_functionality', 'normal')
|
set_player_attr('item_functionality', 'normal')
|
||||||
set_player_attr('timer', False)
|
set_player_attr('timer', False)
|
||||||
set_player_attr('goal', 'ganon')
|
set_player_attr('goal', 'ganon')
|
||||||
set_player_attr('progressive', 'on')
|
|
||||||
set_player_attr('accessibility', 'items')
|
set_player_attr('accessibility', 'items')
|
||||||
set_player_attr('retro', False)
|
set_player_attr('retro', False)
|
||||||
set_player_attr('hints', True)
|
set_player_attr('hints', True)
|
||||||
set_player_attr('player_names', [])
|
|
||||||
set_player_attr('required_medallions', ['Ether', 'Quake'])
|
set_player_attr('required_medallions', ['Ether', 'Quake'])
|
||||||
set_player_attr('swamp_patch_required', False)
|
set_player_attr('swamp_patch_required', False)
|
||||||
set_player_attr('powder_patch_required', False)
|
set_player_attr('powder_patch_required', False)
|
||||||
|
@ -162,10 +159,10 @@ class MultiWorld():
|
||||||
return tuple(player for player in self.player_ids if self.game[player] == game_name)
|
return tuple(player for player in self.player_ids if self.game[player] == game_name)
|
||||||
|
|
||||||
def get_name_string_for_object(self, obj) -> str:
|
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)})'
|
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_name(obj.player)})'
|
||||||
|
|
||||||
def get_player_names(self, player: int) -> str:
|
def get_player_name(self, player: int) -> str:
|
||||||
return ", ".join(self.player_names[player])
|
return self.player_name[player]
|
||||||
|
|
||||||
def initialize_regions(self, regions=None):
|
def initialize_regions(self, regions=None):
|
||||||
for region in regions if regions else self.regions:
|
for region in regions if regions else self.regions:
|
||||||
|
@ -174,7 +171,7 @@ class MultiWorld():
|
||||||
|
|
||||||
@functools.cached_property
|
@functools.cached_property
|
||||||
def world_name_lookup(self):
|
def world_name_lookup(self):
|
||||||
return {self.player_names[player_id][0]: player_id for player_id in self.player_ids}
|
return {self.player_name[player_id]: player_id for player_id in self.player_ids}
|
||||||
|
|
||||||
def _recache(self):
|
def _recache(self):
|
||||||
"""Rebuild world cache"""
|
"""Rebuild world cache"""
|
||||||
|
@ -1132,8 +1129,8 @@ class Spoiler():
|
||||||
def parse_data(self):
|
def parse_data(self):
|
||||||
self.medallions = OrderedDict()
|
self.medallions = OrderedDict()
|
||||||
for player in self.world.get_game_players("A Link to the Past"):
|
for player in self.world.get_game_players("A Link to the Past"):
|
||||||
self.medallions[f'Misery Mire ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][0]
|
self.medallions[f'Misery Mire ({self.world.get_player_name(player)})'] = self.world.required_medallions[player][0]
|
||||||
self.medallions[f'Turtle Rock ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][1]
|
self.medallions[f'Turtle Rock ({self.world.get_player_name(player)})'] = self.world.required_medallions[player][1]
|
||||||
|
|
||||||
self.startinventory = list(map(str, self.world.precollected_items))
|
self.startinventory = list(map(str, self.world.precollected_items))
|
||||||
|
|
||||||
|
@ -1241,7 +1238,6 @@ class Spoiler():
|
||||||
'progressive': self.world.progressive,
|
'progressive': self.world.progressive,
|
||||||
'shufflepots': self.world.shufflepots,
|
'shufflepots': self.world.shufflepots,
|
||||||
'players': self.world.players,
|
'players': self.world.players,
|
||||||
'teams': self.world.teams,
|
|
||||||
'progression_balancing': self.world.progression_balancing,
|
'progression_balancing': self.world.progression_balancing,
|
||||||
'triforce_pieces_available': self.world.triforce_pieces_available,
|
'triforce_pieces_available': self.world.triforce_pieces_available,
|
||||||
'triforce_pieces_required': self.world.triforce_pieces_required,
|
'triforce_pieces_required': self.world.triforce_pieces_required,
|
||||||
|
@ -1261,7 +1257,7 @@ class Spoiler():
|
||||||
out['Starting Inventory'] = self.startinventory
|
out['Starting Inventory'] = self.startinventory
|
||||||
out['Special'] = self.medallions
|
out['Special'] = self.medallions
|
||||||
if self.hashes:
|
if self.hashes:
|
||||||
out['Hashes'] = {f"{self.world.player_names[player][team]} (Team {team+1})": hash for (player, team), hash in self.hashes.items()}
|
out['Hashes'] = self.hashes
|
||||||
if self.shops:
|
if self.shops:
|
||||||
out['Shops'] = self.shops
|
out['Shops'] = self.shops
|
||||||
out['playthrough'] = self.playthrough
|
out['playthrough'] = self.playthrough
|
||||||
|
@ -1286,10 +1282,10 @@ class Spoiler():
|
||||||
self.metadata['version'], self.world.seed))
|
self.metadata['version'], self.world.seed))
|
||||||
outfile.write('Filling Algorithm: %s\n' % self.world.algorithm)
|
outfile.write('Filling Algorithm: %s\n' % self.world.algorithm)
|
||||||
outfile.write('Players: %d\n' % self.world.players)
|
outfile.write('Players: %d\n' % self.world.players)
|
||||||
outfile.write('Teams: %d\n' % self.world.teams)
|
|
||||||
for player in range(1, self.world.players + 1):
|
for player in range(1, self.world.players + 1):
|
||||||
if self.world.players > 1:
|
if self.world.players > 1:
|
||||||
outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player)))
|
outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_name(player)))
|
||||||
outfile.write('Game: %s\n' % self.metadata['game'][player])
|
outfile.write('Game: %s\n' % self.metadata['game'][player])
|
||||||
if self.world.players > 1:
|
if self.world.players > 1:
|
||||||
outfile.write('Progression Balanced: %s\n' % (
|
outfile.write('Progression Balanced: %s\n' % (
|
||||||
|
@ -1303,11 +1299,7 @@ class Spoiler():
|
||||||
outfile.write(f'{displayname + ":":33}{res.get_current_option_name()}\n')
|
outfile.write(f'{displayname + ":":33}{res.get_current_option_name()}\n')
|
||||||
|
|
||||||
if player in self.world.get_game_players("A Link to the Past"):
|
if player in self.world.get_game_players("A Link to the Past"):
|
||||||
for team in range(self.world.teams):
|
outfile.write('%s%s\n' % ('Hash: ', self.hashes[player]))
|
||||||
outfile.write('%s%s\n' % (
|
|
||||||
f"Hash - {self.world.player_names[player][team]} (Team {team + 1}): " if
|
|
||||||
(player in self.world.get_game_players("A Link to the Past") and self.world.teams > 1) else 'Hash: ',
|
|
||||||
self.hashes[player, team]))
|
|
||||||
|
|
||||||
outfile.write('Logic: %s\n' % self.metadata['logic'][player])
|
outfile.write('Logic: %s\n' % self.metadata['logic'][player])
|
||||||
outfile.write('Dark Room Logic: %s\n' % self.metadata['dark_room_logic'][player])
|
outfile.write('Dark Room Logic: %s\n' % self.metadata['dark_room_logic'][player])
|
||||||
|
@ -1326,7 +1318,6 @@ class Spoiler():
|
||||||
self.metadata["triforce_pieces_required"][player])
|
self.metadata["triforce_pieces_required"][player])
|
||||||
outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][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 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])
|
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player])
|
||||||
if self.metadata['shuffle'][player] != "vanilla":
|
if self.metadata['shuffle'][player] != "vanilla":
|
||||||
outfile.write('Entrance Shuffle Seed %s\n' % self.metadata['er_seeds'][player])
|
outfile.write('Entrance Shuffle Seed %s\n' % self.metadata['er_seeds'][player])
|
||||||
|
@ -1369,7 +1360,7 @@ class Spoiler():
|
||||||
self.metadata['shuffle_prizes'][player])
|
self.metadata['shuffle_prizes'][player])
|
||||||
if self.entrances:
|
if self.entrances:
|
||||||
outfile.write('\n\nEntrances:\n\n')
|
outfile.write('\n\nEntrances:\n\n')
|
||||||
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: '
|
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_name(entry["player"])}: '
|
||||||
if self.world.players > 1 else '', entry['entrance'],
|
if self.world.players > 1 else '', entry['entrance'],
|
||||||
'<=>' if entry['direction'] == 'both' else
|
'<=>' if entry['direction'] == 'both' else
|
||||||
'<=' if entry['direction'] == 'exit' else '=>',
|
'<=' if entry['direction'] == 'exit' else '=>',
|
||||||
|
@ -1383,7 +1374,7 @@ class Spoiler():
|
||||||
if factorio_players:
|
if factorio_players:
|
||||||
outfile.write('\n\nRecipes:\n')
|
outfile.write('\n\nRecipes:\n')
|
||||||
for player in factorio_players:
|
for player in factorio_players:
|
||||||
name = self.world.get_player_names(player)
|
name = self.world.get_player_name(player)
|
||||||
for recipe in self.world.worlds[player].custom_recipes.values():
|
for recipe in self.world.worlds[player].custom_recipes.values():
|
||||||
outfile.write(f"\n{recipe.name} ({name}): {recipe.ingredients} -> {recipe.products}")
|
outfile.write(f"\n{recipe.name} ({name}): {recipe.ingredients} -> {recipe.products}")
|
||||||
|
|
||||||
|
@ -1401,7 +1392,7 @@ class Spoiler():
|
||||||
for player in self.world.get_game_players("A Link to the Past"):
|
for player in self.world.get_game_players("A Link to the Past"):
|
||||||
if self.world.boss_shuffle[player] != 'none':
|
if self.world.boss_shuffle[player] != 'none':
|
||||||
bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses
|
bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses
|
||||||
outfile.write(f'\n\nBosses{(f" ({self.world.get_player_names(player)})" if self.world.players > 1 else "")}:\n')
|
outfile.write(f'\n\nBosses{(f" ({self.world.get_player_name(player)})" if self.world.players > 1 else "")}:\n')
|
||||||
outfile.write(' '+'\n '.join([f'{x}: {y}' for x, y in bossmap.items()]))
|
outfile.write(' '+'\n '.join([f'{x}: {y}' for x, y in bossmap.items()]))
|
||||||
outfile.write('\n\nPlaythrough:\n\n')
|
outfile.write('\n\nPlaythrough:\n\n')
|
||||||
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()] if sphere_nr != '0' else [f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()] if sphere_nr != '0' else [f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
||||||
|
|
2
Fill.py
2
Fill.py
|
@ -444,4 +444,4 @@ def distribute_planned(world: MultiWorld):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
placement.warn(f"Could not remove {item} from pool as it's already missing from it.")
|
placement.warn(f"Could not remove {item} from pool as it's already missing from it.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Error running plando for player {player} ({world.player_names[player]})") from e
|
raise Exception(f"Error running plando for player {player} ({world.player_name[player]})") from e
|
||||||
|
|
27
Generate.py
27
Generate.py
|
@ -40,7 +40,6 @@ def mystery_argparse():
|
||||||
help="Input directory for player files.")
|
help="Input directory for player files.")
|
||||||
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
||||||
parser.add_argument('--multi', default=defaults["players"], type=lambda value: min(max(int(value), 1), 255))
|
parser.add_argument('--multi', default=defaults["players"], type=lambda value: min(max(int(value), 1), 255))
|
||||||
parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1))
|
|
||||||
parser.add_argument('--spoiler', type=int, default=defaults["spoiler"])
|
parser.add_argument('--spoiler', type=int, default=defaults["spoiler"])
|
||||||
parser.add_argument('--rom', default=options["lttp_options"]["rom_file"], help="Path to the 1.0 JP LttP Baserom.")
|
parser.add_argument('--rom', default=options["lttp_options"]["rom_file"], help="Path to the 1.0 JP LttP Baserom.")
|
||||||
parser.add_argument('--enemizercli', default=defaults["enemizer_path"])
|
parser.add_argument('--enemizercli', default=defaults["enemizer_path"])
|
||||||
|
@ -128,7 +127,6 @@ def main(args=None, callback=ERmain):
|
||||||
erargs.skip_playthrough = args.spoiler < 2
|
erargs.skip_playthrough = args.spoiler < 2
|
||||||
erargs.outputname = seed_name
|
erargs.outputname = seed_name
|
||||||
erargs.outputpath = args.outputpath
|
erargs.outputpath = args.outputpath
|
||||||
erargs.teams = args.teams
|
|
||||||
|
|
||||||
# set up logger
|
# set up logger
|
||||||
if args.log_level:
|
if args.log_level:
|
||||||
|
@ -179,6 +177,8 @@ def main(args=None, callback=ERmain):
|
||||||
getattr(erargs, k)[player] = v
|
getattr(erargs, k)[player] = v
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
setattr(erargs, k, {player: v})
|
setattr(erargs, k, {player: v})
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Error setting {k} to {v} for player {player}") from e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"File {path} is destroyed. Please fix your yaml.") from e
|
raise ValueError(f"File {path} is destroyed. Please fix your yaml.") from e
|
||||||
else:
|
else:
|
||||||
|
@ -189,8 +189,6 @@ def main(args=None, callback=ERmain):
|
||||||
erargs.name[player] = os.path.splitext(os.path.split(path)[-1])[0]
|
erargs.name[player] = os.path.splitext(os.path.split(path)[-1])[0]
|
||||||
erargs.name[player] = handle_name(erargs.name[player], player, name_counter)
|
erargs.name[player] = handle_name(erargs.name[player], player, name_counter)
|
||||||
|
|
||||||
erargs.names = ",".join(erargs.name[i] for i in range(1, args.multi + 1))
|
|
||||||
del (erargs.name)
|
|
||||||
if args.yaml_output:
|
if args.yaml_output:
|
||||||
import yaml
|
import yaml
|
||||||
important = {}
|
important = {}
|
||||||
|
@ -267,7 +265,7 @@ def handle_name(name: str, player: int, name_counter: Counter):
|
||||||
name] > 1 else ''),
|
name] > 1 else ''),
|
||||||
player=player,
|
player=player,
|
||||||
PLAYER=(player if player > 1 else '')))
|
PLAYER=(player if player > 1 else '')))
|
||||||
new_name = new_name.strip().replace(' ', '_')[:16]
|
new_name = new_name.strip()[:16]
|
||||||
if new_name == "Archipelago":
|
if new_name == "Archipelago":
|
||||||
raise Exception(f"You cannot name yourself \"{new_name}\"")
|
raise Exception(f"You cannot name yourself \"{new_name}\"")
|
||||||
return new_name
|
return new_name
|
||||||
|
@ -610,7 +608,8 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
ret.enemy_damage = {None: 'default',
|
ret.enemy_damage = {None: 'default',
|
||||||
'default': 'default',
|
'default': 'default',
|
||||||
'shuffled': 'shuffled',
|
'shuffled': 'shuffled',
|
||||||
'random': 'chaos'
|
'random': 'chaos', # to be removed
|
||||||
|
'chaos': 'chaos',
|
||||||
}[get_choice('enemy_damage', weights)]
|
}[get_choice('enemy_damage', weights)]
|
||||||
|
|
||||||
ret.enemy_health = get_choice('enemy_health', weights)
|
ret.enemy_health = get_choice('enemy_health', weights)
|
||||||
|
@ -635,8 +634,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
|
|
||||||
ret.dungeon_counters = get_choice('dungeon_counters', weights, 'default')
|
ret.dungeon_counters = get_choice('dungeon_counters', weights, 'default')
|
||||||
|
|
||||||
ret.progressive = convert_to_on_off(get_choice('progressive', weights, 'on'))
|
|
||||||
|
|
||||||
ret.shuffle_prizes = get_choice('shuffle_prizes', weights, "g")
|
ret.shuffle_prizes = get_choice('shuffle_prizes', weights, "g")
|
||||||
|
|
||||||
ret.required_medallions = [get_choice("misery_mire_medallion", weights, "random"),
|
ret.required_medallions = [get_choice("misery_mire_medallion", weights, "random"),
|
||||||
|
@ -737,20 +734,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||||
else:
|
else:
|
||||||
ret.sprite_pool += [key] * int(value)
|
ret.sprite_pool += [key] * int(value)
|
||||||
|
|
||||||
ret.disablemusic = get_choice('disablemusic', weights, False)
|
|
||||||
ret.triforcehud = get_choice('triforcehud', weights, 'hide_goal')
|
|
||||||
ret.quickswap = get_choice('quickswap', weights, True)
|
|
||||||
ret.fastmenu = get_choice('menuspeed', weights, "normal")
|
|
||||||
ret.reduceflashing = get_choice('reduceflashing', weights, False)
|
|
||||||
ret.heartcolor = get_choice('heartcolor', weights, "red")
|
|
||||||
ret.heartbeep = convert_to_on_off(get_choice('heartbeep', weights, "normal"))
|
|
||||||
ret.ow_palettes = get_choice('ow_palettes', weights, "default")
|
|
||||||
ret.uw_palettes = get_choice('uw_palettes', weights, "default")
|
|
||||||
ret.hud_palettes = get_choice('hud_palettes', weights, "default")
|
|
||||||
ret.sword_palettes = get_choice('sword_palettes', weights, "default")
|
|
||||||
ret.shield_palettes = get_choice('shield_palettes', weights, "default")
|
|
||||||
ret.link_palettes = get_choice('link_palettes', weights, "default")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import atexit
|
import atexit
|
||||||
|
|
|
@ -44,7 +44,7 @@ def main():
|
||||||
help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
||||||
parser.add_argument('--loglevel', default='info', const='info', nargs='?',
|
parser.add_argument('--loglevel', default='info', const='info', nargs='?',
|
||||||
choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
|
choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
|
||||||
parser.add_argument('--fastmenu', default='normal', const='normal', nargs='?',
|
parser.add_argument('--menuspeed', default='normal', const='normal', nargs='?',
|
||||||
choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
|
choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
|
||||||
help='''\
|
help='''\
|
||||||
Select the rate at which the menu opens and closes.
|
Select the rate at which the menu opens and closes.
|
||||||
|
@ -100,6 +100,7 @@ def main():
|
||||||
parser.add_argument('--names', default='', type=str)
|
parser.add_argument('--names', default='', type=str)
|
||||||
parser.add_argument('--update_sprites', action='store_true', help='Update Sprite Database, then exit.')
|
parser.add_argument('--update_sprites', action='store_true', help='Update Sprite Database, then exit.')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
args.music = not args.disablemusic
|
||||||
if args.update_sprites:
|
if args.update_sprites:
|
||||||
run_sprite_update()
|
run_sprite_update()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
@ -150,7 +151,7 @@ def adjust(args):
|
||||||
if hasattr(args, "world"):
|
if hasattr(args, "world"):
|
||||||
world = getattr(args, "world")
|
world = getattr(args, "world")
|
||||||
|
|
||||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic,
|
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.menuspeed, args.music,
|
||||||
args.sprite, palettes_options, reduceflashing=args.reduceflashing or racerom, world=world)
|
args.sprite, palettes_options, reduceflashing=args.reduceflashing or racerom, world=world)
|
||||||
path = output_path(f'{os.path.basename(args.rom)[:-4]}_adjusted.sfc')
|
path = output_path(f'{os.path.basename(args.rom)[:-4]}_adjusted.sfc')
|
||||||
rom.write_to_file(path)
|
rom.write_to_file(path)
|
||||||
|
@ -195,14 +196,14 @@ def adjustGUI():
|
||||||
guiargs = Namespace()
|
guiargs = Namespace()
|
||||||
guiargs.heartbeep = rom_vars.heartbeepVar.get()
|
guiargs.heartbeep = rom_vars.heartbeepVar.get()
|
||||||
guiargs.heartcolor = rom_vars.heartcolorVar.get()
|
guiargs.heartcolor = rom_vars.heartcolorVar.get()
|
||||||
guiargs.fastmenu = rom_vars.fastMenuVar.get()
|
guiargs.menuspeed = rom_vars.menuspeedVar.get()
|
||||||
guiargs.ow_palettes = rom_vars.owPalettesVar.get()
|
guiargs.ow_palettes = rom_vars.owPalettesVar.get()
|
||||||
guiargs.uw_palettes = rom_vars.uwPalettesVar.get()
|
guiargs.uw_palettes = rom_vars.uwPalettesVar.get()
|
||||||
guiargs.hud_palettes = rom_vars.hudPalettesVar.get()
|
guiargs.hud_palettes = rom_vars.hudPalettesVar.get()
|
||||||
guiargs.sword_palettes = rom_vars.swordPalettesVar.get()
|
guiargs.sword_palettes = rom_vars.swordPalettesVar.get()
|
||||||
guiargs.shield_palettes = rom_vars.shieldPalettesVar.get()
|
guiargs.shield_palettes = rom_vars.shieldPalettesVar.get()
|
||||||
guiargs.quickswap = bool(rom_vars.quickSwapVar.get())
|
guiargs.quickswap = bool(rom_vars.quickSwapVar.get())
|
||||||
guiargs.disablemusic = bool(rom_vars.disableMusicVar.get())
|
guiargs.music = bool(rom_vars.MusicVar.get())
|
||||||
guiargs.reduceflashing = bool(rom_vars.disableFlashingVar.get())
|
guiargs.reduceflashing = bool(rom_vars.disableFlashingVar.get())
|
||||||
guiargs.rom = romVar2.get()
|
guiargs.rom = romVar2.get()
|
||||||
guiargs.baserom = romVar.get()
|
guiargs.baserom = romVar.get()
|
||||||
|
@ -439,9 +440,10 @@ def get_rom_options_frame(parent=None):
|
||||||
romOptionsFrame.rowconfigure(i, weight=1)
|
romOptionsFrame.rowconfigure(i, weight=1)
|
||||||
vars = Namespace()
|
vars = Namespace()
|
||||||
|
|
||||||
vars.disableMusicVar = IntVar()
|
vars.MusicVar = IntVar()
|
||||||
disableMusicCheckbutton = Checkbutton(romOptionsFrame, text="Disable music", variable=vars.disableMusicVar)
|
vars.MusicVar.set(1)
|
||||||
disableMusicCheckbutton.grid(row=0, column=0, sticky=E)
|
MusicCheckbutton = Checkbutton(romOptionsFrame, text="Music", variable=vars.MusicVar)
|
||||||
|
MusicCheckbutton.grid(row=0, column=0, sticky=E)
|
||||||
|
|
||||||
vars.disableFlashingVar = IntVar(value=1)
|
vars.disableFlashingVar = IntVar(value=1)
|
||||||
disableFlashingCheckbutton = Checkbutton(romOptionsFrame, text="Disable flashing (anti-epilepsy)", variable=vars.disableFlashingVar)
|
disableFlashingCheckbutton = Checkbutton(romOptionsFrame, text="Disable flashing (anti-epilepsy)", variable=vars.disableFlashingVar)
|
||||||
|
@ -485,14 +487,14 @@ def get_rom_options_frame(parent=None):
|
||||||
quickSwapCheckbutton = Checkbutton(romOptionsFrame, text="L/R Quickswapping", variable=vars.quickSwapVar)
|
quickSwapCheckbutton = Checkbutton(romOptionsFrame, text="L/R Quickswapping", variable=vars.quickSwapVar)
|
||||||
quickSwapCheckbutton.grid(row=1, column=0, sticky=E)
|
quickSwapCheckbutton.grid(row=1, column=0, sticky=E)
|
||||||
|
|
||||||
fastMenuFrame = Frame(romOptionsFrame)
|
menuspeedFrame = Frame(romOptionsFrame)
|
||||||
fastMenuFrame.grid(row=1, column=1, sticky=E)
|
menuspeedFrame.grid(row=1, column=1, sticky=E)
|
||||||
fastMenuLabel = Label(fastMenuFrame, text='Menu speed')
|
menuspeedLabel = Label(menuspeedFrame, text='Menu speed')
|
||||||
fastMenuLabel.pack(side=LEFT)
|
menuspeedLabel.pack(side=LEFT)
|
||||||
vars.fastMenuVar = StringVar()
|
vars.menuspeedVar = StringVar()
|
||||||
vars.fastMenuVar.set('normal')
|
vars.menuspeedVar.set('normal')
|
||||||
fastMenuOptionMenu = OptionMenu(fastMenuFrame, vars.fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
|
menuspeedOptionMenu = OptionMenu(menuspeedFrame, vars.menuspeedVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
|
||||||
fastMenuOptionMenu.pack(side=LEFT)
|
menuspeedOptionMenu.pack(side=LEFT)
|
||||||
|
|
||||||
heartcolorFrame = Frame(romOptionsFrame)
|
heartcolorFrame = Frame(romOptionsFrame)
|
||||||
heartcolorFrame.grid(row=2, column=0, sticky=E)
|
heartcolorFrame.grid(row=2, column=0, sticky=E)
|
||||||
|
@ -518,7 +520,7 @@ def get_rom_options_frame(parent=None):
|
||||||
owPalettesLabel.pack(side=LEFT)
|
owPalettesLabel.pack(side=LEFT)
|
||||||
vars.owPalettesVar = StringVar()
|
vars.owPalettesVar = StringVar()
|
||||||
vars.owPalettesVar.set('default')
|
vars.owPalettesVar.set('default')
|
||||||
owPalettesOptionMenu = OptionMenu(owPalettesFrame, vars.owPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
owPalettesOptionMenu = OptionMenu(owPalettesFrame, vars.owPalettesVar, 'default', 'good', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
||||||
owPalettesOptionMenu.pack(side=LEFT)
|
owPalettesOptionMenu.pack(side=LEFT)
|
||||||
|
|
||||||
uwPalettesFrame = Frame(romOptionsFrame)
|
uwPalettesFrame = Frame(romOptionsFrame)
|
||||||
|
@ -527,7 +529,7 @@ def get_rom_options_frame(parent=None):
|
||||||
uwPalettesLabel.pack(side=LEFT)
|
uwPalettesLabel.pack(side=LEFT)
|
||||||
vars.uwPalettesVar = StringVar()
|
vars.uwPalettesVar = StringVar()
|
||||||
vars.uwPalettesVar.set('default')
|
vars.uwPalettesVar.set('default')
|
||||||
uwPalettesOptionMenu = OptionMenu(uwPalettesFrame, vars.uwPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
uwPalettesOptionMenu = OptionMenu(uwPalettesFrame, vars.uwPalettesVar, 'default', 'good', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
||||||
uwPalettesOptionMenu.pack(side=LEFT)
|
uwPalettesOptionMenu.pack(side=LEFT)
|
||||||
|
|
||||||
hudPalettesFrame = Frame(romOptionsFrame)
|
hudPalettesFrame = Frame(romOptionsFrame)
|
||||||
|
@ -536,7 +538,7 @@ def get_rom_options_frame(parent=None):
|
||||||
hudPalettesLabel.pack(side=LEFT)
|
hudPalettesLabel.pack(side=LEFT)
|
||||||
vars.hudPalettesVar = StringVar()
|
vars.hudPalettesVar = StringVar()
|
||||||
vars.hudPalettesVar.set('default')
|
vars.hudPalettesVar.set('default')
|
||||||
hudPalettesOptionMenu = OptionMenu(hudPalettesFrame, vars.hudPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
hudPalettesOptionMenu = OptionMenu(hudPalettesFrame, vars.hudPalettesVar, 'default', 'good', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
||||||
hudPalettesOptionMenu.pack(side=LEFT)
|
hudPalettesOptionMenu.pack(side=LEFT)
|
||||||
|
|
||||||
swordPalettesFrame = Frame(romOptionsFrame)
|
swordPalettesFrame = Frame(romOptionsFrame)
|
||||||
|
@ -545,7 +547,7 @@ def get_rom_options_frame(parent=None):
|
||||||
swordPalettesLabel.pack(side=LEFT)
|
swordPalettesLabel.pack(side=LEFT)
|
||||||
vars.swordPalettesVar = StringVar()
|
vars.swordPalettesVar = StringVar()
|
||||||
vars.swordPalettesVar.set('default')
|
vars.swordPalettesVar.set('default')
|
||||||
swordPalettesOptionMenu = OptionMenu(swordPalettesFrame, vars.swordPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
swordPalettesOptionMenu = OptionMenu(swordPalettesFrame, vars.swordPalettesVar, 'default', 'good', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
||||||
swordPalettesOptionMenu.pack(side=LEFT)
|
swordPalettesOptionMenu.pack(side=LEFT)
|
||||||
|
|
||||||
shieldPalettesFrame = Frame(romOptionsFrame)
|
shieldPalettesFrame = Frame(romOptionsFrame)
|
||||||
|
@ -554,7 +556,7 @@ def get_rom_options_frame(parent=None):
|
||||||
shieldPalettesLabel.pack(side=LEFT)
|
shieldPalettesLabel.pack(side=LEFT)
|
||||||
vars.shieldPalettesVar = StringVar()
|
vars.shieldPalettesVar = StringVar()
|
||||||
vars.shieldPalettesVar.set('default')
|
vars.shieldPalettesVar.set('default')
|
||||||
shieldPalettesOptionMenu = OptionMenu(shieldPalettesFrame, vars.shieldPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
shieldPalettesOptionMenu = OptionMenu(shieldPalettesFrame, vars.shieldPalettesVar, 'default', 'good', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
|
||||||
shieldPalettesOptionMenu.pack(side=LEFT)
|
shieldPalettesOptionMenu.pack(side=LEFT)
|
||||||
|
|
||||||
spritePoolFrame = Frame(romOptionsFrame)
|
spritePoolFrame = Frame(romOptionsFrame)
|
||||||
|
|
107
Main.py
107
Main.py
|
@ -10,17 +10,15 @@ import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
from typing import Dict, Tuple
|
from typing import Dict, Tuple
|
||||||
|
|
||||||
from BaseClasses import MultiWorld, CollectionState, Region, Item
|
from BaseClasses import MultiWorld, CollectionState, Region
|
||||||
from worlds.alttp.Items import item_name_groups
|
from worlds.alttp.Items import item_name_groups
|
||||||
from worlds.alttp.Regions import lookup_vanilla_location_to_entrance
|
from worlds.alttp.Regions import lookup_vanilla_location_to_entrance
|
||||||
from worlds.alttp.Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, get_hash_string
|
|
||||||
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
|
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
|
||||||
from worlds.alttp.Shops import ShopSlotFill, SHOP_ID_START, total_shop_slots, FillDisabledShopSlots
|
from worlds.alttp.Shops import ShopSlotFill, SHOP_ID_START, total_shop_slots, FillDisabledShopSlots
|
||||||
from worlds.alttp.ItemPool import difficulties, fill_prizes
|
from worlds.alttp.ItemPool import difficulties
|
||||||
from Utils import output_path, parse_player_names, get_options, __version__, version_tuple
|
from Utils import output_path, get_options, __version__, version_tuple
|
||||||
from worlds.generic.Rules import locality_rules, exclusion_rules
|
from worlds.generic.Rules import locality_rules, exclusion_rules
|
||||||
from worlds import AutoWorld
|
from worlds import AutoWorld
|
||||||
import Patch
|
|
||||||
|
|
||||||
seeddigits = 20
|
seeddigits = 20
|
||||||
|
|
||||||
|
@ -66,7 +64,6 @@ def main(args, seed=None):
|
||||||
world.difficulty = args.difficulty.copy()
|
world.difficulty = args.difficulty.copy()
|
||||||
world.item_functionality = args.item_functionality.copy()
|
world.item_functionality = args.item_functionality.copy()
|
||||||
world.timer = args.timer.copy()
|
world.timer = args.timer.copy()
|
||||||
world.progressive = args.progressive.copy()
|
|
||||||
world.goal = args.goal.copy()
|
world.goal = args.goal.copy()
|
||||||
world.local_items = args.local_items.copy()
|
world.local_items = args.local_items.copy()
|
||||||
if hasattr(args, "algorithm"): # current GUI options
|
if hasattr(args, "algorithm"): # current GUI options
|
||||||
|
@ -99,7 +96,6 @@ def main(args, seed=None):
|
||||||
world.blue_clock_time = args.blue_clock_time.copy()
|
world.blue_clock_time = args.blue_clock_time.copy()
|
||||||
world.green_clock_time = args.green_clock_time.copy()
|
world.green_clock_time = args.green_clock_time.copy()
|
||||||
world.shufflepots = args.shufflepots.copy()
|
world.shufflepots = args.shufflepots.copy()
|
||||||
world.progressive = args.progressive.copy()
|
|
||||||
world.dungeon_counters = args.dungeon_counters.copy()
|
world.dungeon_counters = args.dungeon_counters.copy()
|
||||||
world.glitch_boots = args.glitch_boots.copy()
|
world.glitch_boots = args.glitch_boots.copy()
|
||||||
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
||||||
|
@ -117,6 +113,10 @@ def main(args, seed=None):
|
||||||
world.required_medallions = args.required_medallions.copy()
|
world.required_medallions = args.required_medallions.copy()
|
||||||
world.game = args.game.copy()
|
world.game = args.game.copy()
|
||||||
world.set_options(args)
|
world.set_options(args)
|
||||||
|
world.player_name = args.name.copy()
|
||||||
|
world.alttp_rom = args.rom
|
||||||
|
world.enemizer = args.enemizercli
|
||||||
|
world.sprite = args.sprite.copy()
|
||||||
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
|
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
|
||||||
|
|
||||||
world.slot_seeds = {player: random.Random(world.random.getrandbits(64)) for player in
|
world.slot_seeds = {player: random.Random(world.random.getrandbits(64)) for player in
|
||||||
|
@ -148,13 +148,6 @@ def main(args, seed=None):
|
||||||
for name, cls in AutoWorld.AutoWorldRegister.world_types.items():
|
for name, cls in AutoWorld.AutoWorldRegister.world_types.items():
|
||||||
logger.info(f" {name:{longest_name}}: {len(cls.item_names):3} Items | {len(cls.location_names):3} Locations")
|
logger.info(f" {name:{longest_name}}: {len(cls.item_names):3} Items | {len(cls.location_names):3} Locations")
|
||||||
|
|
||||||
parsed_names = parse_player_names(args.names, world.players, args.teams)
|
|
||||||
world.teams = len(parsed_names)
|
|
||||||
for i, team in enumerate(parsed_names, 1):
|
|
||||||
if world.players > 1:
|
|
||||||
logger.info('%s%s', 'Team%d: ' % i if world.teams > 1 else 'Players: ', ', '.join(team))
|
|
||||||
for player, name in enumerate(team, 1):
|
|
||||||
world.player_names[player].append(name)
|
|
||||||
|
|
||||||
logger.info('')
|
logger.info('')
|
||||||
for player in world.get_game_players("A Link to the Past"):
|
for player in world.get_game_players("A Link to the Past"):
|
||||||
|
@ -241,63 +234,18 @@ def main(args, seed=None):
|
||||||
|
|
||||||
logger.info('Generating output files.')
|
logger.info('Generating output files.')
|
||||||
outfilebase = 'AP_' + world.seed_name
|
outfilebase = 'AP_' + world.seed_name
|
||||||
rom_names = []
|
|
||||||
|
|
||||||
def _gen_rom(team: int, player: int, output_directory:str):
|
|
||||||
use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player]
|
|
||||||
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
|
|
||||||
or world.shufflepots[player] or world.bush_shuffle[player]
|
|
||||||
or world.killable_thieves[player])
|
|
||||||
|
|
||||||
rom = LocalRom(args.rom)
|
|
||||||
|
|
||||||
patch_rom(world, rom, player, team, use_enemizer)
|
|
||||||
|
|
||||||
if use_enemizer:
|
|
||||||
patch_enemizer(world, team, player, rom, args.enemizercli, output_directory)
|
|
||||||
|
|
||||||
if args.race:
|
|
||||||
patch_race_rom(rom, world, player)
|
|
||||||
|
|
||||||
world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash)
|
|
||||||
|
|
||||||
palettes_options = {
|
|
||||||
'dungeon': args.uw_palettes[player],
|
|
||||||
'overworld': args.ow_palettes[player],
|
|
||||||
'hud': args.hud_palettes[player],
|
|
||||||
'sword': args.sword_palettes[player],
|
|
||||||
'shield': args.shield_palettes[player],
|
|
||||||
'link': args.link_palettes[player]
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
|
|
||||||
args.fastmenu[player], args.disablemusic[player], args.sprite[player],
|
|
||||||
palettes_options, world, player, True,
|
|
||||||
reduceflashing=args.reduceflashing[player] or args.race,
|
|
||||||
triforcehud=args.triforcehud[player])
|
|
||||||
|
|
||||||
outfilepname = f'_P{player}'
|
|
||||||
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" \
|
|
||||||
if world.player_names[player][team] != 'Player%d' % player else ''
|
|
||||||
|
|
||||||
rompath = os.path.join(output_directory, f'{outfilebase}{outfilepname}.sfc')
|
|
||||||
rom.write_to_file(rompath, hide_enemizer=True)
|
|
||||||
Patch.create_patch_file(rompath, player=player, player_name=world.player_names[player][team])
|
|
||||||
os.unlink(rompath)
|
|
||||||
return player, team, bytes(rom.name)
|
|
||||||
|
|
||||||
pool = concurrent.futures.ThreadPoolExecutor()
|
pool = concurrent.futures.ThreadPoolExecutor()
|
||||||
|
|
||||||
output = tempfile.TemporaryDirectory()
|
output = tempfile.TemporaryDirectory()
|
||||||
with output as temp_dir:
|
with output as temp_dir:
|
||||||
check_accessibility_task = pool.submit(world.fulfills_accessibility)
|
check_accessibility_task = pool.submit(world.fulfills_accessibility)
|
||||||
rom_futures = []
|
|
||||||
output_file_futures = []
|
output_file_futures = []
|
||||||
for team in range(world.teams):
|
|
||||||
for player in world.get_game_players("A Link to the Past"):
|
|
||||||
rom_futures.append(pool.submit(_gen_rom, team, player, temp_dir))
|
|
||||||
for player in world.player_ids:
|
for player in world.player_ids:
|
||||||
output_file_futures.append(pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir))
|
output_file_futures.append(pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir))
|
||||||
|
output_file_futures.append(pool.submit(AutoWorld.call_stage, world, "generate_output", temp_dir))
|
||||||
|
|
||||||
def get_entrance_to_region(region: Region):
|
def get_entrance_to_region(region: Region):
|
||||||
for entrance in region.entrances:
|
for entrance in region.entrances:
|
||||||
|
@ -365,12 +313,8 @@ def main(args, seed=None):
|
||||||
|
|
||||||
FillDisabledShopSlots(world)
|
FillDisabledShopSlots(world)
|
||||||
|
|
||||||
def write_multidata(roms, outputs):
|
def write_multidata():
|
||||||
import base64
|
|
||||||
import NetUtils
|
import NetUtils
|
||||||
for future in roms:
|
|
||||||
rom_name = future.result()
|
|
||||||
rom_names.append(rom_name)
|
|
||||||
slot_data = {}
|
slot_data = {}
|
||||||
client_versions = {}
|
client_versions = {}
|
||||||
minimum_versions = {"server": (0, 1, 1), "clients": client_versions}
|
minimum_versions = {"server": (0, 1, 1), "clients": client_versions}
|
||||||
|
@ -378,8 +322,6 @@ def main(args, seed=None):
|
||||||
for slot in world.player_ids:
|
for slot in world.player_ids:
|
||||||
client_versions[slot] = world.worlds[slot].get_required_client_version()
|
client_versions[slot] = world.worlds[slot].get_required_client_version()
|
||||||
games[slot] = world.game[slot]
|
games[slot] = world.game[slot]
|
||||||
connect_names = {base64.b64encode(rom_name).decode(): (team, slot) for
|
|
||||||
slot, team, rom_name in rom_names}
|
|
||||||
precollected_items = {player: [] for player in range(1, world.players + 1)}
|
precollected_items = {player: [] for player in range(1, world.players + 1)}
|
||||||
for item in world.precollected_items:
|
for item in world.precollected_items:
|
||||||
precollected_items[item.player].append(item.code)
|
precollected_items[item.player].append(item.code)
|
||||||
|
@ -390,11 +332,6 @@ def main(args, seed=None):
|
||||||
if world.tech_tree_information[player].value == 2:
|
if world.tech_tree_information[player].value == 2:
|
||||||
sending_visible_players.add(player)
|
sending_visible_players.add(player)
|
||||||
|
|
||||||
for i, team in enumerate(parsed_names):
|
|
||||||
for player, name in enumerate(team, 1):
|
|
||||||
if player not in world.get_game_players("A Link to the Past"):
|
|
||||||
connect_names[name] = (i, player)
|
|
||||||
|
|
||||||
for slot in world.player_ids:
|
for slot in world.player_ids:
|
||||||
slot_data[slot] = world.worlds[slot].fill_slot_data()
|
slot_data[slot] = world.worlds[slot].fill_slot_data()
|
||||||
|
|
||||||
|
@ -414,11 +351,11 @@ def main(args, seed=None):
|
||||||
precollected_hints[location.player].add(hint)
|
precollected_hints[location.player].add(hint)
|
||||||
precollected_hints[location.item.player].add(hint)
|
precollected_hints[location.item.player].add(hint)
|
||||||
|
|
||||||
multidata = zlib.compress(pickle.dumps({
|
multidata = {
|
||||||
"slot_data": slot_data,
|
"slot_data": slot_data,
|
||||||
"games": games,
|
"games": games,
|
||||||
"names": parsed_names,
|
"names": [{player: name for player, name in world.player_name.items()}],
|
||||||
"connect_names": connect_names,
|
"connect_names": {name: (0, player) for player, name in world.player_name.items()},
|
||||||
"remote_items": {player for player in world.player_ids if
|
"remote_items": {player for player in world.player_ids if
|
||||||
world.worlds[player].remote_items},
|
world.worlds[player].remote_items},
|
||||||
"locations": locations_data,
|
"locations": locations_data,
|
||||||
|
@ -431,15 +368,17 @@ def main(args, seed=None):
|
||||||
"tags": ["AP"],
|
"tags": ["AP"],
|
||||||
"minimum_versions": minimum_versions,
|
"minimum_versions": minimum_versions,
|
||||||
"seed_name": world.seed_name
|
"seed_name": world.seed_name
|
||||||
}), 9)
|
}
|
||||||
|
AutoWorld.call_all(world, "modify_multidata", multidata)
|
||||||
|
|
||||||
with open(os.path.join(temp_dir, '%s.archipelago' % outfilebase), 'wb') as f:
|
multidata = zlib.compress(pickle.dumps(multidata), 9)
|
||||||
|
|
||||||
|
with open(os.path.join(temp_dir, f'{outfilebase}.archipelago'), 'wb') as f:
|
||||||
f.write(bytes([1])) # version of format
|
f.write(bytes([1])) # version of format
|
||||||
f.write(multidata)
|
f.write(multidata)
|
||||||
for future in outputs:
|
|
||||||
future.result() # collect errors if they occured
|
|
||||||
|
|
||||||
multidata_task = pool.submit(write_multidata, rom_futures, output_file_futures)
|
|
||||||
|
multidata_task = pool.submit(write_multidata)
|
||||||
if not check_accessibility_task.result():
|
if not check_accessibility_task.result():
|
||||||
if not world.can_beat_game():
|
if not world.can_beat_game():
|
||||||
raise Exception("Game appears as unbeatable. Aborting.")
|
raise Exception("Game appears as unbeatable. Aborting.")
|
||||||
|
@ -451,8 +390,10 @@ def main(args, seed=None):
|
||||||
if not args.skip_playthrough:
|
if not args.skip_playthrough:
|
||||||
logger.info('Calculating playthrough.')
|
logger.info('Calculating playthrough.')
|
||||||
create_playthrough(world)
|
create_playthrough(world)
|
||||||
if args.create_spoiler: # needs spoiler.hashes to be filled, that depend on rom_futures being done
|
if args.create_spoiler:
|
||||||
world.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase))
|
world.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase))
|
||||||
|
for future in output_file_futures:
|
||||||
|
future.result()
|
||||||
zipfilename = output_path(f"AP_{world.seed_name}.zip")
|
zipfilename = output_path(f"AP_{world.seed_name}.zip")
|
||||||
logger.info(f'Creating final archive at {zipfilename}.')
|
logger.info(f'Creating final archive at {zipfilename}.')
|
||||||
with zipfile.ZipFile(zipfilename, mode="w", compression=zipfile.ZIP_DEFLATED,
|
with zipfile.ZipFile(zipfilename, mode="w", compression=zipfile.ZIP_DEFLATED,
|
||||||
|
|
16
Options.py
16
Options.py
|
@ -11,9 +11,11 @@ class AssembleOptions(type):
|
||||||
for base in bases:
|
for base in bases:
|
||||||
if hasattr(base, "options"):
|
if hasattr(base, "options"):
|
||||||
options.update(base.options)
|
options.update(base.options)
|
||||||
name_lookup.update(name_lookup)
|
name_lookup.update(base.name_lookup)
|
||||||
new_options = {name[7:].lower(): option_id for name, option_id in attrs.items() if
|
new_options = {name[7:].lower(): option_id for name, option_id in attrs.items() if
|
||||||
name.startswith("option_")}
|
name.startswith("option_")}
|
||||||
|
if "random" in new_options:
|
||||||
|
raise Exception("Choice option 'random' cannot be manually assigned.")
|
||||||
attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()})
|
attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()})
|
||||||
options.update(new_options)
|
options.update(new_options)
|
||||||
|
|
||||||
|
@ -47,7 +49,12 @@ class Option(metaclass=AssembleOptions):
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.value)
|
return hash(self.value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_key(self) -> str:
|
||||||
|
return self.name_lookup[self.value]
|
||||||
|
|
||||||
def get_current_option_name(self) -> str:
|
def get_current_option_name(self) -> str:
|
||||||
|
"""For display purposes."""
|
||||||
return self.get_option_name(self.value)
|
return self.get_option_name(self.value)
|
||||||
|
|
||||||
def get_option_name(self, value: typing.Any) -> str:
|
def get_option_name(self, value: typing.Any) -> str:
|
||||||
|
@ -122,8 +129,13 @@ class Choice(Option):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_text(cls, text: str) -> Choice:
|
def from_text(cls, text: str) -> Choice:
|
||||||
|
text = text.lower()
|
||||||
|
# TODO: turn on after most people have adjusted their yamls to no longer have suboptions with "random" in them
|
||||||
|
# maybe in 0.2?
|
||||||
|
# if text == "random":
|
||||||
|
# return cls(random.choice(list(cls.options.values())))
|
||||||
for optionname, value in cls.options.items():
|
for optionname, value in cls.options.items():
|
||||||
if optionname == text.lower():
|
if optionname == text:
|
||||||
return cls(value)
|
return cls(value)
|
||||||
raise KeyError(
|
raise KeyError(
|
||||||
f'Could not find option "{text}" for "{cls.__name__}", '
|
f'Could not find option "{text}" for "{cls.__name__}", '
|
||||||
|
|
20
Utils.py
20
Utils.py
|
@ -51,24 +51,6 @@ def snes_to_pc(value):
|
||||||
return ((value & 0x7F0000) >> 1) | (value & 0x7FFF)
|
return ((value & 0x7F0000) >> 1) | (value & 0x7FFF)
|
||||||
|
|
||||||
|
|
||||||
def parse_player_names(names, players, teams):
|
|
||||||
names = tuple(n for n in (n.strip() for n in names.split(",")) if n)
|
|
||||||
if len(names) != len(set(names)):
|
|
||||||
name_counter = collections.Counter(names)
|
|
||||||
raise ValueError(f"Duplicate Player names is not supported, "
|
|
||||||
f'found multiple "{name_counter.most_common(1)[0][0]}".')
|
|
||||||
ret = []
|
|
||||||
while names or len(ret) < teams:
|
|
||||||
team = [n[:16] for n in names[:players]]
|
|
||||||
# 16 bytes in rom per player, which will map to more in unicode, but those characters later get filtered
|
|
||||||
while len(team) != players:
|
|
||||||
team.append(f"Player{len(team) + 1}")
|
|
||||||
ret.append(team)
|
|
||||||
|
|
||||||
names = names[players:]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def cache_argsless(function):
|
def cache_argsless(function):
|
||||||
if function.__code__.co_argcount:
|
if function.__code__.co_argcount:
|
||||||
raise Exception("Can only cache 0 argument functions with this cache.")
|
raise Exception("Can only cache 0 argument functions with this cache.")
|
||||||
|
@ -308,7 +290,7 @@ def get_adjuster_settings(romfile: str) -> typing.Tuple[str, bool]:
|
||||||
adjuster_settings.rom = romfile
|
adjuster_settings.rom = romfile
|
||||||
adjuster_settings.baserom = Patch.get_base_rom_path()
|
adjuster_settings.baserom = Patch.get_base_rom_path()
|
||||||
adjuster_settings.world = None
|
adjuster_settings.world = None
|
||||||
whitelist = {"disablemusic", "fastmenu", "heartbeep", "heartcolor", "ow_palettes", "quickswap",
|
whitelist = {"music", "menuspeed", "heartbeep", "heartcolor", "ow_palettes", "quickswap",
|
||||||
"uw_palettes", "sprite"}
|
"uw_palettes", "sprite"}
|
||||||
printed_options = {name: value for name, value in vars(adjuster_settings).items() if name in whitelist}
|
printed_options = {name: value for name, value in vars(adjuster_settings).items() if name in whitelist}
|
||||||
if hasattr(adjuster_settings, "sprite_pool"):
|
if hasattr(adjuster_settings, "sprite_pool"):
|
||||||
|
|
|
@ -91,8 +91,6 @@ def gen_game(gen_options, race=False, owner=None, sid=None):
|
||||||
erargs.name[player] = os.path.splitext(os.path.split(playerfile)[-1])[0]
|
erargs.name[player] = os.path.splitext(os.path.split(playerfile)[-1])[0]
|
||||||
erargs.name[player] = handle_name(erargs.name[player], player, name_counter)
|
erargs.name[player] = handle_name(erargs.name[player], player, name_counter)
|
||||||
|
|
||||||
erargs.names = ",".join(erargs.name[i] for i in range(1, playercount + 1))
|
|
||||||
del (erargs.name)
|
|
||||||
ERmain(erargs, seed)
|
ERmain(erargs, seed)
|
||||||
|
|
||||||
return upload_to_db(target.name, owner, sid, race)
|
return upload_to_db(target.name, owner, sid, race)
|
||||||
|
|
|
@ -45,9 +45,6 @@ server_options:
|
||||||
log_network: 0
|
log_network: 0
|
||||||
# Options for Generation
|
# Options for Generation
|
||||||
generator:
|
generator:
|
||||||
# Teams
|
|
||||||
# Note that this feature is TODO: to move it to dynamic creation on server, not during generation
|
|
||||||
teams: 1
|
|
||||||
# Location of your Enemizer CLI, available here: https://github.com/Ijwu/Enemizer/releases
|
# Location of your Enemizer CLI, available here: https://github.com/Ijwu/Enemizer/releases
|
||||||
enemizer_path: "EnemizerCLI/EnemizerCLI.Core.exe"
|
enemizer_path: "EnemizerCLI/EnemizerCLI.Core.exe"
|
||||||
# Folder from which the player yaml files are pulled from
|
# Folder from which the player yaml files are pulled from
|
||||||
|
|
|
@ -106,7 +106,7 @@ Factorio:
|
||||||
progressive:
|
progressive:
|
||||||
on: 1
|
on: 1
|
||||||
off: 0
|
off: 0
|
||||||
random: 0
|
grouped_random: 0
|
||||||
tech_tree_information:
|
tech_tree_information:
|
||||||
none: 0
|
none: 0
|
||||||
advancement: 0 # show which items are a logical advancement
|
advancement: 0 # show which items are a logical advancement
|
||||||
|
@ -307,7 +307,7 @@ A Link to the Past:
|
||||||
progressive: # Enable or disable progressive items (swords, shields, bow)
|
progressive: # Enable or disable progressive items (swords, shields, bow)
|
||||||
on: 50 # All items are progressive
|
on: 50 # All items are progressive
|
||||||
off: 0 # No items are progressive
|
off: 0 # No items are progressive
|
||||||
random: 0 # Randomly decides for all items. Swords could be progressive, shields might not be
|
grouped_random: 0 # Randomly decides for all items. Swords could be progressive, shields might not be
|
||||||
entrance_shuffle:
|
entrance_shuffle:
|
||||||
none: 50 # Vanilla game map. All entrances and exits lead to their original locations. You probably want this option
|
none: 50 # Vanilla game map. All entrances and exits lead to their original locations. You probably want this option
|
||||||
dungeonssimple: 0 # Shuffle just dungeons amongst each other, swapping dungeons entirely, so Hyrule Castle is always 1 dungeon
|
dungeonssimple: 0 # Shuffle just dungeons amongst each other, swapping dungeons entirely, so Hyrule Castle is always 1 dungeon
|
||||||
|
@ -596,7 +596,7 @@ A Link to the Past:
|
||||||
off: 0
|
off: 0
|
||||||
ow_palettes: # Change the colors of the overworld
|
ow_palettes: # Change the colors of the overworld
|
||||||
default: 50 # No changes
|
default: 50 # No changes
|
||||||
random: 0 # Shuffle the colors, with harmony in mind
|
good: 0 # Shuffle the colors, with harmony in mind
|
||||||
blackout: 0 # everything black / blind mode
|
blackout: 0 # everything black / blind mode
|
||||||
grayscale: 0
|
grayscale: 0
|
||||||
negative: 0
|
negative: 0
|
||||||
|
@ -606,7 +606,7 @@ A Link to the Past:
|
||||||
puke: 0
|
puke: 0
|
||||||
uw_palettes: # Change the colors of caves and dungeons
|
uw_palettes: # Change the colors of caves and dungeons
|
||||||
default: 50 # No changes
|
default: 50 # No changes
|
||||||
random: 0 # Shuffle the colors, with harmony in mind
|
good: 0 # Shuffle the colors, with harmony in mind
|
||||||
blackout: 0 # everything black / blind mode
|
blackout: 0 # everything black / blind mode
|
||||||
grayscale: 0
|
grayscale: 0
|
||||||
negative: 0
|
negative: 0
|
||||||
|
@ -616,7 +616,7 @@ A Link to the Past:
|
||||||
puke: 0
|
puke: 0
|
||||||
hud_palettes: # Change the colors of the hud
|
hud_palettes: # Change the colors of the hud
|
||||||
default: 50 # No changes
|
default: 50 # No changes
|
||||||
random: 0 # Shuffle the colors, with harmony in mind
|
good: 0 # Shuffle the colors, with harmony in mind
|
||||||
blackout: 0 # everything black / blind mode
|
blackout: 0 # everything black / blind mode
|
||||||
grayscale: 0
|
grayscale: 0
|
||||||
negative: 0
|
negative: 0
|
||||||
|
@ -626,7 +626,7 @@ A Link to the Past:
|
||||||
puke: 0
|
puke: 0
|
||||||
sword_palettes: # Change the colors of swords
|
sword_palettes: # Change the colors of swords
|
||||||
default: 50 # No changes
|
default: 50 # No changes
|
||||||
random: 0 # Shuffle the colors, with harmony in mind
|
good: 0 # Shuffle the colors, with harmony in mind
|
||||||
blackout: 0 # everything black / blind mode
|
blackout: 0 # everything black / blind mode
|
||||||
grayscale: 0
|
grayscale: 0
|
||||||
negative: 0
|
negative: 0
|
||||||
|
@ -636,7 +636,7 @@ A Link to the Past:
|
||||||
puke: 0
|
puke: 0
|
||||||
shield_palettes: # Change the colors of shields
|
shield_palettes: # Change the colors of shields
|
||||||
default: 50 # No changes
|
default: 50 # No changes
|
||||||
random: 0 # Shuffle the colors, with harmony in mind
|
good: 0 # Shuffle the colors, with harmony in mind
|
||||||
blackout: 0 # everything black / blind mode
|
blackout: 0 # everything black / blind mode
|
||||||
grayscale: 0
|
grayscale: 0
|
||||||
negative: 0
|
negative: 0
|
||||||
|
@ -693,7 +693,7 @@ linked_options:
|
||||||
singularity: 1
|
singularity: 1
|
||||||
enemy_damage:
|
enemy_damage:
|
||||||
shuffled: 1
|
shuffled: 1
|
||||||
random: 1
|
chaos: 1
|
||||||
enemy_health:
|
enemy_health:
|
||||||
easy: 1
|
easy: 1
|
||||||
hard: 1
|
hard: 1
|
||||||
|
|
|
@ -51,7 +51,15 @@ def call_all(world: MultiWorld, method_name: str, *args):
|
||||||
for world_type in world_types:
|
for world_type in world_types:
|
||||||
stage_callable = getattr(world_type, f"stage_{method_name}", None)
|
stage_callable = getattr(world_type, f"stage_{method_name}", None)
|
||||||
if stage_callable:
|
if stage_callable:
|
||||||
stage_callable(world)
|
stage_callable(world, *args)
|
||||||
|
|
||||||
|
|
||||||
|
def call_stage(world: MultiWorld, method_name: str, *args):
|
||||||
|
world_types = {world.worlds[player].__class__ for player in world.player_ids}
|
||||||
|
for world_type in world_types:
|
||||||
|
stage_callable = getattr(world_type, f"stage_{method_name}", None)
|
||||||
|
if stage_callable:
|
||||||
|
stage_callable(world, *args)
|
||||||
|
|
||||||
|
|
||||||
class World(metaclass=AutoWorldRegister):
|
class World(metaclass=AutoWorldRegister):
|
||||||
|
@ -127,6 +135,10 @@ class World(metaclass=AutoWorldRegister):
|
||||||
"""Fill in the slot_data field in the Connected network package."""
|
"""Fill in the slot_data field in the Connected network package."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def modify_multidata(self, multidata: dict):
|
||||||
|
"""For deeper modification of server multidata."""
|
||||||
|
pass
|
||||||
|
|
||||||
def get_required_client_version(self) -> Tuple[int, int, int]:
|
def get_required_client_version(self) -> Tuple[int, int, int]:
|
||||||
return 0, 0, 3
|
return 0, 0, 3
|
||||||
|
|
||||||
|
|
|
@ -143,20 +143,7 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
off.
|
off.
|
||||||
Off: Dungeon counters are never shown.
|
Off: Dungeon counters are never shown.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--progressive', default=defval('on'), const='normal', nargs='?', choices=['on', 'off', 'random'],
|
|
||||||
help='''\
|
|
||||||
Select progressive equipment setting. Affects available itempool. (default: %(default)s)
|
|
||||||
On: Swords, Shields, Armor, and Gloves will
|
|
||||||
all be progressive equipment. Each subsequent
|
|
||||||
item of the same type the player finds will
|
|
||||||
upgrade that piece of equipment by one stage.
|
|
||||||
Off: Swords, Shields, Armor, and Gloves will not
|
|
||||||
be progressive equipment. Higher level items may
|
|
||||||
be found at any time. Downgrades are not possible.
|
|
||||||
Random: Swords, Shields, Armor, and Gloves will, per
|
|
||||||
category, be randomly progressive or not.
|
|
||||||
Link will die in one hit.
|
|
||||||
''')
|
|
||||||
parser.add_argument('--algorithm', default=defval('balanced'), const='balanced', nargs='?',
|
parser.add_argument('--algorithm', default=defval('balanced'), const='balanced', nargs='?',
|
||||||
choices=['freshness', 'flood', 'vt25', 'vt26', 'balanced'],
|
choices=['freshness', 'flood', 'vt25', 'vt26', 'balanced'],
|
||||||
help='''\
|
help='''\
|
||||||
|
@ -218,22 +205,7 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
--seed given will produce the same 10 (different) roms each
|
--seed given will produce the same 10 (different) roms each
|
||||||
time).
|
time).
|
||||||
''', type=int)
|
''', type=int)
|
||||||
parser.add_argument('--fastmenu', default=defval('normal'), const='normal', nargs='?',
|
|
||||||
choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
|
|
||||||
help='''\
|
|
||||||
Select the rate at which the menu opens and closes.
|
|
||||||
(default: %(default)s)
|
|
||||||
''')
|
|
||||||
parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true')
|
|
||||||
parser.add_argument('--disablemusic', help='Disables game music.', action='store_true')
|
|
||||||
parser.add_argument('--triforcehud', default='hide_goal', const='hide_goal', nargs='?', choices=['normal', 'hide_goal', 'hide_required', 'hide_both'],
|
|
||||||
help='''\
|
|
||||||
Hide the triforce hud in certain circumstances.
|
|
||||||
hide_goal will hide the hud until finding a triforce piece, hide_required will hide the total amount needed to win
|
|
||||||
(Both can be revealed when speaking to Murahalda)
|
|
||||||
(default: %(default)s)
|
|
||||||
''')
|
|
||||||
parser.add_argument('--enableflashing', help='Reenable flashing animations (unfriendly to epilepsy, always disabled in race roms)', action='store_false', dest="reduceflashing")
|
|
||||||
parser.add_argument('--mapshuffle', default=defval(False),
|
parser.add_argument('--mapshuffle', default=defval(False),
|
||||||
help='Maps are no longer restricted to their dungeons, but can be anywhere',
|
help='Maps are no longer restricted to their dungeons, but can be anywhere',
|
||||||
action='store_true')
|
action='store_true')
|
||||||
|
@ -276,19 +248,6 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
If set, the Pyramid Hole and Ganon's Tower are not
|
If set, the Pyramid Hole and Ganon's Tower are not
|
||||||
included entrance shuffle pool.
|
included entrance shuffle pool.
|
||||||
''', action='store_false', dest='shuffleganon')
|
''', action='store_false', dest='shuffleganon')
|
||||||
parser.add_argument('--heartbeep', default=defval('normal'), const='normal', nargs='?', choices=['double', 'normal', 'half', 'quarter', 'off'],
|
|
||||||
help='''\
|
|
||||||
Select the rate at which the heart beep sound is played at
|
|
||||||
low health. (default: %(default)s)
|
|
||||||
''')
|
|
||||||
parser.add_argument('--heartcolor', default=defval('red'), const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'],
|
|
||||||
help='Select the color of Link\'s heart meter. (default: %(default)s)')
|
|
||||||
parser.add_argument('--ow_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
|
|
||||||
parser.add_argument('--uw_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
|
|
||||||
parser.add_argument('--hud_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
|
|
||||||
parser.add_argument('--shield_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
|
|
||||||
parser.add_argument('--sword_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
|
|
||||||
parser.add_argument('--link_palettes', default=defval('default'), choices=['default', 'random', 'blackout','puke','classic','grayscale','negative','dizzy','sick'])
|
|
||||||
|
|
||||||
parser.add_argument('--sprite', help='''\
|
parser.add_argument('--sprite', help='''\
|
||||||
Path to a sprite sheet to use for Link. Needs to be in
|
Path to a sprite sheet to use for Link. Needs to be in
|
||||||
|
@ -380,15 +339,14 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||||
'local_items', 'non_local_items', 'retro', 'accessibility', 'hints', 'beemizer',
|
'local_items', 'non_local_items', 'retro', 'accessibility', 'hints', 'beemizer',
|
||||||
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
'sprite',
|
||||||
'heartbeep', "progression_balancing", "triforce_pieces_available",
|
"progression_balancing", "triforce_pieces_available",
|
||||||
"triforce_pieces_required", "shop_shuffle",
|
"triforce_pieces_required", "shop_shuffle",
|
||||||
"required_medallions", "start_hints",
|
"required_medallions", "start_hints",
|
||||||
"plando_items", "plando_texts", "plando_connections", "er_seeds",
|
"plando_items", "plando_texts", "plando_connections", "er_seeds",
|
||||||
'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
||||||
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
|
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
|
||||||
'restrict_dungeon_item_on_boss', 'reduceflashing', 'game',
|
'restrict_dungeon_item_on_boss', 'game']:
|
||||||
'hud_palettes', 'sword_palettes', 'shield_palettes', 'link_palettes', 'triforcehud']:
|
|
||||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||||
if player == 1:
|
if player == 1:
|
||||||
setattr(ret, name, {1: value})
|
setattr(ret, name, {1: value})
|
||||||
|
|
|
@ -1064,7 +1064,7 @@ def link_entrances(world, player):
|
||||||
connect_doors(world, single_doors, door_targets, player)
|
connect_doors(world, single_doors, door_targets, player)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f'{world.shuffle[player]} Shuffling not supported yet. Player {world.get_player_names(player)}')
|
f'{world.shuffle[player]} Shuffling not supported yet. Player {world.get_player_name(player)}')
|
||||||
|
|
||||||
# mandatory hybrid major glitches connections
|
# mandatory hybrid major glitches connections
|
||||||
if world.logic[player] in ['hybridglitches', 'nologic']:
|
if world.logic[player] in ['hybridglitches', 'nologic']:
|
||||||
|
|
|
@ -399,7 +399,7 @@ def generate_itempool(world):
|
||||||
if additional_triforce_pieces:
|
if additional_triforce_pieces:
|
||||||
if additional_triforce_pieces > len(nonprogressionitems):
|
if additional_triforce_pieces > len(nonprogressionitems):
|
||||||
raise FillError(f"Not enough non-progression items to replace with Triforce pieces found for player "
|
raise FillError(f"Not enough non-progression items to replace with Triforce pieces found for player "
|
||||||
f"{world.get_player_names(player)}.")
|
f"{world.get_player_name(player)}.")
|
||||||
progressionitems += [ItemFactory("Triforce Piece", player)] * additional_triforce_pieces
|
progressionitems += [ItemFactory("Triforce Piece", player)] * additional_triforce_pieces
|
||||||
nonprogressionitems.sort(key=lambda item: int("Heart" in item.name)) # try to keep hearts in the pool
|
nonprogressionitems.sort(key=lambda item: int("Heart" in item.name)) # try to keep hearts in the pool
|
||||||
nonprogressionitems = nonprogressionitems[additional_triforce_pieces:]
|
nonprogressionitems = nonprogressionitems[additional_triforce_pieces:]
|
||||||
|
@ -563,16 +563,14 @@ def get_pool_core(world, player: int):
|
||||||
assert loc not in placed_items
|
assert loc not in placed_items
|
||||||
placed_items[loc] = item
|
placed_items[loc] = item
|
||||||
|
|
||||||
def want_progressives():
|
|
||||||
return world.random.choice([True, False]) if progressive == 'random' else progressive == 'on'
|
|
||||||
|
|
||||||
# provide boots to major glitch dependent seeds
|
# provide boots to major glitch dependent seeds
|
||||||
if logic in {'owglitches', 'hybridglitches', 'nologic'} and world.glitch_boots[player] and goal != 'icerodhunt':
|
if logic in {'owglitches', 'hybridglitches', 'nologic'} and world.glitch_boots[player] and goal != 'icerodhunt':
|
||||||
precollected_items.append('Pegasus Boots')
|
precollected_items.append('Pegasus Boots')
|
||||||
pool.remove('Pegasus Boots')
|
pool.remove('Pegasus Boots')
|
||||||
pool.append('Rupees (20)')
|
pool.append('Rupees (20)')
|
||||||
|
want_progressives = world.progressive[player].want_progressives
|
||||||
|
|
||||||
if want_progressives():
|
if want_progressives(world.random):
|
||||||
pool.extend(diff.progressiveglove)
|
pool.extend(diff.progressiveglove)
|
||||||
else:
|
else:
|
||||||
pool.extend(diff.basicglove)
|
pool.extend(diff.basicglove)
|
||||||
|
@ -599,22 +597,22 @@ def get_pool_core(world, player: int):
|
||||||
thisbottle = world.random.choice(diff.bottles)
|
thisbottle = world.random.choice(diff.bottles)
|
||||||
pool.append(thisbottle)
|
pool.append(thisbottle)
|
||||||
|
|
||||||
if want_progressives():
|
if want_progressives(world.random):
|
||||||
pool.extend(diff.progressiveshield)
|
pool.extend(diff.progressiveshield)
|
||||||
else:
|
else:
|
||||||
pool.extend(diff.basicshield)
|
pool.extend(diff.basicshield)
|
||||||
|
|
||||||
if want_progressives():
|
if want_progressives(world.random):
|
||||||
pool.extend(diff.progressivearmor)
|
pool.extend(diff.progressivearmor)
|
||||||
else:
|
else:
|
||||||
pool.extend(diff.basicarmor)
|
pool.extend(diff.basicarmor)
|
||||||
|
|
||||||
if want_progressives():
|
if want_progressives(world.random):
|
||||||
pool.extend(diff.progressivemagic)
|
pool.extend(diff.progressivemagic)
|
||||||
else:
|
else:
|
||||||
pool.extend(diff.basicmagic)
|
pool.extend(diff.basicmagic)
|
||||||
|
|
||||||
if want_progressives():
|
if want_progressives(world.random):
|
||||||
pool.extend(diff.progressivebow)
|
pool.extend(diff.progressivebow)
|
||||||
elif (swordless or logic == 'noglitches') and goal != 'icerodhunt':
|
elif (swordless or logic == 'noglitches') and goal != 'icerodhunt':
|
||||||
swordless_bows = ['Bow', 'Silver Bow']
|
swordless_bows = ['Bow', 'Silver Bow']
|
||||||
|
@ -627,7 +625,7 @@ def get_pool_core(world, player: int):
|
||||||
if swordless:
|
if swordless:
|
||||||
pool.extend(diff.swordless)
|
pool.extend(diff.swordless)
|
||||||
else:
|
else:
|
||||||
progressive_swords = want_progressives()
|
progressive_swords = want_progressives(world.random)
|
||||||
pool.extend(diff.progressivesword if progressive_swords else diff.basicsword)
|
pool.extend(diff.progressivesword if progressive_swords else diff.basicsword)
|
||||||
|
|
||||||
extraitems = total_items_to_place - len(pool) - len(placed_items)
|
extraitems = total_items_to_place - len(pool) - len(placed_items)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from Options import Choice, Range, Option
|
from Options import Choice, Range, Option, Toggle, DefaultOnToggle
|
||||||
|
|
||||||
|
|
||||||
class Logic(Choice):
|
class Logic(Choice):
|
||||||
|
@ -70,8 +70,116 @@ class Enemies(Choice):
|
||||||
option_shuffled = 1
|
option_shuffled = 1
|
||||||
option_chaos = 2
|
option_chaos = 2
|
||||||
|
|
||||||
|
|
||||||
|
class Progressive(Choice):
|
||||||
|
displayname = "Progressive Items"
|
||||||
|
option_off = 0
|
||||||
|
option_grouped_random = 1
|
||||||
|
option_on = 2
|
||||||
|
alias_false = 0
|
||||||
|
alias_true = 2
|
||||||
|
default = 2
|
||||||
|
|
||||||
|
def want_progressives(self, random):
|
||||||
|
return random.choice([True, False]) if self.value == self.option_grouped_random else int(self.value)
|
||||||
|
|
||||||
|
class Palette(Choice):
|
||||||
|
option_default = 0
|
||||||
|
option_good = 1
|
||||||
|
option_blackout = 2
|
||||||
|
option_puke = 3
|
||||||
|
option_classic = 4
|
||||||
|
option_grayscale = 5
|
||||||
|
option_negative = 6
|
||||||
|
option_dizzy = 7
|
||||||
|
option_sick = 8
|
||||||
|
|
||||||
|
|
||||||
|
class OWPalette(Palette):
|
||||||
|
displayname = "Overworld Palette"
|
||||||
|
|
||||||
|
|
||||||
|
class UWPalette(Palette):
|
||||||
|
displayname = "Underworld Palette"
|
||||||
|
|
||||||
|
|
||||||
|
class HUDPalette(Palette):
|
||||||
|
displayname = "Menu Palette"
|
||||||
|
|
||||||
|
|
||||||
|
class SwordPalette(Palette):
|
||||||
|
displayname = "Sword Palette"
|
||||||
|
|
||||||
|
|
||||||
|
class ShieldPalette(Palette):
|
||||||
|
displayname = "Shield Palette"
|
||||||
|
|
||||||
|
|
||||||
|
class LinkPalette(Palette):
|
||||||
|
displayname = "Link Palette"
|
||||||
|
|
||||||
|
|
||||||
|
class HeartBeep(Choice):
|
||||||
|
displayname = "Heart Beep Rate"
|
||||||
|
option_normal = 0
|
||||||
|
option_double = 1
|
||||||
|
option_half = 2,
|
||||||
|
option_quarter = 3
|
||||||
|
option_off = 4
|
||||||
|
|
||||||
|
|
||||||
|
class HeartColor(Choice):
|
||||||
|
displayname = "Heart Color"
|
||||||
|
option_red = 0
|
||||||
|
option_blue = 1
|
||||||
|
option_green = 2
|
||||||
|
option_yellow = 3
|
||||||
|
|
||||||
|
|
||||||
|
class QuickSwap(DefaultOnToggle):
|
||||||
|
displayname = "L/R Quickswapping"
|
||||||
|
|
||||||
|
|
||||||
|
class MenuSpeed(Choice):
|
||||||
|
displayname = "Menu Speed"
|
||||||
|
option_normal = 0
|
||||||
|
option_instant = 1,
|
||||||
|
option_double = 2
|
||||||
|
option_triple = 3
|
||||||
|
option_quadruple = 4
|
||||||
|
option_half = 5
|
||||||
|
|
||||||
|
|
||||||
|
class Music(DefaultOnToggle):
|
||||||
|
displayname = "Play music"
|
||||||
|
|
||||||
|
class ReduceFlashing(DefaultOnToggle):
|
||||||
|
displayname = "Reduce Screen Flashes"
|
||||||
|
|
||||||
|
class TriforceHud(Choice):
|
||||||
|
displayname = "Display Method for Triforce Hunt"
|
||||||
|
option_normal = 0
|
||||||
|
option_hide_goal = 1
|
||||||
|
option_hide_required = 2
|
||||||
|
option_hide_both = 3
|
||||||
|
|
||||||
alttp_options: typing.Dict[str, type(Option)] = {
|
alttp_options: typing.Dict[str, type(Option)] = {
|
||||||
"crystals_needed_for_gt": CrystalsTower,
|
"crystals_needed_for_gt": CrystalsTower,
|
||||||
"crystals_needed_for_ganon": CrystalsGanon,
|
"crystals_needed_for_ganon": CrystalsGanon,
|
||||||
|
"progressive": Progressive,
|
||||||
"shop_item_slots": ShopItemSlots,
|
"shop_item_slots": ShopItemSlots,
|
||||||
}
|
"ow_palettes": OWPalette,
|
||||||
|
"uw_palettes": UWPalette,
|
||||||
|
"hud_palettes": HUDPalette,
|
||||||
|
"sword_palettes": SwordPalette,
|
||||||
|
"shield_palettes": ShieldPalette,
|
||||||
|
"link_palettes": LinkPalette,
|
||||||
|
"heartbeep": HeartBeep,
|
||||||
|
"heartcolor": HeartColor,
|
||||||
|
"quickswap": QuickSwap,
|
||||||
|
"menuspeed": MenuSpeed,
|
||||||
|
"music": Music,
|
||||||
|
"reduceflashing": ReduceFlashing,
|
||||||
|
"triforcehud": TriforceHud
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -279,11 +279,11 @@ def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_rand
|
||||||
rom.write_bytes(0x307078 + (i * 0x8000), sprite.glove_palette)
|
rom.write_bytes(0x307078 + (i * 0x8000), sprite.glove_palette)
|
||||||
|
|
||||||
|
|
||||||
def patch_enemizer(world, team: int, player: int, rom: LocalRom, enemizercli, output_directory):
|
def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, output_directory):
|
||||||
check_enemizer(enemizercli)
|
check_enemizer(enemizercli)
|
||||||
randopatch_path = os.path.abspath(os.path.join(output_directory, f'enemizer_randopatch_{team}_{player}.sfc'))
|
randopatch_path = os.path.abspath(os.path.join(output_directory, f'enemizer_randopatch_{player}.sfc'))
|
||||||
options_path = os.path.abspath(os.path.join(output_directory, f'enemizer_options_{team}_{player}.json'))
|
options_path = os.path.abspath(os.path.join(output_directory, f'enemizer_options_{player}.json'))
|
||||||
enemizer_output_path = os.path.abspath(os.path.join(output_directory, f'enemizer_output_{team}_{player}.sfc'))
|
enemizer_output_path = os.path.abspath(os.path.join(output_directory, f'enemizer_output_{player}.sfc'))
|
||||||
|
|
||||||
# write options file for enemizer
|
# write options file for enemizer
|
||||||
options = {
|
options = {
|
||||||
|
@ -756,7 +756,7 @@ def get_nonnative_item_sprite(item: str) -> int:
|
||||||
# https://discord.com/channels/731205301247803413/827141303330406408/852102450822905886
|
# https://discord.com/channels/731205301247803413/827141303330406408/852102450822905886
|
||||||
|
|
||||||
|
|
||||||
def patch_rom(world, rom, player, team, enemized):
|
def patch_rom(world, rom, player, enemized):
|
||||||
local_random = world.slot_seeds[player]
|
local_random = world.slot_seeds[player]
|
||||||
|
|
||||||
# progressive bow silver arrow hint hack
|
# progressive bow silver arrow hint hack
|
||||||
|
@ -1645,7 +1645,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
rom.write_byte(0x4BA1D, tile_set.get_len())
|
rom.write_byte(0x4BA1D, tile_set.get_len())
|
||||||
rom.write_bytes(0x4BA2A, tile_set.get_bytes())
|
rom.write_bytes(0x4BA2A, tile_set.get_bytes())
|
||||||
|
|
||||||
write_strings(rom, world, player, team)
|
write_strings(rom, world, player)
|
||||||
|
|
||||||
# remote items flag, does not currently work
|
# remote items flag, does not currently work
|
||||||
rom.write_byte(0x18637C, int(world.worlds[player].remote_items))
|
rom.write_byte(0x18637C, int(world.worlds[player].remote_items))
|
||||||
|
@ -1654,13 +1654,13 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
# 21 bytes
|
# 21 bytes
|
||||||
from Main import __version__
|
from Main import __version__
|
||||||
# TODO: Adjust Enemizer to accept AP and AD
|
# TODO: Adjust Enemizer to accept AP and AD
|
||||||
rom.name = bytearray(f'BM{__version__.replace(".", "")[0:3]}_{team + 1}_{player}_{world.seed:09}\0', 'utf8')[:21]
|
rom.name = bytearray(f'BM{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21]
|
||||||
rom.name.extend([0] * (21 - len(rom.name)))
|
rom.name.extend([0] * (21 - len(rom.name)))
|
||||||
rom.write_bytes(0x7FC0, rom.name)
|
rom.write_bytes(0x7FC0, rom.name)
|
||||||
|
|
||||||
# set player names
|
# set player names
|
||||||
for p in range(1, min(world.players, 255) + 1):
|
for p in range(1, min(world.players, 255) + 1):
|
||||||
rom.write_bytes(0x195FFC + ((p - 1) * 32), hud_format_text(world.player_names[p][team]))
|
rom.write_bytes(0x195FFC + ((p - 1) * 32), hud_format_text(world.player_name[p]))
|
||||||
|
|
||||||
# Write title screen Code
|
# Write title screen Code
|
||||||
hashint = int(rom.get_hash(), 16)
|
hashint = int(rom.get_hash(), 16)
|
||||||
|
@ -1756,13 +1756,13 @@ def hud_format_text(text):
|
||||||
return output[:32]
|
return output[:32]
|
||||||
|
|
||||||
|
|
||||||
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite: str, palettes_options,
|
def apply_rom_settings(rom, beep, color, quickswap, menuspeed, music: bool, sprite: str, palettes_options,
|
||||||
world=None, player=1, allow_random_on_event=False, reduceflashing=False,
|
world=None, player=1, allow_random_on_event=False, reduceflashing=False,
|
||||||
triforcehud: str = None):
|
triforcehud: str = None):
|
||||||
local_random = random if not world else world.slot_seeds[player]
|
local_random = random if not world else world.slot_seeds[player]
|
||||||
|
disable_music: bool = music
|
||||||
# enable instant item menu
|
# enable instant item menu
|
||||||
if fastmenu == 'instant':
|
if menuspeed == 'instant':
|
||||||
rom.write_byte(0x6DD9A, 0x20)
|
rom.write_byte(0x6DD9A, 0x20)
|
||||||
rom.write_byte(0x6DF2A, 0x20)
|
rom.write_byte(0x6DF2A, 0x20)
|
||||||
rom.write_byte(0x6E0E9, 0x20)
|
rom.write_byte(0x6E0E9, 0x20)
|
||||||
|
@ -1770,15 +1770,15 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
|
||||||
rom.write_byte(0x6DD9A, 0x11)
|
rom.write_byte(0x6DD9A, 0x11)
|
||||||
rom.write_byte(0x6DF2A, 0x12)
|
rom.write_byte(0x6DF2A, 0x12)
|
||||||
rom.write_byte(0x6E0E9, 0x12)
|
rom.write_byte(0x6E0E9, 0x12)
|
||||||
if fastmenu == 'instant':
|
if menuspeed == 'instant':
|
||||||
rom.write_byte(0x180048, 0xE8)
|
rom.write_byte(0x180048, 0xE8)
|
||||||
elif fastmenu == 'double':
|
elif menuspeed == 'double':
|
||||||
rom.write_byte(0x180048, 0x10)
|
rom.write_byte(0x180048, 0x10)
|
||||||
elif fastmenu == 'triple':
|
elif menuspeed == 'triple':
|
||||||
rom.write_byte(0x180048, 0x18)
|
rom.write_byte(0x180048, 0x18)
|
||||||
elif fastmenu == 'quadruple':
|
elif menuspeed == 'quadruple':
|
||||||
rom.write_byte(0x180048, 0x20)
|
rom.write_byte(0x180048, 0x20)
|
||||||
elif fastmenu == 'half':
|
elif menuspeed == 'half':
|
||||||
rom.write_byte(0x180048, 0x04)
|
rom.write_byte(0x180048, 0x04)
|
||||||
else:
|
else:
|
||||||
rom.write_byte(0x180048, 0x08)
|
rom.write_byte(0x180048, 0x08)
|
||||||
|
@ -1854,7 +1854,7 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
|
||||||
while True:
|
while True:
|
||||||
yield ColorF(local_random.random(), local_random.random(), local_random.random())
|
yield ColorF(local_random.random(), local_random.random(), local_random.random())
|
||||||
|
|
||||||
if mode == 'random':
|
if mode == 'good':
|
||||||
mode = 'maseya'
|
mode = 'maseya'
|
||||||
z3pr.randomize(rom.buffer, mode, offset_collections=offsets_array, random_colors=next_color_generator())
|
z3pr.randomize(rom.buffer, mode, offset_collections=offsets_array, random_colors=next_color_generator())
|
||||||
|
|
||||||
|
@ -2075,7 +2075,7 @@ def write_string_to_rom(rom, target, string):
|
||||||
rom.write_bytes(address, MultiByteTextMapper.convert(string, maxbytes))
|
rom.write_bytes(address, MultiByteTextMapper.convert(string, maxbytes))
|
||||||
|
|
||||||
|
|
||||||
def write_strings(rom, world, player, team):
|
def write_strings(rom, world, player):
|
||||||
local_random = world.slot_seeds[player]
|
local_random = world.slot_seeds[player]
|
||||||
|
|
||||||
tt = TextTable()
|
tt = TextTable()
|
||||||
|
@ -2098,11 +2098,11 @@ def write_strings(rom, world, player, team):
|
||||||
hint = dest.hint_text if dest.hint_text else "something"
|
hint = dest.hint_text if dest.hint_text else "something"
|
||||||
if dest.player != player:
|
if dest.player != player:
|
||||||
if ped_hint:
|
if ped_hint:
|
||||||
hint += f" for {world.player_names[dest.player][team]}!"
|
hint += f" for {world.player_name[dest.player]}!"
|
||||||
elif type(dest) in [Region, ALttPLocation]:
|
elif type(dest) in [Region, ALttPLocation]:
|
||||||
hint += f" in {world.player_names[dest.player][team]}'s world"
|
hint += f" in {world.player_name[dest.player]}'s world"
|
||||||
else:
|
else:
|
||||||
hint += f" for {world.player_names[dest.player][team]}"
|
hint += f" for {world.player_name[dest.player]}"
|
||||||
return hint
|
return hint
|
||||||
|
|
||||||
# For hints, first we write hints about entrances, some from the inconvenient list others from all reasonable entrances.
|
# For hints, first we write hints about entrances, some from the inconvenient list others from all reasonable entrances.
|
||||||
|
|
|
@ -118,7 +118,7 @@ def mirrorless_path_to_castle_courtyard(world, player):
|
||||||
else:
|
else:
|
||||||
queue.append((entrance.connected_region, new_path))
|
queue.append((entrance.connected_region, new_path))
|
||||||
|
|
||||||
raise Exception(f"Could not find mirrorless path to castle courtyard for Player {player} ({world.get_player_names(player)})")
|
raise Exception(f"Could not find mirrorless path to castle courtyard for Player {player} ({world.get_player_name(player)})")
|
||||||
|
|
||||||
|
|
||||||
def set_defeat_dungeon_boss_rule(location):
|
def set_defeat_dungeon_boss_rule(location):
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import random
|
import random
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
from BaseClasses import Item, CollectionState
|
from BaseClasses import Item, CollectionState
|
||||||
from .SubClasses import ALttPItem
|
from .SubClasses import ALttPItem
|
||||||
|
@ -11,6 +13,8 @@ from .Rules import set_rules
|
||||||
from .ItemPool import generate_itempool
|
from .ItemPool import generate_itempool
|
||||||
from .Shops import create_shops
|
from .Shops import create_shops
|
||||||
from .Dungeons import create_dungeons
|
from .Dungeons import create_dungeons
|
||||||
|
from .Rom import LocalRom, patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, get_hash_string
|
||||||
|
import Patch
|
||||||
|
|
||||||
from .InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
from .InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||||
from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
|
from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
|
||||||
|
@ -37,6 +41,8 @@ class ALTTPWorld(World):
|
||||||
create_items = generate_itempool
|
create_items = generate_itempool
|
||||||
|
|
||||||
def create_regions(self):
|
def create_regions(self):
|
||||||
|
self.rom_name_available_event = threading.Event()
|
||||||
|
|
||||||
player = self.player
|
player = self.player
|
||||||
world = self.world
|
world = self.world
|
||||||
if world.open_pyramid[player] == 'goal':
|
if world.open_pyramid[player] == 'goal':
|
||||||
|
@ -175,6 +181,67 @@ class ALTTPWorld(World):
|
||||||
from .Dungeons import fill_dungeons_restrictive
|
from .Dungeons import fill_dungeons_restrictive
|
||||||
fill_dungeons_restrictive(world)
|
fill_dungeons_restrictive(world)
|
||||||
|
|
||||||
|
def generate_output(self, output_directory: str):
|
||||||
|
world = self.world
|
||||||
|
player = self.player
|
||||||
|
|
||||||
|
use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player]
|
||||||
|
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
|
||||||
|
or world.shufflepots[player] or world.bush_shuffle[player]
|
||||||
|
or world.killable_thieves[player])
|
||||||
|
|
||||||
|
rom = LocalRom(world.alttp_rom)
|
||||||
|
|
||||||
|
patch_rom(world, rom, player, use_enemizer)
|
||||||
|
|
||||||
|
if use_enemizer:
|
||||||
|
patch_enemizer(world, player, rom, world.enemizer, output_directory)
|
||||||
|
|
||||||
|
if world.is_race:
|
||||||
|
patch_race_rom(rom, world, player)
|
||||||
|
|
||||||
|
world.spoiler.hashes[player] = get_hash_string(rom.hash)
|
||||||
|
|
||||||
|
palettes_options = {
|
||||||
|
'dungeon': world.uw_palettes[player],
|
||||||
|
'overworld': world.ow_palettes[player],
|
||||||
|
'hud': world.hud_palettes[player],
|
||||||
|
'sword': world.sword_palettes[player],
|
||||||
|
'shield': world.shield_palettes[player],
|
||||||
|
'link': world.link_palettes[player]
|
||||||
|
}
|
||||||
|
palettes_options = {key: option.current_key for key, option in palettes_options.items()}
|
||||||
|
|
||||||
|
apply_rom_settings(rom, world.heartbeep[player].current_key,
|
||||||
|
world.heartcolor[player].current_key,
|
||||||
|
world.quickswap[player],
|
||||||
|
world.menuspeed[player].current_key,
|
||||||
|
world.music[player],
|
||||||
|
world.sprite[player],
|
||||||
|
palettes_options, world, player, True,
|
||||||
|
reduceflashing=world.reduceflashing[player] or world.is_race,
|
||||||
|
triforcehud=world.triforcehud[player].current_key)
|
||||||
|
|
||||||
|
outfilepname = f'_P{player}'
|
||||||
|
outfilepname += f"_{world.player_name[player].replace(' ', '_')}" \
|
||||||
|
if world.player_name[player] != 'Player%d' % player else ''
|
||||||
|
|
||||||
|
rompath = os.path.join(output_directory, f'AP_{world.seed_name}{outfilepname}.sfc')
|
||||||
|
rom.write_to_file(rompath, hide_enemizer=True)
|
||||||
|
Patch.create_patch_file(rompath, player=player, player_name=world.player_name[player])
|
||||||
|
os.unlink(rompath)
|
||||||
|
self.rom_name = rom.name
|
||||||
|
self.rom_name_available_event.set()
|
||||||
|
|
||||||
|
def modify_multidata(self, multidata: dict):
|
||||||
|
import base64
|
||||||
|
# wait for self.rom_name to be available.
|
||||||
|
self.rom_name_available_event.wait()
|
||||||
|
new_name = base64.b64encode(bytes(self.rom_name)).decode()
|
||||||
|
payload = multidata["connect_names"][self.world.player_name[self.player]]
|
||||||
|
multidata["connect_names"][new_name] = payload
|
||||||
|
del (multidata["connect_names"][self.world.player_name[self.player]])
|
||||||
|
|
||||||
def get_required_client_version(self) -> tuple:
|
def get_required_client_version(self) -> tuple:
|
||||||
return max((0, 1, 4), super(ALTTPWorld, self).get_required_client_version())
|
return max((0, 1, 4), super(ALTTPWorld, self).get_required_client_version())
|
||||||
|
|
||||||
|
|
|
@ -57,12 +57,12 @@ def generate_mod(world, output_directory: str):
|
||||||
locale_template = template_env.get_template(r"locale/en/locale.cfg")
|
locale_template = template_env.get_template(r"locale/en/locale.cfg")
|
||||||
control_template = template_env.get_template("control.lua")
|
control_template = template_env.get_template("control.lua")
|
||||||
# get data for templates
|
# get data for templates
|
||||||
player_names = {x: multiworld.player_names[x][0] for x in multiworld.player_ids}
|
player_names = {x: multiworld.player_name[x] for x in multiworld.player_ids}
|
||||||
locations = []
|
locations = []
|
||||||
for location in multiworld.get_filled_locations(player):
|
for location in multiworld.get_filled_locations(player):
|
||||||
if location.address:
|
if location.address:
|
||||||
locations.append((location.name, location.item.name, location.item.player, location.item.advancement))
|
locations.append((location.name, location.item.name, location.item.player, location.item.advancement))
|
||||||
mod_name = f"AP-{multiworld.seed_name}-P{player}-{multiworld.player_names[player][0]}"
|
mod_name = f"AP-{multiworld.seed_name}-P{player}-{multiworld.player_name[player]}"
|
||||||
tech_cost_scale = {0: 0.1,
|
tech_cost_scale = {0: 0.1,
|
||||||
1: 0.25,
|
1: 0.25,
|
||||||
2: 0.5,
|
2: 0.5,
|
||||||
|
@ -87,7 +87,7 @@ def generate_mod(world, output_directory: str):
|
||||||
"mod_name": mod_name, "allowed_science_packs": multiworld.max_science_pack[player].get_allowed_packs(),
|
"mod_name": mod_name, "allowed_science_packs": multiworld.max_science_pack[player].get_allowed_packs(),
|
||||||
"tech_cost_scale": tech_cost_scale, "custom_technologies": multiworld.worlds[player].custom_technologies,
|
"tech_cost_scale": tech_cost_scale, "custom_technologies": multiworld.worlds[player].custom_technologies,
|
||||||
"tech_tree_layout_prerequisites": multiworld.tech_tree_layout_prerequisites[player],
|
"tech_tree_layout_prerequisites": multiworld.tech_tree_layout_prerequisites[player],
|
||||||
"slot_name": multiworld.player_names[player][0], "seed_name": multiworld.seed_name,
|
"slot_name": multiworld.player_name[player], "seed_name": multiworld.seed_name,
|
||||||
"starting_items": multiworld.starting_items[player], "recipes": recipes,
|
"starting_items": multiworld.starting_items[player], "recipes": recipes,
|
||||||
"random": random, "flop_random": flop_random,
|
"random": random, "flop_random": flop_random,
|
||||||
"static_nodes": multiworld.worlds[player].static_nodes,
|
"static_nodes": multiworld.worlds[player].static_nodes,
|
||||||
|
|
|
@ -103,14 +103,14 @@ class RecipeTime(Choice):
|
||||||
class Progressive(Choice):
|
class Progressive(Choice):
|
||||||
displayname = "Progressive Technologies"
|
displayname = "Progressive Technologies"
|
||||||
option_off = 0
|
option_off = 0
|
||||||
option_random = 1
|
option_grouped_random = 1
|
||||||
option_on = 2
|
option_on = 2
|
||||||
alias_false = 0
|
alias_false = 0
|
||||||
alias_true = 2
|
alias_true = 2
|
||||||
default = 2
|
default = 2
|
||||||
|
|
||||||
def want_progressives(self, random):
|
def want_progressives(self, random):
|
||||||
return random.choice([True, False]) if self.value == self.option_random else int(self.value)
|
return random.choice([True, False]) if self.value == self.option_grouped_random else int(self.value)
|
||||||
|
|
||||||
|
|
||||||
class RecipeIngredients(Choice):
|
class RecipeIngredients(Choice):
|
||||||
|
|
|
@ -13,7 +13,7 @@ def link_minecraft_structures(world, player):
|
||||||
try:
|
try:
|
||||||
assert len(exits) == len(structs)
|
assert len(exits) == len(structs)
|
||||||
except AssertionError as e: # this should never happen
|
except AssertionError as e: # this should never happen
|
||||||
raise Exception(f"Could not obtain equal numbers of Minecraft exits and structures for player {player} ({world.player_names[player]})")
|
raise Exception(f"Could not obtain equal numbers of Minecraft exits and structures for player {player} ({world.player_name[player]})")
|
||||||
|
|
||||||
pairs = {}
|
pairs = {}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ def link_minecraft_structures(world, player):
|
||||||
exits.remove(exit)
|
exits.remove(exit)
|
||||||
structs.remove(struct)
|
structs.remove(struct)
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Invalid connection: {exit} => {struct} for player {player} ({world.player_names[player]})")
|
raise Exception(f"Invalid connection: {exit} => {struct} for player {player} ({world.player_name[player]})")
|
||||||
|
|
||||||
# Connect plando structures first
|
# Connect plando structures first
|
||||||
if world.plando_connections[player]:
|
if world.plando_connections[player]:
|
||||||
|
@ -38,7 +38,7 @@ def link_minecraft_structures(world, player):
|
||||||
try:
|
try:
|
||||||
exit = world.random.choice([e for e in exits if e not in illegal_connections.get(struct, [])])
|
exit = world.random.choice([e for e in exits if e not in illegal_connections.get(struct, [])])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise Exception(f"No valid structure placements remaining for player {player} ({world.player_names[player]})")
|
raise Exception(f"No valid structure placements remaining for player {player} ({world.player_name[player]})")
|
||||||
set_pair(exit, struct)
|
set_pair(exit, struct)
|
||||||
else: # write remaining default connections
|
else: # write remaining default connections
|
||||||
for (exit, struct) in default_connections:
|
for (exit, struct) in default_connections:
|
||||||
|
@ -49,7 +49,7 @@ def link_minecraft_structures(world, player):
|
||||||
try:
|
try:
|
||||||
assert len(exits) == len(structs) == 0
|
assert len(exits) == len(structs) == 0
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
raise Exception(f"Failed to connect all Minecraft structures for player {player} ({world.player_names[player]})")
|
raise Exception(f"Failed to connect all Minecraft structures for player {player} ({world.player_name[player]})")
|
||||||
|
|
||||||
for exit in exits_spoiler:
|
for exit in exits_spoiler:
|
||||||
world.get_entrance(exit, player).connect(world.get_region(pairs[exit], player))
|
world.get_entrance(exit, player).connect(world.get_region(pairs[exit], player))
|
||||||
|
|
|
@ -43,7 +43,7 @@ class MinecraftLogic(LogicMixin):
|
||||||
|
|
||||||
# Difficulty-dependent functions
|
# Difficulty-dependent functions
|
||||||
def _mc_combat_difficulty(self, player: int):
|
def _mc_combat_difficulty(self, player: int):
|
||||||
return self.world.combat_difficulty[player].get_current_option_name().lower()
|
return self.world.combat_difficulty[player].current_key
|
||||||
|
|
||||||
def _mc_can_adventure(self, player: int):
|
def _mc_can_adventure(self, player: int):
|
||||||
if self._mc_combat_difficulty(player) == 'easy':
|
if self._mc_combat_difficulty(player) == 'easy':
|
||||||
|
|
|
@ -30,7 +30,7 @@ class MinecraftWorld(World):
|
||||||
return {
|
return {
|
||||||
'world_seed': self.world.slot_seeds[self.player].getrandbits(32),
|
'world_seed': self.world.slot_seeds[self.player].getrandbits(32),
|
||||||
'seed_name': self.world.seed_name,
|
'seed_name': self.world.seed_name,
|
||||||
'player_name': self.world.get_player_names(self.player),
|
'player_name': self.world.get_player_name(self.player),
|
||||||
'player_id': self.player,
|
'player_id': self.player,
|
||||||
'client_version': client_version,
|
'client_version': client_version,
|
||||||
'structures': {exit: self.world.get_entrance(exit, self.player).connected_region.name for exit in exits},
|
'structures': {exit: self.world.get_entrance(exit, self.player).connected_region.name for exit in exits},
|
||||||
|
@ -95,7 +95,7 @@ class MinecraftWorld(World):
|
||||||
|
|
||||||
def generate_output(self, output_directory: str):
|
def generate_output(self, output_directory: str):
|
||||||
data = self._get_mc_data()
|
data = self._get_mc_data()
|
||||||
filename = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_player_names(self.player)}.apmc"
|
filename = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_player_name(self.player)}.apmc"
|
||||||
with open(os.path.join(output_directory, filename), 'wb') as f:
|
with open(os.path.join(output_directory, filename), 'wb') as f:
|
||||||
f.write(b64encode(bytes(json.dumps(data), 'utf-8')))
|
f.write(b64encode(bytes(json.dumps(data), 'utf-8')))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue