Version bump 0.2.9.0

This commit is contained in:
codemann8
2022-07-29 22:39:47 -05:00
25 changed files with 571 additions and 98 deletions

View File

@@ -54,7 +54,7 @@ class World(object):
self._entrance_cache = {} self._entrance_cache = {}
self._location_cache = {} self._location_cache = {}
self.required_locations = [] self.required_locations = []
self.shuffle_bonk_prizes = False self.shuffle_bonk_drops = {}
self.light_world_light_cone = False self.light_world_light_cone = False
self.dark_world_light_cone = False self.dark_world_light_cone = False
self.clock_mode = 'none' self.clock_mode = 'none'
@@ -1080,6 +1080,9 @@ class CollectionState(object):
return True return True
return False return False
def can_collect_bonkdrops(self, player):
return self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))
def can_farm_rupees(self, player): def can_farm_rupees(self, player):
tree_pulls = ['Lost Woods East Area', tree_pulls = ['Lost Woods East Area',
'Snitch Lady (East)', 'Snitch Lady (East)',
@@ -1110,7 +1113,7 @@ class CollectionState(object):
for region in tree_pulls: for region in tree_pulls:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
if not self.has('Beat Agahnim 1', player): if not self.has_beaten_aga(player):
for region in pre_aga_tree_pulls: for region in pre_aga_tree_pulls:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
@@ -1125,7 +1128,7 @@ class CollectionState(object):
for region in bush_crabs: for region in bush_crabs:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
if not self.has('Beat Agahnim 1', player): if not self.has_beaten_aga(player):
for region in pre_aga_bush_crabs: for region in pre_aga_bush_crabs:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
@@ -1192,7 +1195,7 @@ class CollectionState(object):
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
if self.has_Boots(player): if not self.world.shuffle_bonk_drops[player] and self.can_collect_bonkdrops(player):
for region in bonk_bombs: for region in bonk_bombs:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
@@ -1202,7 +1205,7 @@ class CollectionState(object):
for region in tree_pulls: for region in tree_pulls:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
if not self.has('Beat Agahnim 1', player): if not self.has_beaten_aga(player):
for region in pre_aga_tree_pulls: for region in pre_aga_tree_pulls:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
@@ -1217,7 +1220,7 @@ class CollectionState(object):
for region in bush_crabs: for region in bush_crabs:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
if not self.has('Beat Agahnim 1', player): if not self.has_beaten_aga(player):
for region in pre_aga_bush_crabs: for region in pre_aga_bush_crabs:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
@@ -1343,6 +1346,9 @@ class CollectionState(object):
self.is_not_bunny(cave, player) self.is_not_bunny(cave, player)
) )
def has_beaten_aga(self, player):
return self.has('Beat Agahnim 1', player) and (self.world.mode[player] != 'standard' or self.has('Zelda Delivered', player))
def has_sword(self, player): def has_sword(self, player):
return self.has('Fighter Sword', player) or self.has('Master Sword', player) or self.has('Tempered Sword', player) or self.has('Golden Sword', player) return self.has('Fighter Sword', player) or self.has('Master Sword', player) or self.has('Tempered Sword', player) or self.has('Golden Sword', player)
@@ -1600,7 +1606,7 @@ class Region(object):
def can_reach(self, state): def can_reach(self, state):
from Utils import stack_size3a from Utils import stack_size3a
from DungeonGenerator import GenerationException from DungeonGenerator import GenerationException
if stack_size3a() > 500: if stack_size3a() > self.world.players * 500:
raise GenerationException(f'Infinite loop detected for "{self.name}" located at \'Region.can_reach\'') raise GenerationException(f'Infinite loop detected for "{self.name}" located at \'Region.can_reach\'')
if state.stale[self.player]: if state.stale[self.player]:
@@ -2693,6 +2699,7 @@ class LocationType(FastEnum):
Shop = 3 Shop = 3
Pot = 4 Pot = 4
Drop = 5 Drop = 5
Bonk = 6
class Item(object): class Item(object):
@@ -2908,6 +2915,7 @@ class Spoiler(object):
'ow_mixed': self.world.owMixed, 'ow_mixed': self.world.owMixed,
'ow_whirlpool': self.world.owWhirlpoolShuffle, 'ow_whirlpool': self.world.owWhirlpoolShuffle,
'ow_fluteshuffle': self.world.owFluteShuffle, 'ow_fluteshuffle': self.world.owFluteShuffle,
'bonk_drops': self.world.shuffle_bonk_drops,
'shuffle': self.world.shuffle, 'shuffle': self.world.shuffle,
'shuffleganon': self.world.shuffle_ganon, 'shuffleganon': self.world.shuffle_ganon,
'shufflelinks': self.world.shufflelinks, 'shufflelinks': self.world.shufflelinks,
@@ -3120,6 +3128,7 @@ class Spoiler(object):
outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player])) outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player]))
outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player])) outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player]))
outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player]) outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player])
outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player]))
outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player]) outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player])
if self.metadata['shuffle'][player] != 'vanilla': if self.metadata['shuffle'][player] != 'vanilla':
outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffleganon'][player])) outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffleganon'][player]))
@@ -3132,7 +3141,7 @@ class Spoiler(object):
outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player]) outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player])
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player])) outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player]))
outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player]) outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player])
outfile.write('Drop Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['dropshuffle'][player])) outfile.write('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['dropshuffle'][player]))
outfile.write('Pottery Mode:'.ljust(line_width) + '%s\n' % self.metadata['pottery'][player]) outfile.write('Pottery Mode:'.ljust(line_width) + '%s\n' % self.metadata['pottery'][player])
outfile.write('Pot Shuffle (Legacy):'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player])) outfile.write('Pot Shuffle (Legacy):'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player]))
outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player])) outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player]))

View File

@@ -1,5 +1,12 @@
# Changelog # Changelog
### 0.2.9.0
- Added Bonk Drop Shuffle
- Fixed disappearing mirror portal issue in Inverted+Crossed OWR
- Fixed 4-digit collection rate in credits
- Fixed Ganon vulnerability to reference Aga2 boss flag rather than pyramid hole
- Fixed issue with pre-opened pyramid when not expected
### 0.2.8.0 ### 0.2.8.0
- ~Merged DR v1.0.1.0 - Pottery options, BPS support, MSU Resume, Collection Rate Counter~ - ~Merged DR v1.0.1.0 - Pottery options, BPS support, MSU Resume, Collection Rate Counter~
- Various improvements to increase generation success rate and reduce generation time - Various improvements to increase generation success rate and reduce generation time

3
CLI.py
View File

@@ -109,7 +109,7 @@ def parse_cli(argv, no_defaults=False):
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
'msu_resume', 'collection_rate', 'colorizepots']: 'msu_resume', 'collection_rate', 'colorizepots', 'bonk_drops']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1: if player == 1:
setattr(ret, name, {1: value}) setattr(ret, name, {1: value})
@@ -159,6 +159,7 @@ def parse_settings():
"ow_mixed": False, "ow_mixed": False,
"ow_whirlpool": False, "ow_whirlpool": False,
"ow_fluteshuffle": "vanilla", "ow_fluteshuffle": "vanilla",
"bonk_drops": False,
"shuffle": "vanilla", "shuffle": "vanilla",
"shufflelinks": False, "shufflelinks": False,
"overworld_map": "default", "overworld_map": "default",

55
Fill.py
View File

