Version bump 0.2.9.0
This commit is contained in:
@@ -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]))
|
||||||
|
|||||||
@@ -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
3
CLI.py
@@ -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
55
Fill.py
@@ -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)
|
||||||
|
|||||||
33
ItemList.py
33
ItemList.py
@@ -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 = {}
|
||||||
|
|||||||
8
Items.py
8
Items.py
@@ -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),
|
||||||
|
|||||||
9
Main.py
9
Main.py
@@ -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()
|
||||||
|
|||||||
@@ -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()]):
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -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
|
||||||
|
|||||||
54
Regions.py
54
Regions.py
@@ -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
71
Rom.py
@@ -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)
|
||||||
|
|||||||
37
Rules.py
37
Rules.py
@@ -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))
|
||||||
|
|
||||||
|
|||||||
32
Tables.py
32
Tables.py
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
244
asm/owrando.asm
244
asm/owrando.asm
@@ -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.
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"topOverworldFrame": {
|
||||||
|
"bonk_drops": {
|
||||||
|
"type": "checkbox",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
"leftOverworldFrame": {
|
"leftOverworldFrame": {
|
||||||
"overworldshuffle": {
|
"overworldshuffle": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user