Merge remote-tracking branch 'pepper/multishop-all' into multishop
# Conflicts: # EntranceRandomizer.py # Rom.py # WebHostLib/static/static/playerSettings.json # data/basepatch.bmbp
This commit is contained in:
commit
e36c6e97c1
|
@ -134,6 +134,8 @@ class World(object):
|
||||||
set_player_attr('triforce_pieces_available', 30)
|
set_player_attr('triforce_pieces_available', 30)
|
||||||
set_player_attr('triforce_pieces_required', 20)
|
set_player_attr('triforce_pieces_required', 20)
|
||||||
set_player_attr('shop_shuffle', 'off')
|
set_player_attr('shop_shuffle', 'off')
|
||||||
|
set_player_attr('shop_shuffle_slots', 0)
|
||||||
|
set_player_attr('potion_shop_shuffle', 'none')
|
||||||
set_player_attr('shuffle_prizes', "g")
|
set_player_attr('shuffle_prizes', "g")
|
||||||
set_player_attr('sprite_pool', [])
|
set_player_attr('sprite_pool', [])
|
||||||
set_player_attr('dark_room_logic', "lamp")
|
set_player_attr('dark_room_logic', "lamp")
|
||||||
|
@ -1158,7 +1160,8 @@ class Shop():
|
||||||
'max': max,
|
'max': max,
|
||||||
'replacement': replacement,
|
'replacement': replacement,
|
||||||
'replacement_price': replacement_price,
|
'replacement_price': replacement_price,
|
||||||
'create_location': create_location
|
'create_location': create_location,
|
||||||
|
'player': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
def push_inventory(self, slot: int, item: str, price: int, max: int = 1):
|
def push_inventory(self, slot: int, item: str, price: int, max: int = 1):
|
||||||
|
@ -1171,7 +1174,8 @@ class Shop():
|
||||||
'max': max,
|
'max': max,
|
||||||
'replacement': self.inventory[slot]["item"],
|
'replacement': self.inventory[slot]["item"],
|
||||||
'replacement_price': self.inventory[slot]["price"],
|
'replacement_price': self.inventory[slot]["price"],
|
||||||
'create_location': self.inventory[slot]["create_location"]
|
'create_location': self.inventory[slot]["create_location"],
|
||||||
|
'player': self.inventory[slot]["player"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1257,6 +1261,10 @@ class Spoiler(object):
|
||||||
if item is None:
|
if item is None:
|
||||||
continue
|
continue
|
||||||
shopdata['item_{}'.format(index)] = "{} — {}".format(item['item'], item['price']) if item['price'] else item['item']
|
shopdata['item_{}'.format(index)] = "{} — {}".format(item['item'], item['price']) if item['price'] else item['item']
|
||||||
|
|
||||||
|
if item['player'] > 0:
|
||||||
|
shopdata['item_{}'.format(index)] = shopdata['item_{}'.format(index)].replace('—', '(Player {}) — '.format(item['player']))
|
||||||
|
|
||||||
if item['max'] == 0:
|
if item['max'] == 0:
|
||||||
continue
|
continue
|
||||||
shopdata['item_{}'.format(index)] += " x {}".format(item['max'])
|
shopdata['item_{}'.format(index)] += " x {}".format(item['max'])
|
||||||
|
@ -1330,6 +1338,8 @@ class Spoiler(object):
|
||||||
'triforce_pieces_available': self.world.triforce_pieces_available,
|
'triforce_pieces_available': self.world.triforce_pieces_available,
|
||||||
'triforce_pieces_required': self.world.triforce_pieces_required,
|
'triforce_pieces_required': self.world.triforce_pieces_required,
|
||||||
'shop_shuffle': self.world.shop_shuffle,
|
'shop_shuffle': self.world.shop_shuffle,
|
||||||
|
'shop_shuffle_slots': self.world.shop_shuffle_slots,
|
||||||
|
'potion_shop_shuffle': self.world.potion_shop_shuffle,
|
||||||
'shuffle_prizes': self.world.shuffle_prizes,
|
'shuffle_prizes': self.world.shuffle_prizes,
|
||||||
'sprite_pool': self.world.sprite_pool,
|
'sprite_pool': self.world.sprite_pool,
|
||||||
'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss
|
'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss
|
||||||
|
|
|
@ -326,10 +326,21 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
parser.add_argument('--beemizer', default=defval(0), type=lambda value: min(max(int(value), 0), 4))
|
parser.add_argument('--beemizer', default=defval(0), type=lambda value: min(max(int(value), 0), 4))
|
||||||
parser.add_argument('--shop_shuffle', default='', help='''\
|
parser.add_argument('--shop_shuffle', default='', help='''\
|
||||||
combine letters for options:
|
combine letters for options:
|
||||||
i: shuffle the inventories of the shops around
|
g: generate default inventories for light and dark world shops, and unique shops
|
||||||
|
f: generate default inventories for each shop individually
|
||||||
|
i: shuffle the default inventories of the shops around
|
||||||
p: randomize the prices of the items in shop inventories
|
p: randomize the prices of the items in shop inventories
|
||||||
u: shuffle capacity upgrades into the item pool
|
u: shuffle capacity upgrades into the item pool
|
||||||
''')
|
''')
|
||||||
|
parser.add_argument('--shop_shuffle_slots', default=defval(0),
|
||||||
|
type=lambda value: min(max(int(value), 1), 96),
|
||||||
|
help='''
|
||||||
|
Maximum amount of shop slots able to be filled by items from the item pool.
|
||||||
|
''')
|
||||||
|
parser.add_argument('--potion_shop_shuffle', default=defval('none'), choices=['none', 'a'], help='''\
|
||||||
|
Determine if potion shop shuffle items should be affected by the rules of shop shuffle.
|
||||||
|
Value `none` will only allow prices to be shuffled, `a` will allow any items to be shuffled.
|
||||||
|
''')
|
||||||
parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb'])
|
parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb'])
|
||||||
parser.add_argument('--sprite_pool', help='''\
|
parser.add_argument('--sprite_pool', help='''\
|
||||||
Specifies a colon separated list of sprites used for random/randomonevent. If not specified, the full sprite pool is used.''')
|
Specifies a colon separated list of sprites used for random/randomonevent. If not specified, the full sprite pool is used.''')
|
||||||
|
@ -390,7 +401,8 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
||||||
'heartbeep', "skip_progression_balancing", "triforce_pieces_available",
|
'heartbeep', "skip_progression_balancing", "triforce_pieces_available",
|
||||||
"triforce_pieces_required", "shop_shuffle", "required_medallions",
|
"triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots", "potion_shop_shuffle",
|
||||||
|
"required_medallions",
|
||||||
"plando_items", "plando_texts", "plando_connections",
|
"plando_items", "plando_texts", "plando_connections",
|
||||||
'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
||||||
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
|
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
|
||||||
|
|
2
Fill.py
2
Fill.py
|
@ -171,6 +171,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
||||||
fill_locations.remove(spot_to_fill)
|
fill_locations.remove(spot_to_fill)
|
||||||
|
|
||||||
world.random.shuffle(fill_locations)
|
world.random.shuffle(fill_locations)
|
||||||
|
|
||||||
prioitempool, fill_locations = fast_fill(world, prioitempool, fill_locations)
|
prioitempool, fill_locations = fast_fill(world, prioitempool, fill_locations)
|
||||||
|
|
||||||
restitempool, fill_locations = fast_fill(world, restitempool, fill_locations)
|
restitempool, fill_locations = fast_fill(world, restitempool, fill_locations)
|
||||||
|
@ -247,6 +248,7 @@ def flood_items(world):
|
||||||
itempool.remove(item_to_place)
|
itempool.remove(item_to_place)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def balance_multiworld_progression(world):
|
def balance_multiworld_progression(world):
|
||||||
balanceable_players = {player for player in range(1, world.players + 1) if world.progression_balancing[player]}
|
balanceable_players = {player for player in range(1, world.players + 1) if world.progression_balancing[player]}
|
||||||
if not balanceable_players:
|
if not balanceable_players:
|
||||||
|
|
18
ItemPool.py
18
ItemPool.py
|
@ -481,18 +481,23 @@ def shuffle_shops(world, items, player: int):
|
||||||
shops = []
|
shops = []
|
||||||
upgrade_shops = []
|
upgrade_shops = []
|
||||||
total_inventory = []
|
total_inventory = []
|
||||||
|
potion_option = world.potion_shop_shuffle[player]
|
||||||
for shop in world.shops:
|
for shop in world.shops:
|
||||||
if shop.region.player == player:
|
if shop.region.player == player:
|
||||||
if shop.type == ShopType.UpgradeShop:
|
if shop.type == ShopType.UpgradeShop:
|
||||||
upgrade_shops.append(shop)
|
upgrade_shops.append(shop)
|
||||||
elif shop.type == ShopType.Shop and shop.region.name != 'Potion Shop':
|
elif shop.type == ShopType.Shop:
|
||||||
shops.append(shop)
|
if shop.region.name == 'Potion Shop' and potion_option in [None, '', 'none']:
|
||||||
total_inventory.extend(shop.inventory)
|
upgrade_shops.append(shop) # just put it with the upgrade shops/caves so we don't shuffle the items, just prices
|
||||||
|
else:
|
||||||
|
shops.append(shop)
|
||||||
|
total_inventory.extend(shop.inventory)
|
||||||
|
|
||||||
if 'p' in option:
|
if 'p' in option:
|
||||||
def price_adjust(price: int) -> int:
|
def price_adjust(price: int) -> int:
|
||||||
# it is important that a base price of 0 always returns 0 as new price!
|
# it is important that a base price of 0 always returns 0 as new price!
|
||||||
return int(price * (0.5 + world.random.random() * 1.5))
|
adjust = 2 if price < 100 else 5
|
||||||
|
return int((price / adjust) * (0.5 + world.random.random() * 1.5)) * adjust
|
||||||
|
|
||||||
def adjust_item(item):
|
def adjust_item(item):
|
||||||
if item:
|
if item:
|
||||||
|
@ -507,6 +512,7 @@ def shuffle_shops(world, items, player: int):
|
||||||
|
|
||||||
if 'i' in option:
|
if 'i' in option:
|
||||||
world.random.shuffle(total_inventory)
|
world.random.shuffle(total_inventory)
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
for shop in shops:
|
for shop in shops:
|
||||||
slots = shop.slots
|
slots = shop.slots
|
||||||
|
@ -577,13 +583,13 @@ def create_dynamic_shop_locations(world, player):
|
||||||
if item is None:
|
if item is None:
|
||||||
continue
|
continue
|
||||||
if item['create_location']:
|
if item['create_location']:
|
||||||
loc = Location(player, "{} Item {}".format(shop.region.name, i+1), parent=shop.region)
|
loc = Location(player, "{} Shop Slot {}".format(shop.region.name, i+1), parent=shop.region)
|
||||||
shop.region.locations.append(loc)
|
shop.region.locations.append(loc)
|
||||||
world.dynamic_locations.append(loc)
|
world.dynamic_locations.append(loc)
|
||||||
|
|
||||||
world.clear_location_cache()
|
world.clear_location_cache()
|
||||||
|
|
||||||
world.push_item(loc, ItemFactory(item['item'], player), False)
|
world.push_item(loc, ItemFactory(item['item'], player), False)
|
||||||
loc.event = True
|
loc.event = True
|
||||||
loc.locked = True
|
loc.locked = True
|
||||||
|
|
||||||
|
|
69
Main.py
69
Main.py
|
@ -83,6 +83,8 @@ def main(args, seed=None):
|
||||||
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
||||||
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
||||||
world.shop_shuffle = args.shop_shuffle.copy()
|
world.shop_shuffle = args.shop_shuffle.copy()
|
||||||
|
world.shop_shuffle_slots = args.shop_shuffle_slots.copy()
|
||||||
|
world.potion_shop_shuffle = args.potion_shop_shuffle.copy()
|
||||||
world.progression_balancing = {player: not balance for player, balance in args.skip_progression_balancing.items()}
|
world.progression_balancing = {player: not balance for player, balance in args.skip_progression_balancing.items()}
|
||||||
world.shuffle_prizes = args.shuffle_prizes.copy()
|
world.shuffle_prizes = args.shuffle_prizes.copy()
|
||||||
world.sprite_pool = args.sprite_pool.copy()
|
world.sprite_pool = args.sprite_pool.copy()
|
||||||
|
@ -205,10 +207,74 @@ def main(args, seed=None):
|
||||||
distribute_items_restrictive(world, True)
|
distribute_items_restrictive(world, True)
|
||||||
elif args.algorithm == 'balanced':
|
elif args.algorithm == 'balanced':
|
||||||
distribute_items_restrictive(world, True)
|
distribute_items_restrictive(world, True)
|
||||||
|
|
||||||
if world.players > 1:
|
if world.players > 1:
|
||||||
balance_multiworld_progression(world)
|
balance_multiworld_progression(world)
|
||||||
|
|
||||||
|
candidates = [l for l in world.get_locations() if l.item.name in ['Bee Trap', 'Shovel', 'Bug Catching Net', 'Cane of Byrna', 'Triforce Piece'] or
|
||||||
|
any([x in l.item.name for x in ['Key', 'Map', 'Compass', 'Clock', 'Heart', 'Sword', 'Shield', 'Bomb', 'Arrow', 'Mail']])]
|
||||||
|
world.random.shuffle(candidates)
|
||||||
|
shop_slots = [item for sublist in [shop.region.locations for shop in world.shops] for item in sublist if item.name != 'Potion Shop']
|
||||||
|
shop_slots_adjusted = []
|
||||||
|
shop_items = []
|
||||||
|
|
||||||
|
for location in shop_slots:
|
||||||
|
slot_num = int(location.name[-1]) - 1
|
||||||
|
shop_item = location.parent_region.shop.inventory[slot_num]
|
||||||
|
item = location.item
|
||||||
|
# if item is a rupee or single bee, or identical, swap it out
|
||||||
|
if (shop_item is not None and shop_item['item'] == item.name) or 'Rupee' in item.name or (item.name in ['Bee']):
|
||||||
|
for c in candidates: # chosen item locations
|
||||||
|
if 'Rupee' in c.item.name or c.item.name in 'Bee': continue
|
||||||
|
if (shop_item is not None and shop_item['item'] == c.item.name): continue
|
||||||
|
if c.item_rule(location.item): # if rule is good...
|
||||||
|
logging.debug('Swapping {} with {}:: {} ||| {}'.format(c, location, c.item, location.item))
|
||||||
|
c.item, location.item = location.item, c.item
|
||||||
|
if not world.can_beat_game():
|
||||||
|
c.item, location.item = location.item, c.item
|
||||||
|
else:
|
||||||
|
shop_slots_adjusted.append(location)
|
||||||
|
break
|
||||||
|
# update table to location data
|
||||||
|
item = location.item
|
||||||
|
if (shop_item is not None and shop_item['item'] == item.name) or 'Rupee' in item.name or (item.name in ['Bee']):
|
||||||
|
# this will filter items that match the item in the shop or Rupees, or single bees
|
||||||
|
# really not a way for the player to know a renewable item from a player pool item
|
||||||
|
# bombs can be sitting on top of arrows or a potion refill, but dunno if that's a big deal
|
||||||
|
# this should rarely happen with the above code in place, and could be an option in config if necessary
|
||||||
|
logging.debug('skipping item shop {}'.format(item.name))
|
||||||
|
else:
|
||||||
|
if shop_item is None:
|
||||||
|
location.parent_region.shop.add_inventory(slot_num, 'None', 0)
|
||||||
|
shop_item = location.parent_region.shop.inventory[slot_num]
|
||||||
|
else:
|
||||||
|
shop_item['replacement'] = shop_item['item']
|
||||||
|
shop_item['replacement_price'] = shop_item['price']
|
||||||
|
shop_item['item'] = item.name
|
||||||
|
if any([x in shop_item['item'] for x in ['Single Bomb', 'Single Arrow']]):
|
||||||
|
shop_item['price'] = world.random.randrange(5,35)
|
||||||
|
elif any([x in shop_item['item'] for x in ['Arrows', 'Bombs', 'Clock']]):
|
||||||
|
shop_item['price'] = world.random.randrange(20,120)
|
||||||
|
elif any([x in shop_item['item'] for x in ['Universal Key', 'Compass', 'Map', 'Small Key', 'Piece of Heart']]):
|
||||||
|
shop_item['price'] = world.random.randrange(50,150)
|
||||||
|
else:
|
||||||
|
shop_item['price'] = world.random.randrange(50,300)
|
||||||
|
|
||||||
|
shop_item['max'] = 1
|
||||||
|
shop_item['player'] = item.player if item.player != location.player else 0
|
||||||
|
shop_items.append(shop_item)
|
||||||
|
|
||||||
|
if len(shop_items) > 0:
|
||||||
|
my_prices = [my_item['price'] for my_item in shop_items]
|
||||||
|
price_scale = (80*max(8, len(my_prices)+2))/sum(my_prices)
|
||||||
|
for i in shop_items:
|
||||||
|
i['price'] *= price_scale
|
||||||
|
if i['price'] < 5: i['price'] = 5
|
||||||
|
else: i['price'] = int((i['price']//5)*5)
|
||||||
|
|
||||||
|
logging.debug('Adjusting {} of {} shop slots'.format(len(shop_slots_adjusted), len(shop_slots)))
|
||||||
|
logging.debug('Adjusted {} into shops'.format([x.item.name for x in shop_slots_adjusted]))
|
||||||
|
|
||||||
logger.info('Patching ROM.')
|
logger.info('Patching ROM.')
|
||||||
|
|
||||||
outfilebase = 'BM_%s' % (args.outputname if args.outputname else world.seed)
|
outfilebase = 'BM_%s' % (args.outputname if args.outputname else world.seed)
|
||||||
|
@ -336,6 +402,7 @@ def main(args, seed=None):
|
||||||
main_entrance = get_entrance_to_region(region)
|
main_entrance = get_entrance_to_region(region)
|
||||||
for location in region.locations:
|
for location in region.locations:
|
||||||
if type(location.address) == int: # skips events and crystals
|
if type(location.address) == int: # skips events and crystals
|
||||||
|
if location.address >= 0x400000: continue
|
||||||
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
|
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
|
||||||
er_hint_data[region.player][location.address] = main_entrance.name
|
er_hint_data[region.player][location.address] = main_entrance.name
|
||||||
|
|
||||||
|
|
|
@ -158,6 +158,11 @@ SCOUT_LOCATION_ADDR = SAVEDATA_START + 0x4D7 # 1 byte
|
||||||
SCOUTREPLY_LOCATION_ADDR = SAVEDATA_START + 0x4D8 # 1 byte
|
SCOUTREPLY_LOCATION_ADDR = SAVEDATA_START + 0x4D8 # 1 byte
|
||||||
SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte
|
SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte
|
||||||
SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte
|
SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte
|
||||||
|
SHOP_ADDR = SAVEDATA_START + 0x302 # 2 bytes
|
||||||
|
|
||||||
|
|
||||||
|
location_shop_order = [ name for name, info in Regions.shop_table.items() ] # probably don't leave this here. This relies on python 3.6+ dictionary keys having defined order
|
||||||
|
location_shop_ids = set([info[0] for name, info in Regions.shop_table.items()])
|
||||||
|
|
||||||
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
|
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
|
||||||
"Blind's Hideout - Left": (0x11d, 0x20),
|
"Blind's Hideout - Left": (0x11d, 0x20),
|
||||||
|
@ -1158,6 +1163,18 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
||||||
ctx.unsafe_locations_checked.add(location)
|
ctx.unsafe_locations_checked.add(location)
|
||||||
ctx.ui_node.log_info("New check: %s (%d/216)" % (location, len(ctx.unsafe_locations_checked)))
|
ctx.ui_node.log_info("New check: %s (%d/216)" % (location, len(ctx.unsafe_locations_checked)))
|
||||||
ctx.ui_node.send_location_check(ctx, location)
|
ctx.ui_node.send_location_check(ctx, location)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if roomid in location_shop_ids:
|
||||||
|
misc_data = await snes_read(ctx, SHOP_ADDR, len(location_shop_order)*3)
|
||||||
|
for cnt, b in enumerate(misc_data):
|
||||||
|
my_check = Regions.shop_table_by_location_id[0x400000 + cnt]
|
||||||
|
if int(b) > 0 and my_check not in ctx.unsafe_locations_checked:
|
||||||
|
new_check(my_check)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
ctx.ui_node.log_info(f"Exception: {e}")
|
||||||
|
|
||||||
|
|
||||||
for location, (loc_roomid, loc_mask) in location_table_uw.items():
|
for location, (loc_roomid, loc_mask) in location_table_uw.items():
|
||||||
try:
|
try:
|
||||||
|
@ -1217,7 +1234,13 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
||||||
for location in ctx.unsafe_locations_checked:
|
for location in ctx.unsafe_locations_checked:
|
||||||
if (location in ctx.items_missing and location not in ctx.locations_checked) or ctx.send_unsafe:
|
if (location in ctx.items_missing and location not in ctx.locations_checked) or ctx.send_unsafe:
|
||||||
ctx.locations_checked.add(location)
|
ctx.locations_checked.add(location)
|
||||||
new_locations.append(Regions.lookup_name_to_id[location])
|
try:
|
||||||
|
my_id = Regions.lookup_name_to_id.get(location, Regions.shop_table_by_location.get(location, -1))
|
||||||
|
new_locations.append(my_id)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
ctx.ui_node.log_info(f"Exception: {e}")
|
||||||
|
|
||||||
|
|
||||||
await ctx.send_msgs([['LocationChecks', new_locations]])
|
await ctx.send_msgs([['LocationChecks', new_locations]])
|
||||||
|
|
||||||
|
|
|
@ -969,7 +969,7 @@ def get_missing_checks(ctx: Context, client: Client) -> list:
|
||||||
#for location_id in [k[0] for k, v in ctx.locations if k[1] == client.slot]:
|
#for location_id in [k[0] for k, v in ctx.locations if k[1] == client.slot]:
|
||||||
# if location_id not in ctx.location_checks[client.team, client.slot]:
|
# if location_id not in ctx.location_checks[client.team, client.slot]:
|
||||||
# locations.append(Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}'))
|
# locations.append(Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}'))
|
||||||
for location_id, location_name in Regions.lookup_id_to_name.items(): # cheat console is -1, keep in mind
|
for location_id, location_name in {**Regions.lookup_id_to_name, **Regions.shop_table_by_location_id}.items(): # cheat console is -1, keep in mind
|
||||||
if location_id != -1 and location_id not in ctx.location_checks[client.team, client.slot] and (location_id, client.slot) in ctx.locations:
|
if location_id != -1 and location_id not in ctx.location_checks[client.team, client.slot] and (location_id, client.slot) in ctx.locations:
|
||||||
locations.append(location_name)
|
locations.append(location_name)
|
||||||
return locations
|
return locations
|
||||||
|
|
|
@ -405,10 +405,16 @@ def roll_settings(weights, plando_options: typing.Set[str] = frozenset(("bosses"
|
||||||
# change minimum to required pieces to avoid problems
|
# change minimum to required pieces to avoid problems
|
||||||
ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90)
|
ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90)
|
||||||
|
|
||||||
|
ret.shop_shuffle_slots = int(get_choice('shop_shuffle_slots', weights, '0'))
|
||||||
|
|
||||||
ret.shop_shuffle = get_choice('shop_shuffle', weights, '')
|
ret.shop_shuffle = get_choice('shop_shuffle', weights, '')
|
||||||
if not ret.shop_shuffle:
|
if not ret.shop_shuffle:
|
||||||
ret.shop_shuffle = ''
|
ret.shop_shuffle = ''
|
||||||
|
|
||||||
|
ret.potion_shop_shuffle = get_choice('potion_shop_shuffle', weights, '')
|
||||||
|
if not ret.potion_shop_shuffle:
|
||||||
|
ret.potion_shop_shuffle = ''
|
||||||
|
|
||||||
ret.mode = get_choice('world_state', weights, None) # legacy support
|
ret.mode = get_choice('world_state', weights, None) # legacy support
|
||||||
if ret.mode == 'retro':
|
if ret.mode == 'retro':
|
||||||
ret.mode = 'open'
|
ret.mode = 'open'
|
||||||
|
|
64
Regions.py
64
Regions.py
|
@ -369,7 +369,39 @@ def create_shops(world, player: int):
|
||||||
cls_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
cls_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
||||||
ShopType.Shop: Shop,
|
ShopType.Shop: Shop,
|
||||||
ShopType.TakeAny: TakeAny}
|
ShopType.TakeAny: TakeAny}
|
||||||
for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in shop_table.items():
|
option = world.shop_shuffle[player]
|
||||||
|
potion_option = world.potion_shop_shuffle[player]
|
||||||
|
my_shop_table = dict(shop_table)
|
||||||
|
|
||||||
|
num_slots = int(world.shop_shuffle_slots[player])
|
||||||
|
|
||||||
|
my_shop_slots = ([True] * num_slots + [False] * (len(shop_table) * 3))[:len(shop_table)*3 - 2]
|
||||||
|
|
||||||
|
world.random.shuffle(my_shop_slots)
|
||||||
|
|
||||||
|
from Items import ItemFactory
|
||||||
|
if 'g' in option or 'f' in option:
|
||||||
|
new_basic_shop = world.random.sample(shop_generation_types['default'], k=3)
|
||||||
|
new_dark_shop = world.random.sample(shop_generation_types['default'], k=3)
|
||||||
|
for name, shop in my_shop_table.items():
|
||||||
|
typ, shop_id, keeper, custom, locked, items = shop
|
||||||
|
new_items = world.random.sample(shop_generation_types['default'], k=3)
|
||||||
|
if 'f' not in option:
|
||||||
|
if items == _basic_shop_defaults:
|
||||||
|
new_items = new_basic_shop
|
||||||
|
elif items == _dark_world_shop_defaults:
|
||||||
|
new_items = new_dark_shop
|
||||||
|
if name == 'Capacity Upgrade':
|
||||||
|
continue
|
||||||
|
if name == 'Potion Shop':
|
||||||
|
if 'b' in potion_option:
|
||||||
|
new_items = world.random.sample(shop_generation_types['potion_discount'] + shop_generation_types['bottle'], k=3)
|
||||||
|
elif 'a' not in potion_option:
|
||||||
|
new_items = items
|
||||||
|
keeper = world.random.choice([0xA0, 0xC1, 0xFF])
|
||||||
|
my_shop_table[name] = (typ, shop_id, keeper, custom, locked, new_items)
|
||||||
|
|
||||||
|
for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in my_shop_table.items():
|
||||||
if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop':
|
if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop':
|
||||||
locked = True
|
locked = True
|
||||||
inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
||||||
|
@ -379,6 +411,21 @@ def create_shops(world, player: int):
|
||||||
world.shops.append(shop)
|
world.shops.append(shop)
|
||||||
for index, item in enumerate(inventory):
|
for index, item in enumerate(inventory):
|
||||||
shop.add_inventory(index, *item)
|
shop.add_inventory(index, *item)
|
||||||
|
if region_name == 'Potion Shop' and 'a' not in potion_option:
|
||||||
|
pass
|
||||||
|
elif region_name == 'Capacity Upgrade':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if my_shop_slots.pop():
|
||||||
|
additional_item = world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
|
||||||
|
world.itempool.append(ItemFactory(additional_item, player))
|
||||||
|
slot_name = "{} Shop Slot {}".format(shop.region.name, index+1)
|
||||||
|
loc = Location(player, slot_name, address=shop_table_by_location[slot_name], parent=shop.region)
|
||||||
|
shop.region.locations.append(loc)
|
||||||
|
world.dynamic_locations.append(loc)
|
||||||
|
|
||||||
|
world.clear_location_cache()
|
||||||
|
|
||||||
|
|
||||||
# (type, room_id, shopkeeper, custom, locked, [items])
|
# (type, room_id, shopkeeper, custom, locked, [items])
|
||||||
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
||||||
|
@ -394,10 +441,21 @@ shop_table = {
|
||||||
'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
'Cave Shop (Lake Hylia)': (0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
'Cave Shop (Lake Hylia)': (0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
'Potion Shop': (0x0109, ShopType.Shop, 0xFF, False, True, [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)]),
|
'Potion Shop': (0x0109, ShopType.Shop, 0xA0, True, False, [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)]),
|
||||||
'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
|
'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shop_table_by_location_id = {0x400000 + cnt: s for cnt, s in enumerate( [item for sublist in [ ["{} Shop Slot {}".format(name, num + 1) for num in range(3)] for name in shop_table ] for item in sublist])}
|
||||||
|
shop_table_by_location = {y:x for x,y in shop_table_by_location_id.items()}
|
||||||
|
|
||||||
|
shop_generation_types = {
|
||||||
|
'default': _basic_shop_defaults + [('Bombs (3)', 20), ('Green Potion', 90), ('Blue Potion', 190), ('Bee', 10), ('Single Arrow', 5), ('Single Bomb', 10)] + [('Red Shield', 500), ('Blue Shield', 50)],
|
||||||
|
'potion': [('Red Potion', 150), ('Green Potion', 90), ('Blue Potion', 190)],
|
||||||
|
'discount_potion': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
|
||||||
|
'bottle': [('Bee', 10)],
|
||||||
|
'time': [('Red Clock', 100), ('Blue Clock', 200), ('Green Clock', 300)],
|
||||||
|
}
|
||||||
|
|
||||||
old_location_address_to_new_location_address = {
|
old_location_address_to_new_location_address = {
|
||||||
0x2eb18: 0x18001b, # Bottle Merchant
|
0x2eb18: 0x18001b, # Bottle Merchant
|
||||||
0x33d68: 0x18001a, # Purple Chest
|
0x33d68: 0x18001a, # Purple Chest
|
||||||
|
@ -705,8 +763,10 @@ location_table: typing.Dict[str,
|
||||||
|
|
||||||
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
||||||
lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}, -1: "cheat console"}
|
lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}, -1: "cheat console"}
|
||||||
|
lookup_id_to_name.update(shop_table_by_location_id)
|
||||||
lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int}
|
lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int}
|
||||||
lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}, "cheat console": -1}
|
lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}, "cheat console": -1}
|
||||||
|
lookup_name_to_id.update(shop_table_by_location)
|
||||||
|
|
||||||
lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 191256: 'Kings Grave Inner Rocks',
|
lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 191256: 'Kings Grave Inner Rocks',
|
||||||
1573194: 'Kings Grave Inner Rocks', 1573189: 'Kings Grave Inner Rocks',
|
1573194: 'Kings Grave Inner Rocks', 1573189: 'Kings Grave Inner Rocks',
|
||||||
|
|
18
Rom.py
18
Rom.py
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '5a607e36a82bbd14180536c8ec3ae49b'
|
RANDOMIZERBASEHASH = '0954a778832b76ba4f96b10eb527ee83'
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
@ -123,6 +123,9 @@ class LocalRom(object):
|
||||||
Patch.create_patch_file(local_path('basepatch.sfc'))
|
Patch.create_patch_file(local_path('basepatch.sfc'))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not os.path.isfile(local_path('data', 'basepatch.bmbp')):
|
||||||
|
raise RuntimeError('Base patch unverified. Unable to continue.')
|
||||||
|
|
||||||
if os.path.isfile(local_path('data', 'basepatch.bmbp')):
|
if os.path.isfile(local_path('data', 'basepatch.bmbp')):
|
||||||
_, target, buffer = Patch.create_rom_bytes(local_path('data', 'basepatch.bmbp'))
|
_, target, buffer = Patch.create_rom_bytes(local_path('data', 'basepatch.bmbp'))
|
||||||
if self.verify(buffer):
|
if self.verify(buffer):
|
||||||
|
@ -130,6 +133,7 @@ class LocalRom(object):
|
||||||
with open(local_path('basepatch.sfc'), 'wb') as stream:
|
with open(local_path('basepatch.sfc'), 'wb') as stream:
|
||||||
stream.write(buffer)
|
stream.write(buffer)
|
||||||
return
|
return
|
||||||
|
raise RuntimeError('Base patch unverified. Unable to continue.')
|
||||||
|
|
||||||
raise RuntimeError('Could not find Base Patch. Unable to continue.')
|
raise RuntimeError('Could not find Base Patch. Unable to continue.')
|
||||||
|
|
||||||
|
@ -677,6 +681,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
distinguished_prog_bow_loc.item.code = 0x65
|
distinguished_prog_bow_loc.item.code = 0x65
|
||||||
|
|
||||||
# patch items
|
# patch items
|
||||||
|
|
||||||
for location in world.get_locations():
|
for location in world.get_locations():
|
||||||
if location.player != player:
|
if location.player != player:
|
||||||
continue
|
continue
|
||||||
|
@ -686,6 +691,9 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
if location.address is None:
|
if location.address is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if 'Shop Slot' in location.name and location.parent_region.shop is not None:
|
||||||
|
continue
|
||||||
|
|
||||||
if not location.crystal:
|
if not location.crystal:
|
||||||
if location.item is not None:
|
if location.item is not None:
|
||||||
# Keys in their native dungeon should use the orignal item code for keys
|
# Keys in their native dungeon should use the orignal item code for keys
|
||||||
|
@ -724,6 +732,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
for music_address in music_addresses:
|
for music_address in music_addresses:
|
||||||
rom.write_byte(music_address, music)
|
rom.write_byte(music_address, music)
|
||||||
|
|
||||||
|
|
||||||
if world.mapshuffle[player]:
|
if world.mapshuffle[player]:
|
||||||
rom.write_byte(0x155C9, local_random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
|
rom.write_byte(0x155C9, local_random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
|
||||||
|
|
||||||
|
@ -1564,14 +1573,13 @@ def write_custom_shops(rom, world, player):
|
||||||
else:
|
else:
|
||||||
sram_offset += shop.item_count
|
sram_offset += shop.item_count
|
||||||
shop_data.extend(bytes)
|
shop_data.extend(bytes)
|
||||||
# [id][item][price-low][price-high][max][repl_id][repl_price-low][repl_price-high]
|
# [id][item][price-low][price-high][max][repl_id][repl_price-low][repl_price-high][player]
|
||||||
for item in shop.inventory:
|
for item in shop.inventory:
|
||||||
if item is None:
|
if item is None:
|
||||||
break
|
break
|
||||||
item_data = [shop_id, ItemFactory(item['item'], player).code] + int16_as_bytes(item['price']) + [
|
item_data = [shop_id, ItemFactory(item['item'], player).code] + int16_as_bytes(item['price']) + [ item['max'],\
|
||||||
item['max'],
|
|
||||||
ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + int16_as_bytes(
|
ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + int16_as_bytes(
|
||||||
item['replacement_price'])
|
item['replacement_price']) + [item['player']]
|
||||||
items_data.extend(item_data)
|
items_data.extend(item_data)
|
||||||
|
|
||||||
rom.write_bytes(0x184800, shop_data)
|
rom.write_bytes(0x184800, shop_data)
|
||||||
|
|
2
Rules.py
2
Rules.py
|
@ -85,7 +85,7 @@ def set_rules(world, player):
|
||||||
add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.world.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or')
|
add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.world.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or')
|
||||||
|
|
||||||
set_bunny_rules(world, player, world.mode[player] == 'inverted')
|
set_bunny_rules(world, player, world.mode[player] == 'inverted')
|
||||||
|
|
||||||
|
|
||||||
def mirrorless_path_to_castle_courtyard(world, player):
|
def mirrorless_path_to_castle_courtyard(world, player):
|
||||||
# If Agahnim is defeated then the courtyard needs to be accessible without using the mirror for the mirror offset glitch.
|
# If Agahnim is defeated then the courtyard needs to be accessible without using the mirror for the mirror offset glitch.
|
||||||
|
|
|
@ -477,9 +477,23 @@
|
||||||
"name": "None",
|
"name": "None",
|
||||||
"value": "none"
|
"value": "none"
|
||||||
},
|
},
|
||||||
{
|
"g": {
|
||||||
"name": "Inventory",
|
"keyString": "shop_shuffle.g",
|
||||||
"value": "i"
|
"friendlyName": "Inventory Generate",
|
||||||
|
"description": "Generates new default base inventories of overworld and underworld shops.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"f": {
|
||||||
|
"keyString": "shop_shuffle.f",
|
||||||
|
"friendlyName": "Full Inventory Generate",
|
||||||
|
"description": "Generates new base inventories of each individual shop.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"i": {
|
||||||
|
"keyString": "shop_shuffle.i",
|
||||||
|
"friendlyName": "Inventory Shuffle",
|
||||||
|
"description": "Shuffles the inventories of shops between each other.",
|
||||||
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Prices",
|
"name": "Prices",
|
||||||
|
@ -493,9 +507,269 @@
|
||||||
"name": "Inventory and Prices",
|
"name": "Inventory and Prices",
|
||||||
"value": "ip"
|
"value": "ip"
|
||||||
},
|
},
|
||||||
{
|
"uip": {
|
||||||
"name": "Inventory, Prices, and Upgrades",
|
"keyString": "shop_shuffle.uip",
|
||||||
"value": "ipu"
|
"friendlyName": "Full Shuffle",
|
||||||
|
"description": "Shuffles the inventory and randomizes the prices of items in shops. Also distributes capacity upgrades throughout the world.",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shop_shuffle_slots": {
|
||||||
|
"keyString": "shop_shuffle_slots",
|
||||||
|
"friendlyName": "Shop Shuffle Slots",
|
||||||
|
"description": "How Many Slots in Shops are dedicated to items from the item pool",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"0": {
|
||||||
|
"keyString": "shop_shuffle_slots.0",
|
||||||
|
"friendlyName": 0,
|
||||||
|
"description": "0 slots",
|
||||||
|
"defaultValue": 50
|
||||||
|
},
|
||||||
|
"15": {
|
||||||
|
"keyString": "shop_shuffle_slots.3",
|
||||||
|
"friendlyName": 3,
|
||||||
|
"description": "3 slots",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"20": {
|
||||||
|
"keyString": "shop_shuffle_slots.6",
|
||||||
|
"friendlyName": 6,
|
||||||
|
"description": "6 slots",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"30": {
|
||||||
|
"keyString": "shop_shuffle_slots.12",
|
||||||
|
"friendlyName": 12,
|
||||||
|
"description": "12 slots",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"40": {
|
||||||
|
"keyString": "shop_shuffle_slots.96",
|
||||||
|
"friendlyName": 96,
|
||||||
|
"description": "96 slots",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"potion_shop_shuffle": {
|
||||||
|
"keyString": "potion_shop_shuffle",
|
||||||
|
"friendlyName": "Potion Shop Shuffle Rules",
|
||||||
|
"description": "Influence on potion shop by shop shuffle options",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"none": {
|
||||||
|
"keyString": "potion_shop_shuffle.none",
|
||||||
|
"friendlyName": "Vanilla Shops",
|
||||||
|
"description": "Shop contents are left unchanged, only prices.",
|
||||||
|
"defaultValue": 50
|
||||||
|
},
|
||||||
|
"a": {
|
||||||
|
"keyString": "potion_shop_shuffle.a",
|
||||||
|
"friendlyName": "Any Items can be shuffled in and out of the shop",
|
||||||
|
"description": "",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shuffle_prizes": {
|
||||||
|
"keyString": "shuffle_prizes",
|
||||||
|
"friendlyName": "Prize Shuffle",
|
||||||
|
"description": "Alters the Prizes from pulling, bonking, enemy kills, digging, and hoarders",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"none": {
|
||||||
|
"keyString": "shuffle_prizes.none",
|
||||||
|
"friendlyName": "None",
|
||||||
|
"description": "All prizes from pulling, bonking, enemy kills, digging, hoarders are vanilla.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"g": {
|
||||||
|
"keyString": "shuffle_prizes.g",
|
||||||
|
"friendlyName": "\"General\" prize shuffle",
|
||||||
|
"description": "Shuffles the prizes from pulling, enemy kills, digging, hoarders",
|
||||||
|
"defaultValue": 50
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"keyString": "shuffle_prizes.b",
|
||||||
|
"friendlyName": "Bonk prize shuffle",
|
||||||
|
"description": "Shuffles the prizes from bonking into trees.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"bg": {
|
||||||
|
"keyString": "shuffle_prizes.bg",
|
||||||
|
"friendlyName": "Both",
|
||||||
|
"description": "Shuffles both of the options.",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timer": {
|
||||||
|
"keyString": "timer",
|
||||||
|
"friendlyName": "Timed Modes",
|
||||||
|
"description": "Add a timer to the game UI, and cause it to have various effects.",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"none": {
|
||||||
|
"keyString": "timer.none",
|
||||||
|
"friendlyName": "Disabled",
|
||||||
|
"description": "No timed mode is applied to the game.",
|
||||||
|
"defaultValue": 50
|
||||||
|
},
|
||||||
|
"timed": {
|
||||||
|
"keyString": "timer.timed",
|
||||||
|
"friendlyName": "Timed Mode",
|
||||||
|
"description": "Starts with clock at zero. Green clocks subtract 4 minutes (total 20). Blue clocks subtract 2 minutes (total 10). Red clocks add two minutes (total 10). Winner is the player with the lowest time at the end.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"timed_ohko": {
|
||||||
|
"keyString": "timer.timed_ohko",
|
||||||
|
"friendlyName": "Timed OHKO",
|
||||||
|
"description": "Starts the clock at ten minutes. Green clocks add five minutes (total 25). As long as the clock as at zero, Link will die in one hit.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"ohko": {
|
||||||
|
"keyString": "timer.ohko",
|
||||||
|
"friendlyName": "One-Hit KO",
|
||||||
|
"description": "Timer always at zero. Permanent OHKO.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"timed_countdown": {
|
||||||
|
"keyString": "timer.timed_countdown",
|
||||||
|
"friendlyName": "Timed Countdown",
|
||||||
|
"description": "Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"display": {
|
||||||
|
"keyString": "timer.display",
|
||||||
|
"friendlyName": "Timer Only",
|
||||||
|
"description": "Displays a timer, but otherwise does not affect gameplay or the item pool.",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"countdown_start_time": {
|
||||||
|
"keyString": "countdown_start_time",
|
||||||
|
"friendlyName": "Countdown Starting Time",
|
||||||
|
"description": "The amount of time, in minutes, to start with in Timed Countdown and Timed OHKO modes.",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"0": {
|
||||||
|
"keyString": "countdown_start_time.0",
|
||||||
|
"friendlyName": 0,
|
||||||
|
"description": "Start with no time on the timer. In Timed OHKO mode, start in OHKO mode.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"10": {
|
||||||
|
"keyString": "countdown_start_time.10",
|
||||||
|
"friendlyName": 10,
|
||||||
|
"description": "Start with 10 minutes on the timer.",
|
||||||
|
"defaultValue": 50
|
||||||
|
},
|
||||||
|
"20": {
|
||||||
|
"keyString": "countdown_start_time.20",
|
||||||
|
"friendlyName": 20,
|
||||||
|
"description": "Start with 20 minutes on the timer.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"30": {
|
||||||
|
"keyString": "countdown_start_time.30",
|
||||||
|
"friendlyName": 30,
|
||||||
|
"description": "Start with 30 minutes on the timer.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"60": {
|
||||||
|
"keyString": "countdown_start_time.60",
|
||||||
|
"friendlyName": 60,
|
||||||
|
"description": "Start with an hour on the timer.",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"red_clock_time": {
|
||||||
|
"keyString": "red_clock_time",
|
||||||
|
"friendlyName": "Red Clock Time",
|
||||||
|
"description": "The amount of time, in minutes, to add to or subtract from the timer upon picking up a red clock.",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"-2": {
|
||||||
|
"keyString": "red_clock_time.-2",
|
||||||
|
"friendlyName": -2,
|
||||||
|
"description": "Subtract 2 minutes from the timer upon picking up a red clock.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"keyString": "red_clock_time.1",
|
||||||
|
"friendlyName": 1,
|
||||||
|
"description": "Add a minute to the timer upon picking up a red clock.",
|
||||||
|
"defaultValue": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"blue_clock_time": {
|
||||||
|
"keyString": "blue_clock_time",
|
||||||
|
"friendlyName": "Blue Clock Time",
|
||||||
|
"description": "The amount of time, in minutes, to add to or subtract from the timer upon picking up a blue clock.",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"1": {
|
||||||
|
"keyString": "blue_clock_time.1",
|
||||||
|
"friendlyName": 1,
|
||||||
|
"description": "Add a minute to the timer upon picking up a blue clock.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"keyString": "blue_clock_time.2",
|
||||||
|
"friendlyName": 2,
|
||||||
|
"description": "Add 2 minutes to the timer upon picking up a blue clock.",
|
||||||
|
"defaultValue": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"green_clock_time": {
|
||||||
|
"keyString": "green_clock_time",
|
||||||
|
"friendlyName": "Green Clock Time",
|
||||||
|
"description": "The amount of time, in minutes, to add to or subtract from the timer upon picking up a green clock.",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"4": {
|
||||||
|
"keyString": "green_clock_time.4",
|
||||||
|
"friendlyName": 4,
|
||||||
|
"description": "Add 4 minutes to the timer upon picking up a green clock.",
|
||||||
|
"defaultValue": 50
|
||||||
|
},
|
||||||
|
"10": {
|
||||||
|
"keyString": "green_clock_time.10",
|
||||||
|
"friendlyName": 10,
|
||||||
|
"description": "Add 10 minutes to the timer upon picking up a green clock.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"15": {
|
||||||
|
"keyString": "green_clock_time.15",
|
||||||
|
"friendlyName": 15,
|
||||||
|
"description": "Add 15 minutes to the timer upon picking up a green clock.",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"glitch_boots": {
|
||||||
|
"keyString": "glitch_boots",
|
||||||
|
"friendlyName": "Glitch Boots",
|
||||||
|
"description": "Start with Pegasus Boots in any glitched logic mode that makes use of them.",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"on": {
|
||||||
|
"keyString": "glitch_boots.on",
|
||||||
|
"friendlyName": "On",
|
||||||
|
"description": "Enable glitch boots.",
|
||||||
|
"defaultValue": 50
|
||||||
|
},
|
||||||
|
"off": {
|
||||||
|
"keyString": "glitch_boots.off",
|
||||||
|
"friendlyName": "Off",
|
||||||
|
"description": "Disable glitch boots.",
|
||||||
|
"defaultValue": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -211,12 +211,24 @@ beemizer: # Remove items from the global item pool and replace them with single
|
||||||
2: 0 # 60% of the non-essential item pool is replaced with bee traps, of which 20% could be single bees
|
2: 0 # 60% of the non-essential item pool is replaced with bee traps, of which 20% could be single bees
|
||||||
3: 0 # 100% of the non-essential item pool is replaced with bee traps, of which 50% could be single bees
|
3: 0 # 100% of the non-essential item pool is replaced with bee traps, of which 50% could be single bees
|
||||||
4: 0 # 100% of the non-essential item pool is replaced with bee traps
|
4: 0 # 100% of the non-essential item pool is replaced with bee traps
|
||||||
|
### Item Shuffle (shop)
|
||||||
|
shop_shuffle_slots: # Maximum amount of allowed shop slots to place item pool items
|
||||||
|
0: 50
|
||||||
|
5: 0
|
||||||
|
15: 0
|
||||||
|
999: 0
|
||||||
|
potion_shop_shuffle: # influence of potion shop by shop shuffle
|
||||||
|
none: 50 # only shuffle price
|
||||||
|
a: 0 # generate/shuffle in any items
|
||||||
shop_shuffle:
|
shop_shuffle:
|
||||||
none: 50
|
none: 50
|
||||||
i: 0 # Shuffle the inventories of the shops around
|
g: 0 # Generate new default inventories for overworld/underworld shops, and unique shops
|
||||||
|
f: 0 # Generate new default inventories for every shop independently
|
||||||
|
i: 0 # Shuffle default inventories of the shops around
|
||||||
p: 0 # Randomize the prices of the items in shop inventories
|
p: 0 # Randomize the prices of the items in shop inventories
|
||||||
u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld)
|
u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld)
|
||||||
ip: 0 # Shuffle inventories and randomize prices
|
ip: 0 # Shuffle inventories and randomize prices
|
||||||
|
fpu: 0 # Generate new inventories, randomize prices and shuffle capacity upgrades into item pool
|
||||||
uip: 0 # Shuffle inventories, randomize prices and shuffle capacity upgrades into the item pool
|
uip: 0 # Shuffle inventories, randomize prices and shuffle capacity upgrades into the item pool
|
||||||
# You can add more combos
|
# You can add more combos
|
||||||
shuffle_prizes: # aka drops
|
shuffle_prizes: # aka drops
|
||||||
|
@ -253,11 +265,6 @@ green_clock_time: # For all timer modes, the amount of time in minutes to gain o
|
||||||
# - "Small Keys"
|
# - "Small Keys"
|
||||||
# - "Big Keys"
|
# - "Big Keys"
|
||||||
# Can be uncommented to use it
|
# Can be uncommented to use it
|
||||||
# non_local_items: # Force certain items to appear outside your world only, always across the multiworld. Recognizes some group names, like "Swords"
|
|
||||||
# - "Moon Pearl"
|
|
||||||
# - "Small Keys"
|
|
||||||
# - "Big Keys"
|
|
||||||
# Can be uncommented to use it
|
|
||||||
# startinventory: # Begin the file with the listed items/upgrades
|
# startinventory: # Begin the file with the listed items/upgrades
|
||||||
# Pegasus Boots: on
|
# Pegasus Boots: on
|
||||||
# Bomb Upgrade (+10): 4
|
# Bomb Upgrade (+10): 4
|
||||||
|
|
Loading…
Reference in New Issue