LttP: move game specific fill to new AutoWorld fill_hook
This commit is contained in:
parent
299036ecca
commit
9ec0680ce5
54
Fill.py
54
Fill.py
|
@ -7,6 +7,7 @@ from BaseClasses import CollectionState, Location, MultiWorld
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory
|
||||||
from worlds.alttp.Regions import key_drop_data
|
from worlds.alttp.Regions import key_drop_data
|
||||||
from worlds.generic import PlandoItem
|
from worlds.generic import PlandoItem
|
||||||
|
from worlds.AutoWorld import call_all
|
||||||
|
|
||||||
|
|
||||||
class FillError(RuntimeError):
|
class FillError(RuntimeError):
|
||||||
|
@ -69,7 +70,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations,
|
||||||
itempool.extend(unplaced_items)
|
itempool.extend(unplaced_items)
|
||||||
|
|
||||||
|
|
||||||
def distribute_items_restrictive(world: MultiWorld, gftower_trash=False, fill_locations=None):
|
def distribute_items_restrictive(world: MultiWorld, fill_locations=None):
|
||||||
# If not passed in, then get a shuffled list of locations to fill in
|
# If not passed in, then get a shuffled list of locations to fill in
|
||||||
if not fill_locations:
|
if not fill_locations:
|
||||||
fill_locations = world.get_unfilled_locations()
|
fill_locations = world.get_unfilled_locations()
|
||||||
|
@ -92,51 +93,9 @@ def distribute_items_restrictive(world: MultiWorld, gftower_trash=False, fill_lo
|
||||||
else:
|
else:
|
||||||
restitempool.append(item)
|
restitempool.append(item)
|
||||||
|
|
||||||
standard_keyshuffle_players = set()
|
|
||||||
|
|
||||||
# fill in gtower locations with trash first
|
|
||||||
for player in world.get_game_players("A Link to the Past"):
|
|
||||||
if not gftower_trash or not world.ganonstower_vanilla[player] or \
|
|
||||||
world.logic[player] in {'owglitches', 'hybridglitches', "nologic"}:
|
|
||||||
gtower_trash_count = 0
|
|
||||||
elif 'triforcehunt' in world.goal[player] and ('local' in world.goal[player] or world.players == 1):
|
|
||||||
gtower_trash_count = world.random.randint(world.crystals_needed_for_gt[player] * 2,
|
|
||||||
world.crystals_needed_for_gt[player] * 4)
|
|
||||||
else:
|
|
||||||
gtower_trash_count = world.random.randint(0, world.crystals_needed_for_gt[player] * 2)
|
|
||||||
|
|
||||||
if gtower_trash_count:
|
|
||||||
gtower_locations = [location for location in fill_locations if
|
|
||||||
'Ganons Tower' in location.name and location.player == player]
|
|
||||||
world.random.shuffle(gtower_locations)
|
|
||||||
trashcnt = 0
|
|
||||||
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 trashcnt < gtower_trash_count:
|
|
||||||
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)
|
|
||||||
trashcnt += 1
|
|
||||||
if world.mode[player] == 'standard' and world.keyshuffle[player] is True:
|
|
||||||
standard_keyshuffle_players.add(player)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
world.random.shuffle(fill_locations)
|
world.random.shuffle(fill_locations)
|
||||||
|
call_all(world, "fill_hook", progitempool, nonexcludeditempool, localrestitempool, restitempool, fill_locations)
|
||||||
|
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool)
|
fill_restrictive(world, world.state, fill_locations, progitempool)
|
||||||
|
|
||||||
if nonexcludeditempool:
|
if nonexcludeditempool:
|
||||||
|
@ -167,11 +126,8 @@ def distribute_items_restrictive(world: MultiWorld, gftower_trash=False, fill_lo
|
||||||
unplaced = [item for item in progitempool + restitempool]
|
unplaced = [item for item in progitempool + restitempool]
|
||||||
unfilled = [location.name for location in fill_locations]
|
unfilled = [location.name for location in fill_locations]
|
||||||
|
|
||||||
for location in fill_locations:
|
|
||||||
world.push_item(location, ItemFactory('Nothing', location.player), False)
|
|
||||||
|
|
||||||
if unplaced or unfilled:
|
if unplaced or unfilled:
|
||||||
logging.warning(f'Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}')
|
raise FillError(f'Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}')
|
||||||
|
|
||||||
|
|
||||||
def fast_fill(world: MultiWorld, item_pool: typing.List, fill_locations: typing.List) -> typing.Tuple[typing.List, typing.List]:
|
def fast_fill(world: MultiWorld, item_pool: typing.List, fill_locations: typing.List) -> typing.Tuple[typing.List, typing.List]:
|
||||||
|
|
6
Main.py
6
Main.py
|
@ -218,12 +218,8 @@ def main(args, seed=None):
|
||||||
|
|
||||||
if world.algorithm == 'flood':
|
if world.algorithm == 'flood':
|
||||||
flood_items(world) # different algo, biased towards early game progress items
|
flood_items(world) # different algo, biased towards early game progress items
|
||||||
elif world.algorithm == 'vt25':
|
|
||||||
distribute_items_restrictive(world, False)
|
|
||||||
elif world.algorithm == 'vt26':
|
|
||||||
distribute_items_restrictive(world, True)
|
|
||||||
elif world.algorithm == 'balanced':
|
elif world.algorithm == 'balanced':
|
||||||
distribute_items_restrictive(world, True)
|
distribute_items_restrictive(world)
|
||||||
|
|
||||||
logger.info("Filling Shop Slots")
|
logger.info("Filling Shop Slots")
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import annotations
|
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):
|
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."""
|
"""Optional method that is supposed to be used for special fill stages. This is run *after* plando."""
|
||||||
pass
|
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):
|
def generate_output(self, output_directory: str):
|
||||||
"""This method gets called from a threadpool, do not use world.random here.
|
"""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."""
|
If you need any last-second randomization, use MultiWorld.slot_seeds[slot] instead."""
|
||||||
|
|
|
@ -143,6 +143,7 @@ class HeartColor(Choice):
|
||||||
# remove when this becomes a base Choice feature
|
# remove when this becomes a base Choice feature
|
||||||
if text == "random":
|
if text == "random":
|
||||||
return cls(random.randint(0, 3))
|
return cls(random.randint(0, 3))
|
||||||
|
return super(HeartColor, cls).from_text(text)
|
||||||
|
|
||||||
class QuickSwap(DefaultOnToggle):
|
class QuickSwap(DefaultOnToggle):
|
||||||
displayname = "L/R Quickswapping"
|
displayname = "L/R Quickswapping"
|
||||||
|
|
|
@ -184,7 +184,7 @@ class ALTTPWorld(World):
|
||||||
def generate_output(self, output_directory: str):
|
def generate_output(self, output_directory: str):
|
||||||
world = self.world
|
world = self.world
|
||||||
player = self.player
|
player = self.player
|
||||||
|
try:
|
||||||
use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[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.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
|
||||||
or world.shufflepots[player] or world.bush_shuffle[player]
|
or world.shufflepots[player] or world.bush_shuffle[player]
|
||||||
|
@ -231,12 +231,18 @@ class ALTTPWorld(World):
|
||||||
Patch.create_patch_file(rompath, player=player, player_name=world.player_name[player])
|
Patch.create_patch_file(rompath, player=player, player_name=world.player_name[player])
|
||||||
os.unlink(rompath)
|
os.unlink(rompath)
|
||||||
self.rom_name = rom.name
|
self.rom_name = rom.name
|
||||||
self.rom_name_available_event.set()
|
except:
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
self.rom_name_available_event.set() # make sure threading continues and errors are collected
|
||||||
|
|
||||||
def modify_multidata(self, multidata: dict):
|
def modify_multidata(self, multidata: dict):
|
||||||
import base64
|
import base64
|
||||||
# wait for self.rom_name to be available.
|
# wait for self.rom_name to be available.
|
||||||
self.rom_name_available_event.wait()
|
self.rom_name_available_event.wait()
|
||||||
|
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()
|
new_name = base64.b64encode(bytes(self.rom_name)).decode()
|
||||||
payload = multidata["connect_names"][self.world.player_name[self.player]]
|
payload = multidata["connect_names"][self.world.player_name[self.player]]
|
||||||
multidata["connect_names"][new_name] = payload
|
multidata["connect_names"][new_name] = payload
|
||||||
|
@ -248,4 +254,51 @@ class ALTTPWorld(World):
|
||||||
def create_item(self, name: str) -> Item:
|
def create_item(self, name: str) -> Item:
|
||||||
return ALttPItem(name, self.player, **as_dict_item_table[name])
|
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
|
||||||
|
|
|
@ -231,7 +231,6 @@ def set_location_rule(world, player, loc):
|
||||||
|
|
||||||
|
|
||||||
def set_rules(world, player):
|
def set_rules(world, player):
|
||||||
logging.warning(type(location_table))
|
|
||||||
for loc in location_table:
|
for loc in location_table:
|
||||||
set_location_rule(world, player, loc)
|
set_location_rule(world, player, loc)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue