Merge branch 'DoorDevUnstable' into Customizer
# Conflicts: # Bosses.py # ItemList.py # Main.py # Mystery.py # RELEASENOTES.md # Rom.py # data/base2current.bps
This commit is contained in:
@@ -2530,7 +2530,7 @@ class Spoiler(object):
|
|||||||
def mystery_meta_to_file(self, filename):
|
def mystery_meta_to_file(self, filename):
|
||||||
self.parse_meta()
|
self.parse_meta()
|
||||||
with open(filename, 'w') as outfile:
|
with open(filename, 'w') as outfile:
|
||||||
outfile.write('ALttP Dungeon Randomizer Version %s - Seed: %s\n\n' % (self.metadata['version'], self.world.seed))
|
outfile.write(f'ALttP Dungeon Randomizer Version {self.metadata["version"]}\n\n')
|
||||||
for player in range(1, self.world.players + 1):
|
for player in range(1, self.world.players + 1):
|
||||||
if 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_names(player)))
|
||||||
|
|||||||
@@ -223,12 +223,15 @@ def place_bosses(world, player):
|
|||||||
for u, level in used_bosses:
|
for u, level in used_bosses:
|
||||||
if not level:
|
if not level:
|
||||||
bosses.remove(u)
|
bosses.remove(u)
|
||||||
|
gt_bosses = []
|
||||||
|
|
||||||
for [loc, level] in boss_locations:
|
for [loc, level] in boss_locations:
|
||||||
loc_text = loc + (' ('+level+')' if level else '')
|
loc_text = loc + (' ('+level+')' if level else '')
|
||||||
try:
|
try:
|
||||||
if level:
|
if level:
|
||||||
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)])
|
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)
|
||||||
|
and b not in gt_bosses])
|
||||||
|
gt_bosses.append(boss)
|
||||||
else:
|
else:
|
||||||
boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)])
|
boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)])
|
||||||
bosses.remove(boss)
|
bosses.remove(boss)
|
||||||
|
|||||||
1
CLI.py
1
CLI.py
@@ -163,6 +163,7 @@ def parse_settings():
|
|||||||
"accessibility": "items",
|
"accessibility": "items",
|
||||||
"algorithm": "balanced",
|
"algorithm": "balanced",
|
||||||
'mystery': False,
|
'mystery': False,
|
||||||
|
'suppress_meta': False,
|
||||||
"restrict_boss_items": "none",
|
"restrict_boss_items": "none",
|
||||||
|
|
||||||
# Shuffle Ganon defaults to TRUE
|
# Shuffle Ganon defaults to TRUE
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ def choose_portals(world, player):
|
|||||||
if world.doorShuffle[player] in ['basic', 'crossed']:
|
if world.doorShuffle[player] in ['basic', 'crossed']:
|
||||||
cross_flag = world.doorShuffle[player] == 'crossed'
|
cross_flag = world.doorShuffle[player] == 'crossed'
|
||||||
# key drops allow the big key in the right place in Desert Tiles 2
|
# key drops allow the big key in the right place in Desert Tiles 2
|
||||||
bk_shuffle = world.bigkeyshuffle[player] or world.dropshuffle[player]
|
bk_shuffle = world.bigkeyshuffle[player] or world.pottery[player] not in ['none', 'cave']
|
||||||
std_flag = world.mode[player] == 'standard'
|
std_flag = world.mode[player] == 'standard'
|
||||||
# roast incognito doors
|
# roast incognito doors
|
||||||
world.get_room(0x60, player).delete(5)
|
world.get_room(0x60, player).delete(5)
|
||||||
|
|||||||
84
Fill.py
84
Fill.py
@@ -3,6 +3,7 @@ import collections
|
|||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
from BaseClasses import CollectionState, FillError, LocationType
|
from BaseClasses import CollectionState, FillError, LocationType
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
@@ -35,17 +36,6 @@ def dungeon_tracking(world):
|
|||||||
|
|
||||||
def fill_dungeons_restrictive(world, shuffled_locations):
|
def fill_dungeons_restrictive(world, shuffled_locations):
|
||||||
dungeon_tracking(world)
|
dungeon_tracking(world)
|
||||||
all_state_base = world.get_all_state()
|
|
||||||
|
|
||||||
# for player in range(1, world.players + 1):
|
|
||||||
# pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
|
||||||
# if world.retro[player]:
|
|
||||||
# world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
|
||||||
# else:
|
|
||||||
# world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
|
||||||
# pinball_room.event = True
|
|
||||||
# pinball_room.locked = True
|
|
||||||
# shuffled_locations.remove(pinball_room)
|
|
||||||
|
|
||||||
# with shuffled dungeon items they are distributed as part of the normal item pool
|
# with shuffled dungeon items they are distributed as part of the normal item pool
|
||||||
for item in world.get_items():
|
for item in world.get_items():
|
||||||
@@ -55,17 +45,28 @@ def fill_dungeons_restrictive(world, shuffled_locations):
|
|||||||
item.priority = True
|
item.priority = True
|
||||||
|
|
||||||
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)]
|
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)]
|
||||||
|
bigs, smalls, others = [], [], []
|
||||||
|
for i in dungeon_items:
|
||||||
|
(bigs if i.bigkey else smalls if i.smallkey else others).append(i)
|
||||||
|
|
||||||
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
def fill(base_state, items, key_pool):
|
||||||
sort_order = {"BigKey": 3, "SmallKey": 2}
|
fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True)
|
||||||
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
|
||||||
|
|
||||||
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items,
|
all_state_base = world.get_all_state()
|
||||||
keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)},
|
big_state_base = all_state_base.copy()
|
||||||
single_player_placement=True)
|
for x in smalls + others:
|
||||||
|
big_state_base.collect(x, True)
|
||||||
|
fill(big_state_base, bigs, smalls)
|
||||||
|
random.shuffle(shuffled_locations)
|
||||||
|
small_state_base = all_state_base.copy()
|
||||||
|
for x in others:
|
||||||
|
small_state_base.collect(x, True)
|
||||||
|
fill(small_state_base, smalls, list(smalls))
|
||||||
|
random.shuffle(shuffled_locations)
|
||||||
|
fill(all_state_base, others, None)
|
||||||
|
|
||||||
|
|
||||||
def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=None, single_player_placement=False,
|
def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False,
|
||||||
vanilla=False):
|
vanilla=False):
|
||||||
def sweep_from_pool():
|
def sweep_from_pool():
|
||||||
new_state = base_state.copy()
|
new_state = base_state.copy()
|
||||||
@@ -101,8 +102,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
|
|||||||
item_locations = filter_locations(item_to_place, locations, world, vanilla)
|
item_locations = filter_locations(item_to_place, locations, world, vanilla)
|
||||||
for location in item_locations:
|
for location in item_locations:
|
||||||
spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state,
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state,
|
||||||
single_player_placement, perform_access_check, itempool,
|
single_player_placement, perform_access_check, key_pool, world)
|
||||||
keys_in_itempool, world)
|
|
||||||
if spot_to_fill:
|
if spot_to_fill:
|
||||||
break
|
break
|
||||||
if spot_to_fill is None:
|
if spot_to_fill is None:
|
||||||
@@ -111,7 +111,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
|
|||||||
continue
|
continue
|
||||||
spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state,
|
spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state,
|
||||||
base_state, itempool, perform_access_check, item_locations,
|
base_state, itempool, perform_access_check, item_locations,
|
||||||
keys_in_itempool, single_player_placement)
|
key_pool, single_player_placement)
|
||||||
if spot_to_fill is None:
|
if spot_to_fill is None:
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
||||||
unplaced_items.insert(0, item_to_place)
|
unplaced_items.insert(0, item_to_place)
|
||||||
@@ -123,6 +123,9 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
|
|||||||
raise FillError('No more spots to place %s' % item_to_place)
|
raise FillError('No more spots to place %s' % item_to_place)
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
if item_to_place.smallkey:
|
||||||
|
with suppress(ValueError):
|
||||||
|
key_pool.remove(item_to_place)
|
||||||
track_outside_keys(item_to_place, spot_to_fill, world)
|
track_outside_keys(item_to_place, spot_to_fill, world)
|
||||||
track_dungeon_items(item_to_place, spot_to_fill, world)
|
track_dungeon_items(item_to_place, spot_to_fill, world)
|
||||||
locations.remove(spot_to_fill)
|
locations.remove(spot_to_fill)
|
||||||
@@ -132,7 +135,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
|
|||||||
|
|
||||||
|
|
||||||
def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check,
|
def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check,
|
||||||
itempool, keys_in_itempool, world):
|
key_pool, world):
|
||||||
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
|
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
|
||||||
location.item = item_to_place
|
location.item = item_to_place
|
||||||
test_state = max_exp_state.copy()
|
test_state = max_exp_state.copy()
|
||||||
@@ -141,8 +144,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl
|
|||||||
test_state = max_exp_state
|
test_state = max_exp_state
|
||||||
if not single_player_placement or location.player == item_to_place.player:
|
if not single_player_placement or location.player == item_to_place.player:
|
||||||
if location.can_fill(test_state, item_to_place, perform_access_check):
|
if location.can_fill(test_state, item_to_place, perform_access_check):
|
||||||
test_pool = itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool
|
if valid_key_placement(item_to_place, location, key_pool, world):
|
||||||
if valid_key_placement(item_to_place, location, test_pool, world):
|
|
||||||
if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world):
|
if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world):
|
||||||
return location
|
return location
|
||||||
if item_to_place.smallkey or item_to_place.bigkey:
|
if item_to_place.smallkey or item_to_place.bigkey:
|
||||||
@@ -150,7 +152,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def valid_key_placement(item, location, itempool, world):
|
def valid_key_placement(item, location, key_pool, world):
|
||||||
if not valid_reserved_placement(item, location, world):
|
if not valid_reserved_placement(item, location, world):
|
||||||
return False
|
return False
|
||||||
if ((not item.smallkey and not item.bigkey) or item.player != location.player
|
if ((not item.smallkey and not item.bigkey) or item.player != location.player
|
||||||
@@ -161,7 +163,7 @@ def valid_key_placement(item, location, itempool, world):
|
|||||||
if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name):
|
if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name):
|
||||||
return True
|
return True
|
||||||
key_logic = world.key_logic[item.player][dungeon.name]
|
key_logic = world.key_logic[item.player][dungeon.name]
|
||||||
unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player])
|
unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player])
|
||||||
prize_loc = None
|
prize_loc = None
|
||||||
if key_logic.prize_location:
|
if key_logic.prize_location:
|
||||||
prize_loc = world.get_location(key_logic.prize_location, location.player)
|
prize_loc = world.get_location(key_logic.prize_location, location.player)
|
||||||
@@ -216,16 +218,16 @@ def is_dungeon_item(item, world):
|
|||||||
|
|
||||||
|
|
||||||
def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted,
|
def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted,
|
||||||
keys_in_itempool=None, single_player_placement=False):
|
key_pool=None, single_player_placement=False):
|
||||||
logging.getLogger('').debug(f'Could not place {item_to_place} attempting recovery')
|
logging.getLogger('').debug(f'Could not place {item_to_place} attempting recovery')
|
||||||
if world.algorithm in ['balanced', 'equitable']:
|
if world.algorithm in ['balanced', 'equitable']:
|
||||||
return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, keys_in_itempool,
|
return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, key_pool,
|
||||||
single_player_placement)
|
single_player_placement)
|
||||||
elif world.algorithm == 'vanilla_fill':
|
elif world.algorithm == 'vanilla_fill':
|
||||||
if item_to_place.type == 'Crystal':
|
if item_to_place.type == 'Crystal':
|
||||||
possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
|
possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
|
||||||
return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool,
|
return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool,
|
||||||
keys_in_itempool, single_player_placement)
|
key_pool, single_player_placement)
|
||||||
else:
|
else:
|
||||||
i, config = 0, world.item_pool_config
|
i, config = 0, world.item_pool_config
|
||||||
tried = set(attempted)
|
tried = set(attempted)
|
||||||
@@ -235,7 +237,7 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp
|
|||||||
other_locs = [x for x in locations if x.name in fallback_locations]
|
other_locs = [x for x in locations if x.name in fallback_locations]
|
||||||
for location in other_locs:
|
for location in other_locs:
|
||||||
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
perform_access_check, itempool, keys_in_itempool, world)
|
perform_access_check, key_pool, world)
|
||||||
if spot_to_fill:
|
if spot_to_fill:
|
||||||
return spot_to_fill
|
return spot_to_fill
|
||||||
i += 1
|
i += 1
|
||||||
@@ -244,14 +246,14 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp
|
|||||||
other_locations = vanilla_fallback(item_to_place, locations, world)
|
other_locations = vanilla_fallback(item_to_place, locations, world)
|
||||||
for location in other_locations:
|
for location in other_locations:
|
||||||
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
perform_access_check, itempool, keys_in_itempool, world)
|
perform_access_check, key_pool, world)
|
||||||
if spot_to_fill:
|
if spot_to_fill:
|
||||||
return spot_to_fill
|
return spot_to_fill
|
||||||
tried.update(other_locations)
|
tried.update(other_locations)
|
||||||
other_locations = [x for x in locations if x not in tried]
|
other_locations = [x for x in locations if x not in tried]
|
||||||
for location in other_locations:
|
for location in other_locations:
|
||||||
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
perform_access_check, itempool, keys_in_itempool, world)
|
perform_access_check, key_pool, world)
|
||||||
if spot_to_fill:
|
if spot_to_fill:
|
||||||
return spot_to_fill
|
return spot_to_fill
|
||||||
return None
|
return None
|
||||||
@@ -259,14 +261,14 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp
|
|||||||
other_locations = [x for x in locations if x not in attempted]
|
other_locations = [x for x in locations if x not in attempted]
|
||||||
for location in other_locations:
|
for location in other_locations:
|
||||||
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
perform_access_check, itempool, keys_in_itempool, world)
|
perform_access_check, key_pool, world)
|
||||||
if spot_to_fill:
|
if spot_to_fill:
|
||||||
return spot_to_fill
|
return spot_to_fill
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool,
|
def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool,
|
||||||
keys_in_itempool=None, single_player_placement=False):
|
key_pool=None, single_player_placement=False):
|
||||||
def location_preference(loc):
|
def location_preference(loc):
|
||||||
if not loc.item.advancement:
|
if not loc.item.advancement:
|
||||||
return 1
|
return 1
|
||||||
@@ -284,21 +286,21 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite
|
|||||||
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item]
|
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item]
|
||||||
swap_locations = sorted(possible_swaps, key=location_preference)
|
swap_locations = sorted(possible_swaps, key=location_preference)
|
||||||
return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
||||||
keys_in_itempool, single_player_placement)
|
key_pool, single_player_placement)
|
||||||
|
|
||||||
|
|
||||||
def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
||||||
keys_in_itempool=None, single_player_placement=False):
|
key_pool=None, single_player_placement=False):
|
||||||
for location in swap_locations:
|
for location in swap_locations:
|
||||||
old_item = location.item
|
old_item = location.item
|
||||||
new_pool = list(itempool) + [old_item]
|
new_pool = list(itempool) + [old_item]
|
||||||
new_spot = find_spot_for_item(item_to_place, [location], world, base_state, new_pool,
|
new_spot = find_spot_for_item(item_to_place, [location], world, base_state, new_pool,
|
||||||
keys_in_itempool, single_player_placement)
|
key_pool, single_player_placement)
|
||||||
if new_spot:
|
if new_spot:
|
||||||
restore_item = new_spot.item
|
restore_item = new_spot.item
|
||||||
new_spot.item = item_to_place
|
new_spot.item = item_to_place
|
||||||
swap_spot = find_spot_for_item(old_item, locations, world, base_state, itempool,
|
swap_spot = find_spot_for_item(old_item, locations, world, base_state, itempool,
|
||||||
keys_in_itempool, single_player_placement)
|
key_pool, single_player_placement)
|
||||||
if swap_spot:
|
if swap_spot:
|
||||||
logging.getLogger('').debug(f'Swapping {old_item} for {item_to_place}')
|
logging.getLogger('').debug(f'Swapping {old_item} for {item_to_place}')
|
||||||
world.push_item(swap_spot, old_item, False)
|
world.push_item(swap_spot, old_item, False)
|
||||||
@@ -414,13 +416,13 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
|
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
|
||||||
# todo: crossed
|
# todo: crossed
|
||||||
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0)
|
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0)
|
||||||
keys_in_pool = {player: world.keyshuffle[player] or world.algorithm != 'balanced' for player in range(1, world.players + 1)}
|
key_pool = [x for x in progitempool if x.smallkey]
|
||||||
|
|
||||||
# sort maps and compasses to the back -- this may not be viable in equitable & ambrosia
|
# sort maps and compasses to the back -- this may not be viable in equitable & ambrosia
|
||||||
progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1)
|
progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1)
|
||||||
if world.algorithm == 'vanilla_fill':
|
if world.algorithm == 'vanilla_fill':
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool, vanilla=True)
|
fill_restrictive(world, world.state, fill_locations, progitempool, key_pool, vanilla=True)
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool)
|
fill_restrictive(world, world.state, fill_locations, progitempool, key_pool)
|
||||||
random.shuffle(fill_locations)
|
random.shuffle(fill_locations)
|
||||||
if world.algorithm == 'balanced':
|
if world.algorithm == 'balanced':
|
||||||
fast_fill(world, prioitempool, fill_locations)
|
fast_fill(world, prioitempool, fill_locations)
|
||||||
|
|||||||
6
Main.py
6
Main.py
@@ -33,7 +33,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
|
|||||||
from source.tools.BPS import create_bps_from_data
|
from source.tools.BPS import create_bps_from_data
|
||||||
from source.classes.CustomSettings import CustomSettings
|
from source.classes.CustomSettings import CustomSettings
|
||||||
|
|
||||||
__version__ = '1.0.2.7-w'
|
__version__ = '1.0.2.8-w'
|
||||||
|
|
||||||
from source.classes.BabelFish import BabelFish
|
from source.classes.BabelFish import BabelFish
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ def main(args, seed=None, fish=None):
|
|||||||
if args.create_spoiler and not args.jsonout:
|
if args.create_spoiler and not args.jsonout:
|
||||||
logger.info(world.fish.translate("cli", "cli", "create.meta"))
|
logger.info(world.fish.translate("cli", "cli", "create.meta"))
|
||||||
world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
|
world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
|
||||||
if args.mystery:
|
if args.mystery and not args.suppress_meta:
|
||||||
world.spoiler.mystery_meta_to_file(output_path(f'{outfilebase}_meta.txt'))
|
world.spoiler.mystery_meta_to_file(output_path(f'{outfilebase}_meta.txt'))
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
@@ -376,7 +376,7 @@ def main(args, seed=None, fish=None):
|
|||||||
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
|
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
|
||||||
f.write(multidata)
|
f.write(multidata)
|
||||||
|
|
||||||
if args.mystery:
|
if args.mystery and not args.suppress_meta:
|
||||||
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_meta.txt'))
|
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_meta.txt'))
|
||||||
elif args.create_spoiler and not args.jsonout:
|
elif args.create_spoiler and not args.jsonout:
|
||||||
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
|
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ def main():
|
|||||||
parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1))
|
parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1))
|
||||||
parser.add_argument('--create_spoiler', action='store_true')
|
parser.add_argument('--create_spoiler', action='store_true')
|
||||||
parser.add_argument('--suppress_rom', action='store_true')
|
parser.add_argument('--suppress_rom', action='store_true')
|
||||||
|
parser.add_argument('--suppress_meta', action='store_true')
|
||||||
parser.add_argument('--bps', action='store_true')
|
parser.add_argument('--bps', action='store_true')
|
||||||
parser.add_argument('--rom')
|
parser.add_argument('--rom')
|
||||||
parser.add_argument('--enemizercli')
|
parser.add_argument('--enemizercli')
|
||||||
@@ -65,6 +66,7 @@ def main():
|
|||||||
erargs.names = args.names
|
erargs.names = args.names
|
||||||
erargs.create_spoiler = args.create_spoiler
|
erargs.create_spoiler = args.create_spoiler
|
||||||
erargs.suppress_rom = args.suppress_rom
|
erargs.suppress_rom = args.suppress_rom
|
||||||
|
erargs.suppress_meta = args.suppress_meta
|
||||||
erargs.bps = args.bps
|
erargs.bps = args.bps
|
||||||
erargs.race = True
|
erargs.race = True
|
||||||
erargs.outputname = seedname
|
erargs.outputname = seedname
|
||||||
|
|||||||
@@ -787,12 +787,13 @@ vanilla_pots = {
|
|||||||
Pot(230, 27, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF5E, [0xCF, 0xDF, 0xFA]))],
|
Pot(230, 27, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF5E, [0xCF, 0xDF, 0xFA]))],
|
||||||
0x108: [Pot(166, 19, PotItem.Chicken, 'Chicken House', obj=RoomObject(0x03EFA9, [0x4F, 0x9F, 0xFA]))],
|
0x108: [Pot(166, 19, PotItem.Chicken, 'Chicken House', obj=RoomObject(0x03EFA9, [0x4F, 0x9F, 0xFA]))],
|
||||||
0x10C: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy', obj=RoomObject(0x03F329, [0xB3, 0x73, 0xFA]))],
|
0x10C: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy', obj=RoomObject(0x03F329, [0xB3, 0x73, 0xFA]))],
|
||||||
0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x23, 0xFA])),
|
# note: these addresses got moved thanks to waterfall fairy edit
|
||||||
Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x23, 0xFA])),
|
0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79A, [0xBB, 0x23, 0xFA])),
|
||||||
Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x2B, 0xFA])),
|
Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79D, [0xC3, 0x23, 0xFA])),
|
||||||
Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x2B, 0xFA])),
|
Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x2B, 0xFA])),
|
||||||
Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7AC, [0xBB, 0x53, 0xFA])),
|
Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x2B, 0xFA])),
|
||||||
Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7AF, [0xC3, 0x53, 0xFA]))],
|
Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x53, 0xFA])),
|
||||||
|
Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x53, 0xFA]))],
|
||||||
0x117: [Pot(138, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB2, [0x17, 0x1F, 0xFA])), # 0x38A -> 38A
|
0x117: [Pot(138, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB2, [0x17, 0x1F, 0xFA])), # 0x38A -> 38A
|
||||||
Pot(142, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB8, [0x1F, 0x1F, 0xFA])),
|
Pot(142, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB8, [0x1F, 0x1F, 0xFA])),
|
||||||
Pot(166, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC1, [0x4F, 0x1F, 0xFA])),
|
Pot(166, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC1, [0x4F, 0x1F, 0xFA])),
|
||||||
|
|||||||
248
RELEASENOTES.md
248
RELEASENOTES.md
@@ -25,7 +25,7 @@ Note for multiworld: due to the design of the pottery lottery, only 256 items fo
|
|||||||
|
|
||||||
### Colorize Pots
|
### Colorize Pots
|
||||||
|
|
||||||
If the pottery mode is dynamic, this option is forced to be on (clustered and reduced). It is allowed to be on in all other pottery modes. Exception "none" where no pots would be colored, and "lottery" where all pots would be. This option colors the pots differently that have been chosen to be part of the location pool. If not specified, you are expected to remember the pottery setting you chose.
|
If the pottery mode is dynamic, this option is forced to be on (clustered and reduced). It is allowed to be on in all other pottery modes. Exceptions include "none" where no pots would be colored, and "lottery" where all pots would be. This option colors the pots differently that have been chosen to be part of the location pool. If not specified, you are expected to remember the pottery setting you chose. Note that Mystery will colorize all pots if lottery is chosen randomly.
|
||||||
|
|
||||||
CLI `--colorizepots`
|
CLI `--colorizepots`
|
||||||
|
|
||||||
@@ -39,11 +39,37 @@ CLI `--dropshuffle`
|
|||||||
|
|
||||||
"Drop and Pot Keys" or `--keydropshuffle` is still availabe for use. This simply sets the pottery to keys and turns dropshuffle on as well to have the same behavior as the old setting.
|
"Drop and Pot Keys" or `--keydropshuffle` is still availabe for use. This simply sets the pottery to keys and turns dropshuffle on as well to have the same behavior as the old setting.
|
||||||
|
|
||||||
The old "Pot Shuffle" option is still available under "Pot Shuffle (Legacy)" or `--shufflepots` and works the same by shuffling all pots on a supertile. It works with the lottery option as well to move the switches while having every pot in the pool.
|
The old "Pot Shuffle" option is still available under "Pot Shuffle (Legacy)" or `--shufflepots` and works the same by shuffling all pots on a supertile. It works with the lottery option as well to move the switches to any valid pot on the supertile regardless of the pots chosen in the pottery mode. This may increase the number of pot locations slightly depending on the mode.
|
||||||
|
|
||||||
#### Tracking Notes
|
#### Tracking Notes
|
||||||
|
|
||||||
The sram locations for pots and sprite drops are not yet final, please reach out for assistance or investigate the rom changes.
|
The sram locations for pots and sprite drops are now final, please reach out for assistance or investigate the rom changes if needed.
|
||||||
|
|
||||||
|
## New Options
|
||||||
|
|
||||||
|
### Collection Rate
|
||||||
|
|
||||||
|
You can set the collection rate counter on using the "Display Collection Rate" on the Game Options tab are using the CLI option `--collection_rate`. Mystery seeds will not display the total.
|
||||||
|
|
||||||
|
### Goal: Trinity
|
||||||
|
|
||||||
|
Triforces are placed behind Ganon, on the pedestal, and on Murahdahla with 8/10 triforce pieces required. Recommend to run with 4-5 Crystal requirement for Ganon. Automatically pre-opens the pyramid.
|
||||||
|
|
||||||
|
### Boss Shuffle: Unique
|
||||||
|
|
||||||
|
At least one boss each of the prize bosses will be present guarding the prizes. GT bosses can be anything.
|
||||||
|
|
||||||
|
### MSU Resume
|
||||||
|
|
||||||
|
Turns on msu resume support. Found on "Game Options" tab, the "Adjust/Patch" tab, or use the `--msu_resume` CLI option.
|
||||||
|
|
||||||
|
### BPS Patch
|
||||||
|
|
||||||
|
Creates a bps patch for the seed. Found on the "Generation Setup" tab called "Create BPS Patches" or `--bps`. Can turn off generating a rom using the existing "Create Patched ROM" option or `--suppress_rom`. There is an option on the Adjust/Patch tab to select a bps file to apply to the Base Rom selected on the Generation Setup tab using the Patch Rom button. Selected adjustments will be applied during patching.
|
||||||
|
|
||||||
|
## New Font
|
||||||
|
|
||||||
|
Font updated to support lowercase English. Lowercase vs. uppercase typos may exist. Note, you can use lowercase English letters on the file name.
|
||||||
|
|
||||||
## Customizer
|
## Customizer
|
||||||
|
|
||||||
@@ -56,7 +82,6 @@ To support customizer and future entrance shuffle modes (perhaps even customizab
|
|||||||
|
|
||||||
## Restricted Item Placement Algorithm
|
## Restricted Item Placement Algorithm
|
||||||
|
|
||||||
|
|
||||||
The "Item Sorting" option or ```--algorithm``` has been updated with new placement algorithms. Older algorithms have been removed.
|
The "Item Sorting" option or ```--algorithm``` has been updated with new placement algorithms. Older algorithms have been removed.
|
||||||
|
|
||||||
When referenced below, Major Items include all Y items, all A items, all equipment (swords, shields, & armor) and Heart Containers. Dungeon items are considered major if shuffled outside of dungeons. Bomb and arrows upgrades are Major if shopsanity is turned on. The arrow quiver and universal small keys are Major if retro is turned on. Triforce Pieces are Major if that is the goal, and the Bomb Bag is Major if that is enabled.
|
When referenced below, Major Items include all Y items, all A items, all equipment (swords, shields, & armor) and Heart Containers. Dungeon items are considered major if shuffled outside of dungeons. Bomb and arrows upgrades are Major if shopsanity is turned on. The arrow quiver and universal small keys are Major if retro is turned on. Triforce Pieces are Major if that is the goal, and the Bomb Bag is Major if that is enabled.
|
||||||
@@ -83,12 +108,12 @@ The fill attempts to place all major items in dungeons. It will overflow to the
|
|||||||
|
|
||||||
### District Restriction
|
### District Restriction
|
||||||
|
|
||||||
The world is divided up into different regions or districts. Each dungeon is it's own district. The overworld consists of the following districts:
|
The world is divided up into different regions or districts. Each dungeon is its own district. The overworld consists of the following districts:
|
||||||
|
|
||||||
Light world:
|
Light world:
|
||||||
|
|
||||||
* Kakariko (The main screen, blacksmith screen, and library/maze race screens)
|
* Kakariko (The main screen, blacksmith screen, and library/maze race screens)
|
||||||
* Northwest Hyrule (The lost woods and fortune teller all the way to the rive west of the potion shop)
|
* Northwest Hyrule (The lost woods and fortune teller screens all the way to the river west of the potion shop)
|
||||||
* Central Hyrule (Hyrule castle, Link's House, the marsh, and the haunted grove)
|
* Central Hyrule (Hyrule castle, Link's House, the marsh, and the haunted grove)
|
||||||
* Desert (From the thief to the main desert screen)
|
* Desert (From the thief to the main desert screen)
|
||||||
* Lake Hylia (Around the lake)
|
* Lake Hylia (Around the lake)
|
||||||
@@ -117,10 +142,9 @@ In multiworld, the districts chosen apply to all players.
|
|||||||
|
|
||||||
## New Hints
|
## New Hints
|
||||||
|
|
||||||
Based on the district algorithm above (whether it is enabled or not,) new hints can appear about that district or dungeon. For each district and dungeon, it is evaluated whether it contains vital items and how many. If it has not any vital item, items then it moves onto useful items. Useful items are generally safeties or convenience items: shields, mails, half magic, bottles, medallions that aren't required, etc. If it contains none of those and is an overworld district, then it check for a couple more things. First, if dungeons are shuffled, it looks to see if any are in the district, if so, one of those dungeons is picked for the hint. Then, if connectors are shuffled, it checks to see if you can get to unique region through a connector in that district. If none of the above apply, the district or dungeon is considered completely foolish. At least two "foolish" districts are chosen and the rest are random.
|
Based on the district algorithm above (whether it is enabled or not,) new hints can appear about that district or dungeon. For each district and dungeon, it is evaluated whether it contains vital items and how many. If it has not any vital item, items then it moves onto useful items. Useful items are generally safeties or convenience items: shields, mails, half magic, bottles, medallions that aren't required, etc. If it contains none of those and is an overworld district, then it checks for a couple more things. First, if dungeons are shuffled, it looks to see if any are in the district, if so, one of those dungeons is picked for the hint. Then, if connectors are shuffled, it checks to see if you can get to unique region through a connector in that district. If none of the above apply, the district or dungeon is considered completely foolish.
|
||||||
|
|
||||||
|
## Overworld Map shows Dungeon Entrances
|
||||||
### Overworld Map shows dungeon location
|
|
||||||
|
|
||||||
Option to move indicators on overworld map to reference dungeon location. The non-default options include indicators for Hyrule Castle, Agahnim's Tower, and Ganon's Tower.
|
Option to move indicators on overworld map to reference dungeon location. The non-default options include indicators for Hyrule Castle, Agahnim's Tower, and Ganon's Tower.
|
||||||
|
|
||||||
@@ -156,7 +180,9 @@ As before, the boss may have any item including any dungeon item that could occu
|
|||||||
|
|
||||||
##### mapcompass
|
##### mapcompass
|
||||||
|
|
||||||
The map and compass are logically required to defeat a boss. This prevents both of those from appearing on the dungeon boss. Note that this does affect item placement logic and the placement algorithm as maps and compasses are considered as required items to beat a boss.
|
~~The map and compass are logically required to defeat a boss. This prevents both of those from appearing on the dungeon boss. Note that this does affect item placement logic and the placement algorithm as maps and compasses are considered as required items to beat a boss.~~
|
||||||
|
|
||||||
|
Currently bugged, not recommended for use.
|
||||||
|
|
||||||
##### dungeon
|
##### dungeon
|
||||||
|
|
||||||
@@ -166,145 +192,87 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o
|
|||||||
|
|
||||||
#### Customizer
|
#### Customizer
|
||||||
|
|
||||||
|
* Fixed an issue where Interior Key Doors were missing from custom yaml output
|
||||||
|
* Updated lite/lean ER for pottery settings
|
||||||
|
|
||||||
* Fixed an issue with lite/lean ER not generating
|
* Fixed an issue with lite/lean ER not generating
|
||||||
* Fixed up the GUI selection of the customizer file.
|
* Fixed up the GUI selection of the customizer file.
|
||||||
* Fixed up the item_pool section to skip a lot of pool manipulations. Key items will be added (like the bow) if not detected. Extra dungeon items can be added to the pool and will be confined to the dungeon if possible (and not shuffled). If the pool isn't full, junk items are added to the pool to fill it out.
|
* Fixed up the item_pool section to skip a lot of pool manipulations. Key items will be added (like the bow) if not detected. Extra dungeon items can be added to the pool and will be confined to the dungeon if possible (and not shuffled). If the pool isn't full, junk items are added to the pool to fill it out.
|
||||||
|
|
||||||
#### Volatile
|
|
||||||
|
|
||||||
* 1.0.2.7
|
|
||||||
* Revised: Fix for Waterfall of Wishing logic in open. You must have flippers to exit the Waterfall (flippers also required in glitched modes as well)
|
|
||||||
* 1.0.2.6
|
|
||||||
* Fix for Zelda (or any follower) going to the maiden cell supertile and the boss is not Blind. The follower will not despawn unless the boss is Blind, then the maiden will spawn as normal.
|
|
||||||
* Added a check for package requirements before running code. GUI and console both for better error messages. Thanks to mtrethewey for the idea.
|
|
||||||
* Refactored spoiler to generate in stages for better error collection. A meta file will be generated additionally for mystery seeds. Some random settings moved later in the spoiler to have the meta section at the top not spoil certain things. (GT/Ganon requirements.) Thanks to codemann and OWR for most of this work.
|
|
||||||
* Fix for Waterfall of Wishing logic in open. You must have flippers to exit the Waterfall (or moon pearl in glitched modes that allow minor glitches in logic)
|
|
||||||
* 1.0.2.5
|
|
||||||
* Some textual changes for hints (capitalization standardization)
|
|
||||||
* Item will be highlighted in red if experimental is on
|
|
||||||
* Bug with 0 GT crystals not opening GT
|
|
||||||
* Settings code fix
|
|
||||||
* Fix for pottery not counting items in certain caves that share a supertile with shops
|
|
||||||
* 1.0.2.4
|
|
||||||
* Updated tourney winners (included Doors Async League winners)
|
|
||||||
* Fixed a couple issues with dungeon counters and the DungeonCompletion field for autotracking
|
|
||||||
* 1.0.2.3
|
|
||||||
* Fix MultiClient for new shop data location in SRAM
|
|
||||||
* Some minor text updates
|
|
||||||
* 1.0.2.2
|
|
||||||
* Change to all key pots and enemy key drops: always use the same address
|
|
||||||
* Don't colorize key pots in mystery if the item is "forced"
|
|
||||||
* 1.0.2.1
|
|
||||||
* Fix for paired doors
|
|
||||||
* Fix for forbidding certain dashable doors (it actually does something this time)
|
|
||||||
* 1.0.2.0
|
|
||||||
* Updated baserom to bleeding edge
|
|
||||||
* Pottery and enemy SRAM re-located to final destination
|
|
||||||
* Bulk of work on new font
|
|
||||||
* Updated TFH to support up to 850 pieces
|
|
||||||
* Fix for major item algorithm and pottery
|
|
||||||
* Updated map display on keysanity menu to work better with overworld_amp option
|
|
||||||
* Minor bug in crossed doors
|
|
||||||
* Minor bug in MultiClient which would count switches
|
|
||||||
* 1.0.1.13
|
|
||||||
* New pottery modes
|
|
||||||
* Trinity goal added
|
|
||||||
* Potential fix for pottery hera key
|
|
||||||
* Fix for arrows sneaking into item pool with rupee bow
|
|
||||||
* Fixed msu resume bug on patcher
|
|
||||||
* Bonk Recoil OHKO fix (again)
|
|
||||||
* 1.0.1.12
|
|
||||||
* Fix for Multiworld forfeits, shops and pot items now included
|
|
||||||
* Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill.
|
|
||||||
* MultiServer fix for ssl certs and python
|
|
||||||
* Inverted bug
|
|
||||||
* Fix for hammerdashing pots, if sprite limit is reached, items won't spawn, but error beep won't play either because of other SFX
|
|
||||||
* Arrghus splash no longer used for pottery sprites (used apple instead)
|
|
||||||
* Killing enemies via freeze + hammer properly results in the droppable item instead of the freeze prize
|
|
||||||
* Forbid certain doors from being dashable when you either can't dash them open (but bombs would work) or you'd fall into a pit from the bonk recoil in OHKO
|
|
||||||
* Logic refinements
|
|
||||||
* Skull X Room requires Boots or access to Skull Back Drop
|
|
||||||
* GT Falling Torches requires Boots to get over the falling tile gap (this is a stop-gap measure until more sophisticated crystal switch traversal is possible)
|
|
||||||
* Fixed a couple rain state issues
|
|
||||||
* 1.0.1.11
|
|
||||||
* Separated Collection Rate counter from experimental
|
|
||||||
* Added MSU Resume option
|
|
||||||
* Ensured pots in TR Dark Ride need lamp
|
|
||||||
* Fix for GT Crystal Conveyor not requiring Somaria/Bombs to get through
|
|
||||||
* Fixes for Links House being at certain entrances (did not generate)
|
|
||||||
* 1.0.1.10
|
|
||||||
* More location count fixes
|
|
||||||
* Add major_only algorithm to code
|
|
||||||
* Include 1.0.0.2 fixes
|
|
||||||
* 1.0.1.9
|
|
||||||
* Every pot you pick up that wasn't part of the location pool does not count toward the location count
|
|
||||||
* Fix for items spawning where a thrown pot was
|
|
||||||
* Fix for vanilla_fill, it now prioritizes heart container placements
|
|
||||||
* Fix for dungeon counter showing up in AT/HC in crossed dungeon mode
|
|
||||||
* Fix for TR Dark Ride (again) and some ohko rules refinement
|
|
||||||
* 1.0.1.8
|
|
||||||
* Every pot you pick up now counts toward the location count
|
|
||||||
* A pot will de-spawn before the item under it does, error beep only plays if it still can't spawn
|
|
||||||
* Updated item counter & credits to support 4 digits
|
|
||||||
* Updated compass counter to support 3 digits (up to 255)
|
|
||||||
* Updated retro take-anys to not replace pot locations when pottery options are used
|
|
||||||
* Updated mystery_example.yml
|
|
||||||
* Fixed usestartinventory with mystery
|
|
||||||
* Fixed a bug with the old pot shuffle (crashed when used)
|
|
||||||
* 1.0.1.7
|
|
||||||
* Expanded Mystery logic options (e.g. owglitches)
|
|
||||||
* Allowed Mystery.py to create BPS patches
|
|
||||||
* Allow creation of BPS and SFC files (no longer mutually exclusive)
|
|
||||||
* Pedestal goal + vanilla swords places a random sword in the pool
|
|
||||||
* Rebalanced trash ditching algo for seeds with lots of triforce pieces
|
|
||||||
* Added a few more places Links House shouldn't go when shuffled
|
|
||||||
* Fixed a bug with shopsanity + district algorithm where pre-placed potions messed up the placeholder count
|
|
||||||
* Fixed usestartinventory flag (can be use on a per player basis)
|
|
||||||
* Fix for map indicators on keysanity menu not showing up
|
|
||||||
* Potential sprite selector fix for systems with SSL issues
|
|
||||||
* 1.0.1.6
|
|
||||||
* A couple new options for lighter pottery modes (Cave Pots and Dungeon Pots)
|
|
||||||
* New option for Boss Shuffle: Unique (Prize bosses will be one of each, but GT bosses can be anything)
|
|
||||||
* Support for BPS patch creation and applying patches during adjustment
|
|
||||||
* Fix for SFX shuffle
|
|
||||||
* Fix for Standard ER where locations in rain state could be in logic
|
|
||||||
* Fix for Ice Refill room pots, require being able to hit a switch for bombbag mode
|
|
||||||
* 1.0.1.5
|
|
||||||
* Fix for Hera Basement Cage item inheriting last pot checked
|
|
||||||
* Update indicators on keysanity menu for overworld map option
|
|
||||||
* 1.0.1.4
|
|
||||||
* Reverted SRAM change (the underlying refactor isn't done yet)
|
|
||||||
* 1.0.1.3
|
|
||||||
* Fixed inverted generation issues with pottery option
|
|
||||||
* Moved SRAM according to SRAM standard
|
|
||||||
* Removed equitable algorithm
|
|
||||||
* Upped TFH goal limit to 254
|
|
||||||
* Cuccos should no longer cause trap door rooms to not open
|
|
||||||
* Added double click fix for install.py
|
|
||||||
* Fix for pottery item palettes near bonkable torches
|
|
||||||
* Fix for multiworld progression balancing would place Nothing or Arrow items
|
|
||||||
* 1.0.1.2
|
|
||||||
* Fixed logic for pots in TR Hub and TR Dark Ride
|
|
||||||
* Fix for districting + shopsanity
|
|
||||||
* Hint typo correction
|
|
||||||
* 1.0.1.1
|
|
||||||
* Fixed logic for pots in the Ice Hammer Block room (Glove + Hammer required)
|
|
||||||
* Fixed logic for 2 pots in the Ice Antechamber (Glove required)
|
|
||||||
* Fixed retro not saving keys when grabbed from under pots in caves
|
|
||||||
* Fixed GUI not applying Drop shuffle when "Pot and Drops" are marked
|
|
||||||
* Fixed dungeon counts when one of Pottery or Drops are disabled
|
|
||||||
|
|
||||||
#### Unstable
|
#### Unstable
|
||||||
|
|
||||||
|
* 1.0.1.2
|
||||||
|
* Fixed an issue with small key bias rework
|
||||||
|
* Fixed an issue where trinity goal would open pyramid unexpectedly. (No longer does so if ER mdoe is shuffling holes). Crystals goal updated to match that behavior.
|
||||||
|
* Fixed a playthrough issue that was not respecting pot rules
|
||||||
|
* Fixed an issue that was conflicting with downstream OWR project
|
||||||
|
* 1.0.1.1
|
||||||
|
* Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be
|
||||||
|
* Certain pot items no longer reload when reloading the supertile (matches original pot behavior better)
|
||||||
|
* Changed the key distribution that made small keys placement more random when keys are in their own dungeon
|
||||||
|
* Unique boss shuffle no longer allows repeat bosses in GT (e.g. only one Trinexx in GT, so exactly 3 bosses are repeated in the seed. This is a difference process than full which does affect the probability distribution.)
|
||||||
|
* Removed text color in hints due to vanilla bug
|
||||||
|
* 1.0.1.0
|
||||||
|
* Large features
|
||||||
|
* New pottery modes - see notes above
|
||||||
|
* Pot substitutions added for red rupees, 10 bomb packs, 3 bomb packs, and 10 arrows have been added. They use objects that can result from a tree pull or other drop. The 3 bomb pack becomes a 4 bomb pack and the 10 bomb pack becomes an 8 pack. These substitutions are repeatable like all other normal pot contents.
|
||||||
|
* Updated TFH to support up to 850 pieces
|
||||||
|
* New font support
|
||||||
|
* Trinity goal added
|
||||||
|
* Separated Collection Rate counter from experimental
|
||||||
|
* Added MSU Resume option
|
||||||
|
* Support for BPS patch creation and applying patches during adjustment
|
||||||
|
* New option for Boss Shuffle: Unique (Prize bosses will be one of each, but GT bosses can be anything)
|
||||||
|
* Logic Notes
|
||||||
|
* Skull X Room requires Boots or access to Skull Back Drop
|
||||||
|
* GT Falling Torches requires Boots to get over the falling tile gap (this is a stop-gap measure until more sophisticated crystal switch traversal is possible)
|
||||||
|
* Waterfall of Wishing logic in open. You must have flippers to exit the Waterfall (flippers also required in glitched modes as well)
|
||||||
|
* Fix for GT Crystal Conveyor not requiring Somaria/Bombs to get through
|
||||||
|
* Pedestal goal + vanilla swords places a random sword in the pool
|
||||||
|
* Added a few more places Links House shouldn't go when shuffled
|
||||||
|
* Small features
|
||||||
|
* Added a check for python package requirements before running code. GUI and console both for better error messages. Thanks to mtrethewey for the idea.
|
||||||
|
* Refactored spoiler to generate in stages for better error collection. A meta file will be generated additionally for mystery seeds. Some random settings moved later in the spoiler to have the meta section at the top not spoil certain things. (GT/Ganon requirements.) Thanks to codemann and OWR for most of this work.
|
||||||
|
* Updated tourney winners (included Doors Async League winners)
|
||||||
|
* Some textual changes for hints (capitalization standardization)
|
||||||
|
* Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill.
|
||||||
|
* Expanded Mystery logic options (e.g. owglitches)
|
||||||
|
* Updated indicators on keysanity menu for overworld map option
|
||||||
|
* Bug fixes:
|
||||||
|
* Fix for Zelda (or any follower) going to the maiden cell supertile and the boss is not Blind. The follower will not despawn unless the boss is Blind, then the maiden will spawn as normal.
|
||||||
|
* Bug with 0 GT crystals not opening GT
|
||||||
|
* Fixed a couple issues with dungeon counters and the DungeonCompletion field for autotracking
|
||||||
|
* Settings code fix
|
||||||
|
* Fix for forbidding certain dashable doors (it actually does something this time)
|
||||||
|
* Fix for major item algorithm and pottery
|
||||||
|
* Updated map display on keysanity menu to work better with overworld_map option
|
||||||
|
* Minor bug in crossed doors
|
||||||
|
* Fix for Multiworld forfeits, shops and pot items now included
|
||||||
|
* MultiServer fix for ssl certs and python
|
||||||
|
* forbid certain doors from being dashable when you either can't dash them open (but bombs would work) or you'd fall into a pit from the bonk recoil in OHKO
|
||||||
|
* Fixed a couple rain state issues
|
||||||
|
* Add major_only algorithm to settings code
|
||||||
|
* Fixes for Links House being at certain entrances (did not generate)
|
||||||
|
* Fix for vanilla_fill, it now prioritizes heart container placements
|
||||||
|
* Fix for dungeon counter showing up in AT/HC in crossed dungeon mode
|
||||||
|
* Fixed usestartinventory with mystery
|
||||||
|
* Added double click fix for install.py
|
||||||
|
* Fix for SFX shuffle
|
||||||
|
* Fix for districting + shopsanity
|
||||||
|
* Fix for multiworld progression balancing would place Nothing or Arrow items
|
||||||
|
* Fixed a bug with shopsanity + district algorithm where pre-placed potions messed up the placeholder count
|
||||||
|
* Fixed usestartinventory flag (can be use on a per player basis)
|
||||||
|
* Sprite selector fix for systems with SSL issues
|
||||||
|
* Fix for Standard ER where locations in rain state could be in logic
|
||||||
* 1.0.0.3
|
* 1.0.0.3
|
||||||
* overworld_map=map mode fixed. Location of dungeons with maps are not shown until map is retrieved. (Dungeon that do not have map like Castle Tower are simply never shown)
|
* overworld_map=map mode fixed. Location of dungeons with maps are not shown until map is retrieved. (Dungeon that do not have map like Castle Tower are simply never shown)
|
||||||
* Aga2 completion on overworld_map now tied to boss defeat flag instead of pyramid hole being opened (fast ganon fix)
|
* Aga2 completion on overworld_map now tied to boss defeat flag instead of pyramid hole being opened (fast ganon fix)
|
||||||
* Minor issue in dungeon_only algorithm fixed (minorly affected major_only keyshuffle and vanilla fallbacks)
|
* Minor issue in dungeon_only algorithm fixed (minorly affected major_only keyshuffle and vanilla fallbacks)
|
||||||
* 1.0.0.2
|
* 1.0.0.2
|
||||||
* Include 1.0.1 fixes
|
* Include 1.0.1 fixes
|
||||||
* District hint rework
|
* District hint rework
|
||||||
* 1.0.0.1
|
* 1.0.0.1
|
||||||
* Add Light Hype Fairy to bombbag mode as needing bombs
|
* Add Light Hype Fairy to bombbag mode as needing bombs
|
||||||
|
|
||||||
### From stable DoorDev
|
### From stable DoorDev
|
||||||
|
|
||||||
|
|||||||
37
Rom.py
37
Rom.py
@@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = 'a8b35a6396c104e9419ff1e46342e4db'
|
RANDOMIZERBASEHASH = '0f96237c73cccaf7a250343fe3e8c887'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -663,18 +663,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
if world.mapshuffle[player]:
|
if world.mapshuffle[player]:
|
||||||
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
|
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
|
||||||
|
|
||||||
if world.pottery[player] not in ['none']:
|
|
||||||
rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000))
|
|
||||||
# make hammer pegs use different tiles
|
|
||||||
Room0127.write_to_rom(snes_to_pc(0x2A8000), rom)
|
|
||||||
|
|
||||||
if world.pot_contents[player]:
|
|
||||||
colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery']
|
|
||||||
and (world.colorizepots[player]
|
|
||||||
or world.pottery[player] in ['reduced', 'clustered']))
|
|
||||||
if world.pot_contents[player].size() > 0x2800:
|
|
||||||
raise Exception('Pot table is too big for current area')
|
|
||||||
world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots)
|
|
||||||
# fix for swamp drains if necessary
|
# fix for swamp drains if necessary
|
||||||
swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player)
|
swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player)
|
||||||
if not swamp1location.pot.indicator:
|
if not swamp1location.pot.indicator:
|
||||||
@@ -1277,7 +1265,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest
|
rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest
|
||||||
rom.write_byte(0x50599, 0x00) # disable below ganon chest
|
rom.write_byte(0x50599, 0x00) # disable below ganon chest
|
||||||
rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest
|
rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest
|
||||||
if world.open_pyramid[player] or world.goal[player] == 'trinity':
|
if world.open_pyramid[player] or (world.goal[player] in ['trinity', 'crystals'] and world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']):
|
||||||
rom.initial_sram.pre_open_pyramid_hole()
|
rom.initial_sram.pre_open_pyramid_hole()
|
||||||
if world.crystals_needed_for_gt[player] == 0:
|
if world.crystals_needed_for_gt[player] == 0:
|
||||||
rom.initial_sram.pre_open_ganons_tower()
|
rom.initial_sram.pre_open_ganons_tower()
|
||||||
@@ -1547,6 +1535,19 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
if room.player == player and room.modified:
|
if room.player == player and room.modified:
|
||||||
rom.write_bytes(room.address(), room.rom_data())
|
rom.write_bytes(room.address(), room.rom_data())
|
||||||
|
|
||||||
|
if world.pottery[player] not in ['none']:
|
||||||
|
rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2B8000))
|
||||||
|
# make hammer pegs use different tiles
|
||||||
|
Room0127.write_to_rom(snes_to_pc(0x2B8000), rom)
|
||||||
|
|
||||||
|
if world.pot_contents[player]:
|
||||||
|
colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery']
|
||||||
|
and (world.colorizepots[player]
|
||||||
|
or world.pottery[player] in ['reduced', 'clustered']))
|
||||||
|
if world.pot_contents[player].size() > 0x2800:
|
||||||
|
raise Exception('Pot table is too big for current area')
|
||||||
|
world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots)
|
||||||
|
|
||||||
write_strings(rom, world, player, team)
|
write_strings(rom, world, player, team)
|
||||||
|
|
||||||
# write initial sram
|
# write initial sram
|
||||||
@@ -1980,8 +1981,6 @@ def write_strings(rom, world, player, team):
|
|||||||
else:
|
else:
|
||||||
if isinstance(dest, Region) and dest.type == RegionType.Dungeon and dest.dungeon:
|
if isinstance(dest, Region) and dest.type == RegionType.Dungeon and dest.dungeon:
|
||||||
hint = dest.dungeon.name
|
hint = dest.dungeon.name
|
||||||
elif isinstance(dest, Item) and world.experimental[player]:
|
|
||||||
hint = f'{{C:RED}}{dest.hint_text}{{C:WHITE}}' if dest.hint_text else 'something'
|
|
||||||
else:
|
else:
|
||||||
hint = dest.hint_text if dest.hint_text else "something"
|
hint = dest.hint_text if dest.hint_text else "something"
|
||||||
if dest.player != player:
|
if dest.player != player:
|
||||||
@@ -2162,8 +2161,7 @@ def write_strings(rom, world, player, team):
|
|||||||
if this_location:
|
if this_location:
|
||||||
item_name = this_location[0].item.hint_text
|
item_name = this_location[0].item.hint_text
|
||||||
item_name = item_name[0].upper() + item_name[1:]
|
item_name = item_name[0].upper() + item_name[1:]
|
||||||
item_format = f'{{C:RED}}{item_name}{{C:WHITE}}' if world.experimental[player] else item_name
|
this_hint = f'{item_name} can be found {hint_text(this_location[0])}.'
|
||||||
this_hint = f'{item_format} can be found {hint_text(this_location[0])}.'
|
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
hint_count -= 1
|
hint_count -= 1
|
||||||
|
|
||||||
@@ -2217,8 +2215,7 @@ def write_strings(rom, world, player, team):
|
|||||||
elif hint_type == 'path':
|
elif hint_type == 'path':
|
||||||
if item_count == 1:
|
if item_count == 1:
|
||||||
the_item = text_for_item(next(iter(choice_set)), world, player, team)
|
the_item = text_for_item(next(iter(choice_set)), world, player, team)
|
||||||
item_format = f'{{C:RED}}{the_item}{{C:WHITE}}' if world.experimental[player] else the_item
|
hint_candidates.append((hint_type, f'{name} conceals only {the_item}'))
|
||||||
hint_candidates.append((hint_type, f'{name} conceals only {item_format}'))
|
|
||||||
else:
|
else:
|
||||||
hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items'))
|
hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items'))
|
||||||
district_hints = min(len(hint_candidates), len(hint_locations))
|
district_hints = min(len(hint_candidates), len(hint_locations))
|
||||||
|
|||||||
Binary file not shown.
@@ -1,11 +1,10 @@
|
|||||||
description: A test suite for testing various combinations
|
description: A test suite for testing various combinations
|
||||||
# Not yet in this branch
|
algorithm:
|
||||||
#algorithm:
|
major_only: 1
|
||||||
# major_only: 1
|
dungeon_only: 1
|
||||||
# dungeon_only: 1
|
vanilla_fill: 1
|
||||||
# vanilla_fill: 1
|
balanced: 10
|
||||||
# balanced: 10
|
district: 1
|
||||||
# district: 1
|
|
||||||
door_shuffle:
|
door_shuffle:
|
||||||
vanilla: 1
|
vanilla: 1
|
||||||
basic: 2
|
basic: 2
|
||||||
@@ -13,10 +12,19 @@ door_shuffle:
|
|||||||
intensity:
|
intensity:
|
||||||
1: 1
|
1: 1
|
||||||
2: 1
|
2: 1
|
||||||
3: 2 # intensity 3 usuall yield more errors
|
3: 2 # intensity 3 usually yield more errors
|
||||||
keydropshuffle:
|
dropshuffle:
|
||||||
on: 1
|
on: 1
|
||||||
off: 1
|
off: 1
|
||||||
|
pottery:
|
||||||
|
none: 10 # fewer locations
|
||||||
|
keys: 1
|
||||||
|
cave: 1
|
||||||
|
cavekeys: 1
|
||||||
|
dungeon: 1
|
||||||
|
reduced: 1
|
||||||
|
clustered: 1
|
||||||
|
lottery: 1
|
||||||
shopsanity:
|
shopsanity:
|
||||||
on: 1
|
on: 1
|
||||||
off: 1
|
off: 1
|
||||||
@@ -49,9 +57,10 @@ retro:
|
|||||||
goals:
|
goals:
|
||||||
ganon: 1
|
ganon: 1
|
||||||
fast_ganon: 1
|
fast_ganon: 1
|
||||||
dungeons: 2 # this yields more errors so is preferred
|
dungeons: 3 # this yields more errors so is preferred
|
||||||
pedestal: 1
|
pedestal: 1
|
||||||
triforce-hunt: 1
|
triforce-hunt: 1
|
||||||
|
trinity: 1
|
||||||
triforce_goal_min: 20
|
triforce_goal_min: 20
|
||||||
triforce_goal_max: 30
|
triforce_goal_max: 30
|
||||||
triforce_pool_min: 30
|
triforce_pool_min: 30
|
||||||
@@ -86,7 +95,7 @@ accessibility:
|
|||||||
none: 0 # i'm not really interested in this yet
|
none: 0 # i'm not really interested in this yet
|
||||||
restrict_boss_items:
|
restrict_boss_items:
|
||||||
none: 1
|
none: 1
|
||||||
mapcompass: 1
|
# mapcompass: 1 has confirmed issues
|
||||||
dungeon: 1
|
dungeon: 1
|
||||||
tower_open:
|
tower_open:
|
||||||
"0": 1
|
"0": 1
|
||||||
@@ -111,6 +120,7 @@ ganon_open:
|
|||||||
boss_shuffle:
|
boss_shuffle:
|
||||||
none: 1
|
none: 1
|
||||||
simple: 1
|
simple: 1
|
||||||
|
unique: 1
|
||||||
full: 1
|
full: 1
|
||||||
random: 1
|
random: 1
|
||||||
enemy_shuffle: # shouldn't affect generation
|
enemy_shuffle: # shouldn't affect generation
|
||||||
|
|||||||
@@ -347,6 +347,10 @@
|
|||||||
"dest": "create_rom",
|
"dest": "create_rom",
|
||||||
"help": "suppress"
|
"help": "suppress"
|
||||||
},
|
},
|
||||||
|
"suppress_meta": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
"shuffleganon": {
|
"shuffleganon": {
|
||||||
"action": "store_false",
|
"action": "store_false",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
|
|||||||
@@ -113,12 +113,14 @@ def roll_settings(weights):
|
|||||||
|
|
||||||
ret.crystals_ganon = get_choice('ganon_open')
|
ret.crystals_ganon = get_choice('ganon_open')
|
||||||
|
|
||||||
goal_min = get_choice_default('triforce_goal_min', default=20)
|
from ItemList import set_default_triforce
|
||||||
goal_max = get_choice_default('triforce_goal_max', default=20)
|
default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0)
|
||||||
pool_min = get_choice_default('triforce_pool_min', default=30)
|
goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal)
|
||||||
pool_max = get_choice_default('triforce_pool_max', default=30)
|
goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal)
|
||||||
|
pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool)
|
||||||
|
pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool)
|
||||||
ret.triforce_goal = random.randint(int(goal_min), int(goal_max))
|
ret.triforce_goal = random.randint(int(goal_min), int(goal_max))
|
||||||
min_diff = get_choice_default('triforce_min_difference', default=10)
|
min_diff = get_choice_default('triforce_min_difference', default=default_tf_pool-default_tf_goal)
|
||||||
ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
|
ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
|
||||||
|
|
||||||
ret.mode = get_choice('world_state')
|
ret.mode = get_choice('world_state')
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def main(args=None):
|
|||||||
|
|
||||||
def test(testname: str, command: str):
|
def test(testname: str, command: str):
|
||||||
tests[testname] = [command]
|
tests[testname] = [command]
|
||||||
basecommand = f"python3.8 Mystery.py --suppress_rom"
|
basecommand = f"python3.8 Mystery.py --suppress_rom --suppress_meta"
|
||||||
|
|
||||||
def gen_seed():
|
def gen_seed():
|
||||||
taskcommand = basecommand + " " + command
|
taskcommand = basecommand + " " + command
|
||||||
|
|||||||
Reference in New Issue
Block a user