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():
|
||||
debug_types = False
|
||||
player_names: Dict[int, List[str]]
|
||||
player_name: Dict[int, str]
|
||||
_region_cache: Dict[int, Dict[str, Region]]
|
||||
difficulty_requirements: dict
|
||||
required_medallions: dict
|
||||
|
@ -36,7 +36,6 @@ class MultiWorld():
|
|||
def __init__(self, players: int):
|
||||
self.random = random.Random() # world-local random state is saved for multiple generations running concurrently
|
||||
self.players = players
|
||||
self.teams = 1
|
||||
self.glitch_triforce = False
|
||||
self.algorithm = 'balanced'
|
||||
self.dungeons = []
|
||||
|
@ -83,11 +82,9 @@ class MultiWorld():
|
|||
set_player_attr('item_functionality', 'normal')
|
||||
set_player_attr('timer', False)
|
||||
set_player_attr('goal', 'ganon')
|
||||
set_player_attr('progressive', 'on')
|
||||
set_player_attr('accessibility', 'items')
|
||||
set_player_attr('retro', False)
|
||||
set_player_attr('hints', True)
|
||||
set_player_attr('player_names', [])
|
||||
set_player_attr('required_medallions', ['Ether', 'Quake'])
|
||||
set_player_attr('swamp_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)
|
||||
|
||||
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:
|
||||
return ", ".join(self.player_names[player])
|
||||
def get_player_name(self, player: int) -> str:
|
||||
return self.player_name[player]
|
||||
|
||||
def initialize_regions(self, regions=None):
|
||||
for region in regions if regions else self.regions:
|
||||
|
@ -174,7 +171,7 @@ class MultiWorld():
|
|||
|
||||
@functools.cached_property
|
||||
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):
|
||||
"""Rebuild world cache"""
|
||||
|
@ -1132,8 +1129,8 @@ class Spoiler():
|
|||
def parse_data(self):
|
||||
self.medallions = OrderedDict()
|
||||
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'Turtle Rock ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][1]
|
||||
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_name(player)})'] = self.world.required_medallions[player][1]
|
||||
|
||||
self.startinventory = list(map(str, self.world.precollected_items))
|
||||
|
||||
|
@ -1241,7 +1238,6 @@ class Spoiler():
|
|||
'progressive': self.world.progressive,
|
||||
'shufflepots': self.world.shufflepots,
|
||||
'players': self.world.players,
|
||||
'teams': self.world.teams,
|
||||
'progression_balancing': self.world.progression_balancing,
|
||||
'triforce_pieces_available': self.world.triforce_pieces_available,
|
||||
'triforce_pieces_required': self.world.triforce_pieces_required,
|
||||
|
@ -1261,7 +1257,7 @@ class Spoiler():
|
|||
out['Starting Inventory'] = self.startinventory
|
||||
out['Special'] = self.medallions
|
||||
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:
|
||||
out['Shops'] = self.shops
|
||||
out['playthrough'] = self.playthrough
|
||||
|
@ -1286,10 +1282,10 @@ class Spoiler():
|
|||
self.metadata['version'], self.world.seed))
|
||||
outfile.write('Filling Algorithm: %s\n' % self.world.algorithm)
|
||||
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):
|
||||
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])
|
||||
if self.world.players > 1:
|
||||
outfile.write('Progression Balanced: %s\n' % (
|
||||
|
@ -1303,11 +1299,7 @@ class Spoiler():
|
|||
outfile.write(f'{displayname + ":":33}{res.get_current_option_name()}\n')
|
||||
|
||||
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' % (
|
||||
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('%s%s\n' % ('Hash: ', self.hashes[player]))
|
||||
|
||||
outfile.write('Logic: %s\n' % self.metadata['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])
|
||||
outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][player])
|
||||
outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player])
|
||||
outfile.write('Item Progression: %s\n' % self.metadata['progressive'][player])
|
||||
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player])
|
||||
if self.metadata['shuffle'][player] != "vanilla":
|
||||
outfile.write('Entrance Shuffle Seed %s\n' % self.metadata['er_seeds'][player])
|
||||
|
@ -1369,7 +1360,7 @@ class Spoiler():
|
|||
self.metadata['shuffle_prizes'][player])
|
||||
if self.entrances:
|
||||
outfile.write('\n\nEntrances:\n\n')
|
||||
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: '
|
||||
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 entry['direction'] == 'both' else
|
||||
'<=' if entry['direction'] == 'exit' else '=>',
|
||||
|
@ -1383,7 +1374,7 @@ class Spoiler():
|
|||
if factorio_players:
|
||||
outfile.write('\n\nRecipes:\n')
|
||||
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():
|
||||
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"):
|
||||
if self.world.boss_shuffle[player] != 'none':
|
||||
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\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()]))
|
||||
|
|
2
Fill.py
2
Fill.py
|
@ -444,4 +444,4 @@ def distribute_planned(world: MultiWorld):
|
|||
except ValueError:
|
||||
placement.warn(f"Could not remove {item} from pool as it's already missing from it.")
|
||||
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.")
|
||||
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('--teams', default=1, type=lambda value: max(int(value), 1))
|
||||
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('--enemizercli', default=defaults["enemizer_path"])
|
||||
|
@ -128,7 +127,6 @@ def main(args=None, callback=ERmain):
|
|||
erargs.skip_playthrough = args.spoiler < 2
|
||||
erargs.outputname = seed_name
|
||||
erargs.outputpath = args.outputpath
|
||||
erargs.teams = args.teams
|
||||
|
||||
# set up logger
|
||||
if args.log_level:
|
||||
|
@ -179,6 +177,8 @@ def main(args=None, callback=ERmain):
|
|||
getattr(erargs, k)[player] = v
|
||||
except AttributeError:
|
||||
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:
|
||||
raise ValueError(f"File {path} is destroyed. Please fix your yaml.") from e
|
||||
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] = 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:
|
||||
import yaml
|
||||
important = {}
|
||||
|
@ -267,7 +265,7 @@ def handle_name(name: str, player: int, name_counter: Counter):
|
|||
name] > 1 else ''),
|
||||
player=player,
|
||||
PLAYER=(player if player > 1 else '')))
|
||||
new_name = new_name.strip().replace(' ', '_')[:16]
|
||||
new_name = new_name.strip()[:16]
|
||||
if new_name == "Archipelago":
|
||||
raise Exception(f"You cannot name yourself \"{new_name}\"")
|
||||
return new_name
|
||||
|
@ -610,7 +608,8 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
|||
ret.enemy_damage = {None: 'default',
|
||||
'default': 'default',
|
||||
'shuffled': 'shuffled',
|
||||
'random': 'chaos'
|
||||
'random': 'chaos', # to be removed
|
||||
'chaos': 'chaos',
|
||||
}[get_choice('enemy_damage', 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.progressive = convert_to_on_off(get_choice('progressive', weights, 'on'))
|
||||
|
||||
ret.shuffle_prizes = get_choice('shuffle_prizes', weights, "g")
|
||||
|
||||
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:
|
||||
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__':
|
||||
import atexit
|
||||
|
|
|
@ -44,7 +44,7 @@ def main():
|
|||
help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
||||
parser.add_argument('--loglevel', default='info', const='info', nargs='?',
|
||||
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'],
|
||||
help='''\
|
||||
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('--update_sprites', action='store_true', help='Update Sprite Database, then exit.')
|
||||
args = parser.parse_args()
|
||||
args.music = not args.disablemusic
|
||||
if args.update_sprites:
|
||||
run_sprite_update()
|
||||
sys.exit()
|
||||
|
@ -150,7 +151,7 @@ def adjust(args):
|
|||
if hasattr(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)
|
||||
path = output_path(f'{os.path.basename(args.rom)[:-4]}_adjusted.sfc')
|
||||
rom.write_to_file(path)
|
||||
|
@ -195,14 +196,14 @@ def adjustGUI():
|
|||
guiargs = Namespace()
|
||||
guiargs.heartbeep = rom_vars.heartbeepVar.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.uw_palettes = rom_vars.uwPalettesVar.get()
|
||||
guiargs.hud_palettes = rom_vars.hudPalettesVar.get()
|
||||
guiargs.sword_palettes = rom_vars.swordPalettesVar.get()
|
||||
guiargs.shield_palettes = rom_vars.shieldPalettesVar.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.rom = romVar2.get()
|
||||
guiargs.baserom = romVar.get()
|
||||
|
@ -439,9 +440,10 @@ def get_rom_options_frame(parent=None):
|
|||
romOptionsFrame.rowconfigure(i, weight=1)
|
||||
vars = Namespace()
|
||||
|
||||
vars.disableMusicVar = IntVar()
|
||||
disableMusicCheckbutton = Checkbutton(romOptionsFrame, text="Disable music", variable=vars.disableMusicVar)
|
||||
disableMusicCheckbutton.grid(row=0, column=0, sticky=E)
|
||||
vars.MusicVar = IntVar()
|
||||
vars.MusicVar.set(1)
|
||||
MusicCheckbutton = Checkbutton(romOptionsFrame, text="Music", variable=vars.MusicVar)
|
||||
MusicCheckbutton.grid(row=0, column=0, sticky=E)
|
||||
|
||||
vars.disableFlashingVar = IntVar(value=1)
|
||||
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.grid(row=1, column=0, sticky=E)
|
||||
|
||||
fastMenuFrame = Frame(romOptionsFrame)
|
||||
fastMenuFrame.grid(row=1, column=1, sticky=E)
|
||||
fastMenuLabel = Label(fastMenuFrame, text='Menu speed')
|
||||
fastMenuLabel.pack(side=LEFT)
|
||||
vars.fastMenuVar = StringVar()
|
||||
vars.fastMenuVar.set('normal')
|
||||
fastMenuOptionMenu = OptionMenu(fastMenuFrame, vars.fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
|
||||
fastMenuOptionMenu.pack(side=LEFT)
|
||||
menuspeedFrame = Frame(romOptionsFrame)
|
||||
menuspeedFrame.grid(row=1, column=1, sticky=E)
|
||||
menuspeedLabel = Label(menuspeedFrame, text='Menu speed')
|
||||
menuspeedLabel.pack(side=LEFT)
|
||||
vars.menuspeedVar = StringVar()
|
||||
vars.menuspeedVar.set('normal')
|
||||
menuspeedOptionMenu = OptionMenu(menuspeedFrame, vars.menuspeedVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
|
||||
menuspeedOptionMenu.pack(side=LEFT)
|
||||
|
||||
heartcolorFrame = Frame(romOptionsFrame)
|
||||
heartcolorFrame.grid(row=2, column=0, sticky=E)
|
||||
|
@ -518,7 +520,7 @@ def get_rom_options_frame(parent=None):
|
|||
owPalettesLabel.pack(side=LEFT)
|
||||
vars.owPalettesVar = StringVar()
|
||||
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)
|
||||
|
||||
uwPalettesFrame = Frame(romOptionsFrame)
|
||||
|
@ -527,7 +529,7 @@ def get_rom_options_frame(parent=None):
|
|||
uwPalettesLabel.pack(side=LEFT)
|
||||
vars.uwPalettesVar = StringVar()
|
||||
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)
|
||||
|
||||
hudPalettesFrame = Frame(romOptionsFrame)
|
||||
|
@ -536,7 +538,7 @@ def get_rom_options_frame(parent=None):
|
|||
hudPalettesLabel.pack(side=LEFT)
|
||||
vars.hudPalettesVar = StringVar()
|
||||
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)
|
||||
|
||||
swordPalettesFrame = Frame(romOptionsFrame)
|
||||
|
@ -545,7 +547,7 @@ def get_rom_options_frame(parent=None):
|
|||
swordPalettesLabel.pack(side=LEFT)
|
||||
vars.swordPalettesVar = StringVar()
|
||||
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)
|
||||
|
||||
shieldPalettesFrame = Frame(romOptionsFrame)
|
||||
|
@ -554,7 +556,7 @@ def get_rom_options_frame(parent=None):
|
|||
shieldPalettesLabel.pack(side=LEFT)
|
||||
vars.shieldPalettesVar = StringVar()
|
||||
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)
|
||||
|
||||
spritePoolFrame = Frame(romOptionsFrame)
|
||||
|
|
107
Main.py
107
Main.py
|
@ -10,17 +10,15 @@ import tempfile
|
|||
import zipfile
|
||||
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.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 worlds.alttp.Shops import ShopSlotFill, SHOP_ID_START, total_shop_slots, FillDisabledShopSlots
|
||||
from worlds.alttp.ItemPool import difficulties, fill_prizes
|
||||
from Utils import output_path, parse_player_names, get_options, __version__, version_tuple
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from Utils import output_path, get_options, __version__, version_tuple
|
||||
from worlds.generic.Rules import locality_rules, exclusion_rules
|
||||
from worlds import AutoWorld
|
||||
import Patch
|
||||
|
||||
seeddigits = 20
|
||||
|
||||
|
@ -66,7 +64,6 @@ def main(args, seed=None):
|
|||
world.difficulty = args.difficulty.copy()
|
||||
world.item_functionality = args.item_functionality.copy()
|
||||
world.timer = args.timer.copy()
|
||||
world.progressive = args.progressive.copy()
|
||||
world.goal = args.goal.copy()
|
||||
world.local_items = args.local_items.copy()
|
||||
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.green_clock_time = args.green_clock_time.copy()
|
||||
world.shufflepots = args.shufflepots.copy()
|
||||
world.progressive = args.progressive.copy()
|
||||
world.dungeon_counters = args.dungeon_counters.copy()
|
||||
world.glitch_boots = args.glitch_boots.copy()
|
||||
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
||||
|
@ -117,6 +113,10 @@ def main(args, seed=None):
|
|||
world.required_medallions = args.required_medallions.copy()
|
||||
world.game = args.game.copy()
|
||||
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.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():
|
||||
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('')
|
||||
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.')
|
||||
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()
|
||||
|
||||
output = tempfile.TemporaryDirectory()
|
||||
with output as temp_dir:
|
||||
check_accessibility_task = pool.submit(world.fulfills_accessibility)
|
||||
rom_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:
|
||||
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):
|
||||
for entrance in region.entrances:
|
||||
|
@ -365,12 +313,8 @@ def main(args, seed=None):
|
|||
|
||||
FillDisabledShopSlots(world)
|
||||
|
||||
def write_multidata(roms, outputs):
|
||||
import base64
|
||||
def write_multidata():
|
||||
import NetUtils
|
||||
for future in roms:
|
||||
rom_name = future.result()
|
||||
rom_names.append(rom_name)
|
||||
slot_data = {}
|
||||
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:
|
||||
client_versions[slot] = world.worlds[slot].get_required_client_version()
|
||||
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)}
|
||||
for item in world.precollected_items:
|
||||
precollected_items[item.player].append(item.code)
|
||||
|
@ -390,11 +332,6 @@ def main(args, seed=None):
|
|||
if world.tech_tree_information[player].value == 2:
|
||||
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:
|
||||
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.item.player].add(hint)
|
||||
|
||||
multidata = zlib.compress(pickle.dumps({
|
||||
multidata = {
|
||||
"slot_data": slot_data,
|
||||
"games": games,
|
||||
"names": parsed_names,
|
||||
"connect_names": connect_names,
|
||||
"names": [{player: name for player, name in world.player_name.items()}],
|
||||
"connect_names": {name: (0, player) for player, name in world.player_name.items()},
|
||||
"remote_items": {player for player in world.player_ids if
|
||||
world.worlds[player].remote_items},
|
||||
"locations": locations_data,
|
||||
|
@ -431,15 +368,17 @@ def main(args, seed=None):
|
|||
"tags": ["AP"],
|
||||
"minimum_versions": minimum_versions,
|
||||
"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(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 world.can_beat_game():
|
||||
raise Exception("Game appears as unbeatable. Aborting.")
|
||||
|
@ -451,8 +390,10 @@ def main(args, seed=None):
|
|||
if not args.skip_playthrough:
|
||||
logger.info('Calculating playthrough.')
|
||||
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))
|
||||
for future in output_file_futures:
|
||||
future.result()
|
||||
zipfilename = output_path(f"AP_{world.seed_name}.zip")
|
||||
logger.info(f'Creating final archive at {zipfilename}.')
|
||||
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:
|
||||
if hasattr(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
|
||||
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()})
|
||||
options.update(new_options)
|
||||
|
||||
|
@ -47,7 +49,12 @@ class Option(metaclass=AssembleOptions):
|
|||
def __hash__(self):
|
||||
return hash(self.value)
|
||||
|
||||
@property
|
||||
def current_key(self) -> str:
|
||||
return self.name_lookup[self.value]
|
||||
|
||||
def get_current_option_name(self) -> str:
|
||||
"""For display purposes."""
|
||||
return self.get_option_name(self.value)
|
||||
|
||||
def get_option_name(self, value: typing.Any) -> str:
|
||||
|
@ -122,8 +129,13 @@ class Choice(Option):
|
|||
|
||||
@classmethod
|
||||
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():
|
||||
if optionname == text.lower():
|
||||
if optionname == text:
|
||||
return cls(value)
|
||||
raise KeyError(
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
if function.__code__.co_argcount:
|
||||
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.baserom = Patch.get_base_rom_path()
|
||||
adjuster_settings.world = None
|
||||
whitelist = {"disablemusic", "fastmenu", "heartbeep", "heartcolor", "ow_palettes", "quickswap",
|
||||
whitelist = {"music", "menuspeed", "heartbeep", "heartcolor", "ow_palettes", "quickswap",
|
||||
"uw_palettes", "sprite"}
|
||||
printed_options = {name: value for name, value in vars(adjuster_settings).items() if name in whitelist}
|
||||
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] = 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)
|
||||
|
||||
return upload_to_db(target.name, owner, sid, race)
|
||||
|
|
|
@ -45,9 +45,6 @@ server_options:
|
|||
log_network: 0
|
||||
# Options for Generation
|
||||
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
|
||||
enemizer_path: "EnemizerCLI/EnemizerCLI.Core.exe"
|
||||
# Folder from which the player yaml files are pulled from
|
||||
|
|
|
@ -106,7 +106,7 @@ Factorio:
|
|||
progressive:
|
||||
on: 1
|
||||
off: 0
|
||||
random: 0
|
||||
grouped_random: 0
|
||||
tech_tree_information:
|
||||
none: 0
|
||||
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)
|
||||
on: 50 # All 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:
|
||||
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
|
||||
|
@ -596,7 +596,7 @@ A Link to the Past:
|
|||
off: 0
|
||||
ow_palettes: # Change the colors of the overworld
|
||||
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
|
||||
grayscale: 0
|
||||
negative: 0
|
||||
|
@ -606,7 +606,7 @@ A Link to the Past:
|
|||
puke: 0
|
||||
uw_palettes: # Change the colors of caves and dungeons
|
||||
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
|
||||
grayscale: 0
|
||||
negative: 0
|
||||
|
@ -616,7 +616,7 @@ A Link to the Past:
|
|||
puke: 0
|
||||
hud_palettes: # Change the colors of the hud
|
||||
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
|
||||
grayscale: 0
|
||||
negative: 0
|
||||
|
@ -626,7 +626,7 @@ A Link to the Past:
|
|||
puke: 0
|
||||
sword_palettes: # Change the colors of swords
|
||||
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
|
||||
grayscale: 0
|
||||
negative: 0
|
||||
|
@ -636,7 +636,7 @@ A Link to the Past:
|
|||
puke: 0
|
||||
shield_palettes: # Change the colors of shields
|
||||
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
|
||||
grayscale: 0
|
||||
negative: 0
|
||||
|
@ -693,7 +693,7 @@ linked_options:
|
|||
singularity: 1
|
||||
enemy_damage:
|
||||
shuffled: 1
|
||||
random: 1
|
||||
chaos: 1
|
||||
enemy_health:
|
||||
easy: 1
|
||||
hard: 1
|
||||
|
|
|
@ -51,7 +51,15 @@ def call_all(world: MultiWorld, method_name: str, *args):
|
|||
for world_type in world_types:
|
||||
stage_callable = getattr(world_type, f"stage_{method_name}", None)
|
||||
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):
|
||||
|
@ -127,6 +135,10 @@ class World(metaclass=AutoWorldRegister):
|
|||
"""Fill in the slot_data field in the Connected network package."""
|
||||
return {}
|
||||
|
||||
def modify_multidata(self, multidata: dict):
|
||||
"""For deeper modification of server multidata."""
|
||||
pass
|
||||
|
||||
def get_required_client_version(self) -> Tuple[int, int, int]:
|
||||
return 0, 0, 3
|
||||
|
||||
|
|
|
@ -143,20 +143,7 @@ def parse_arguments(argv, no_defaults=False):
|
|||
off.
|
||||
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='?',
|
||||
choices=['freshness', 'flood', 'vt25', 'vt26', 'balanced'],
|
||||
help='''\
|
||||
|
@ -218,22 +205,7 @@ def parse_arguments(argv, no_defaults=False):
|
|||
--seed given will produce the same 10 (different) roms each
|
||||
time).
|
||||
''', 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),
|
||||
help='Maps are no longer restricted to their dungeons, but can be anywhere',
|
||||
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
|
||||
included entrance shuffle pool.
|
||||
''', 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='''\
|
||||
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',
|
||||
'local_items', 'non_local_items', 'retro', 'accessibility', 'hints', 'beemizer',
|
||||
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
||||
'heartbeep', "progression_balancing", "triforce_pieces_available",
|
||||
'sprite',
|
||||
"progression_balancing", "triforce_pieces_available",
|
||||
"triforce_pieces_required", "shop_shuffle",
|
||||
"required_medallions", "start_hints",
|
||||
"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',
|
||||
'restrict_dungeon_item_on_boss', 'reduceflashing', 'game',
|
||||
'hud_palettes', 'sword_palettes', 'shield_palettes', 'link_palettes', 'triforcehud']:
|
||||
'restrict_dungeon_item_on_boss', 'game']:
|
||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||
if player == 1:
|
||||
setattr(ret, name, {1: value})
|
||||
|
|
|
@ -1064,7 +1064,7 @@ def link_entrances(world, player):
|
|||
connect_doors(world, single_doors, door_targets, player)
|
||||
else:
|
||||
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
|
||||
if world.logic[player] in ['hybridglitches', 'nologic']:
|
||||
|
|
|
@ -399,7 +399,7 @@ def generate_itempool(world):
|
|||
if additional_triforce_pieces:
|
||||
if additional_triforce_pieces > len(nonprogressionitems):
|
||||
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
|
||||
nonprogressionitems.sort(key=lambda item: int("Heart" in item.name)) # try to keep hearts in the pool
|
||||
nonprogressionitems = nonprogressionitems[additional_triforce_pieces:]
|
||||
|
@ -563,16 +563,14 @@ def get_pool_core(world, player: int):
|
|||
assert loc not in placed_items
|
||||
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
|
||||
if logic in {'owglitches', 'hybridglitches', 'nologic'} and world.glitch_boots[player] and goal != 'icerodhunt':
|
||||
precollected_items.append('Pegasus Boots')
|
||||
pool.remove('Pegasus Boots')
|
||||
pool.append('Rupees (20)')
|
||||
want_progressives = world.progressive[player].want_progressives
|
||||
|
||||
if want_progressives():
|
||||
if want_progressives(world.random):
|
||||
pool.extend(diff.progressiveglove)
|
||||
else:
|
||||
pool.extend(diff.basicglove)
|
||||
|
@ -599,22 +597,22 @@ def get_pool_core(world, player: int):
|
|||
thisbottle = world.random.choice(diff.bottles)
|
||||
pool.append(thisbottle)
|
||||
|
||||
if want_progressives():
|
||||
if want_progressives(world.random):
|
||||
pool.extend(diff.progressiveshield)
|
||||
else:
|
||||
pool.extend(diff.basicshield)
|
||||
|
||||
if want_progressives():
|
||||
if want_progressives(world.random):
|
||||
pool.extend(diff.progressivearmor)
|
||||
else:
|
||||
pool.extend(diff.basicarmor)
|
||||
|
||||
if want_progressives():
|
||||
if want_progressives(world.random):
|
||||
pool.extend(diff.progressivemagic)
|
||||
else:
|
||||
pool.extend(diff.basicmagic)
|
||||
|
||||
if want_progressives():
|
||||
if want_progressives(world.random):
|
||||
pool.extend(diff.progressivebow)
|
||||
elif (swordless or logic == 'noglitches') and goal != 'icerodhunt':
|
||||
swordless_bows = ['Bow', 'Silver Bow']
|
||||
|
@ -627,7 +625,7 @@ def get_pool_core(world, player: int):
|
|||
if swordless:
|
||||
pool.extend(diff.swordless)
|
||||
else:
|
||||
progressive_swords = want_progressives()
|
||||
progressive_swords = want_progressives(world.random)
|
||||
pool.extend(diff.progressivesword if progressive_swords else diff.basicsword)
|
||||
|
||||
extraitems = total_items_to_place - len(pool) - len(placed_items)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import typing
|
||||
|
||||
from Options import Choice, Range, Option
|
||||
from Options import Choice, Range, Option, Toggle, DefaultOnToggle
|
||||
|
||||
|
||||
class Logic(Choice):
|
||||
|
@ -70,8 +70,116 @@ class Enemies(Choice):
|
|||
option_shuffled = 1
|
||||
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)] = {
|
||||
"crystals_needed_for_gt": CrystalsTower,
|
||||
"crystals_needed_for_ganon": CrystalsGanon,
|
||||
"progressive": Progressive,
|
||||
"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)
|
||||
|
||||
|
||||
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)
|
||||
randopatch_path = os.path.abspath(os.path.join(output_directory, f'enemizer_randopatch_{team}_{player}.sfc'))
|
||||
options_path = os.path.abspath(os.path.join(output_directory, f'enemizer_options_{team}_{player}.json'))
|
||||
enemizer_output_path = os.path.abspath(os.path.join(output_directory, f'enemizer_output_{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_{player}.json'))
|
||||
enemizer_output_path = os.path.abspath(os.path.join(output_directory, f'enemizer_output_{player}.sfc'))
|
||||
|
||||
# write options file for enemizer
|
||||
options = {
|
||||
|
@ -756,7 +756,7 @@ def get_nonnative_item_sprite(item: str) -> int:
|
|||
# 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]
|
||||
|
||||
# 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_bytes(0x4BA2A, tile_set.get_bytes())
|
||||
|
||||
write_strings(rom, world, player, team)
|
||||
write_strings(rom, world, player)
|
||||
|
||||
# remote items flag, does not currently work
|
||||
rom.write_byte(0x18637C, int(world.worlds[player].remote_items))
|
||||
|
@ -1654,13 +1654,13 @@ def patch_rom(world, rom, player, team, enemized):
|
|||
# 21 bytes
|
||||
from Main import __version__
|
||||
# TODO: Adjust Enemizer to accept AP and AD
|
||||
rom.name = bytearray(f'BM{__version__.replace(".", "")[0:3]}_{team + 1}_{player}_{world.seed:09}\0', 'utf8')[:21]
|
||||
rom.name = bytearray(f'BM{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21]
|
||||
rom.name.extend([0] * (21 - len(rom.name)))
|
||||
rom.write_bytes(0x7FC0, rom.name)
|
||||
|
||||
# set player names
|
||||
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
|
||||
hashint = int(rom.get_hash(), 16)
|
||||
|
@ -1756,13 +1756,13 @@ def hud_format_text(text):
|
|||
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,
|
||||
triforcehud: str = None):
|
||||
local_random = random if not world else world.slot_seeds[player]
|
||||
|
||||
disable_music: bool = music
|
||||
# enable instant item menu
|
||||
if fastmenu == 'instant':
|
||||
if menuspeed == 'instant':
|
||||
rom.write_byte(0x6DD9A, 0x20)
|
||||
rom.write_byte(0x6DF2A, 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(0x6DF2A, 0x12)
|
||||
rom.write_byte(0x6E0E9, 0x12)
|
||||
if fastmenu == 'instant':
|
||||
if menuspeed == 'instant':
|
||||
rom.write_byte(0x180048, 0xE8)
|
||||
elif fastmenu == 'double':
|
||||
elif menuspeed == 'double':
|
||||
rom.write_byte(0x180048, 0x10)
|
||||
elif fastmenu == 'triple':
|
||||
elif menuspeed == 'triple':
|
||||
rom.write_byte(0x180048, 0x18)
|
||||
elif fastmenu == 'quadruple':
|
||||
elif menuspeed == 'quadruple':
|
||||
rom.write_byte(0x180048, 0x20)
|
||||
elif fastmenu == 'half':
|
||||
elif menuspeed == 'half':
|
||||
rom.write_byte(0x180048, 0x04)
|
||||
else:
|
||||
rom.write_byte(0x180048, 0x08)
|
||||
|
@ -1854,7 +1854,7 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
|
|||
while True:
|
||||
yield ColorF(local_random.random(), local_random.random(), local_random.random())
|
||||
|
||||
if mode == 'random':
|
||||
if mode == 'good':
|
||||
mode = 'maseya'
|
||||
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))
|
||||
|
||||
|
||||
def write_strings(rom, world, player, team):
|
||||
def write_strings(rom, world, player):
|
||||
local_random = world.slot_seeds[player]
|
||||
|
||||
tt = TextTable()
|
||||
|
@ -2098,11 +2098,11 @@ def write_strings(rom, world, player, team):
|
|||
hint = dest.hint_text if dest.hint_text else "something"
|
||||
if dest.player != player:
|
||||
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]:
|
||||
hint += f" in {world.player_names[dest.player][team]}'s world"
|
||||
hint += f" in {world.player_name[dest.player]}'s world"
|
||||
else:
|
||||
hint += f" for {world.player_names[dest.player][team]}"
|
||||
hint += f" for {world.player_name[dest.player]}"
|
||||
return hint
|
||||
|
||||
# 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:
|
||||
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):
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import random
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
|
||||
from BaseClasses import Item, CollectionState
|
||||
from .SubClasses import ALttPItem
|
||||
|
@ -11,6 +13,8 @@ from .Rules import set_rules
|
|||
from .ItemPool import generate_itempool
|
||||
from .Shops import create_shops
|
||||
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 .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
|
||||
|
@ -37,6 +41,8 @@ class ALTTPWorld(World):
|
|||
create_items = generate_itempool
|
||||
|
||||
def create_regions(self):
|
||||
self.rom_name_available_event = threading.Event()
|
||||
|
||||
player = self.player
|
||||
world = self.world
|
||||
if world.open_pyramid[player] == 'goal':
|
||||
|
@ -175,6 +181,67 @@ class ALTTPWorld(World):
|
|||
from .Dungeons import fill_dungeons_restrictive
|
||||
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:
|
||||
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")
|
||||
control_template = template_env.get_template("control.lua")
|
||||
# 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 = []
|
||||
for location in multiworld.get_filled_locations(player):
|
||||
if location.address:
|
||||
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,
|
||||
1: 0.25,
|
||||
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(),
|
||||
"tech_cost_scale": tech_cost_scale, "custom_technologies": multiworld.worlds[player].custom_technologies,
|
||||
"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,
|
||||
"random": random, "flop_random": flop_random,
|
||||
"static_nodes": multiworld.worlds[player].static_nodes,
|
||||
|
|
|
@ -103,14 +103,14 @@ class RecipeTime(Choice):
|
|||
class Progressive(Choice):
|
||||
displayname = "Progressive Technologies"
|
||||
option_off = 0
|
||||
option_random = 1
|
||||
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_random else int(self.value)
|
||||
return random.choice([True, False]) if self.value == self.option_grouped_random else int(self.value)
|
||||
|
||||
|
||||
class RecipeIngredients(Choice):
|
||||
|
|
|
@ -13,7 +13,7 @@ def link_minecraft_structures(world, player):
|
|||
try:
|
||||
assert len(exits) == len(structs)
|
||||
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 = {}
|
||||
|
||||
|
@ -23,7 +23,7 @@ def link_minecraft_structures(world, player):
|
|||
exits.remove(exit)
|
||||
structs.remove(struct)
|
||||
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
|
||||
if world.plando_connections[player]:
|
||||
|
@ -38,7 +38,7 @@ def link_minecraft_structures(world, player):
|
|||
try:
|
||||
exit = world.random.choice([e for e in exits if e not in illegal_connections.get(struct, [])])
|
||||
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)
|
||||
else: # write remaining default connections
|
||||
for (exit, struct) in default_connections:
|
||||
|
@ -49,7 +49,7 @@ def link_minecraft_structures(world, player):
|
|||
try:
|
||||
assert len(exits) == len(structs) == 0
|
||||
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:
|
||||
world.get_entrance(exit, player).connect(world.get_region(pairs[exit], player))
|
||||
|
|
|
@ -43,7 +43,7 @@ class MinecraftLogic(LogicMixin):
|
|||
|
||||
# Difficulty-dependent functions
|
||||
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):
|
||||
if self._mc_combat_difficulty(player) == 'easy':
|
||||
|
|
|
@ -30,7 +30,7 @@ class MinecraftWorld(World):
|
|||
return {
|
||||
'world_seed': self.world.slot_seeds[self.player].getrandbits(32),
|
||||
'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,
|
||||
'client_version': client_version,
|
||||
'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):
|
||||
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:
|
||||
f.write(b64encode(bytes(json.dumps(data), 'utf-8')))
|
||||
|
||||
|
|
Loading…
Reference in New Issue