@@ -355,18 +355,24 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
# handle pot shuffle # handle pot shuffle
pots_used = False pots_used = False
pot_item_pool = collections.defaultdict(list) pot_item_pool = collections.defaultdict(list)
# guarantee one big magic in a bonk location
for player in range(1, world.players + 1):
if world.shuffle_bonk_drops[player]:
for item in world.itempool: for item in world.itempool:
if item.name in ['Chicken', 'Big Magic']: # can only fill these in that players world if item.name in ['Big Magic'] and item.player == player:
pot_item_pool[item.player].append(item) pot_item_pool[player].append(item)
for player, pot_pool in pot_item_pool.items(): break
if pot_pool: from Regions import bonk_prize_table
for pot_item in pot_pool: for player, magic_pool in pot_item_pool.items():
world.itempool.remove(pot_item) if len(magic_pool) > 0:
pot_locations = [location for location in fill_locations world.itempool.remove(magic_pool[0])
if location.type == LocationType.Pot and location.player == player] pot_locations = [location for location in fill_locations if location.player == player
and location.name in [n for n, (_, _, aga, _, _, _) in bonk_prize_table.items() if not aga]]
pot_locations = filter_pot_locations(pot_locations, world) pot_locations = filter_pot_locations(pot_locations, world)
fast_fill_helper(world, pot_pool, pot_locations) fast_fill_helper(world, magic_pool, pot_locations)
pots_used = True pots_used = True
if pots_used: if pots_used:
fill_locations = world.get_unfilled_locations() fill_locations = world.get_unfilled_locations()
random.shuffle(fill_locations) random.shuffle(fill_locations)
@@ -466,7 +472,7 @@ def calc_trash_locations(world, player):
total_count, gt_count = 0, 0 total_count, gt_count = 0, 0
for loc in world.get_locations(): for loc in world.get_locations():
if (loc.player == player and loc.item is None if (loc.player == player and loc.item is None
and (loc.type not in {LocationType.Pot, LocationType.Drop, LocationType.Normal} or not loc.forced_item) and (loc.type not in {LocationType.Bonk, LocationType.Pot, LocationType.Drop, LocationType.Normal} or not loc.forced_item)
and (loc.type != LocationType.Shop or world.shopsanity[player]) and (loc.type != LocationType.Shop or world.shopsanity[player])
and loc.parent_region.dungeon): and loc.parent_region.dungeon):
total_count += 1 total_count += 1
@@ -477,18 +483,22 @@ def calc_trash_locations(world, player):
def ensure_good_pots(world, write_skips=False): def ensure_good_pots(world, write_skips=False):
for loc in world.get_locations(): for loc in world.get_locations():
# convert Arrows 5 and Nothing when necessary # # convert Arrows 5 when necessary
if (loc.item.name in {'Arrows (5)', 'Nothing'} # if (loc.item.name in {'Arrows (5)'}
# and loc.type not in [LocationType.Pot, LocationType.Bonk]):
# loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player)
# convert Nothing when necessary
if (loc.item.name in {'Nothing'}
and (loc.type != LocationType.Pot or loc.item.player != loc.player)): and (loc.type != LocationType.Pot or loc.item.player != loc.player)):
loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player) loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player)
# can be placed here by multiworld balancing or shop balancing # # can be placed here by multiworld balancing or shop balancing
# change it to something normal for the player it got swapped to # # change it to something normal for the player it got swapped to
elif (loc.item.name in {'Chicken', 'Big Magic'} # elif (loc.item.name in {'Chicken', 'Big Magic'}
and (loc.type != LocationType.Pot or loc.item.player != loc.player)): # and (loc.type != LocationType.Pot or loc.item.player != loc.player)):
if loc.type == LocationType.Pot: # if loc.type == LocationType.Pot:
loc.item.player = loc.player # loc.item.player = loc.player
else: # else:
loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player) # loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player)
# do the arrow retro check # do the arrow retro check
if world.retro[loc.item.player] and loc.item.name in {'Arrows (5)', 'Arrows (10)'}: if world.retro[loc.item.player] and loc.item.name in {'Arrows (5)', 'Arrows (10)'}:
loc.item = ItemFactory('Rupees (5)', loc.item.player) loc.item = ItemFactory('Rupees (5)', loc.item.player)
@@ -820,7 +830,12 @@ def balance_money_progression(world):
return False return False
done = False done = False
attempts = world.players * 20 + 20
while not done: while not done:
attempts -= 1
if attempts < 0:
from DungeonGenerator import GenerationException
raise GenerationException(f'Infinite loop detected at "balance_money_progression"')
sphere_costs = {player: 0 for player in range(1, world.players+1)} sphere_costs = {player: 0 for player in range(1, world.players+1)}
locked_by_money = {player: set() for player in range(1, world.players+1)} locked_by_money = {player: set() for player in range(1, world.players+1)}
sphere_locations = get_sphere_locations(state, unchecked_locations) sphere_locations = get_sphere_locations(state, unchecked_locations)

View File

@@ -3,11 +3,12 @@ import logging
import math import math
import RaceRandom as random import RaceRandom as random
from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState, PotItem from BaseClasses import LocationType, Region, RegionType, Shop, ShopType, Location, CollectionState, PotItem
from EntranceShuffle import connect_entrance from EntranceShuffle import connect_entrance
from Regions import shop_to_location_table, retro_shops, shop_table_by_location, valid_pot_location from Regions import shop_to_location_table, retro_shops, shop_table_by_location, valid_pot_location
from Fill import FillError, fill_restrictive, fast_fill, get_dungeon_item_pool from Fill import FillError, fill_restrictive, fast_fill, get_dungeon_item_pool
from PotShuffle import vanilla_pots from PotShuffle import vanilla_pots
from Tables import bonk_prize_lookup
from Items import ItemFactory from Items import ItemFactory
from source.item.FillUtil import trash_items, pot_items from source.item.FillUtil import trash_items, pot_items
@@ -411,6 +412,10 @@ def generate_itempool(world, player):
if world.pottery[player] not in ['none', 'keys']: if world.pottery[player] not in ['none', 'keys']:
add_pot_contents(world, player) add_pot_contents(world, player)
if world.shuffle_bonk_drops[player]:
create_dynamic_bonkdrop_locations(world, player)
add_bonkdrop_contents(world, player)
take_any_locations = [ take_any_locations = [
'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut',
@@ -500,6 +505,21 @@ def create_dynamic_shop_locations(world, player):
loc.locked = True loc.locked = True
def create_dynamic_bonkdrop_locations(world, player):
from Regions import bonk_prize_table
for bonk_location, (_, _, _, _, region_name, hint_text) in bonk_prize_table.items():
region = world.get_region(region_name, player)
loc = Location(player, bonk_location, 0, region, hint_text)
loc.type = LocationType.Bonk
loc.parent_region = region
loc.address = 0x2abb00 + (bonk_prize_table[loc.name][0] * 6) + 3
region.locations.append(loc)
world.dynamic_locations.append(loc)
world.clear_location_cache()
def fill_prizes(world, attempts=15): def fill_prizes(world, attempts=15):
all_state = world.get_all_state(keys=True) all_state = world.get_all_state(keys=True)
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
@@ -779,6 +799,17 @@ def add_pot_contents(world, player):
world.itempool.append(ItemFactory(item, player)) world.itempool.append(ItemFactory(item, player))
def add_bonkdrop_contents(world, player):
from Items import item_table
for item_name, (_, count, alt_item) in bonk_prize_lookup.items():
if item_name not in item_table:
item_name = alt_item
while (count > 0):
item = ItemFactory(item_name, player)
world.itempool.append(item)
count -= 1
def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic, flute_activated): def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic, flute_activated):
pool = [] pool = []
placed_items = {} placed_items = {}

View File

@@ -81,10 +81,10 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), 'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), 'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Single Bomb': (False, False, None, 0x27, 5, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'), 'Single Bomb': (False, False, None, 0x27, 5, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'),
'Arrows (5)': (False, False, None, 0x5A, 15, 'This will give\nyou five shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'five arrows'), 'Arrows (5)': (False, False, None, 0xB5, 15, 'This will give\nyou five shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'five arrows'),
'Small Magic': (False, False, None, 0x45, 5, 'A bit of magic', 'and the bit of magic', 'bit-o-magic kid', 'magic bit for sale', 'fungus for magic', 'magic boy conjures again', 'a bit of magic'), 'Small Magic': (False, False, None, 0x45, 5, 'A bit of magic', 'and the bit of magic', 'bit-o-magic kid', 'magic bit for sale', 'fungus for magic', 'magic boy conjures again', 'a bit of magic'),
'Big Magic': (False, False, None, 0x5A, 40, 'A lot of magic', 'and lots of magic', 'lot-o-magic kid', 'magic refill for sale', 'fungus for magic', 'magic boy conjures again', 'a magic refill'), 'Big Magic': (False, False, None, 0xB4, 40, 'A lot of magic', 'and lots of magic', 'lot-o-magic kid', 'magic refill for sale', 'fungus for magic', 'magic boy conjures again', 'a magic refill'),
'Chicken': (False, False, None, 0x5A, 999, 'Cucco of Legend', 'and the legendary cucco', 'chicken kid', 'fried chicken for sale', 'fungus for chicken', 'cucco boy clucks again', 'a cucco'), 'Chicken': (False, False, None, 0xB3, 999, 'Cucco of Legend', 'and the legendary cucco', 'chicken kid', 'fried chicken for sale', 'fungus for chicken', 'cucco boy clucks again', 'a cucco'),
'Bombs (3)': (False, False, None, 0x28, 15, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'), 'Bombs (3)': (False, False, None, 0x28, 15, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'),
'Bombs (10)': (False, False, None, 0x31, 50, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'), 'Bombs (10)': (False, False, None, 0x31, 50, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'),
'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, 'Increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), 'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, 'Increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
@@ -177,6 +177,8 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
'Blue Potion': (False, False, None, 0x30, 160, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'), 'Blue Potion': (False, False, None, 0x30, 160, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'),
'Bee': (False, False, None, 0x0E, 10, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bee'), 'Bee': (False, False, None, 0x0E, 10, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bee'),
'Small Heart': (False, False, None, 0x42, 10, 'Just a little\npiece of love!', 'and the heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart'), 'Small Heart': (False, False, None, 0x42, 10, 'Just a little\npiece of love!', 'and the heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart'),
'Apples': (False, False, None, 0xB1, 30, 'Just a few pieces of fruit!', 'and the juicy fruit', 'the fruity kid', 'the fruit stand', 'expired fruit', 'bottle boy has fruit again', 'an apple hoard'),
'Fairy': (False, False, None, 0xB2, 50, 'Just a pixie!', 'and the pixie', 'the pixie kid', 'pixie for sale', 'pixie fungus', 'bottle boy has pixie again', 'a pixie'),
'Beat Agahnim 1': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Beat Agahnim 1': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Beat Agahnim 2': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Beat Agahnim 2': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Get Frog': (True, False, 'Event', 999, None, None, None, None, None, None, None, None), 'Get Frog': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),

View File

