mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-08 14:58:16 -07:00
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:
@@ -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')))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user