LttP: move game specific fill to new AutoWorld fill_hook

This commit is contained in:
Fabian Dill
2021-08-10 09:03:44 +02:00
parent 299036ecca
commit 9ec0680ce5
6 changed files with 111 additions and 100 deletions

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from typing import Dict, Set, Tuple
from typing import Dict, Set, Tuple, List
from BaseClasses import MultiWorld, Item, CollectionState
from BaseClasses import MultiWorld, Item, CollectionState, Location
class AutoWorldRegister(type):
@@ -126,6 +126,12 @@ class World(metaclass=AutoWorldRegister):
"""Optional method that is supposed to be used for special fill stages. This is run *after* plando."""
pass
def fill_hook(cls, progitempool: List[Item], nonexcludeditempool: List[Item],
localrestitempool: Dict[int, List[Item]], restitempool: List[Item], fill_locations: List[Location]):
"""Special method that gets called as part of distribute_items_restrictive (main fill).
This gets called once per present world type."""
pass
def generate_output(self, output_directory: str):
"""This method gets called from a threadpool, do not use world.random here.
If you need any last-second randomization, use MultiWorld.slot_seeds[slot] instead."""

View File

@@ -143,6 +143,7 @@ class HeartColor(Choice):
# remove when this becomes a base Choice feature
if text == "random":
return cls(random.randint(0, 3))
return super(HeartColor, cls).from_text(text)
class QuickSwap(DefaultOnToggle):
displayname = "L/R Quickswapping"

View File

@@ -184,63 +184,69 @@ class ALTTPWorld(World):
def generate_output(self, output_directory: str):
world = self.world
player = self.player
try:
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])
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)
rom = LocalRom(world.alttp_rom)
patch_rom(world, rom, player, use_enemizer)
patch_rom(world, rom, player, use_enemizer)
if use_enemizer:
patch_enemizer(world, player, rom, world.enemizer, output_directory)
if use_enemizer:
patch_enemizer(world, player, rom, world.enemizer, output_directory)
if world.is_race:
patch_race_rom(rom, world, player)
if world.is_race:
patch_race_rom(rom, world, player)
world.spoiler.hashes[player] = get_hash_string(rom.hash)
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()}
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)
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 ''
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()
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
except:
raise
finally:
self.rom_name_available_event.set() # make sure threading continues and errors are collected
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]])
rom_name = getattr(self, "rom_name", None)
# we skip in case of error, so that the original error in the output thread is the one that gets raised
if rom_name:
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())
@@ -248,4 +254,51 @@ class ALTTPWorld(World):
def create_item(self, name: str) -> Item:
return ALttPItem(name, self.player, **as_dict_item_table[name])
@classmethod
def stage_fill_hook(cls, world, progitempool, nonexcludeditempool, localrestitempool, restitempool, fill_locations):
trash_counts = {}
standard_keyshuffle_players = set()
for player in world.get_game_players("A Link to the Past"):
if world.mode[player] == 'standard' and world.keyshuffle[player] is True:
standard_keyshuffle_players.add(player)
if not world.ganonstower_vanilla[player] or \
world.logic[player] in {'owglitches', 'hybridglitches', "nologic"}:
pass
elif 'triforcehunt' in world.goal[player] and ('local' in world.goal[player] or world.players == 1):
trash_counts[player] = world.random.randint(world.crystals_needed_for_gt[player] * 2,
world.crystals_needed_for_gt[player] * 4)
else:
trash_counts[player] = world.random.randint(0, world.crystals_needed_for_gt[player] * 2)
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
if standard_keyshuffle_players:
progitempool.sort(
key=lambda item: 1 if item.name == 'Small Key (Hyrule Castle)' and
item.player in standard_keyshuffle_players else 0)
if trash_counts:
locations_mapping = {player: [] for player in trash_counts}
for location in fill_locations:
if 'Ganons Tower' in location.name and location.player in locations_mapping:
locations_mapping[location.player].append(location)
for player, trash_count in trash_counts.items():
gtower_locations = locations_mapping[player]
world.random.shuffle(gtower_locations)
localrest = localrestitempool[player]
if localrest:
gt_item_pool = restitempool + localrest
world.random.shuffle(gt_item_pool)
else:
gt_item_pool = restitempool.copy()
while gtower_locations and gt_item_pool and trash_count > 0:
spot_to_fill = gtower_locations.pop()
item_to_place = gt_item_pool.pop()
if item_to_place in localrest:
localrest.remove(item_to_place)
else:
restitempool.remove(item_to_place)
world.push_item(spot_to_fill, item_to_place, False)
fill_locations.remove(spot_to_fill) # very slow, unfortunately
trash_count -= 1

View File

@@ -231,7 +231,6 @@ def set_location_rule(world, player, loc):
def set_rules(world, player):
logging.warning(type(location_table))
for loc in location_table:
set_location_rule(world, player, loc)