@@ -91,6 +91,7 @@ def main(args, seed=None, fish=None):
world.owKeepSimilar = args.ow_keepsimilar.copy() world.owKeepSimilar = args.ow_keepsimilar.copy()
world.owWhirlpoolShuffle = args.ow_whirlpool.copy() world.owWhirlpoolShuffle = args.ow_whirlpool.copy()
world.owFluteShuffle = args.ow_fluteshuffle.copy() world.owFluteShuffle = args.ow_fluteshuffle.copy()
world.shuffle_bonk_drops = args.bonk_drops.copy()
world.open_pyramid = args.openpyramid.copy() world.open_pyramid = args.openpyramid.copy()
world.boss_shuffle = args.shufflebosses.copy() world.boss_shuffle = args.shufflebosses.copy()
world.enemy_shuffle = args.shuffleenemies.copy() world.enemy_shuffle = args.shuffleenemies.copy()
@@ -138,10 +139,7 @@ def main(args, seed=None, fish=None):
world.player_names[player].append(name) world.player_names[player].append(name)
logger.info('') logger.info('')
if world.owShuffle[1] != 'vanilla' or world.owCrossed[1] not in ['none', 'polar'] or world.owMixed[1] or world.owWhirlpoolShuffle[1] or world.owFluteShuffle[1] != 'vanilla' or str(args.outputname).startswith('M'):
outfilebase = f'OR_{args.outputname if args.outputname else world.seed}' outfilebase = f'OR_{args.outputname if args.outputname else world.seed}'
else:
outfilebase = f'DR_{args.outputname if args.outputname else world.seed}'
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
world.difficulty_requirements[player] = difficulties[world.difficulty[player]] world.difficulty_requirements[player] = difficulties[world.difficulty[player]]
@@ -159,7 +157,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 and not args.suppress_meta: if args.mystery and not (args.suppress_meta or args.create_spoiler):
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):
@@ -358,7 +356,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 and not args.suppress_meta: if args.mystery and not (args.suppress_meta or args.create_spoiler):
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'))
@@ -438,6 +436,7 @@ def copy_world(world, partial_copy=False):
ret.owKeepSimilar = world.owKeepSimilar.copy() ret.owKeepSimilar = world.owKeepSimilar.copy()
ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy() ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy()
ret.owFluteShuffle = world.owFluteShuffle.copy() ret.owFluteShuffle = world.owFluteShuffle.copy()
ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
ret.open_pyramid = world.open_pyramid.copy() ret.open_pyramid = world.open_pyramid.copy()
ret.boss_shuffle = world.boss_shuffle.copy() ret.boss_shuffle = world.boss_shuffle.copy()
ret.enemy_shuffle = world.enemy_shuffle.copy() ret.enemy_shuffle = world.enemy_shuffle.copy()

View File

@@ -929,14 +929,22 @@ async def track_locations(ctx : Context, roomid, roomdata):
ow_unchecked = {} ow_unchecked = {}
for location, screenid in location_table_ow.items(): for location, screenid in location_table_ow.items():
if location not in ctx.locations_checked: if location not in ctx.locations_checked:
ow_unchecked[location] = screenid ow_unchecked[location] = (screenid, 0x40)
ow_begin = min(ow_begin, screenid)
ow_end = max(ow_end, screenid + 1)
from Regions import bonk_prize_table
from OWEdges import OWTileRegions
for location, (_, flag, _, _, region_name, _) in bonk_prize_table.items():
if location not in ctx.locations_checked:
screenid = OWTileRegions[region_name]
ow_unchecked[location] = (screenid, flag)
ow_begin = min(ow_begin, screenid) ow_begin = min(ow_begin, screenid)
ow_end = max(ow_end, screenid + 1) ow_end = max(ow_end, screenid + 1)
if ow_begin < ow_end: if ow_begin < ow_end:
ow_data = await snes_read(ctx, SAVEDATA_START + 0x280 + ow_begin, ow_end - ow_begin) ow_data = await snes_read(ctx, SAVEDATA_START + 0x280 + ow_begin, ow_end - ow_begin)
if ow_data is not None: if ow_data is not None:
for location, screenid in ow_unchecked.items(): for location, (screenid, flag) in ow_unchecked.items():
if ow_data[screenid - ow_begin] & 0x40 != 0: if ow_data[screenid - ow_begin] & flag != 0:
new_check(location) new_check(location)
if not all([location in ctx.locations_checked for location in location_table_npc.keys()]): if not all([location in ctx.locations_checked for location in location_table_npc.keys()]):

View File

@@ -174,6 +174,7 @@ def roll_settings(weights):
ret.ow_whirlpool = get_choice('whirlpool_shuffle') == 'on' ret.ow_whirlpool = get_choice('whirlpool_shuffle') == 'on'
overworld_flute = get_choice('flute_shuffle') overworld_flute = get_choice('flute_shuffle')
ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla' ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla'
ret.bonk_drops = get_choice('bonk_drops') == 'on'
entrance_shuffle = get_choice('entrance_shuffle') entrance_shuffle = get_choice('entrance_shuffle')
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla' ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
overworld_map = get_choice('overworld_map') overworld_map = get_choice('overworld_map')

View File

@@ -6,7 +6,7 @@ from Regions import mark_dark_world_regions, mark_light_world_regions
from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel
from Utils import bidict from Utils import bidict
version_number = '0.2.8.0' version_number = '0.2.9.0'
version_branch = '' version_branch = ''
__version__ = '%s%s' % (version_number, version_branch) __version__ = '%s%s' % (version_number, version_branch)

View File

@@ -48,7 +48,7 @@ Alternatively, run ```Gui.py``` for a simple graphical user interface.
# Settings # Settings
Only extra settings are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md) Only extra settings added by this Overworld Shuffle fork are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md)
## Overworld Layout Shuffle (--ow_shuffle) ## Overworld Layout Shuffle (--ow_shuffle)
@@ -136,6 +136,37 @@ New flute spots are chosen at random, with restrictions that limit the promixity
New flute spots are chosen at random with minimum bias. New flute spots are chosen at random with minimum bias.
## Bonk Drop Shuffle (--bonk_drops)
This adds 41 new item locations to the game. These bonk locations are limited to the ones that drop a static item in the vanilla game.
- Bonk Locations consist of some trees, rocks, and statues
- 33 Trees
- 8 of the tree locations require Agahnim to be defeated to access the item
- 6 Rocks
- 1 of the rocks drops 2 items
- 1 Statue
- Bonk locations can be collected by bonking into them with the Pegasus Boots or using the Quake Medallion
- One of the bonk locations are guaranteed to have a full magic decanter
- Some of the drops can be farmed repeatedly, but only increments the collection rate once
- All of the bonk trees have been given an alternate color (and all non-bonk trees are reverted to normal tree color)
- Some screens are coded to change the "alternate tree color", some of them are strange (just how the vanilla game does it)
- Rocks and statues are unable to be made to have a different color
- Since Fairies and Apples are new items that can appear in plain sight, they don't have a proper graphic for them yet. For now, they show up as Power Stars
Future Note: This does NOT include the Good Bee (Cold Bee) Cave Statue...yet. In the future, this could be an additional item location.
#### Items Added To Pool:
- 15 Fairies
- 8 Apples
- 6 Bee Traps
- 3 Red Rupees
- 3 Blue Rupees
- 2 Single Bomb
- 2 Small Hearts
- 1 Large Magic Decanter
- 1 8x Bomb Pack
## New Goal Options (--goal) ## New Goal Options (--goal)
### Trinity ### Trinity
@@ -216,3 +247,9 @@ This gives each OW tile a random chance to be swapped to the opposite world
``` ```
For randomizing the flute spots around the overworld For randomizing the flute spots around the overworld
```
--bonk_drops
```
This extends the item pool to bonk locations and makes them additional item locations

View File

