Merge branch 'main' into breaking_changes
# Conflicts: # BaseClasses.py # Mystery.py # WebHostLib/downloads.py # WebHostLib/models.py # WebHostLib/templates/macros.html # WebHostLib/upload.py # worlds/alttp/ItemPool.py # worlds/alttp/Main.py
This commit is contained in:
@@ -135,10 +135,11 @@ def fill_dungeons_restrictive(world):
|
||||
elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
|
||||
item.priority = True
|
||||
|
||||
dungeon_items = [item for item in get_dungeon_item_pool(world) if ((item.smallkey and not world.keyshuffle[item.player])
|
||||
dungeon_items = [item for item in get_dungeon_item_pool(world) if (((item.smallkey and not world.keyshuffle[item.player])
|
||||
or (item.bigkey and not world.bigkeyshuffle[item.player])
|
||||
or (item.map and not world.mapshuffle[item.player])
|
||||
or (item.compass and not world.compassshuffle[item.player]))]
|
||||
or (item.compass and not world.compassshuffle[item.player])
|
||||
) and world.goal[item.player] != 'icerodhunt')] #
|
||||
if dungeon_items:
|
||||
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
||||
sort_order = {"BigKey": 3, "SmallKey": 2}
|
||||
|
||||
@@ -7,6 +7,7 @@ from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.EntranceShuffle import connect_entrance
|
||||
from Fill import FillError, fill_restrictive
|
||||
from worlds.alttp.Items import ItemFactory
|
||||
from Rules import forbid_items_for_player
|
||||
|
||||
# This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
|
||||
# Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
|
||||
@@ -17,6 +18,7 @@ alwaysitems = ['Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod
|
||||
'Cane of Byrna', 'Blue Boomerang', 'Red Boomerang']
|
||||
progressivegloves = ['Progressive Glove'] * 2
|
||||
basicgloves = ['Power Glove', 'Titans Mitts']
|
||||
legacyinsanity = ['Magic Mirror', 'Moon Pearl']
|
||||
|
||||
normalbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Fairy)',
|
||||
'Bottle (Bee)', 'Bottle (Good Bee)']
|
||||
@@ -43,6 +45,7 @@ Difficulty = namedtuple('Difficulty',
|
||||
['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield',
|
||||
'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'progressivemagic', 'basicmagic',
|
||||
'progressivesword', 'basicsword', 'progressivebow', 'basicbow', 'timedohko', 'timedother',
|
||||
'progressiveglove', 'basicglove', 'alwaysitems', 'legacyinsanity',
|
||||
'universal_keys',
|
||||
'extras', 'progressive_sword_limit', 'progressive_shield_limit',
|
||||
'progressive_armor_limit', 'progressive_bottle_limit',
|
||||
@@ -69,6 +72,10 @@ difficulties = {
|
||||
basicbow=['Bow', 'Silver Bow'] * 2,
|
||||
timedohko=['Green Clock'] * 25,
|
||||
timedother=['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
progressiveglove=progressivegloves,
|
||||
basicglove=basicgloves,
|
||||
alwaysitems=alwaysitems,
|
||||
legacyinsanity=legacyinsanity,
|
||||
universal_keys=['Small Key (Universal)'] * 28,
|
||||
extras=[easyfirst15extra, easysecond15extra, easythird10extra, easyfourth5extra, easyfinal25extra],
|
||||
progressive_sword_limit=8,
|
||||
@@ -97,6 +104,10 @@ difficulties = {
|
||||
basicbow=['Bow', 'Silver Bow'],
|
||||
timedohko=['Green Clock'] * 25,
|
||||
timedother=['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
progressiveglove=progressivegloves,
|
||||
basicglove=basicgloves,
|
||||
alwaysitems=alwaysitems,
|
||||
legacyinsanity=legacyinsanity,
|
||||
universal_keys=['Small Key (Universal)'] * 18 + ['Rupees (20)'] * 10,
|
||||
extras=[normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit=4,
|
||||
@@ -125,6 +136,10 @@ difficulties = {
|
||||
basicbow=['Bow'] * 2,
|
||||
timedohko=['Green Clock'] * 25,
|
||||
timedother=['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
progressiveglove=progressivegloves,
|
||||
basicglove=basicgloves,
|
||||
alwaysitems=alwaysitems,
|
||||
legacyinsanity=legacyinsanity,
|
||||
universal_keys=['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 16,
|
||||
extras=[normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit=3,
|
||||
@@ -154,6 +169,10 @@ difficulties = {
|
||||
basicbow=['Bow'] * 2,
|
||||
timedohko=['Green Clock'] * 20 + ['Red Clock'] * 5,
|
||||
timedother=['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
progressiveglove=progressivegloves,
|
||||
basicglove=basicgloves,
|
||||
alwaysitems=alwaysitems,
|
||||
legacyinsanity=legacyinsanity,
|
||||
universal_keys=['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 16,
|
||||
extras=[normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit=2,
|
||||
@@ -166,11 +185,46 @@ difficulties = {
|
||||
),
|
||||
}
|
||||
|
||||
ice_rod_hunt_difficulties = dict()
|
||||
for diff in {'easy', 'normal', 'hard', 'expert'}:
|
||||
ice_rod_hunt_difficulties[diff] = Difficulty(
|
||||
baseitems=['Nothing'] * 41,
|
||||
bottles=['Nothing'] * 4,
|
||||
bottle_count=difficulties[diff].bottle_count,
|
||||
same_bottle=difficulties[diff].same_bottle,
|
||||
progressiveshield=['Nothing'] * 3,
|
||||
basicshield=['Nothing'] * 3,
|
||||
progressivearmor=['Nothing'] * 2,
|
||||
basicarmor=['Nothing'] * 2,
|
||||
swordless=['Nothing'] * 4,
|
||||
progressivemagic=['Nothing'] * 2,
|
||||
basicmagic=['Nothing'] * 2,
|
||||
progressivesword=['Nothing'] * 4,
|
||||
basicsword=['Nothing'] * 4,
|
||||
progressivebow=['Nothing'] * 2,
|
||||
basicbow=['Nothing'] * 2,
|
||||
timedohko=difficulties[diff].timedohko,
|
||||
timedother=difficulties[diff].timedother,
|
||||
progressiveglove=['Nothing'] * 2,
|
||||
basicglove=['Nothing'] * 2,
|
||||
alwaysitems=['Ice Rod'] + ['Nothing'] * 19,
|
||||
legacyinsanity=['Nothing'] * 2,
|
||||
universal_keys=['Nothing'] * 28,
|
||||
extras=[['Nothing'] * 15, ['Nothing'] * 15, ['Nothing'] * 10, ['Nothing'] * 5, ['Nothing'] * 25],
|
||||
progressive_sword_limit=difficulties[diff].progressive_sword_limit,
|
||||
progressive_shield_limit=difficulties[diff].progressive_shield_limit,
|
||||
progressive_armor_limit=difficulties[diff].progressive_armor_limit,
|
||||
progressive_bow_limit=difficulties[diff].progressive_bow_limit,
|
||||
progressive_bottle_limit=difficulties[diff].progressive_bottle_limit,
|
||||
boss_heart_container_limit=difficulties[diff].boss_heart_container_limit,
|
||||
heart_piece_limit=difficulties[diff].heart_piece_limit,
|
||||
)
|
||||
|
||||
|
||||
def generate_itempool(world, player: int):
|
||||
if world.difficulty[player] not in difficulties:
|
||||
raise NotImplementedError(f"Diffulty {world.difficulty[player]}")
|
||||
if world.goal[player] not in {'ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt',
|
||||
if world.goal[player] not in {'ganon', 'pedestal', 'dungeons', 'triforcehunt', 'localtriforcehunt', 'icerodhunt',
|
||||
'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'}:
|
||||
raise NotImplementedError(f"Goal {world.goal[player]}")
|
||||
if world.mode[player] not in {'open', 'standard', 'inverted'}:
|
||||
@@ -180,7 +234,7 @@ def generate_itempool(world, player: int):
|
||||
|
||||
if world.timer[player] in ['ohko', 'timed-ohko']:
|
||||
world.can_take_damage[player] = False
|
||||
if world.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt']:
|
||||
if world.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt', 'icerodhunt']:
|
||||
world.push_item(world.get_location('Ganon', player), ItemFactory('Nothing', player), False)
|
||||
else:
|
||||
world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False)
|
||||
@@ -200,6 +254,46 @@ def generate_itempool(world, player: int):
|
||||
loc.event = True
|
||||
loc.locked = True
|
||||
|
||||
if world.goal[player] == 'icerodhunt':
|
||||
world.progression_balancing[player] = False
|
||||
loc = world.get_location('Turtle Rock - Boss', player)
|
||||
world.push_item(loc, ItemFactory('Triforce', player), False)
|
||||
if world.boss_shuffle[player] != 'none':
|
||||
if 'turtle rock-' not in world.boss_shuffle[player]:
|
||||
world.boss_shuffle[player] = f'Turtle Rock-Trinexx;{world.boss_shuffle[player]}'
|
||||
else:
|
||||
logging.warning(f'Cannot guarantee that Trinexx is the boss of Turtle Rock for player {player}')
|
||||
loc.event = True
|
||||
loc.locked = True
|
||||
forbid_items_for_player(loc, {'Red Pendant', 'Green Pendant', 'Blue Pendant', 'Crystal 5', 'Crystal 6'}, player)
|
||||
itemdiff = difficulties[world.difficulty[player]]
|
||||
itempool = []
|
||||
itempool.extend(itemdiff.alwaysitems)
|
||||
itempool.remove('Ice Rod')
|
||||
|
||||
itempool.extend(['Single Arrow', 'Sanctuary Heart Container'])
|
||||
itempool.extend(['Boss Heart Container'] * itemdiff.boss_heart_container_limit)
|
||||
itempool.extend(['Piece of Heart'] * itemdiff.heart_piece_limit)
|
||||
itempool.extend(itemdiff.bottles)
|
||||
itempool.extend(itemdiff.basicbow)
|
||||
itempool.extend(itemdiff.basicarmor)
|
||||
if world.swords[player] != 'swordless':
|
||||
itempool.extend(itemdiff.basicsword)
|
||||
itempool.extend(itemdiff.basicmagic)
|
||||
itempool.extend(itemdiff.basicglove)
|
||||
itempool.extend(itemdiff.basicshield)
|
||||
itempool.extend(itemdiff.legacyinsanity)
|
||||
itempool.extend(['Rupees (300)'] * 34)
|
||||
itempool.extend(['Bombs (10)'] * 5)
|
||||
itempool.extend(['Arrows (10)'] * 7)
|
||||
if world.keyshuffle[player] == 'universal':
|
||||
itempool.extend(itemdiff.universal_keys)
|
||||
itempool.append('Small Key (Universal)')
|
||||
|
||||
for item in itempool:
|
||||
world.push_precollected(ItemFactory(item, player))
|
||||
|
||||
|
||||
world.get_location('Ganon', player).event = True
|
||||
world.get_location('Ganon', player).locked = True
|
||||
world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False)
|
||||
@@ -271,18 +365,26 @@ def generate_itempool(world, player: int):
|
||||
if treasure_hunt_icon is not None:
|
||||
world.treasure_hunt_icon[player] = treasure_hunt_icon
|
||||
|
||||
world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player
|
||||
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.player == player
|
||||
and ((item.smallkey and world.keyshuffle[player])
|
||||
or (item.bigkey and world.bigkeyshuffle[player])
|
||||
or (item.map and world.mapshuffle[player])
|
||||
or (item.compass and world.compassshuffle[player]))])
|
||||
or (item.compass and world.compassshuffle[player])
|
||||
or world.goal[player] == 'icerodhunt')]
|
||||
|
||||
if world.goal[player] == 'icerodhunt':
|
||||
for item in dungeon_items:
|
||||
world.itempool.append(ItemFactory('Nothing', player))
|
||||
world.push_precollected(item)
|
||||
else:
|
||||
world.itempool.extend([item for item in dungeon_items])
|
||||
|
||||
# logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
|
||||
# rather than making all hearts/heart pieces progression items (which slows down generation considerably)
|
||||
# We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)
|
||||
if world.difficulty[player] in ['easy', 'normal', 'hard'] and not (world.custom and world.customitemarray[30] == 0):
|
||||
if world.goal[player] != 'icerodhunt' and world.difficulty[player] in ['easy', 'normal', 'hard'] and not (world.custom and world.customitemarray[30] == 0):
|
||||
next(item for item in items if item.name == 'Boss Heart Container').advancement = True
|
||||
elif world.difficulty[player] in ['expert'] and not (world.custom and world.customitemarray[29] < 4):
|
||||
elif world.goal[player] != 'icerodhunt' and world.difficulty[player] in ['expert'] and not (world.custom and world.customitemarray[29] < 4):
|
||||
adv_heart_pieces = (item for item in items if item.name == 'Piece of Heart')
|
||||
for i in range(4):
|
||||
next(adv_heart_pieces).advancement = True
|
||||
@@ -348,6 +450,7 @@ def shuffle_shops(world, items, player: int):
|
||||
if 'u' in option:
|
||||
progressive = world.progressive[player]
|
||||
progressive = world.random.choice([True, False]) if progressive == 'random' else progressive == 'on'
|
||||
progressive &= world.goal == 'icerodhunt'
|
||||
new_items = ["Bomb Upgrade (+5)"] * 6
|
||||
new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
|
||||
|
||||
@@ -362,13 +465,17 @@ def shuffle_shops(world, items, player: int):
|
||||
shop.region.name == "Capacity Upgrade":
|
||||
shop.clear_inventory()
|
||||
|
||||
for i, item in enumerate(items):
|
||||
if not "Heart" in item.name:
|
||||
items[i] = ItemFactory(new_items.pop(), player)
|
||||
if not new_items:
|
||||
break
|
||||
if world.goal[player] != 'icerodhunt':
|
||||
for i, item in enumerate(items):
|
||||
if "Heart" not in item.name:
|
||||
items[i] = ItemFactory(new_items.pop(), player)
|
||||
if not new_items:
|
||||
break
|
||||
else:
|
||||
logging.warning(f"Not all upgrades put into Player{player}' item pool. Still missing: {new_items}")
|
||||
else:
|
||||
logging.warning(f"Not all upgrades put into Player{player}' item pool. Still missing: {new_items}")
|
||||
for item in new_items:
|
||||
world.push_precollected(ItemFactory(item, player))
|
||||
|
||||
if 'p' in option or 'i' in option:
|
||||
shops = []
|
||||
@@ -417,10 +524,17 @@ take_any_locations = {
|
||||
'Palace of Darkness Hint', 'East Dark World Hint', 'Archery Game', 'Dark Lake Hylia Ledge Hint',
|
||||
'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Desert Hint'}
|
||||
|
||||
take_any_locations_inverted = list(take_any_locations - {"Dark Sanctuary Hint", "Archery Game"})
|
||||
take_any_locations = list(take_any_locations)
|
||||
# sets are sorted by the element's hash, python's hash is seeded at startup, resulting in different sorting each run
|
||||
take_any_locations_inverted.sort()
|
||||
take_any_locations.sort()
|
||||
|
||||
|
||||
def set_up_take_anys(world, player):
|
||||
# these are references, do not modify these lists in-place
|
||||
if world.mode[player] == 'inverted':
|
||||
take_any_locs = take_any_locations - {"Dark Sanctuary Hint", "Archery Game"}
|
||||
take_any_locs = take_any_locations_inverted
|
||||
else:
|
||||
take_any_locs = take_any_locations
|
||||
|
||||
@@ -553,7 +667,8 @@ def get_pool_core(world, player: int):
|
||||
treasure_hunt_count = None
|
||||
treasure_hunt_icon = None
|
||||
|
||||
pool.extend(alwaysitems)
|
||||
diff = ice_rod_hunt_difficulties[difficulty] if goal == 'icerodhunt' else difficulties[difficulty]
|
||||
pool.extend(diff.alwaysitems)
|
||||
|
||||
def place_item(loc, item):
|
||||
assert loc not in placed_items
|
||||
@@ -563,37 +678,35 @@ def get_pool_core(world, player: int):
|
||||
return world.random.choice([True, False]) if progressive == 'random' else progressive == 'on'
|
||||
|
||||
# provide boots to major glitch dependent seeds
|
||||
if logic in {'owglitches', 'nologic'} and world.glitch_boots[player]:
|
||||
if logic in {'owglitches', 'nologic'} and world.glitch_boots[player] and goal != 'icerodhunt':
|
||||
precollected_items.append('Pegasus Boots')
|
||||
pool.remove('Pegasus Boots')
|
||||
pool.append('Rupees (20)')
|
||||
|
||||
if want_progressives():
|
||||
pool.extend(progressivegloves)
|
||||
pool.extend(diff.progressiveglove)
|
||||
else:
|
||||
pool.extend(basicgloves)
|
||||
pool.extend(diff.basicglove)
|
||||
|
||||
# insanity legacy shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start
|
||||
if shuffle == 'insanity_legacy':
|
||||
place_item('Link\'s House', 'Magic Mirror')
|
||||
place_item('Sanctuary', 'Moon Pearl')
|
||||
place_item('Link\'s House', diff.legacyinsanity[0])
|
||||
place_item('Sanctuary', diff.legacyinsanity[1])
|
||||
else:
|
||||
pool.extend(['Magic Mirror', 'Moon Pearl'])
|
||||
pool.extend(diff.legacyinsanity)
|
||||
|
||||
if timer == 'display':
|
||||
clock_mode = 'stopwatch'
|
||||
elif timer == 'ohko':
|
||||
clock_mode = 'ohko'
|
||||
|
||||
diff = difficulties[difficulty]
|
||||
pool.extend(diff.baseitems)
|
||||
|
||||
# expert+ difficulties produce the same contents for
|
||||
# all bottles, since only one bottle is available
|
||||
if diff.same_bottle:
|
||||
thisbottle = world.random.choice(diff.bottles)
|
||||
thisbottle = None
|
||||
for _ in range(diff.bottle_count):
|
||||
if not diff.same_bottle:
|
||||
if not diff.same_bottle or not thisbottle:
|
||||
thisbottle = world.random.choice(diff.bottles)
|
||||
pool.append(thisbottle)
|
||||
|
||||
@@ -614,7 +727,7 @@ def get_pool_core(world, player: int):
|
||||
|
||||
if want_progressives():
|
||||
pool.extend(diff.progressivebow)
|
||||
elif swords == 'swordless' or logic == 'noglitches':
|
||||
elif (swords == 'swordless' or logic == 'noglitches') and goal != 'icerodhunt':
|
||||
swordless_bows = ['Bow', 'Silver Bow']
|
||||
if difficulty == "easy":
|
||||
swordless_bows *= 2
|
||||
@@ -641,7 +754,7 @@ def get_pool_core(world, player: int):
|
||||
else:
|
||||
progressive_swords = want_progressives()
|
||||
pool.extend(diff.progressivesword if progressive_swords else diff.basicsword)
|
||||
if swords == 'assured':
|
||||
if swords == 'assured' and goal != 'icerodhunt':
|
||||
if progressive_swords:
|
||||
precollected_items.append('Progressive Sword')
|
||||
pool.remove('Progressive Sword')
|
||||
@@ -688,13 +801,14 @@ def get_pool_core(world, player: int):
|
||||
pool = ['Rupees (5)' if item in replace else item for item in pool]
|
||||
if world.keyshuffle[player] == "universal":
|
||||
pool.extend(diff.universal_keys)
|
||||
item_to_place = 'Small Key (Universal)' if goal != 'icerodhunt' else 'Nothing'
|
||||
if mode == 'standard':
|
||||
key_location = world.random.choice(
|
||||
['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest',
|
||||
'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
||||
place_item(key_location, 'Small Key (Universal)')
|
||||
place_item(key_location, item_to_place)
|
||||
else:
|
||||
pool.extend(['Small Key (Universal)'])
|
||||
pool.extend([item_to_place])
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon,
|
||||
additional_pieces_to_place)
|
||||
|
||||
@@ -798,10 +912,9 @@ def make_custom_item_pool(world, player):
|
||||
|
||||
# expert+ difficulties produce the same contents for
|
||||
# all bottles, since only one bottle is available
|
||||
if diff.same_bottle:
|
||||
thisbottle = world.random.choice(diff.bottles)
|
||||
thisbottle = None
|
||||
for _ in range(customitemarray[18]):
|
||||
if not diff.same_bottle:
|
||||
if not diff.same_bottle or not thisbottle:
|
||||
thisbottle = world.random.choice(diff.bottles)
|
||||
pool.append(thisbottle)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from worlds.alttp.EntranceShuffle import link_entrances, link_inverted_entrances
|
||||
from worlds.alttp.Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, get_hash_string
|
||||
from worlds.alttp.Rules import set_rules
|
||||
from worlds.alttp.Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
||||
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression
|
||||
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
|
||||
from worlds.alttp.ItemPool import generate_itempool, difficulties, fill_prizes
|
||||
from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple
|
||||
import Patch
|
||||
@@ -120,6 +120,11 @@ def main(args, seed=None):
|
||||
item.strip() in item_table}
|
||||
world.non_local_items[player] = {item.strip() for item in args.non_local_items[player].split(',') if
|
||||
item.strip() in item_table}
|
||||
|
||||
# enforce pre-defined local items.
|
||||
if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]:
|
||||
world.local_items[player].add('Triforce Piece')
|
||||
|
||||
# items can't be both local and non-local, prefer local
|
||||
world.non_local_items[player] -= world.local_items[player]
|
||||
|
||||
@@ -180,42 +185,7 @@ def main(args, seed=None):
|
||||
|
||||
logger.info("Running Item Plando")
|
||||
|
||||
world_name_lookup = {world.player_names[player_id][0]: player_id for player_id in world.player_ids}
|
||||
|
||||
for player in world.player_ids:
|
||||
placement: PlandoItem
|
||||
for placement in world.plando_items[player]:
|
||||
target_world: int = placement.world
|
||||
if target_world is False or world.players == 1:
|
||||
target_world = player # in own world
|
||||
elif target_world is True: # in any other world
|
||||
target_world = player
|
||||
while target_world == player:
|
||||
target_world = world.random.randint(1, world.players + 1)
|
||||
elif target_world is None: # any random world
|
||||
target_world = world.random.randint(1, world.players + 1)
|
||||
elif type(target_world) == int: # target world by player id
|
||||
pass
|
||||
else: # find world by name
|
||||
target_world = world_name_lookup[target_world]
|
||||
|
||||
location = world.get_location(placement.location, target_world)
|
||||
if location.item:
|
||||
raise Exception(f"Cannot place item into already filled location {location}.")
|
||||
item = ItemFactory(placement.item, player)
|
||||
if placement.from_pool:
|
||||
try:
|
||||
world.itempool.remove(item)
|
||||
except ValueError:
|
||||
logger.warning(f"Could not remove {item} from pool as it's already missing from it.")
|
||||
|
||||
if location.can_fill(world.state, item, False):
|
||||
world.push_item(location, item, collect=False)
|
||||
location.event = True # flag location to be checked during fill
|
||||
location.locked = True
|
||||
logger.debug(f"Plando placed {item} at {location}")
|
||||
else:
|
||||
raise Exception(f"Can't place {item} at {location} due to fill condition not met.")
|
||||
distribute_planned(world)
|
||||
|
||||
logger.info('Placing Dungeon Items.')
|
||||
|
||||
@@ -343,6 +313,7 @@ def main(args, seed=None):
|
||||
|
||||
pool = concurrent.futures.ThreadPoolExecutor()
|
||||
multidata_task = None
|
||||
check_beatability_task = pool.submit(world.can_beat_game)
|
||||
if not args.suppress_rom:
|
||||
|
||||
rom_futures = []
|
||||
@@ -427,7 +398,8 @@ def main(args, seed=None):
|
||||
f.write(multidata)
|
||||
|
||||
multidata_task = pool.submit(write_multidata, rom_futures)
|
||||
|
||||
if not check_beatability_task.result():
|
||||
raise Exception("Game appears unbeatable. Aborting.")
|
||||
if not args.skip_playthrough:
|
||||
logger.info('Calculating playthrough.')
|
||||
create_playthrough(world)
|
||||
@@ -573,10 +545,6 @@ def create_playthrough(world):
|
||||
old_world = world
|
||||
world = copy_world(world)
|
||||
|
||||
# if we only check for beatable, we can do this sanity check first before writing down spheres
|
||||
if not world.can_beat_game():
|
||||
raise RuntimeError('Cannot beat game. Something went terribly wrong here!')
|
||||
|
||||
# get locations containing progress items
|
||||
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
|
||||
state_cache = [None]
|
||||
|
||||
@@ -1337,7 +1337,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
(0x02 if 'bombs' in world.escape_assist[player] else 0x00) |
|
||||
(0x04 if 'magic' in world.escape_assist[player] else 0x00))) # Escape assist
|
||||
|
||||
if world.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt']:
|
||||
if world.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt', 'icerodhunt']:
|
||||
rom.write_byte(0x18003E, 0x01) # make ganon invincible
|
||||
elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
|
||||
rom.write_byte(0x18003E, 0x05) # make ganon invincible until enough triforce pieces are collected
|
||||
@@ -2067,14 +2067,10 @@ def write_strings(rom, world, player, team):
|
||||
items_to_hint.extend(BigKeys)
|
||||
local_random.shuffle(items_to_hint)
|
||||
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8
|
||||
while hint_count > 0:
|
||||
while hint_count > 0 and items_to_hint:
|
||||
this_item = items_to_hint.pop(0)
|
||||
this_location = world.find_items(this_item, player)
|
||||
local_random.shuffle(this_location)
|
||||
# This looks dumb but prevents hints for Skull Woods Pinball Room's key safely with any item pool.
|
||||
if this_location:
|
||||
if this_location[0].name == 'Skull Woods - Pinball Room':
|
||||
this_location.pop(0)
|
||||
if this_location:
|
||||
this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.'
|
||||
tt[hint_locations.pop(0)] = this_hint
|
||||
@@ -2133,6 +2129,10 @@ def write_strings(rom, world, player, team):
|
||||
else:
|
||||
tt['sign_ganon'] = f'You need {world.crystals_needed_for_ganon[player]} crystals to beat Ganon and ' \
|
||||
f'have beaten Agahnim atop Ganons Tower'
|
||||
elif world.goal[player] == "icerodhunt":
|
||||
tt['sign_ganon'] = 'Go find the Ice Rod and Kill Trinexx... Ganon is invincible!'
|
||||
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Go kill Trinexx instead.'
|
||||
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
|
||||
else:
|
||||
if world.crystals_needed_for_ganon[player] == 1:
|
||||
tt['sign_ganon'] = 'You need a crystal to beat Ganon.'
|
||||
|
||||
@@ -174,8 +174,6 @@ def item_name(state, location, player):
|
||||
|
||||
|
||||
def locality_rules(world, player):
|
||||
if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]:
|
||||
world.local_items[player].add('Triforce Piece')
|
||||
if world.local_items[player]:
|
||||
for location in world.get_locations():
|
||||
if location.player != player:
|
||||
|
||||
@@ -222,7 +222,7 @@ TavernMan_texts = [
|
||||
"Helmasaur is\nthrowing a\nparty.\nI hope it's\na masquerade!",
|
||||
"I'd like to\nknow Arrghus\nbetter.\nBut he won't\ncome out of\nhis shell!",
|
||||
"Mothula didn't\nhave much fun\nat the party.\nHe's immune to\nspiked punch!",
|
||||
"Don't set me\nup with that\nchick from\nSteve's Town.\n\n\nI'm not\ninterested in\na Blind date!",
|
||||
"Don't set me\nup with that\nlady from\nSteve's Town.\n\n\nI'm not\ninterested in\na Blind date!",
|
||||
"Kholdstare is\nafraid to go\nto the circus.\nHungry kids\nthought he was\ncotton candy!",
|
||||
"I asked who\nVitreous' best\nfriends are.\nHe said,\n'Me, Myself,\nand Eye!'",
|
||||
"Trinexx can be\na hothead or\nhe can be an\nice guy. In\nthe end, he's\na solid\nindividual!",
|
||||
|
||||
Reference in New Issue
Block a user