@@ -1266,7 +1266,57 @@ def pot_address(pot_index, super_tile):
return 0x7f6018 + super_tile * 2 + (pot_index << 24) return 0x7f6018 + super_tile * 2 + (pot_index << 24)
# (type, room_id, shopkeeper, custom, locked, [items]) # bonk location: record id, OW flag bitmask, aga required, default item, region, hint text
bonk_prize_table = {
'Lost Woods Hideout Tree': (0x00, 0x10, False, '', 'Lost Woods East Area', 'in a tree'),
'Death Mountain Bonk Rocks': (0x01, 0x10, False, '', 'East Death Mountain (Top East)', 'encased in stone'),
'Mountain Entry Pull Tree': (0x02, 0x10, False, '', 'Mountain Entry Area', 'in a tree'),
'Mountain Entry Southeast Tree': (0x03, 0x08, False, '', 'Mountain Entry Area', 'in a tree'),
'Lost Woods Pass West Tree': (0x04, 0x10, False, '', 'Lost Woods Pass West Area', 'in a tree'),
'Kakariko Portal Tree': (0x05, 0x08, False, '', 'Lost Woods Pass East Top Area', 'in a tree'),
'Fortune Bonk Rocks': (0x06, 0x10, False, '', 'Kakariko Fortune Area', 'in a tree'),
'Kakariko Pond Tree': (0x07, 0x10, True, '', 'Kakariko Pond Area', 'in a tree'),
'Bonk Rocks Tree': (0x08, 0x10, True, '', 'Bonk Rock Ledge', 'in a tree'),
'Sanctuary Tree': (0x09, 0x08, False, '', 'Sanctuary Area', 'in a tree'),
'River Bend West Tree': (0x0a, 0x10, True, '', 'River Bend Area', 'in a tree'),
'River Bend East Tree': (0x0b, 0x08, False, '', 'River Bend East Bank', 'in a tree'),
'Blinds Hideout Tree': (0x0c, 0x10, False, '', 'Kakariko Area', 'in a tree'),
'Kakariko Welcome Tree': (0x0d, 0x08, False, '', 'Kakariko Area', 'in a tree'),
'Forgotten Forest Southwest Tree': (0x0e, 0x10, False, '', 'Forgotten Forest Area', 'in a tree'),
'Forgotten Forest Central Tree': (0x0f, 0x08, False, '', 'Forgotten Forest Area', 'in a tree'),
#'Forgotten Forest Southeast Tree': (0x10, 0x04, False, '', 'Forgotten Forest Area', 'in a tree'),
'Hyrule Castle Tree': (0x11, 0x10, False, '', 'Hyrule Castle Courtyard', 'in a tree'),
'Wooden Bridge Tree': (0x12, 0x10, False, '', 'Wooden Bridge Area', 'in a tree'),
'Eastern Palace Tree': (0x13, 0x10, True, '', 'Eastern Palace Area', 'in a tree'),
'Flute Boy South Tree': (0x14, 0x10, True, '', 'Flute Boy Area', 'in a tree'),
'Flute Boy East Tree': (0x15, 0x08, True, '', 'Flute Boy Area', 'in a tree'),
'Central Bonk Rocks Tree': (0x16, 0x10, False, '', 'Central Bonk Rocks Area', 'in a tree'),
'Tree Line Tree 2': (0x17, 0x10, True, '', 'Tree Line Area', 'in a tree'),
'Tree Line Tree 4': (0x18, 0x08, True, '', 'Tree Line Area', 'in a tree'),
'Flute Boy Approach South Tree': (0x19, 0x10, False, '', 'Flute Boy Approach Area', 'in a tree'),
'Flute Boy Approach North Tree': (0x1a, 0x08, False, '', 'Flute Boy Approach Area', 'in a tree'),
'Dark Lumberjack Tree': (0x1b, 0x10, False, '', 'Dark Lumberjack Area', 'in a tree'),
'Dark Fortune Bonk Rocks (Drop 1)': (0x1c, 0x10, False, '', 'Dark Fortune Area', 'encased in stone'),
'Dark Fortune Bonk Rocks (Drop 2)': (0x1d, 0x08, False, '', 'Dark Fortune Area', 'encased in stone'),
'Dark Graveyard West Bonk Rocks': (0x1e, 0x10, False, '', 'Dark Graveyard Area', 'encased in stone'),
'Dark Graveyard North Bonk Rocks': (0x1f, 0x08, False, '', 'Dark Graveyard North', 'encased in stone'),
'Dark Graveyard Tomb Bonk Rocks': (0x20, 0x04, False, '', 'Dark Graveyard North', 'encased in stone'),
'Qirn Jump West Tree': (0x21, 0x10, False, '', 'Qirn Jump Area', 'in a tree'),
'Qirn Jump East Tree': (0x22, 0x08, False, '', 'Qirn Jump East Bank', 'in a tree'),
'Dark Witch Tree': (0x23, 0x10, False, '', 'Dark Witch Area', 'in a tree'),
'Pyramid Tree': (0x24, 0x10, False, '', 'Pyramid Area', 'in a tree'),
'Palace of Darkness Tree': (0x25, 0x10, False, '', 'Palace of Darkness Area', 'in a tree'),
'Dark Tree Line Tree 2': (0x26, 0x10, False, '', 'Dark Tree Line Area', 'in a tree'),
'Dark Tree Line Tree 3': (0x27, 0x08, False, '', 'Dark Tree Line Area', 'in a tree'),
'Dark Tree Line Tree 4': (0x28, 0x04, False, '', 'Dark Tree Line Area', 'in a tree'),
'Hype Cave Statue': (0x29, 0x10, False, '', 'Hype Cave Area', 'encased in stone')
}
bonk_table_by_location_id = {0x2ABB00+(data[0]*6)+3: name for name, data in bonk_prize_table.items()}
bonk_table_by_location = {y: x for x, y in bonk_table_by_location_id.items()}
# (room_id, type, shopkeeper, custom, locked, [items])
# item = (item, price, max=0, replacement=None, replacement_price=0) # item = (item, price, max=0, replacement=None, replacement_price=0)
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)] _basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)] _dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
@@ -1615,5 +1665,7 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
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.update(shop_table_by_location_id) lookup_id_to_name.update(shop_table_by_location_id)
lookup_id_to_name.update(bonk_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.update(shop_table_by_location) lookup_name_to_id.update(shop_table_by_location)
lookup_name_to_id.update(bonk_table_by_location)

71
Rom.py
View File

@@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '210e4631353e3d094f01bf91562844a5' RANDOMIZERBASEHASH = '0574a782e225a87b90637db0847c5ae0'
class JsonRom(object): class JsonRom(object):
@@ -630,6 +630,14 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(sprite_pointer+1, 0) rom.write_byte(sprite_pointer+1, 0)
rom.write_byte(sprite_pointer+2, code) rom.write_byte(sprite_pointer+2, code)
continue continue
elif location.type == LocationType.Bonk:
address = snes_to_pc(location.address)
rom.write_byte(address, handle_native_dungeon(location, itemid))
if location.item.player != player:
rom.write_byte(address+1, location.item.player)
else:
rom.write_byte(address+1, 0)
continue
if location.address is None or (type(location.address) is int and location.address >= 0x400000): if location.address is None or (type(location.address) is int and location.address >= 0x400000):
continue continue
@@ -771,17 +779,41 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
# set world flag # set world flag
world_flag = 0x00 if b >= 0x40 and b < 0x80 else 0x40 world_flag = 0x00 if b >= 0x40 and b < 0x80 else 0x40
rom.write_byte(0x153A00 + b, world_flag) rom.write_byte(0x1539B0 + b, world_flag)
if b & 0xBF in megatiles: if b & 0xBF in megatiles:
rom.write_byte(0x153A00 + b + 1, world_flag) rom.write_byte(0x1539B0 + b + 1, world_flag)
rom.write_byte(0x153A00 + b + 8, world_flag) rom.write_byte(0x1539B0 + b + 8, world_flag)
rom.write_byte(0x153A00 + b + 9, world_flag) rom.write_byte(0x1539B0 + b + 9, world_flag)
for edge in world.owedges: for edge in world.owedges:
if edge.dest is not None and isinstance(edge.dest, OWEdge) and edge.player == player: if edge.dest is not None and isinstance(edge.dest, OWEdge) and edge.player == player:
write_int16(rom, edge.getAddress() + 0x0a, edge.vramLoc) write_int16(rom, edge.getAddress() + 0x0a, edge.vramLoc)
if not edge.specialExit: if not edge.specialExit:
rom.write_byte(0x1539e0 + (edge.specialID - 0x80) * 2 if edge.specialEntrance else edge.getAddress() + 0x0e, edge.getTarget()) rom.write_byte(0x1539A0 + (edge.specialID - 0x80) * 2 if edge.specialEntrance else edge.getAddress() + 0x0e, edge.getTarget())
# patch bonk prizes
if world.shuffle_bonk_drops[player]:
bonk_prizes = [0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xAC, 0xE3, 0xE3, 0xDA, 0xE3, 0xDA, 0xD8, 0xAC, 0xAC, 0xE3, 0xD8, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xDC, 0xDB, 0xE3, 0xDA, 0x79, 0x79, 0xE3, 0xE3,
0xDA, 0x79, 0xAC, 0xAC, 0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xE3, 0x79, 0xDE, 0xE3, 0xAC, 0xDB, 0x79, 0xE3, 0xD8, 0xAC, 0x79, 0xE3, 0xDB, 0xDB, 0xE3, 0xE3, 0x79, 0xD8, 0xDD]
bonk_addresses = [0x4CF6C, 0x4CFBA, 0x4CFE0, 0x4CFFB, 0x4D018, 0x4D01B, 0x4D028, 0x4D03C, 0x4D059, 0x4D07A, 0x4D09E, 0x4D0A8, 0x4D0AB, 0x4D0AE, 0x4D0BE, 0x4D0DD,
0x4D16A, 0x4D1E5, 0x4D1EE, 0x4D20B, 0x4CBBF, 0x4CBBF, 0x4CC17, 0x4CC1A, 0x4CC4A, 0x4CC4D, 0x4CC53, 0x4CC69, 0x4CC6F, 0x4CC7C, 0x4CCEF, 0x4CD51,
0x4CDC0, 0x4CDC3, 0x4CDC6, 0x4CE37, 0x4D2DE, 0x4D32F, 0x4D355, 0x4D367, 0x4D384, 0x4D387, 0x4D397, 0x4D39E, 0x4D3AB, 0x4D3AE, 0x4D3D1, 0x4D3D7,
0x4D3F8, 0x4D416, 0x4D420, 0x4D423, 0x4D42D, 0x4D449, 0x4D48C, 0x4D4D9, 0x4D4DC, 0x4D4E3, 0x4D504, 0x4D507, 0x4D55E, 0x4D56A]
# # legacy bonk prize shuffle, shuffles bonk prizes amongst themselves
# random.shuffle(bonk_prizes)
# for prize, address in zip(bonk_prizes, bonk_addresses):
# rom.write_byte(address, prize)
owFlags |= 0x200
# setting spriteID to D8, a placeholder sprite we use to inform ROM to spawn a dynamic item
#for address in bonk_addresses:
for address in [b for b in bonk_addresses if b != 0x4D0AE]: # temp fix for screen 1A murahdahla sprite replacement
rom.write_byte(address, 0xD8)
# temporary fix for screen 1A
rom.write_byte(snes_to_pc(0x09AE32), 0xD8)
rom.write_byte(snes_to_pc(0x09AE35), 0xD8)
write_int16(rom, 0x150002, owMode) write_int16(rom, 0x150002, owMode)
write_int16(rom, 0x150004, owFlags) write_int16(rom, 0x150004, owFlags)
@@ -875,6 +907,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
valid_locations = [l for l in my_locations if ((l.type == LocationType.Pot and not l.forced_item) valid_locations = [l for l in my_locations if ((l.type == LocationType.Pot and not l.forced_item)
or (l.type == LocationType.Drop and not l.forced_item) or (l.type == LocationType.Drop and not l.forced_item)
or (l.type == LocationType.Normal and not l.forced_item) or (l.type == LocationType.Normal and not l.forced_item)
or (l.type == LocationType.Bonk and not l.forced_item)
or (l.type == LocationType.Shop and world.shopsanity[player]))] or (l.type == LocationType.Shop and world.shopsanity[player]))]
valid_loc_by_dungeon = valid_dungeon_locations(valid_locations) valid_loc_by_dungeon = valid_dungeon_locations(valid_locations)
@@ -1039,7 +1072,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(cr_pc+0x1f, thousands_bot) rom.write_byte(cr_pc+0x1f, thousands_bot)
# modify stat config # modify stat config
stat_address = 0x23B969 stat_address = 0x23B969
stat_pc = snes_to_pc(stat_address) owr_difference = 0x26 # can't remember why there is a difference between DR fork
stat_pc = snes_to_pc(stat_address - owr_difference)
rom.write_byte(stat_pc, 0xa9) # change to pos 21 (from b1) rom.write_byte(stat_pc, 0xa9) # change to pos 21 (from b1)
rom.write_byte(stat_pc+2, 0xc0) # change to 12 bits (from a0) rom.write_byte(stat_pc+2, 0xc0) # change to 12 bits (from a0)
rom.write_byte(stat_pc+3, 0x80) # change to four digits (from 60) rom.write_byte(stat_pc+3, 0x80) # change to four digits (from 60)
@@ -1251,18 +1285,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
# fill enemy prize packs # fill enemy prize packs
rom.write_bytes(0x37A78, pack_prizes) rom.write_bytes(0x37A78, pack_prizes)
# set bonk prizes
bonk_prizes = [0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xAC, 0xE3, 0xE3, 0xDA, 0xE3, 0xDA, 0xD8, 0xAC, 0xAC, 0xE3, 0xD8, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xDC, 0xDB, 0xE3, 0xDA, 0x79, 0x79, 0xE3, 0xE3,
0xDA, 0x79, 0xAC, 0xAC, 0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xE3, 0x79, 0xDE, 0xE3, 0xAC, 0xDB, 0x79, 0xE3, 0xD8, 0xAC, 0x79, 0xE3, 0xDB, 0xDB, 0xE3, 0xE3, 0x79, 0xD8, 0xDD]
bonk_addresses = [0x4CF6C, 0x4CFBA, 0x4CFE0, 0x4CFFB, 0x4D018, 0x4D01B, 0x4D028, 0x4D03C, 0x4D059, 0x4D07A, 0x4D09E, 0x4D0A8, 0x4D0AB, 0x4D0AE, 0x4D0BE, 0x4D0DD,
0x4D16A, 0x4D1E5, 0x4D1EE, 0x4D20B, 0x4CBBF, 0x4CBBF, 0x4CC17, 0x4CC1A, 0x4CC4A, 0x4CC4D, 0x4CC53, 0x4CC69, 0x4CC6F, 0x4CC7C, 0x4CCEF, 0x4CD51,
0x4CDC0, 0x4CDC3, 0x4CDC6, 0x4CE37, 0x4D2DE, 0x4D32F, 0x4D355, 0x4D367, 0x4D384, 0x4D387, 0x4D397, 0x4D39E, 0x4D3AB, 0x4D3AE, 0x4D3D1, 0x4D3D7,
0x4D3F8, 0x4D416, 0x4D420, 0x4D423, 0x4D42D, 0x4D449, 0x4D48C, 0x4D4D9, 0x4D4DC, 0x4D4E3, 0x4D504, 0x4D507, 0x4D55E, 0x4D56A]
if world.shuffle_bonk_prizes:
random.shuffle(bonk_prizes)
for prize, address in zip(bonk_prizes, bonk_addresses):
rom.write_byte(address, prize)
# Fill in item substitutions table # Fill in item substitutions table
rom.write_bytes(0x184000, [ rom.write_bytes(0x184000, [
# original_item, limit, replacement_item, filler # original_item, limit, replacement_item, filler
@@ -1390,7 +1412,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]: if world.is_pyramid_open(player):
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()
@@ -1644,6 +1666,13 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
# temporarally we are just nopping out this check we will conver this to a rom fix soon. # temporarally we are just nopping out this check we will conver this to a rom fix soon.
rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required[player] else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F]) rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required[player] else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F])
# sprite patches
rom.write_byte(snes_to_pc(0x0DB7D1), 0x03) # patch apple sprites to not permadeatch like enemies
rom.write_byte(snes_to_pc(0x0DB4F8), 0x40) # patch apples to not prevent kill rooms from opening
if world.shuffle_bonk_drops[player]:
# warning, this temporary patch might cause fairies to respawn differently?, limiting this to bonk drop mode only
rom.write_byte(snes_to_pc(0x0DB808), 0x03) # patch fairies sprites to not permadeath like enemies
# allow smith into multi-entrance caves in appropriate shuffles # allow smith into multi-entrance caves in appropriate shuffles
if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'):
rom.write_byte(0x18004C, 0x01) rom.write_byte(0x18004C, 0x01)
@@ -2754,7 +2783,7 @@ def set_inverted_mode(world, player, rom, inverted_buffer):
# apply inverted map changes # apply inverted map changes
for b in range(0x00, len(inverted_buffer)): for b in range(0x00, len(inverted_buffer)):
rom.write_byte(0x153B00 + b, inverted_buffer[b]) rom.write_byte(0x153A70 + b, inverted_buffer[b])
def patch_shuffled_dark_sanc(world, rom, player): def patch_shuffled_dark_sanc(world, rom, player):
dark_sanc = world.get_region('Dark Sanctuary Hint', player) dark_sanc = world.get_region('Dark Sanctuary Hint', player)

View File

@@ -54,7 +54,7 @@ def set_rules(world, player):
if world.goal[player] == 'dungeons': if world.goal[player] == 'dungeons':
# require all dungeons to beat ganon # require all dungeons to beat ganon
add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player) and state.has('Beat Agahnim 1', player) and state.has('Beat Agahnim 2', player) and state.has_crystals(7, player)) add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player) and state.has_beaten_aga(player) and state.has('Beat Agahnim 2', player) and state.has_crystals(7, player))
elif world.goal[player] == 'ganon': elif world.goal[player] == 'ganon':
# require aga2 to beat ganon # require aga2 to beat ganon
add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player))
@@ -356,7 +356,7 @@ def global_rules(world, player):
# byrna could work with sufficient magic # byrna could work with sufficient magic
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
loc = world.get_location('Misery Mire - Spikes Pot Key', player) loc = world.get_location('Misery Mire - Spikes Pot Key', player)
if loc.pot.x == 48 and loc.pot.y == 28: # pot shuffled to spike area if loc.pot is not None and loc.pot.x == 48 and loc.pot.y == 28: # pot shuffled to spike area
set_rule(loc, lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) set_rule(loc, lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player))
@@ -807,7 +807,7 @@ def pot_rules(world, player):
def default_rules(world, player): def default_rules(world, player):
set_rule(world.get_entrance('Other World S&Q', player), lambda state: state.has_Mirror(player) and state.has('Beat Agahnim 1', player)) set_rule(world.get_entrance('Other World S&Q', player), lambda state: state.has_Mirror(player) and state.has_beaten_aga(player))
# Underworld Logic # Underworld Logic
set_rule(world.get_entrance('Old Man Cave Exit (West)', player), lambda state: False) # drop cannot be climbed up set_rule(world.get_entrance('Old Man Cave Exit (West)', player), lambda state: False) # drop cannot be climbed up
@@ -823,8 +823,20 @@ def default_rules(world, player):
set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player)) set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player))
set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player)) set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player))
# Bonk Item Access
if world.shuffle_bonk_drops[player]:
if world.get_region('Big Bomb Shop', player).entrances: # just some location that is placed late in the ER algorithm, prevent standard rules from applying when trying to search reachability in the overworld
from Regions import bonk_prize_table
for location_name, (_, _, aga_required, _, _, _) in bonk_prize_table.items():
loc = world.get_location(location_name, player)
if not aga_required:
set_rule(loc, lambda state: state.can_collect_bonkdrops(player))
else:
set_rule(loc, lambda state: state.can_collect_bonkdrops(player) and state.has_beaten_aga(player))
add_bunny_rule(loc, player)
# Entrance Access # Entrance Access
set_rule(world.get_entrance('Lumberjack Tree Tree', player), lambda state: state.has_Boots(player) and state.has('Beat Agahnim 1', player)) set_rule(world.get_entrance('Lumberjack Tree Tree', player), lambda state: state.has_Boots(player) and state.has_beaten_aga(player))
set_rule(world.get_entrance('Bonk Rock Cave', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('Bonk Rock Cave', player), lambda state: state.has_Boots(player))
set_rule(world.get_entrance('Sanctuary Grave', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Sanctuary Grave', player), lambda state: state.can_lift_rocks(player))
set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player))
@@ -950,7 +962,7 @@ def ow_rules(world, player):
if world.is_atgt_swapped(player): if world.is_atgt_swapped(player):
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player))
else: else:
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_beam_sword(player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_beam_sword(player) or state.has_beaten_aga(player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle
set_rule(world.get_entrance('GT Entry Approach', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) set_rule(world.get_entrance('GT Entry Approach', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player))
set_rule(world.get_entrance('GT Entry Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'lite', 'lean', 'crossed', 'insanity')) set_rule(world.get_entrance('GT Entry Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'lite', 'lean', 'crossed', 'insanity'))
@@ -1109,7 +1121,7 @@ def ow_rules(world, player):
if not world.is_tile_swapped(0x1b, player): if not world.is_tile_swapped(0x1b, player):
set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: False) set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: False)
set_rule(world.get_entrance('Inverted Pyramid Entrance', player), lambda state: False) set_rule(world.get_entrance('Inverted Pyramid Entrance', player), lambda state: False)
set_rule(world.get_entrance('Pyramid Hole', player), lambda state: world.open_pyramid[player] or state.has('Beat Agahnim 2', player)) set_rule(world.get_entrance('Pyramid Hole', player), lambda state: world.is_pyramid_open(player) or state.has('Beat Agahnim 2', player))
set_rule(world.get_entrance('HC Area Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('HC Area Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('HC Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('HC Ledge Mirror Spot', player), lambda state: state.has_Mirror(player))
@@ -1117,8 +1129,8 @@ def ow_rules(world, player):
set_rule(world.get_entrance('HC East Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('HC East Entry Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('HC Courtyard Left Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('HC Courtyard Left Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('HC Area South Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('HC Area South Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Top of Pyramid', player), lambda state: state.has('Beat Agahnim 1', player)) set_rule(world.get_entrance('Top of Pyramid', player), lambda state: state.has_beaten_aga(player))
set_rule(world.get_entrance('Top of Pyramid (Inner)', player), lambda state: state.has('Beat Agahnim 1', player)) set_rule(world.get_entrance('Top of Pyramid (Inner)', player), lambda state: state.has_beaten_aga(player))
else: else:
set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: world.is_pyramid_open(player) or state.has('Beat Agahnim 2', player)) set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: world.is_pyramid_open(player) or state.has('Beat Agahnim 2', player))
set_rule(world.get_entrance('Pyramid Hole', player), lambda state: False) set_rule(world.get_entrance('Pyramid Hole', player), lambda state: False)
@@ -1130,7 +1142,7 @@ def ow_rules(world, player):
set_rule(world.get_entrance('Pyramid Uncle Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Pyramid Uncle Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Pyramid From Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Pyramid From Ledge Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Pyramid Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Pyramid Entry Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Post Aga Inverted Teleporter', player), lambda state: state.has('Beat Agahnim 1', player)) set_rule(world.get_entrance('Post Aga Inverted Teleporter', player), lambda state: state.has_beaten_aga(player))
if not world.is_tile_swapped(0x1d, player): if not world.is_tile_swapped(0x1d, player):
set_rule(world.get_entrance('Wooden Bridge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Wooden Bridge Mirror Spot', player), lambda state: state.has_Mirror(player))
@@ -1620,7 +1632,7 @@ def swordless_rules(world, player):
set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop
if not world.is_atgt_swapped(player): if not world.is_atgt_swapped(player):
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has_beaten_aga(player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle
set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!)
set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock Ledge', 'Region', player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock Ledge', 'Region', player)) # sword not required to use medallion for opening in swordless (!)
@@ -1731,6 +1743,11 @@ def standard_rules(world, player):
add_rule(world.get_entrance('Hyrule Castle Ledge Drop', player), lambda state: state.has('Zelda Delivered', player)) add_rule(world.get_entrance('Hyrule Castle Ledge Drop', player), lambda state: state.has('Zelda Delivered', player))
add_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has('Zelda Delivered', player)) add_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has('Zelda Delivered', player))
if world.shuffle_bonk_drops[player]:
if world.get_region('Big Bomb Shop', player).entrances: # just some location that is placed late in the ER algorithm, prevent standard rules from applying when trying to search reachability in the overworld
add_rule(world.get_location('Hyrule Castle Tree', player), lambda state: state.has('Zelda Delivered', player))
add_rule(world.get_location('Central Bonk Rocks Tree', player), lambda state: state.has('Zelda Delivered', player))
# don't allow bombs to get past here before zelda is rescued # don't allow bombs to get past here before zelda is rescued
set_rule(world.get_entrance('GT Hookshot South Entry to Ranged Crystal', player), lambda state: (state.can_use_bombs(player) and state.has('Zelda Delivered', player)) or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player)) # or state.has('Cane of Somaria', player)) set_rule(world.get_entrance('GT Hookshot South Entry to Ranged Crystal', player), lambda state: (state.can_use_bombs(player) and state.has('Zelda Delivered', player)) or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player)) # or state.has('Cane of Somaria', player))

View File

@@ -125,17 +125,23 @@ divisor_lookup = {
# 0xf8: 0xbac, 0xf9: 0xbba, 0xfa: 0xbc1, 0xfb: 0xbcc, 0xfc: 0xbd7, 0xfd: 0xbd7, 0xfe: 0xbba, 0xff: 0xbe3 # 0xf8: 0xbac, 0xf9: 0xbba, 0xfa: 0xbc1, 0xfb: 0xbcc, 0xfc: 0xbd7, 0xfd: 0xbd7, 0xfe: 0xbba, 0xff: 0xbe3
# } # }
prize_lookup = { # item name: (spriteID, pool count, replacement item)
0xd8: 'Small Magic Refill', bonk_prize_lookup = {
0xd9: 'Rupee (1)', 'Chicken': (0x0b, 0, None),
0xda: 'Rupees (5)', 'Bee Trap': (0x79, 6, None),
0xdb: 'Rupees (20)', 'Apples': (0xac, 8, None),
0xdc: 'Bomb (1)', 'Small Heart': (0xd8, 2, None),
0xdd: 'Bombs (4)', 'Rupee (1)': (0xd9, 0, None),
0xde: 'Bombs (8)', 'Rupees (5)': (0xda, 3, None), # TODO: add in murahdahla tree rupee
0xdf: 'Heart', 'Rupees (20)': (0xdb, 3, None),
0xe0: 'Fairy', 'Single Bomb': (0xdc, 2, None),
0xe1: 'Arrows (5)', 'Bombs (3)': (None, 0, 'Bombs (4)'),
0xe2: 'Arrows (10)', 'Bombs (4)': (0xdd, 0, 'Bombs (3)'),
0xe3: 'Full Magic Refill' 'Bombs (8)': (0xde, 1, 'Bombs (10)'),
'Bombs (10)': (None, 0, 'Bombs (8)'),
'Small Magic': (0xdf, 0, None),
'Big Magic': (0xe0, 1, None),
'Arrows (5)': (0xe1, 0, None),
'Arrows (10)': (0xe2, 0, None),
'Fairy': (0xe3, 15, None)
} }

View File

@@ -143,6 +143,9 @@ jsl.l OWWorldCheck16 : nop
org $02b16e ; AND #$3F : ORA 7EF3CA org $02b16e ; AND #$3F : ORA 7EF3CA
and #$7f : eor #$40 : nop #2 and #$7f : eor #$40 : nop #2
org $06AD4C
jsl.l OWBonkDrops : nop #4
;Code ;Code
org $aa8800 org $aa8800
OWTransitionDirection: OWTransitionDirection:
@@ -268,7 +271,9 @@ OWMirrorSpriteRestore:
OWLightWorldOrCrossed: OWLightWorldOrCrossed:
{ {
lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq + lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq +
lda #$00 : rtl lda.l InvertedMode : beq +
lda #$40 : rtl
+ lda #$00 : rtl
+ jsl OWWorldCheck : rtl + jsl OWWorldCheck : rtl
} }
@@ -368,6 +373,136 @@ LoadMapDarkOrMixed:
dw $0400+$0210 ; bottom right dw $0400+$0210 ; bottom right
} }
; Y = sprite slot index of bonk sprite
OWBonkDrops:
{
CMP.b #$D8 : BEQ +
RTL
+ LDA.l OWFlags+1 : AND.b #$02 : BNE +
JSL.l Sprite_TransmuteToBomb : RTL
+
; loop thru rando bonk table to find match
PHB : PHK : PLB
LDA.b $8A
LDX.b #(41*6) ; 41 bonk items, 6 bytes each
- CMP.w OWBonkPrizeData,X : BNE +
INX
LDA.w $0D10,Y : LSR A : LSR A : LSR A : LSR A
EOR.w $0D00,Y : CMP.w OWBonkPrizeData,X : BNE ++ ; X = row + 1
BRA .found_match
++ DEX : LDA.b $8A
+ CPX.b #$00 : BNE +
PLB : RTL
+ DEX : DEX : DEX : DEX : DEX : DEX : BRA -
.found_match
INX : LDA.w OWBonkPrizeData,X : PHX : PHA ; S = FlagBitmask, X (row + 2)
LDX.b $8A : LDA.l OverworldEventDataWRAM,X : AND 1,S : PHA : BNE + ; S = Collected, FlagBitmask, X (row + 2)
LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx
+
LDA 3,S : TAX : INX : LDA.w OWBonkPrizeData,X
PHA : INX : LDA.w OWBonkPrizeData,X : BEQ +
; multiworld item
DEX : PLA ; X = row + 3
JMP .spawn_item
+ DEX : PLA ; X = row + 3
.determine_type ; A = item id ; S = Collected, FlagBitmask, X (row + 2)
CMP.b #$B0 : BNE +
LDA.b #$79 : JMP .sprite_transform ; transform to bees
+ CMP.b #$42 : BNE +
JSL.l Sprite_TransmuteToBomb ; transform a heart to bomb, vanilla behavior
JMP .mark_collected
+ CMP.b #$34 : BNE +
LDA.b #$D9 : CLC : JMP .sprite_transform ; transform to single rupee
+ CMP.b #$35 : BNE +
LDA.b #$DA : CLC : BRA .sprite_transform ; transform to blue rupee
+ CMP.b #$36 : BNE +
LDA.b #$DB : CLC : BRA .sprite_transform ; transform to red rupee
+ CMP.b #$27 : BNE +
LDA.b #$DC : CLC : BRA .sprite_transform ; transform to 1 bomb
+ CMP.b #$28 : BNE +
LDA.b #$DD : CLC : BRA .sprite_transform ; transform to 4 bombs
+ CMP.b #$31 : BNE +
LDA.b #$DE : CLC : BRA .sprite_transform ; transform to 8 bombs
+ CMP.b #$45 : BNE +
LDA.b #$DF : CLC : BRA .sprite_transform ; transform to small magic
+ CMP.b #$B4 : BNE +
LDA.b #$E0 : CLC : BRA .sprite_transform ; transform to big magic
+ CMP.b #$B5 : BNE +
LDA.b #$E1 : CLC : BRA .sprite_transform ; transform to 5 arrows
+ CMP.b #$44 : BNE +
LDA.b #$E2 : CLC : BRA .sprite_transform ; transform to 10 arrows
+ CMP.b #$B1 : BNE +
LDA.b #$AC : BRA .sprite_transform ; transform to apples
+ CMP.b #$B2 : BNE +
LDA.b #$E3 : BRA .sprite_transform ; transform to fairy
+ CMP.b #$B3 : BNE .spawn_item
INX : INX : LDA.w OWBonkPrizeData,X ; X = row + 5
CLC : ADC.b #$08 : PHA
LDA.w $0D00,Y : SEC : SBC.b 1,S : STA.w $0D00,Y
LDA.w $0D20,Y : SBC.b #$00 : STA.w $0D20,Y : PLX
LDA.b #$0B : SEC ; BRA .sprite_transform ; transform to chicken
.sprite_transform
STA.w $0E20,Y
TYX : JSL.l Sprite_LoadProperties
BEQ +
; these are sprite properties that make it fall out of the tree to the east
LDA #$30 : STA $0F80,Y ; amount of force (related to speed)
LDA #$10 : STA $0D50,Y ; eastward rate of speed
LDA #$FF : STA $0B58,Y ; expiration timer
+
.mark_collected ; S = Collected, FlagBitmask, X (row + 2)
PLA : BNE + ; S = FlagBitmask, X (row + 2)
LDX.b $8A : LDA.l OverworldEventDataWRAM,X : ORA 1,S : STA.l OverworldEventDataWRAM,X
REP #$20
LDA.l TotalItemCounter : INC : STA.l TotalItemCounter
SEP #$20
+ JMP .return
; spawn itemget item
.spawn_item ; A = item id ; Y = tree sprite slot ; S = Collected, FlagBitmask, X (row + 2)
PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : JMP .return ; S = FlagBitmask, X (row + 2)
+ LDA 2,S : TAX : INX : INX
LDA.w OWBonkPrizeData,X : STA.l !MULTIWORLD_SPRITEITEM_PLAYER_ID
DEX
LDA.b #$01 : STA !REDRAW
LDA.b #$EB
STA.l $7FFE00
JSL Sprite_SpawnDynamically+15 ; +15 to skip finding a new slot, use existing sprite
; affects the rate the item moves in the Y/X direction
LDA.b #$00 : STA.w $0D40,Y
LDA.b #$0A : STA.w $0D50,Y
LDA.b #$20 : STA.w $0F80,Y ; amount of force (gives height to the arch)
LDA.b #$FF : STA.w $0B58,Y ; stun timer
LDA.b #$30 : STA.w $0F10,Y ; aux delay timer 4 ?? dunno what that means
LDA.b #$00 : STA.w $0F20,Y ; layer the sprite is on
; sets OW event bitmask flag, uses free RAM
PLA : STA.w $0ED0,Y ; S = X (row + 2)
; determines the initial spawn point of item
PLX : INX : INX : INX
LDA.w $0D00,Y : SEC : SBC.w OWBonkPrizeData,X : STA.w $0D00,Y
LDA.w $0D20,Y : SBC #$00 : STA.w $0D20,Y
LDA.b #$01 : STA !REDRAW : STA !FORCE_HEART_SPAWN
PLB : RTL
.return
PLA : PLA : PLB : RTL
}
org $aa9000 org $aa9000
OWDetectEdgeTransition: OWDetectEdgeTransition:
{ {
@@ -1130,11 +1265,11 @@ dw $0f20, $0f40, $0020, $0f30, $757e, $0000, $0000, $0049
dw $0f70, $0fb8, $0048, $0f94, $757e, $0000, $0000, $004a dw $0f70, $0fb8, $0048, $0f94, $757e, $0000, $0000, $004a
dw $0058, $00c0, $0068, $008c, $8080, $0000, $0000, $0017 ;Hobo (unused) dw $0058, $00c0, $0068, $008c, $8080, $0000, $0000, $0017 ;Hobo (unused)
org $aab9e0 ;PC 1539e0 org $aab9a0 ;PC 1539a0
OWSpecialDestIndex: OWSpecialDestIndex:
dw $0080, $0081, $0082 dw $0080, $0081, $0082
org $aaba00 ;PC 153a00 org $aab9b0 ;PC 1539b0
OWTileWorldAssoc: OWTileWorldAssoc:
db 0, 0, 0, 0, 0, 0, 0, 0 db 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0 db 0, 0, 0, 0, 0, 0, 0, 0
@@ -1154,7 +1289,7 @@ db $40, $40, $40, $40, $40, $40, $40, $40
db $40, $40, $40, $40, $40, $40, $40, $40 db $40, $40, $40, $40, $40, $40, $40, $40
db $00, $00 db $00, $00
org $aabb00 ;PC 153b00 org $aaba70 ;PC 153a70
OWTileMapAlt: OWTileMapAlt:
db 0, 0, 0, 0, 0, 0, 0, 0 db 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0 db 0, 0, 0, 0, 0, 0, 0, 0
@@ -1175,3 +1310,104 @@ db 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0, 0, 0, 0, 0, 0, 0 db 0, 0, 0, 0, 0, 0, 0, 0
db 0, 0 db 0, 0
;================================================================================
; Bonk Prize Data ($AABB00 - $AABBFB)
;--------------------------------------------------------------------------------
; This table stores data relating to bonk locations for Bonk Drop Shuffle
;
; Example: We can use OWBonkPrizeTable[$09].loot to read what item is in the
; east tree on the Sanctuary screen
;--------------------------------------------------------------------------------
; Search Criteria - The following two fields are used as a unique index
; .owid = OW screen ID
; .yx = Y & X coordinate data *see below*
;
; .flag = OW event flag bitmask
; .loot = Loot ID
; .mw_player = Multiworld player ID
; .vert_offset = Vertical offset, # of pixels the sprite moves up when activated
;
; .yx field is a combination of both the least significant digits of the Y and X
; coordinates of the static location of the sprite located in a bonk location.
; All sprites, when initialized, are aligned by a 16 pixel increment.
; The coordinate system in LTTP is handled by two bytes:
; (high) (low)
; - - - w w w w s s s s s s s s s
; w = world absolute coords, every screen is $200 pixels in each dimension
; s = local screen coords, coords relative to the bounds of the current screen
; Because of the 16 pixel alignment of sprites, the last four bits of the coords
; are unset. This leaves 5 bits remaining, we simply disregard the highest bit
; and then combine the Y and X coords together to be used as search criteria.
; This does open the possibility of a false positive match from 3 other coords
; on the same screen (15 on megatile screens) but there are no bonk sprites that
; have collision in this regard.
;--------------------------------------------------------------------------------
struct OWBonkPrizeTable $AABB00
.owid: skip 1
.yx: skip 1
.flag: skip 1
.loot: skip 1
.mw_player: skip 1
.vert_offset: skip 1
endstruct align 6
org $aabb00 ;PC 153b00
OWBonkPrizeData:
; OWID YX Flag Item MW Offset
db $00, $59, $10, $b0, $00, $20
db $05, $04, $10, $b2, $00, $00
db $0a, $4e, $10, $b0, $00, $20
db $0a, $a9, $08, $b1, $00, $20
db $10, $c7, $10, $b1, $00, $20
db $10, $f7, $08, $b4, $00, $20
db $11, $08, $10, $27, $00, $00
db $12, $a4, $10, $b2, $00, $20
db $13, $c7, $10, $31, $00, $20
db $13, $98, $08, $b1, $00, $20
db $15, $a4, $10, $b1, $00, $20
db $15, $fb, $08, $b2, $00, $20
db $18, $a8, $10, $b2, $00, $20
db $18, $36, $08, $35, $00, $20
db $1a, $8a, $10, $42, $00, $20
db $1a, $1d, $08, $b2, $00, $20
db $ff, $77, $04, $35, $00, $20 ; pre aga ONLY ; hijacked murahdahla bonk tree
db $1b, $46, $10, $b1, $00, $10
db $1d, $6b, $10, $b1, $00, $20
db $1e, $72, $10, $b2, $00, $20
db $2a, $8f, $10, $36, $00, $20
db $2a, $45, $08, $36, $00, $20
db $2b, $d6, $10, $b2, $00, $20
db $2e, $9c, $10, $b2, $00, $20
db $2e, $b4, $08, $b0, $00, $20
db $32, $29, $10, $42, $00, $20
db $32, $9a, $08, $b2, $00, $20
db $42, $66, $10, $b2, $00, $20
db $51, $08, $10, $b2, $00, $04
db $51, $09, $08, $b2, $00, $04
db $54, $b5, $10, $27, $00, $14
db $54, $ef, $08, $b2, $00, $08
db $54, $b9, $04, $36, $00, $00
db $55, $aa, $10, $b0, $00, $20
db $55, $fb, $08, $35, $00, $20
db $56, $e4, $10, $b0, $00, $20
db $5b, $a7, $10, $b2, $00, $20
db $5e, $00, $10, $b2, $00, $20
db $6e, $8c, $10, $35, $00, $10
db $6e, $90, $08, $b0, $00, $10
db $6e, $a4, $04, $b1, $00, $10
db $74, $4e, $10, $b1, $00, $1c
; temporary fix - murahdahla replaces one of the bonk tree prizes
; so we copy the sprite table here and update the pointer
; longterm solution should be to spawn in murahdahla separately
org $09AE2A
Overworld_Sprites_Screen1A_2:
db $08, $0F, $41 ; yx:{ 0x080, 0x0F0 }
db $0E, $0C, $41 ; yx:{ 0x0E0, 0x0C0 }
db $11, $0D, $E3 ; yx:{ 0x110, 0x0D0 }
db $18, $0A, $D8 ; yx:{ 0x180, 0x0A0 }
db $18, $0F, $45 ; yx:{ 0x180, 0x0F0 }
db $FF ; END
org $09CA55
dw Overworld_Sprites_Screen1A_2&$FFFF

Binary file not shown.

View File

@@ -22,6 +22,9 @@
vanilla: 0 vanilla: 0
balanced: 1 balanced: 1
random: 1 random: 1
bonk_drops:
on: 1
off: 1
door_shuffle: door_shuffle:
vanilla: 0 vanilla: 0
basic: 2 basic: 2

View File

@@ -167,6 +167,10 @@
"action": "store_true", "action": "store_true",
"type": "bool" "type": "bool"
}, },
"bonk_drops": {
"action": "store_true",
"type": "bool"
},
"ow_fluteshuffle": { "ow_fluteshuffle": {
"choices": [ "choices": [
"vanilla", "vanilla",

View File

@@ -234,6 +234,9 @@
"ow_whirlpool": [ "ow_whirlpool": [
"Whirlpools will be shuffled and paired together." "Whirlpools will be shuffled and paired together."
], ],
"bonk_drops": [
"Bonk drops from trees, rocks, and statues are shuffled with the item pool."
],
"ow_fluteshuffle": [ "ow_fluteshuffle": [
"This randomizes the flute spot destinations.", "This randomizes the flute spot destinations.",
"Vanilla: All flute spots remain unchanged.", "Vanilla: All flute spots remain unchanged.",

View File

@@ -145,6 +145,8 @@
"randomizer.overworld.whirlpool": "Whirlpool Shuffle", "randomizer.overworld.whirlpool": "Whirlpool Shuffle",
"randomizer.overworld.bonk_drops": "Bonk Drops",
"randomizer.overworld.overworldflute": "Flute Shuffle", "randomizer.overworld.overworldflute": "Flute Shuffle",
"randomizer.overworld.overworldflute.vanilla": "Vanilla", "randomizer.overworld.overworldflute.vanilla": "Vanilla",
"randomizer.overworld.overworldflute.balanced": "Balanced", "randomizer.overworld.overworldflute.balanced": "Balanced",

View File

@@ -1,4 +1,10 @@
{ {
"topOverworldFrame": {
"bonk_drops": {
"type": "checkbox",
"default": false
}
},
"leftOverworldFrame": { "leftOverworldFrame": {
"overworldshuffle": { "overworldshuffle": {
"type": "selectbox", "type": "selectbox",

View File

@@ -81,6 +81,7 @@ SETTINGSTOPROCESS = {
"keepsimilar": "ow_keepsimilar", "keepsimilar": "ow_keepsimilar",
"mixed": "ow_mixed", "mixed": "ow_mixed",
"whirlpool": "ow_whirlpool", "whirlpool": "ow_whirlpool",
"bonk_drops": "bonk_drops",
"overworldflute": "ow_fluteshuffle" "overworldflute": "ow_fluteshuffle"
}, },
"entrance": { "entrance": {

View File

@@ -15,13 +15,17 @@ def overworld_page(parent):
# Load Overworld Shuffle option widgets as defined by JSON file # Load Overworld Shuffle option widgets as defined by JSON file
# Defns include frame name, widget type, widget options, widget placement attributes # Defns include frame name, widget type, widget options, widget placement attributes
# These get split left & right self.frames["topOverworldFrame"] = Frame(self)
self.frames["leftOverworldFrame"] = Frame(self) self.frames["leftOverworldFrame"] = Frame(self)
self.frames["rightOverworldFrame"] = Frame(self) self.frames["rightOverworldFrame"] = Frame(self)
self.frames["topOverworldFrame"].pack(side=TOP, anchor=NW)
self.frames["leftOverworldFrame"].pack(side=LEFT, anchor=NW, fill=Y) self.frames["leftOverworldFrame"].pack(side=LEFT, anchor=NW, fill=Y)
self.frames["rightOverworldFrame"].pack(anchor=NW, fill=Y) self.frames["rightOverworldFrame"].pack(anchor=NW, fill=Y)
shuffleLabel = Label(self.frames["topOverworldFrame"], text="Shuffle: ")
shuffleLabel.pack(side=LEFT)
with open(os.path.join("resources","app","gui","randomize","overworld","widgets.json")) as overworldWidgets: with open(os.path.join("resources","app","gui","randomize","overworld","widgets.json")) as overworldWidgets:
myDict = json.load(overworldWidgets) myDict = json.load(overworldWidgets)
for framename,theseWidgets in myDict.items(): for framename,theseWidgets in myDict.items():
@@ -33,7 +37,7 @@ def overworld_page(parent):
packAttrs = {"side":LEFT, "pady":(18,0)} packAttrs = {"side":LEFT, "pady":(18,0)}
elif key == "overworldflute": elif key == "overworldflute":
packAttrs["pady"] = (20,0) packAttrs["pady"] = (20,0)
elif key in ["whirlpool", "mixed"]: elif key in ["mixed", "whirlpool"]:
packAttrs = {"anchor":W, "padx":(79,0)} packAttrs = {"anchor":W, "padx":(79,0)}
self.widgets[key].pack(packAttrs) self.widgets[key].pack(packAttrs)

View File

@@ -792,8 +792,8 @@ trash_items = {
'Bee Trap': 0, 'Bee Trap': 0,
'Rupee (1)': 1, 'Rupees (5)': 1, 'Small Heart': 1, 'Bee': 1, 'Arrows (5)': 1, 'Chicken': 1, 'Single Bomb': 1, 'Rupee (1)': 1, 'Rupees (5)': 1, 'Small Heart': 1, 'Bee': 1, 'Arrows (5)': 1, 'Chicken': 1, 'Single Bomb': 1,
'Rupees (20)': 2, 'Small Magic': 2, 'Rupees (20)': 2, 'Small Magic': 2,
'Bombs (3)': 3, 'Arrows (10)': 3, 'Bombs (10)': 3, 'Bombs (3)': 3, 'Arrows (10)': 3, 'Bombs (10)': 3, 'Apples': 3,
'Big Magic': 4, 'Red Potion': 4, 'Blue Shield': 4, 'Rupees (50)': 4, 'Rupees (100)': 4, 'Fairy': 4, 'Big Magic': 4, 'Red Potion': 4, 'Blue Shield': 4, 'Rupees (50)': 4, 'Rupees (100)': 4,
'Rupees (300)': 5, 'Rupees (300)': 5,
'Piece of Heart': 17 'Piece of Heart': 17
} }