Compare commits
47 Commits
7bab5b0434
...
36e57bcdf4
| Author | SHA1 | Date | |
|---|---|---|---|
| 36e57bcdf4 | |||
| b72a04511b | |||
| 5beeea2f83 | |||
| df928f0378 | |||
| 1644535557 | |||
| dcc4121c43 | |||
| 444ebda072 | |||
| 2055ed6d96 | |||
| 1a9cea452d | |||
| 8451a64984 | |||
| f5dde931dc | |||
| b82ec31f98 | |||
| 2ff19b3781 | |||
| 5278f9f2c6 | |||
| 5f1c08920f | |||
| 57db996f27 | |||
| 5af7073ed1 | |||
| 33cd2d076c | |||
| cd5bc9a206 | |||
| 78819bbd9d | |||
| b2d2aae61a | |||
| 320e518519 | |||
| c39a31b299 | |||
| 0ef55c5f6a | |||
| fb57e9fd34 | |||
| ab2e8f7818 | |||
| 151765d8d2 | |||
| 1ae9da59bb | |||
| 84270544bf | |||
| 8ae1aefcf3 | |||
| 821c0ee46e | |||
| a946c2b3b0 | |||
| ec89afaa5e | |||
| d530b68e86 | |||
| 924bd7a616 | |||
| cdfdc15219 | |||
| d2a5bb56d8 | |||
| e86312f145 | |||
| 97aceb3f43 | |||
| b6f73ffda4 | |||
| c4687a3be6 | |||
| 4d63168e9a | |||
| 6474975b07 | |||
| 4de84df7a8 | |||
| b3afa57677 | |||
| 24ebf3037a | |||
| d64a58f636 |
@@ -164,6 +164,7 @@ class World(object):
|
||||
set_player_attr('bigkeyshuffle', 'none')
|
||||
set_player_attr('prizeshuffle', 'none')
|
||||
set_player_attr('showloot', 'never')
|
||||
set_player_attr('loothud', 'never')
|
||||
set_player_attr('showmap', 'map')
|
||||
set_player_attr('restrict_boss_items', 'none')
|
||||
set_player_attr('bombbag', False)
|
||||
@@ -202,6 +203,7 @@ class World(object):
|
||||
set_player_attr('damage_challenge', 'normal')
|
||||
set_player_attr('shuffle_damage_table', 'vanilla')
|
||||
set_player_attr('crystal_book', False)
|
||||
set_player_attr('extra_keys', 0)
|
||||
set_player_attr('collection_rate', False)
|
||||
set_player_attr('colorizepots', True)
|
||||
set_player_attr('pot_pool', {})
|
||||
@@ -1517,7 +1519,10 @@ class CollectionState(object):
|
||||
if not item:
|
||||
return
|
||||
changed = False
|
||||
if item.name.startswith('Progressive '):
|
||||
if item.name == "Sword and Shield":
|
||||
self.prog_items["Fighter Sword", item.player] += 1
|
||||
self.prog_items["Blue Shield", item.player] += 1
|
||||
elif item.name.startswith('Progressive '):
|
||||
if 'Sword' in item.name:
|
||||
if self.has('Golden Sword', item.player):
|
||||
pass
|
||||
@@ -1978,6 +1983,7 @@ class Dungeon(object):
|
||||
self.prize = None
|
||||
self.big_key = big_key
|
||||
self.small_keys = small_keys
|
||||
self.extra_small_keys = 0
|
||||
self.dungeon_items = dungeon_items
|
||||
self.bosses = dict()
|
||||
self.player = player
|
||||
@@ -2892,6 +2898,10 @@ class Item(object):
|
||||
def compass(self):
|
||||
return self.type == 'Compass'
|
||||
|
||||
@property
|
||||
def event(self):
|
||||
return self.type == 'Event'
|
||||
|
||||
@property
|
||||
def dungeon(self):
|
||||
if not self.smallkey and not self.bigkey and not self.map and not self.compass:
|
||||
@@ -3154,6 +3164,7 @@ class Spoiler(object):
|
||||
'bigkeyshuffle': self.world.bigkeyshuffle,
|
||||
'prizeshuffle': self.world.prizeshuffle,
|
||||
'showloot': self.world.showloot,
|
||||
'loothud': self.world.loothud,
|
||||
'showmap': self.world.showmap,
|
||||
'boss_shuffle': self.world.boss_shuffle,
|
||||
'enemy_shuffle': self.world.enemy_shuffle,
|
||||
@@ -3174,6 +3185,7 @@ class Spoiler(object):
|
||||
'damage_challenge': self.world.damage_challenge,
|
||||
'shuffle_damage_table': self.world.shuffle_damage_table,
|
||||
'crystal_book': self.world.crystal_book,
|
||||
'extra_keys': self.world.extra_keys,
|
||||
'triforcegoal': self.world.treasure_hunt_count,
|
||||
'triforcepool': self.world.treasure_hunt_total,
|
||||
'race': self.world.settings.world_rep['meta']['race'],
|
||||
@@ -3181,6 +3193,8 @@ class Spoiler(object):
|
||||
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)},
|
||||
'seed': self.world.seed
|
||||
}
|
||||
if self.hashes:
|
||||
self.metadata['hash'] = {p: self.hashes[p, 0] for p in range(1, self.world.players + 1)} # TODO: make this work for multiple teams
|
||||
|
||||
for p in range(1, self.world.players + 1):
|
||||
from ItemList import set_default_triforce
|
||||
@@ -3427,6 +3441,7 @@ class Spoiler(object):
|
||||
outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['bigkeyshuffle'][player])
|
||||
outfile.write('Prize Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['prizeshuffle'][player])
|
||||
outfile.write('Show Value of Checks:'.ljust(line_width) + '%s\n' % self.metadata['showloot'][player])
|
||||
outfile.write('Show Value of Checks on HUD:'.ljust(line_width) + '%s\n' % self.metadata['loothud'][player])
|
||||
outfile.write('Show Map:'.ljust(line_width) + '%s\n' % self.metadata['showmap'][player])
|
||||
outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player])
|
||||
outfile.write('\n')
|
||||
@@ -3453,6 +3468,7 @@ class Spoiler(object):
|
||||
outfile.write('Damage Challenge:'.ljust(line_width) + '%s\n' % self.metadata['damage_challenge'][player])
|
||||
outfile.write('Damage Table Randomization:'.ljust(line_width) + '%s\n' % self.metadata['shuffle_damage_table'][player])
|
||||
outfile.write('Crystal Book:'.ljust(line_width) + '%s\n' % yn(self.metadata['crystal_book'][player]))
|
||||
outfile.write('Extra Keys:'.ljust(line_width) + '%d%%\n' % self.metadata['extra_keys'][player])
|
||||
outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player]))
|
||||
outfile.write('Race:'.ljust(line_width) + '%s\n' % yn(self.world.settings.world_rep['meta']['race']))
|
||||
|
||||
|
||||
63
CLI.py
63
CLI.py
@@ -89,6 +89,7 @@ def parse_cli(argv, no_defaults=False):
|
||||
parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli", "help", "count")), type=int)
|
||||
parser.add_argument('--tries', default=defval(int(settings["tries"]) if settings["tries"] != "" and settings["tries"] is not None else 1), help="\n".join(fish.translate("cli", "help", "tries")), type=int)
|
||||
parser.add_argument('--customitemarray', default={}, help=argparse.SUPPRESS)
|
||||
parser.add_argument('--skip_money_balance', action="store_true", help=argparse.SUPPRESS)
|
||||
|
||||
# included for backwards compatibility
|
||||
parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255))
|
||||
@@ -106,7 +107,14 @@ def parse_cli(argv, no_defaults=False):
|
||||
ret = parser.parse_args(argv)
|
||||
|
||||
if ret.keysanity:
|
||||
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = ['wild'] * 4
|
||||
if ret.mapshuffle == "none":
|
||||
ret.mapshuffle = "wild"
|
||||
if ret.compassshuffle == "none":
|
||||
ret.compassshuffle = "wild"
|
||||
if ret.keyshuffle == "none":
|
||||
ret.keyshuffle = "wild"
|
||||
if ret.bigkeyshuffle == "none":
|
||||
ret.bigkeyshuffle = "wild"
|
||||
|
||||
if ret.keydropshuffle:
|
||||
ret.dropshuffle = 'keys' if ret.dropshuffle == 'none' else ret.dropshuffle
|
||||
@@ -153,31 +161,32 @@ def parse_cli(argv, no_defaults=False):
|
||||
'crystals_ganon', 'crystals_gt', 'bosses_ganon',
|
||||
'bosshunt_include_agas', 'ganon_item', 'openpyramid',
|
||||
'mapshuffle', 'compassshuffle', 'keyshuffle',
|
||||
'bigkeyshuffle', 'prizeshuffle', 'showloot', 'showmap',
|
||||
'startinventory', 'usestartinventory', 'bombbag',
|
||||
'shuffleganon', 'overworld_map', 'restrict_boss_items',
|
||||
'triforce_max_difference', 'triforce_pool_min',
|
||||
'triforce_pool_max', 'triforce_goal_min',
|
||||
'triforce_goal_max', 'triforce_min_difference',
|
||||
'triforce_goal', 'triforce_pool', 'shufflelinks',
|
||||
'shuffletavern', 'skullwoods', 'linked_drops',
|
||||
'pseudoboots', 'mirrorscroll', 'dark_rooms',
|
||||
'damage_challenge', 'shuffle_damage_table',
|
||||
'crystal_book', 'retro', 'accessibility', 'hints',
|
||||
'beemizer', 'experimental', 'dungeon_counters',
|
||||
'shufflebosses', 'shuffleenemies', 'enemy_health',
|
||||
'enemy_damage', 'shufflepots', 'ow_palettes',
|
||||
'uw_palettes', 'sprite', 'triforce_gfx', 'disablemusic',
|
||||
'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
||||
'remote_items', 'shopsanity', 'dropshuffle', 'pottery',
|
||||
'keydropshuffle', 'mixed_travel',
|
||||
'standardize_palettes', 'code', 'reduce_flashing',
|
||||
'shuffle_sfx', 'shuffle_sfxinstruments',
|
||||
'shuffle_songinstruments', 'msu_resume',
|
||||
'collection_rate', 'colorizepots', 'decoupledoors',
|
||||
'door_type_mode', 'bonk_drops', 'trap_door_mode',
|
||||
'key_logic_algorithm', 'door_self_loops',
|
||||
'any_enemy_logic', 'aga_randomness', 'money_balance']:
|
||||
'bigkeyshuffle', 'prizeshuffle', 'showloot', 'loothud',
|
||||
'showmap', 'startinventory', 'usestartinventory',
|
||||
'bombbag', 'shuffleganon', 'overworld_map',
|
||||
'restrict_boss_items', 'triforce_max_difference',
|
||||
'triforce_pool_min', 'triforce_pool_max',
|
||||
'triforce_goal_min', 'triforce_goal_max',
|
||||
'triforce_min_difference', 'triforce_goal',
|
||||
'triforce_pool', 'shufflelinks', 'shuffletavern',
|
||||
'skullwoods', 'linked_drops', 'pseudoboots',
|
||||
'mirrorscroll', 'dark_rooms', 'damage_challenge',
|
||||
'shuffle_damage_table', 'crystal_book', 'extra_keys',
|
||||
'retro', 'accessibility', 'hints', 'beemizer',
|
||||
'experimental', 'dungeon_counters', 'shufflebosses',
|
||||
'shuffleenemies', 'enemy_health', 'enemy_damage',
|
||||
'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite',
|
||||
'triforce_gfx', 'disablemusic', 'quickswap', 'fastmenu',
|
||||
'heartcolor', 'heartbeep', 'remote_items', 'shopsanity',
|
||||
'dropshuffle', 'pottery', 'keydropshuffle',
|
||||
'mixed_travel', 'standardize_palettes', 'code',
|
||||
'reduce_flashing', 'shuffle_sfx',
|
||||
'shuffle_sfxinstruments', 'shuffle_songinstruments',
|
||||
'msu_resume', 'collection_rate', 'colorizepots',
|
||||
'decoupledoors', 'door_type_mode', 'bonk_drops',
|
||||
'trap_door_mode', 'key_logic_algorithm',
|
||||
'door_self_loops', 'any_enemy_logic', 'aga_randomness',
|
||||
'money_balance', 'patches']:
|
||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||
if player == 1:
|
||||
setattr(ret, name, {1: value})
|
||||
@@ -254,6 +263,7 @@ def parse_settings():
|
||||
"damage_challenge": "normal",
|
||||
"shuffle_damage_table": "vanilla",
|
||||
"crystal_book": False,
|
||||
"extra_keys": 0,
|
||||
|
||||
"shuffleenemies": "none",
|
||||
"shufflebosses": "none",
|
||||
@@ -273,6 +283,7 @@ def parse_settings():
|
||||
"bigkeyshuffle": "none",
|
||||
"prizeshuffle": "none",
|
||||
"showloot": "never",
|
||||
"loothud": "never",
|
||||
"showmap": "map",
|
||||
"keysanity": False,
|
||||
"door_shuffle": "vanilla",
|
||||
|
||||
@@ -3,6 +3,7 @@ import time
|
||||
from collections import defaultdict, deque
|
||||
from enum import Flag, unique
|
||||
from itertools import chain
|
||||
from math import ceil
|
||||
from typing import DefaultDict, Dict, List
|
||||
|
||||
import RaceRandom as random
|
||||
@@ -1599,7 +1600,11 @@ def assign_cross_keys(dungeon_builders, world, player):
|
||||
if actual_chest_keys == 0:
|
||||
dungeon.small_keys = []
|
||||
else:
|
||||
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
||||
extra_keys = ceil(actual_chest_keys * world.extra_keys[player] / 100)
|
||||
logger.debug(f'Adding {extra_keys} extra small keys to {name}')
|
||||
dungeon.extra_small_keys = extra_keys
|
||||
created_keys = actual_chest_keys + extra_keys
|
||||
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * created_keys
|
||||
logger.info(f'{world.fish.translate("cli", "cli", "keydoor.shuffle.time.crossed")}: {time.process_time()-start}')
|
||||
|
||||
|
||||
@@ -2171,7 +2176,10 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_
|
||||
if actual_chest_keys == 0:
|
||||
dungeon.small_keys = []
|
||||
else:
|
||||
dungeon.small_keys = [ItemFactory(dungeon_keys[dungeon_name], player) for _ in range(actual_chest_keys)]
|
||||
extra_keys = ceil(actual_chest_keys * world.extra_keys[player] / 100)
|
||||
dungeon.extra_small_keys = extra_keys
|
||||
created_keys = actual_chest_keys + extra_keys
|
||||
dungeon.small_keys = [ItemFactory(dungeon_keys[dungeon_name], player) for _ in range(created_keys)]
|
||||
|
||||
for name, small_list in small_map.items():
|
||||
used_doors.update(flatten_pair_list(small_list))
|
||||
|
||||
5
Fill.py
5
Fill.py
@@ -252,6 +252,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl
|
||||
or (world.algorithm == 'vanilla_fill' and item_to_place.is_near_dungeon_item(world)))) \
|
||||
or valid_dungeon_placement(item_to_place, location, world):
|
||||
return location
|
||||
|
||||
if item_to_place.smallkey or item_to_place.bigkey or item_to_place.prize:
|
||||
location.item = None
|
||||
location.event = False
|
||||
@@ -311,7 +312,9 @@ def valid_dungeon_placement(item, location, world):
|
||||
dungeon = check_dungeon
|
||||
if dungeon:
|
||||
layout = world.dungeon_layouts[location.player][dungeon.name]
|
||||
if not is_dungeon_item(item, world) or item.player != location.player:
|
||||
if item.event:
|
||||
return True
|
||||
elif not is_dungeon_item(item, world) or item.player != location.player:
|
||||
if item.prize and item.is_near_dungeon_item(world):
|
||||
return item.dungeon_object == dungeon and layout.free_items > 0
|
||||
return layout.free_items > 0
|
||||
|
||||
26
ItemList.py
26
ItemList.py
@@ -372,6 +372,13 @@ def generate_itempool(world, player):
|
||||
world.push_precollected(ItemFactory(item, player))
|
||||
|
||||
if world.mode[player] == 'standard' and not world.state.has_blunt_weapon(player):
|
||||
if world.customizer:
|
||||
placements = world.customizer.get_placements()
|
||||
if placements:
|
||||
custom_uncle = placements.get(player, {}).get("Link's Uncle")
|
||||
if custom_uncle:
|
||||
placed_items["Link's Uncle"] = custom_uncle
|
||||
|
||||
if "Link's Uncle" not in placed_items:
|
||||
found_sword = False
|
||||
found_bow = False
|
||||
@@ -865,7 +872,7 @@ def set_up_shops(world, player):
|
||||
rss.locked = True
|
||||
cap_shop = world.get_region('Capacity Upgrade', player).shop
|
||||
cap_shop.inventory[1] = None # remove arrow capacity upgrades in retro
|
||||
for shop in random.sample([s for s in world.shops[player] if not s.locked and s.region.player == player], 5):
|
||||
for shop in random.sample([s for s in world.shops[player] if not s.locked and s.region.name != "Red Shield Shop" and s.region.player == player], 5):
|
||||
shop.custom = True
|
||||
shop.locked = True
|
||||
if retro_bow:
|
||||
@@ -1669,12 +1676,15 @@ def fill_specific_items(world):
|
||||
item_to_place, event_flag = get_item_and_event_flag(item, world, player,
|
||||
dungeon_pool, prize_set, prize_pool)
|
||||
if item_to_place:
|
||||
world.push_item(loc, item_to_place, False)
|
||||
loc.locked = True
|
||||
track_outside_keys(item_to_place, loc, world)
|
||||
track_dungeon_items(item_to_place, loc, world)
|
||||
loc.event = (event_flag or item_to_place.advancement
|
||||
or item_to_place.bigkey or item_to_place.smallkey)
|
||||
if not loc.item:
|
||||
world.push_item(loc, item_to_place, False)
|
||||
loc.locked = True
|
||||
track_outside_keys(item_to_place, loc, world)
|
||||
track_dungeon_items(item_to_place, loc, world)
|
||||
loc.event = (event_flag or item_to_place.advancement
|
||||
or item_to_place.bigkey or item_to_place.smallkey)
|
||||
elif loc.item != item_to_place:
|
||||
logging.getLogger('').warning("Failed to place item %s at location %s because it already contained %s", item_to_place, loc.name, loc.item)
|
||||
else:
|
||||
raise Exception(f'Did not find "{item}" in item pool to place at "{location}"')
|
||||
advanced_placements = world.customizer.get_advanced_placements()
|
||||
@@ -1802,7 +1812,7 @@ def shuffle_event_items(world, player):
|
||||
continue
|
||||
break
|
||||
else:
|
||||
raise FillError(f'Unable to place followers: {", ".join(list(map(lambda d: d.hint_text, follower_locations)))}')
|
||||
raise FillError(f'Unable to place followers: {", ".join(list(map(lambda f: f.name, pickup_items)))}')
|
||||
|
||||
|
||||
def get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_pool):
|
||||
|
||||
8
Items.py
8
Items.py
@@ -55,7 +55,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'Bow!\nJoin the archer class
|
||||
'Master Sword': (True, False, 'Sword', 0x50, 100, 'Master Sword!\nEvil\'s bane!', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'),
|
||||
'Tempered Sword': (True, False, 'Sword', 0x02, 150, 'Tempered Sword!\nMore slashy!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'),
|
||||
'Fighter Sword': (True, False, 'Sword', 0x49, 50, 'Fighter Sword!\nStarter level slashy!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the Fighter Sword'),
|
||||
'Sword and Shield': (True, False, 'Sword', 0x00, 'Sword and Shield!\nUncle sword ahoy!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'the small sword and shield'),
|
||||
'Sword and Shield': (True, False, 'Sword', 0x00, 50, 'Sword and Shield!\nUncle sword ahoy!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'the small sword and shield'),
|
||||
'Golden Sword': (True, False, 'Sword', 0x03, 200, 'Golden Sword!\nBest slashy!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'),
|
||||
'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'Sword!\nA better sword for your time!', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'),
|
||||
'Progressive Glove': (True, False, None, 0x61, 150, 'Glove!\nLift more than you can now!', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a Glove'),
|
||||
@@ -134,9 +134,9 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'Bow!\nJoin the archer class
|
||||
'Big Key (Agahnims Tower)': (False, False, 'BigKey', 0x9B, 60, 'A big key for\nAgahnim\'s Tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Castle Tower'),
|
||||
'Compass (Agahnims Tower)': (False, True, 'Compass', 0x8B, 10, 'A compass for\nAgahnim\'s Tower', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Castle Tower'),
|
||||
'Map (Agahnims Tower)': (False, True, 'Map', 0x7B, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Castle Tower'),
|
||||
'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 40, 'A small key for\nDark Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'),
|
||||
'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 60, 'A big key for\nDark Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'),
|
||||
'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 10, 'A compass for\nDark Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'),
|
||||
'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 40, 'A small key for\nPalace of Darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'),
|
||||
'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 60, 'A big key for\nPalace of Darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'),
|
||||
'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 10, 'A compass for\nPalace of Darkness', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'),
|
||||
'Map (Palace of Darkness)': (False, True, 'Map', 0x79, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Palace of Darkness'),
|
||||
'Small Key (Thieves Town)': (False, False, 'SmallKey', 0xAB, 40, 'A small key for\nThieves Town', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Thieves\' Town'),
|
||||
'Big Key (Thieves Town)': (False, False, 'BigKey', 0x94, 60, 'A big key for\nThieves Town', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Thieves\' Town'),
|
||||
|
||||
13
Main.py
13
Main.py
@@ -67,6 +67,7 @@ from Rom import (
|
||||
JsonRom,
|
||||
LocalRom,
|
||||
apply_rom_settings,
|
||||
apply_rom_patches,
|
||||
get_hash_string,
|
||||
patch_race_rom,
|
||||
patch_rom,
|
||||
@@ -335,7 +336,7 @@ def main(args, seed=None, fish=None):
|
||||
for player in range(1, world.players+1):
|
||||
if world.shopsanity[player]:
|
||||
customize_shops(world, player)
|
||||
if args.algorithm in ['balanced', 'equitable']:
|
||||
if not args.skip_money_balance and args.algorithm in ['balanced', 'equitable']:
|
||||
balance_money_progression(world)
|
||||
ensure_good_items(world, True)
|
||||
|
||||
@@ -364,6 +365,8 @@ def main(args, seed=None, fish=None):
|
||||
rom_names.append((player, team, list(rom.name)))
|
||||
world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash)
|
||||
|
||||
if args.patches[player]:
|
||||
apply_rom_patches(rom, map(lambda arg: arg.strip(), args.patches[player].split(",")))
|
||||
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
|
||||
args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.triforce_gfx[player],
|
||||
args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player],
|
||||
@@ -516,6 +519,7 @@ def init_world(args, fish):
|
||||
world.bigkeyshuffle = args.bigkeyshuffle.copy()
|
||||
world.prizeshuffle = args.prizeshuffle.copy()
|
||||
world.showloot = args.showloot.copy()
|
||||
world.loothud = args.loothud.copy()
|
||||
world.showmap = args.showmap.copy()
|
||||
world.bombbag = args.bombbag.copy()
|
||||
world.flute_mode = args.flute_mode.copy()
|
||||
@@ -564,13 +568,14 @@ def init_world(args, fish):
|
||||
world.damage_challenge = args.damage_challenge.copy()
|
||||
world.shuffle_damage_table = args.shuffle_damage_table.copy()
|
||||
world.crystal_book = args.crystal_book.copy()
|
||||
world.extra_keys = {player: int(args.extra_keys[player]) for player in range(1, world.players + 1)}
|
||||
world.overworld_map = args.overworld_map.copy()
|
||||
world.take_any = args.take_any.copy()
|
||||
world.restrict_boss_items = args.restrict_boss_items.copy()
|
||||
world.collection_rate = args.collection_rate.copy()
|
||||
world.colorizepots = args.colorizepots.copy()
|
||||
world.aga_randomness = args.aga_randomness.copy()
|
||||
world.money_balance = args.money_balance.copy()
|
||||
world.money_balance = {player: int(args.money_balance[player]) for player in range(1, world.players + 1)}
|
||||
|
||||
# custom settings - these haven't been promoted to full settings yet
|
||||
in_progress_settings = ['force_enemy']
|
||||
@@ -836,6 +841,7 @@ def copy_world(world):
|
||||
ret.bigkeyshuffle = world.bigkeyshuffle.copy()
|
||||
ret.prizeshuffle = world.prizeshuffle.copy()
|
||||
ret.showloot = world.showloot.copy()
|
||||
ret.loothud = world.loothud.copy()
|
||||
ret.showmap = world.showmap.copy()
|
||||
ret.bombbag = world.bombbag.copy()
|
||||
ret.flute_mode = world.flute_mode.copy()
|
||||
@@ -868,6 +874,7 @@ def copy_world(world):
|
||||
ret.damage_challenge = world.damage_challenge.copy()
|
||||
ret.shuffle_damage_table = world.shuffle_damage_table.copy()
|
||||
ret.crystal_book = world.crystal_book.copy()
|
||||
ret.extra_keys = world.extra_keys.copy()
|
||||
ret.overworld_map = world.overworld_map.copy()
|
||||
ret.take_any = world.take_any.copy()
|
||||
ret.boss_shuffle = world.boss_shuffle.copy()
|
||||
@@ -1068,6 +1075,7 @@ def copy_world_premature(world, player, create_flute_exits=True):
|
||||
ret.bigkeyshuffle = world.bigkeyshuffle.copy()
|
||||
ret.prizeshuffle = world.prizeshuffle.copy()
|
||||
ret.showloot = world.showloot.copy()
|
||||
ret.loothud = world.loothud.copy()
|
||||
ret.showmap = world.showmap.copy()
|
||||
ret.bombbag = world.bombbag.copy()
|
||||
ret.flute_mode = world.flute_mode.copy()
|
||||
@@ -1100,6 +1108,7 @@ def copy_world_premature(world, player, create_flute_exits=True):
|
||||
ret.damage_challenge = world.damage_challenge.copy()
|
||||
ret.shuffle_damage_table = world.shuffle_damage_table.copy()
|
||||
ret.crystal_book = world.crystal_book.copy()
|
||||
ret.extra_keys = world.extra_keys.copy()
|
||||
ret.overworld_map = world.overworld_map.copy()
|
||||
ret.take_any = world.take_any.copy()
|
||||
ret.boss_shuffle = world.boss_shuffle.copy()
|
||||
|
||||
@@ -509,7 +509,7 @@ boots_clips = [
|
||||
|
||||
mirror_clips_local = [
|
||||
('Desert East Mirror Clip', 'Mire Area', 'Desert Mouth'),
|
||||
('EDDM Mirror Clip', 'East Dark Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom)'),
|
||||
('EDDM Bridge Mirror Clip', 'East Dark Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom)'),
|
||||
('EDDM Mirror Clip', 'East Dark Death Mountain (Top)', 'Dark Death Mountain Ledge')
|
||||
]
|
||||
|
||||
@@ -520,4 +520,4 @@ mirror_clips = [
|
||||
mirror_offsets = [
|
||||
(['DM Offset Mirror', 'DDM Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Courtyard Northeast', 'Pyramid Crack'], ['Pyramid Area', 'Hyrule Castle Courtyard']),
|
||||
(['DM To HC Ledge Offset Mirror', 'DDM To Pyramid Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Ledge', 'Pyramid Area'], ['Pyramid Area', 'Hyrule Castle Area'])
|
||||
]
|
||||
]
|
||||
|
||||
202
Rom.py
202
Rom.py
@@ -85,7 +85,7 @@ from Utils import int16_as_bytes, int32_as_bytes, local_path, snes_to_pc
|
||||
from Versions import DRVersion, GKVersion, ORVersion
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '8945e9fdcefc02eb3ff3ad2a8892a180'
|
||||
RANDOMIZERBASEHASH = 'abaa7ce289fd18e0ab898f2ca1263db1'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
@@ -818,11 +818,13 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
rom.write_byte(0x138002, 2)
|
||||
for name, layout in world.key_layout[player].items():
|
||||
offset = compass_data[name][4]//2
|
||||
dungeon = world.get_dungeon(name, player)
|
||||
if world.keyshuffle[player] == 'universal':
|
||||
rom.write_byte(0x187010+offset, layout.max_chests + layout.max_drops)
|
||||
else:
|
||||
rom.write_byte(0x13f020+offset, layout.max_chests + layout.max_drops) # not currently used
|
||||
rom.write_byte(0x187010+offset, layout.max_chests)
|
||||
rom.write_byte(0x13f020+offset, layout.max_chests + layout.max_drops + dungeon.extra_small_keys) # not currently used
|
||||
rom.write_byte(0x187010+offset, layout.max_chests + dungeon.extra_small_keys)
|
||||
rom.write_byte(0x187000+offset, dungeon.extra_small_keys)
|
||||
builder = world.dungeon_layouts[player][name]
|
||||
bk_status = 1 if builder.bk_required else 0
|
||||
bk_status = 2 if builder.bk_provided else bk_status
|
||||
@@ -946,7 +948,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
rom.write_byte(cr_pc+0x1e, 0xEE) # slash
|
||||
rom.write_byte(cr_pc+0x1f, thousands_bot)
|
||||
# modify stat config
|
||||
stat_address = 0x23983E
|
||||
stat_address = 0xA39895
|
||||
stat_pc = snes_to_pc(stat_address)
|
||||
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)
|
||||
@@ -992,7 +994,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
set_inverted_mode(world, player, rom, inverted_buffer)
|
||||
|
||||
uncle_location = world.get_location('Link\'s Uncle', player)
|
||||
if uncle_location.item is None or uncle_location.item.name not in ['Master Sword', 'Tempered Sword', 'Fighter Sword', 'Golden Sword', 'Progressive Sword']:
|
||||
if uncle_location.item is None or uncle_location.item.name not in ['Master Sword', 'Tempered Sword', 'Fighter Sword', 'Golden Sword', 'Progressive Sword', 'Sword and Shield']:
|
||||
# disable sword sprite from uncle
|
||||
rom.write_bytes(0x6D263, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||
rom.write_bytes(0x6D26B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||
@@ -1005,6 +1007,10 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
rom.write_bytes(0x6D31B, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
|
||||
rom.write_bytes(0x6D323, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
|
||||
|
||||
bridge_item = world.get_location("Hobo", player).item
|
||||
if bridge_item is None or not bridge_item.name.startswith("Bottle"):
|
||||
rom.write_bytes(0x1E9C0, [0xFB, 0xFF, 0x03, 0x00, 0xAB, 0x00, 0x00, 0x00])
|
||||
|
||||
# set light cones
|
||||
if world.dark_rooms[player] == 'no_dark_rooms':
|
||||
light_cone = 0x20
|
||||
@@ -1495,24 +1501,40 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
loot_source = 0x09
|
||||
if world.prizeshuffle[player] != 'none':
|
||||
loot_source |= 0x10
|
||||
if world.pottery[player] not in ['none', 'cave']:
|
||||
if world.pottery[player] != 'none':
|
||||
loot_source |= 0x02
|
||||
if world.dropshuffle[player] != 'none':
|
||||
loot_source |= 0x04
|
||||
rom.write_byte(0x1CFF10, loot_source)
|
||||
rom.write_byte(0x1CFF20, loot_source)
|
||||
|
||||
if world.loothud[player] == 'never':
|
||||
rom.write_bytes(0x1CFF10, [0x00, 0x00, 0x00, 0x00])
|
||||
rom.write_byte(0x1CFF17, 0x00)
|
||||
elif world.loothud[player] == 'presence':
|
||||
rom.write_bytes(0x1CFF10, [0x01, 0x01, 0x00, 0x00])
|
||||
rom.write_byte(0x1CFF17, 0x01)
|
||||
elif world.loothud[player] == 'value':
|
||||
rom.write_bytes(0x1CFF10, [0x03, 0x03, 0x00, 0x00])
|
||||
rom.write_byte(0x1CFF17, 0x01)
|
||||
elif world.loothud[player] == 'dungeon_value':
|
||||
rom.write_bytes(0x1CFF10, [0x01, 0x03, 0x00, 0x00])
|
||||
rom.write_byte(0x1CFF17, 0x01)
|
||||
elif world.loothud[player] == 'cave_value':
|
||||
rom.write_bytes(0x1CFF10, [0x03, 0x01, 0x00, 0x00])
|
||||
rom.write_byte(0x1CFF17, 0x01)
|
||||
|
||||
if world.showloot[player] == 'never':
|
||||
rom.write_bytes(0x1CFF08, [0x00, 0x00, 0x00, 0x00])
|
||||
rom.write_byte(0x1CFF11, 0x00)
|
||||
rom.write_byte(0x1CFF0F, 0x00)
|
||||
elif world.showloot[player] == 'presence':
|
||||
rom.write_bytes(0x1CFF08, [0x01, 0x00, 0x00, 0x00])
|
||||
rom.write_byte(0x1CFF11, 0x00)
|
||||
rom.write_byte(0x1CFF0F, 0x01)
|
||||
elif world.showloot[player] == 'compass':
|
||||
rom.write_bytes(0x1CFF08, [0x01, 0x00, 0x02, 0x00])
|
||||
rom.write_byte(0x1CFF11, 0x01)
|
||||
rom.write_bytes(0x1CFF08, [0x02, 0x00, 0x03, 0x00])
|
||||
rom.write_byte(0x1CFF0F, 0x01)
|
||||
elif world.showloot[player] == 'always':
|
||||
rom.write_bytes(0x1CFF08, [0x02, 0x00, 0x00, 0x00])
|
||||
rom.write_byte(0x1CFF11, 0x00)
|
||||
rom.write_bytes(0x1CFF08, [0x03, 0x00, 0x00, 0x00])
|
||||
rom.write_byte(0x1CFF0F, 0x01)
|
||||
|
||||
if world.showmap[player] == 'visited':
|
||||
rom.write_bytes(0x1CFF00, [0x01, 0x00, 0x00, 0x05])
|
||||
@@ -1526,7 +1548,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
rom.write_byte(loot_icons + 0x52, 0x0B) # bomb bag is major
|
||||
|
||||
triforce_piece_ids = [0x6B, 0x6C]
|
||||
if world.treasure_hunt_count[player] > 20:
|
||||
if world.treasure_hunt_count[player] > 100:
|
||||
for triforce_piece_id in triforce_piece_ids:
|
||||
rom.write_byte(loot_icons + triforce_piece_id, 0x04)
|
||||
|
||||
@@ -1764,53 +1786,40 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
rom.write_byte(0x180358, 0x01 if glitches_enabled else 0x00)
|
||||
rom.write_byte(0x18008B, 0x01 if glitches_enabled else 0x00)
|
||||
|
||||
# remove shield from uncle
|
||||
rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||
rom.write_bytes(0x6D25B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||
rom.write_bytes(0x6D283, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||
rom.write_bytes(0x6D28B, [0x00, 0x00, 0xf7, 0xff, 0x00, 0x0E])
|
||||
rom.write_bytes(0x6D2CB, [0x00, 0x00, 0xf6, 0xff, 0x02, 0x0E])
|
||||
rom.write_bytes(0x6D2FB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
|
||||
rom.write_bytes(0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
|
||||
if uncle_location.item is None or uncle_location.item.name not in ['Blue Shield', 'Red Shield', 'Mirror Shield', 'Progressive Shield', 'Sword and Shield']:
|
||||
# remove shield from uncle
|
||||
rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||
rom.write_bytes(0x6D25B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||
rom.write_bytes(0x6D283, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||
rom.write_bytes(0x6D28B, [0x00, 0x00, 0xf7, 0xff, 0x00, 0x0E])
|
||||
rom.write_bytes(0x6D2CB, [0x00, 0x00, 0xf6, 0xff, 0x02, 0x0E])
|
||||
rom.write_bytes(0x6D2FB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
|
||||
rom.write_bytes(0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
|
||||
|
||||
write_int16(rom, 0x180183, 0) # Escape fill rupee bow
|
||||
# Uncle / Zelda / Mantle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180185, [0] * 9)
|
||||
|
||||
rom.write_byte(0x18004E, 0) # Escape Fill (nothing)
|
||||
write_int16(rom, 0x180183, 300) # Escape fill rupee bow
|
||||
rom.write_bytes(0x180185, [0, 0, 0]) # Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180188, [0, 0, 0]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [0, 0, 0]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
bow_max, bomb_max, magic_max = 0, 0, 0
|
||||
bow_small, bomb_small, magic_small = 10, 3, 0x20
|
||||
if world.mode[player] == 'standard':
|
||||
if world.doorShuffle[player] not in ['vanilla']:
|
||||
# If door shuffle, give player small bit of all three ammos on respawn during escape
|
||||
# Uncle / Zelda / Mantle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180185, [0x20, 3, 10] * 3)
|
||||
|
||||
# Always fully fill ammo of starting weapon on respawn during escape
|
||||
if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']:
|
||||
rom.write_byte(0x18004E, 1) # Escape Fill (arrows)
|
||||
write_int16(rom, 0x180183, 300) # Escape fill rupee bow
|
||||
rom.write_bytes(0x180185, [0, 0, 70]) # Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180188, [0, 0, 70]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [0, 0, 70]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
bow_max = 70
|
||||
rom.write_byte(0x180187, 70) # Uncle respawn refill arrows
|
||||
rom.write_byte(0x18018A, 70) # Zelda respawn refill arrows
|
||||
rom.write_byte(0x18018D, 70) # Mantle respawn refill arrowss
|
||||
elif uncle_location.item is not None and uncle_location.item.name in ['Bomb Upgrade (+10)' if world.bombbag[player] else 'Bombs (10)']:
|
||||
rom.write_byte(0x18004E, 2) # Escape Fill (bombs)
|
||||
rom.write_bytes(0x180185, [0, 50, 0]) # Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180188, [0, 50, 0]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [0, 50, 0]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
bomb_max = 50
|
||||
rom.write_byte(0x180186, 50) # Uncle respawn refill bombs
|
||||
rom.write_byte(0x180189, 50) # Zelda respawn refill bombs
|
||||
rom.write_byte(0x18018C, 50) # Mantle respawn refill bombs
|
||||
elif uncle_location.item is not None and uncle_location.item.name in ['Cane of Somaria', 'Cane of Byrna', 'Fire Rod']:
|
||||
rom.write_byte(0x18004E, 4) # Escape Fill (magic)
|
||||
rom.write_bytes(0x180185, [0x80, 0, 0]) # Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180188, [0x80, 0, 0]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [0x80, 0, 0]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
magic_max = 0x80
|
||||
if world.doorShuffle[player] not in ['vanilla', 'basic']:
|
||||
# Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180185, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)])
|
||||
# Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180188, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)])
|
||||
# Mantle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)])
|
||||
elif world.doorShuffle[player] == 'basic': # just in case a bomb is needed to get to a chest
|
||||
rom.write_bytes(0x180185, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)])
|
||||
rom.write_bytes(0x180188, [magic_small, max(bomb_small, bomb_max), bow_small]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [magic_small, max(bomb_small, bomb_max), bow_small]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
rom.write_byte(0x180185, 0x80) # Uncle respawn refill magic
|
||||
rom.write_byte(0x180188, 0x80) # Zelda respawn refill magic
|
||||
rom.write_byte(0x18018B, 0x80) # Mantle respawn refill magic
|
||||
|
||||
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved
|
||||
rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00)
|
||||
@@ -1930,7 +1939,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
|
||||
write_enemizer_tweaks(rom, world, player)
|
||||
randomize_damage_table(rom, world, player)
|
||||
write_strings(rom, world, player, team)
|
||||
write_strings(rom, world, player, team, is_mystery)
|
||||
|
||||
# write initial sram
|
||||
rom.write_initial_sram()
|
||||
@@ -2073,6 +2082,49 @@ def hud_format_text(text):
|
||||
output += b'\x7f\x00'
|
||||
return output[:32]
|
||||
|
||||
def read_bytes(f, count):
|
||||
values = f.read(count)
|
||||
if len(values) < count:
|
||||
raise EOFError
|
||||
return values
|
||||
|
||||
def apply_rom_patches(rom, patches):
|
||||
for patch in patches:
|
||||
if not os.path.exists(f"patches/{patch}.ips"):
|
||||
logging.getLogger('').warning("Patch %s not found -- skipping", patch)
|
||||
continue
|
||||
|
||||
with open(f"patches/{patch}.ips", "rb") as f:
|
||||
byte_changes = []
|
||||
try:
|
||||
header = read_bytes(f, 5)
|
||||
if header != b"PATCH":
|
||||
logging.getLogger('').warning("Patch %s invalid -- skipping", patch)
|
||||
continue
|
||||
|
||||
while True:
|
||||
address = read_bytes(f, 3)
|
||||
if address == b"EOF":
|
||||
break
|
||||
|
||||
address = int.from_bytes(address, byteorder="big")
|
||||
length = int.from_bytes(read_bytes(f, 2), byteorder="big")
|
||||
|
||||
if length > 0:
|
||||
byte_changes.append((address, list(f.read(length))))
|
||||
else:
|
||||
length = int.from_bytes(read_bytes(f, 2), byteorder="big")
|
||||
value = read_bytes(f, 1)
|
||||
byte_changes.append((address, length * list(value)))
|
||||
|
||||
for address, values in byte_changes:
|
||||
rom.write_bytes(address, values)
|
||||
|
||||
logging.getLogger("").info("Patch %s applied successfully", patch)
|
||||
|
||||
except EOFError:
|
||||
logging.getLogger('').warning("Patch %s invalid -- skipping", patch)
|
||||
continue
|
||||
|
||||
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, triforce_gfx,
|
||||
ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx,
|
||||
@@ -2389,7 +2441,7 @@ def write_string_to_rom(rom, target, string):
|
||||
rom.write_bytes(address, MultiByteTextMapper.convert(string, maxbytes))
|
||||
|
||||
|
||||
def write_strings(rom, world, player, team):
|
||||
def write_strings(rom, world, player, team, is_mystery=False):
|
||||
tt = TextTable()
|
||||
tt.removeUnwantedText()
|
||||
if world.shuffle[player] != 'vanilla':
|
||||
@@ -2440,6 +2492,15 @@ def write_strings(rom, world, player, team):
|
||||
|
||||
# For hints, first we write hints about entrances, some from the inconvenient list others from all reasonable entrances.
|
||||
if world.hints[player]:
|
||||
zoraitem = world.get_location('King Zora', player).item.hint_text
|
||||
if len(zoraitem) <= 15:
|
||||
tt['zora_meeting'] = f"Whaddaya want?\n ≥ {zoraitem.title()}\n Nothin'{{CHOICE}}"
|
||||
else:
|
||||
tt['zora_meeting'] = f"Do you want {zoraitem}?\n ≥ I'll pay\n No thanks{{CHOICE}}"
|
||||
|
||||
bottleitem = world.get_location('Bottle Merchant', player).item.hint_text
|
||||
tt['bottle_vendor_choice'] = f"Do you want {bottleitem}?\n ≥ I'll take it\n No thanks!\n{{CHOICE}}"
|
||||
|
||||
tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!'
|
||||
hint_locations = HintLocations.copy()
|
||||
random.shuffle(hint_locations)
|
||||
@@ -2904,7 +2965,22 @@ def write_strings(rom, world, player, team):
|
||||
sanc_text = "Dark Chapel"
|
||||
tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s " + lh_text + "\n " + sanc_text + "\n{CHOICE3}"
|
||||
tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s " + lh_text + "\n " + sanc_text + "\n Mountain Cave\n{CHOICE2}"
|
||||
if world.mode[player] == 'inverted':
|
||||
|
||||
if is_mystery:
|
||||
tt['intro_main'] = CompressedTextMapper.convert(
|
||||
"{INTRO}\n Episode III"
|
||||
+ "{PAUSE3}\n A Link to the Past"
|
||||
+ "{PAUSE3}\n Mystery Randomizer"
|
||||
+ "{PAUSE3}\n\n\n"
|
||||
+ "{PAUSE3}\nAfter mostly disregarding what happened in the first two games,"
|
||||
+ "{PAUSE3}\nLink awakens to a massive mystery."
|
||||
+ "{PAUSE3}\nHe doesn't know why he's here, or what he must do."
|
||||
+ "{PAUSE3} {CHANGEPIC}\nGanon has moved around all the items in Hyrule."
|
||||
+ "{PAUSE7}\nYou will have to find all the items necessary to achieve your goal."
|
||||
+ "{PAUSE7}\nThis is your chance to be a hero."
|
||||
+ "{PAUSE3} {CHANGEPIC}\nGood luck out there, and try not to die."
|
||||
+ "{PAUSE9} {CHANGEPIC}", False)
|
||||
elif world.mode[player] == 'inverted':
|
||||
tt['intro_main'] = CompressedTextMapper.convert(
|
||||
"{INTRO}\n Episode III"
|
||||
+ "{PAUSE3}\n A Link to the Past"
|
||||
@@ -2918,6 +2994,7 @@ def write_strings(rom, world, player, team):
|
||||
+ "{PAUSE7}\nThis is your chance to be a hero."
|
||||
+ "{PAUSE3} {CHANGEPIC}\nYou must get the 7 crystals to beat Ganon."
|
||||
+ "{PAUSE9} {CHANGEPIC}", False)
|
||||
|
||||
rom.write_bytes(0xE0000, tt.getBytes())
|
||||
|
||||
credits = Credits()
|
||||
@@ -3526,7 +3603,10 @@ Prizes = ['Green Pendant',
|
||||
]
|
||||
|
||||
hash_alphabet = [
|
||||
"Bow", "Boomerang", "Hookshot", "Bomb", "Mushroom", "Powder", "Rod", "Pendant", "Bombos", "Ether", "Quake",
|
||||
"Lamp", "Hammer", "Shovel", "Ocarina", "Bug Net", "Book", "Bottle", "Potion", "Cane", "Cape", "Mirror", "Boots",
|
||||
"Gloves", "Flippers", "Pearl", "Shield", "Tunic", "Heart", "Map", "Compass", "Key"
|
||||
"Bow", "Boomerang", "Hookshot", "Bomb", "Mushroom", "Powder",
|
||||
"Ice Rod", "Green Pendant", "Bombos", "Ether", "Quake", "Lamp",
|
||||
"Hammer", "Shovel", "Ocarina", "Bug Net", "Book", "Bottle",
|
||||
"Green Potion", "Somaria", "Cape", "Mirror", "Boots", "Gloves",
|
||||
"Flippers", "Pearl", "Shield", "Green Tunic", "Heart", "Map",
|
||||
"Compass", "Key",
|
||||
]
|
||||
|
||||
5
Rules.py
5
Rules.py
@@ -505,7 +505,7 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('Skull Woods Rock (West)', player), lambda state: state.can_lift_rocks(player))
|
||||
set_rule(world.get_entrance('Skull Woods Rock (East)', player), lambda state: state.can_lift_rocks(player))
|
||||
# this more like an ohko rule - dependent on bird being present too - so enemizer could turn this off?
|
||||
set_rule(world.get_entrance('Bumper Cave Ledge Drop', player), lambda state: world.can_take_damage or state.has('Cape', player) or state.has('Cane of Byrna', player) or state.has_sword(player))
|
||||
set_rule(world.get_entrance('Bumper Cave Ledge Drop', player), lambda state: state.has('Cape', player) or state.has('Cane of Byrna', player) or state.has_sword(player))
|
||||
set_rule(world.get_entrance('Bumper Cave Rock (Outer)', player), lambda state: state.can_lift_rocks(player))
|
||||
set_rule(world.get_entrance('Bumper Cave Rock (Inner)', player), lambda state: state.can_lift_rocks(player))
|
||||
set_rule(world.get_entrance('Skull Woods Pass Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player))
|
||||
@@ -1404,8 +1404,7 @@ def ow_bunny_rules(world, player):
|
||||
add_bunny_rule(world.get_entrance('Skull Woods Forgotten Bush (East)', player), player)
|
||||
add_bunny_rule(world.get_entrance('Skull Woods Second Section Hole', player), player)
|
||||
add_bunny_rule(world.get_entrance('East Dark Death Mountain Bushes', player), player)
|
||||
if not world.can_take_damage:
|
||||
add_bunny_rule(world.get_entrance('Bumper Cave Ledge Drop', player), player)
|
||||
add_bunny_rule(world.get_entrance('Bumper Cave Ledge Drop', player), player)
|
||||
add_bunny_rule(world.get_entrance('Bumper Cave Rock (Outer)', player), player)
|
||||
add_bunny_rule(world.get_entrance('Bumper Cave Rock (Inner)', player), player)
|
||||
add_bunny_rule(world.get_entrance('Skull Woods Pass Bush Row (West)', player), player)
|
||||
|
||||
35
Text.py
35
Text.py
@@ -376,31 +376,30 @@ Sahasrahla_names = [
|
||||
|
||||
Kakariko_texts = ["{}'s homecoming"]
|
||||
Blacksmiths_texts = [
|
||||
'frogs for bread',
|
||||
'That\'s not a sword',
|
||||
'The Rupeesmiths'
|
||||
] * 1 + [
|
||||
"the dwarven breadsmiths"
|
||||
"Frogs for bread",
|
||||
"That's not a sword",
|
||||
"The rupeesmiths",
|
||||
"The dwarven breadsmiths",
|
||||
]
|
||||
DeathMountain_texts = [
|
||||
"the lost old man",
|
||||
"gary the old man",
|
||||
"Your ad here"
|
||||
"The lost old man",
|
||||
"Gary the old man",
|
||||
"Your ad here",
|
||||
]
|
||||
LostWoods_texts = [
|
||||
'thieves\' stump',
|
||||
] * 2 + [
|
||||
"the forest thief",
|
||||
"dancing pickles",
|
||||
"flying crows",
|
||||
"Thieves' stump",
|
||||
"Thieves' stump",
|
||||
"The forest thief",
|
||||
"Dancing pickles",
|
||||
"Flying crows",
|
||||
]
|
||||
WishingWell_texts = [
|
||||
"venus. queen of faeries",
|
||||
"Venus, Queen of Faeries",
|
||||
"Venus was her name",
|
||||
"I'm your Venus",
|
||||
"Yeah, baby, shes got it",
|
||||
"Yeah, baby, she's got it",
|
||||
"Venus, I'm your fire",
|
||||
"Venus, At your desire",
|
||||
"Venus, at your desire",
|
||||
"Venus Love Chain",
|
||||
"Venus Crescent Beam",
|
||||
]
|
||||
@@ -1766,7 +1765,7 @@ class TextTable(object):
|
||||
text['sign_catfish'] = CompressedTextMapper.convert("Toss rocks\nToss items\nToss cookies")
|
||||
text['sign_north_village_of_outcasts'] = CompressedTextMapper.convert("↑ Skull Woods\n\n↓ Steve's Town")
|
||||
text['sign_south_of_bumper_cave'] = CompressedTextMapper.convert("\n→ Karkats cave")
|
||||
text['sign_east_of_pyramid'] = CompressedTextMapper.convert("\n→ Dark Palace")
|
||||
text['sign_east_of_pyramid'] = CompressedTextMapper.convert("\n→ Palace of Darkness")
|
||||
text['sign_east_of_bomb_shop'] = CompressedTextMapper.convert("\n← Bomb Shoppe")
|
||||
text['sign_east_of_mire'] = CompressedTextMapper.convert("\n← Misery Mire\n no way in.\n no way out.")
|
||||
text['sign_village_of_outcasts'] = CompressedTextMapper.convert("Have a Trulie Awesome Day!")
|
||||
@@ -1870,7 +1869,7 @@ class TextTable(object):
|
||||
# 100
|
||||
text['dark_sanctuary_no'] = CompressedTextMapper.convert("Then go away!")
|
||||
text['dark_sanctuary_hint_1'] = CompressedTextMapper.convert("There is a thief in the desert, he can open creepy chests that follow you. But now that we have that out of the way, Do you like my hair? I've spent eons getting it this way.")
|
||||
text['dark_sanctuary_yes'] = CompressedTextMapper.convert("With Crystals 5&6, you can find a great fairy in the pyramid.\n\nFlomp Flomp, Whizzle Whomp")
|
||||
text['dark_sanctuary_yes'] = CompressedTextMapper.convert("With crystals 5 and 6, you can find a great fairy in the pyramid.\n\nFlomp Flomp, Whizzle Whomp")
|
||||
text['dark_sanctuary_hint_2'] = CompressedTextMapper.convert(
|
||||
"All I can say is that my life is pretty plain,\n"
|
||||
+ "I like watchin' the puddles gather rain,\n"
|
||||
|
||||
Binary file not shown.
BIN
patches/2way_mirror.ips
Normal file
BIN
patches/2way_mirror.ips
Normal file
Binary file not shown.
BIN
patches/all_starting.ips
Normal file
BIN
patches/all_starting.ips
Normal file
Binary file not shown.
BIN
patches/colordorm.ips
Normal file
BIN
patches/colordorm.ips
Normal file
Binary file not shown.
BIN
patches/no_dungeon_item_popups.ips
Normal file
BIN
patches/no_dungeon_item_popups.ips
Normal file
Binary file not shown.
BIN
patches/no_escape_assist.ips
Normal file
BIN
patches/no_escape_assist.ips
Normal file
Binary file not shown.
BIN
patches/pod_key_swap.ips
Normal file
BIN
patches/pod_key_swap.ips
Normal file
Binary file not shown.
BIN
patches/quiet_zora.ips
Normal file
BIN
patches/quiet_zora.ips
Normal file
Binary file not shown.
@@ -486,6 +486,15 @@
|
||||
"always"
|
||||
]
|
||||
},
|
||||
"loothud": {
|
||||
"choices": [
|
||||
"never",
|
||||
"presence",
|
||||
"value",
|
||||
"dungeon_value",
|
||||
"cave_value"
|
||||
]
|
||||
},
|
||||
"showmap": {
|
||||
"choices": [
|
||||
"visited",
|
||||
@@ -665,6 +674,9 @@
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"extra_keys": {
|
||||
"type": "int"
|
||||
},
|
||||
"calc_playthrough": {
|
||||
"action": "store_false",
|
||||
"type": "bool"
|
||||
@@ -743,6 +755,10 @@
|
||||
"type": "bool"
|
||||
},
|
||||
"money_balance": {},
|
||||
"patches": {
|
||||
"type": "str",
|
||||
"help": "suppress"
|
||||
},
|
||||
"settingsonload": {
|
||||
"choices": [
|
||||
"default",
|
||||
|
||||
@@ -425,6 +425,7 @@
|
||||
"AlwaysInLogic: Dark rooms are always considered to be in logic, even if the player cannot see"
|
||||
],
|
||||
"crystal_book": [ " Book can be used indoors to flip the state of colored pegs (default: %(default)s)"],
|
||||
"extra_keys": [ " Percentage of extra small keys to create for each dungeon when door shuffle is enabled (default: %(default)s)"],
|
||||
"bombbag": ["Start with 0 bomb capacity. Two capacity upgrades (+10) are added to the pool (default: %(default)s)" ],
|
||||
"any_enemy_logic": [
|
||||
"How to handle potential traversal between dungeon in Crossed door shuffle",
|
||||
|
||||
@@ -84,7 +84,10 @@ UwGeneralDeny:
|
||||
- [ 0x0039, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 1"
|
||||
- [0x0039, 5, ["RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Bumper", "AntiFairyCircle"]] #"Skull Woods - Play Pen - Hardhat Beetle"
|
||||
- [ 0x0039, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 2"
|
||||
- [0x003a, 1, ["RollerVerticalUp", "RollerVerticalDown"]]
|
||||
- [ 0x003a, 0, ["RollerVerticalUp", "RollerVerticalDown"]]
|
||||
- [ 0x003a, 1, ["RollerVerticalUp", "RollerVerticalDown"]]
|
||||
- [ 0x003a, 3, ["RollerHorizontalLeft", "RollerHorizontalRight"]]
|
||||
- [ 0x003a, 4, ["RollerHorizontalLeft", "RollerHorizontalRight"]]
|
||||
- [ 0x003b, 1, [ "Bumper", "AntiFairyCircle" ]]
|
||||
- [ 0x003b, 4, ["RollerVerticalUp", "RollerVerticalDown"]]
|
||||
- [ 0x003c, 0, ["BigSpike"]]
|
||||
@@ -306,7 +309,7 @@ UwGeneralDeny:
|
||||
- [0x00b0, 7, [ "StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay
|
||||
- [0x00b0, 8, [ "StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay
|
||||
- [ 0x00b1, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Misery Mire - Hourglass - Spike Trap 1"
|
||||
- [ 0x00b1, 3, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Misery Mire - Hourglass - Spike Trap 2"
|
||||
- [ 0x00b1, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Bumper" ] ] #"Misery Mire - Hourglass - Spike Trap 2"
|
||||
- [0x00b1, 4, ["Bumper", "BigSpike", "AntiFairyCircle", "Statue"]] # Wizzrobe near door
|
||||
- [ 0x00b2, 1, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots
|
||||
- [ 0x00b2, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots
|
||||
@@ -339,11 +342,11 @@ UwGeneralDeny:
|
||||
- [ 0x00c2, 5, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots
|
||||
- [ 0x00c5, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Catwalk - Mini Helmasaur"
|
||||
- [ 0x00c5, 7, [ "Statue" ] ] #"Turtle Rock - Catwalk - Laser Eye (Left) 4"
|
||||
- [0x00c6, 2, ["Bumper", "AntiFairyCircle"]]
|
||||
- [0x00c6, 3, ["Bumper", "AntiFairyCircle"]]
|
||||
- [0x00c6, 4, ["Bumper", "AntiFairyCircle"]]
|
||||
- [0x00c6, 5, ["Bumper", "AntiFairyCircle"]]
|
||||
- [0x00c6, 6, ["Bumper", "AntiFairyCircle"]]
|
||||
- [ 0x00c6, 2, ["Bumper", "AntiFairyCircle", "BigSpike" ]]
|
||||
- [ 0x00c6, 3, ["Bumper", "AntiFairyCircle", "BigSpike" ]]
|
||||
- [ 0x00c6, 4, ["Bumper", "AntiFairyCircle", "BigSpike" ]]
|
||||
- [ 0x00c6, 5, ["Bumper", "AntiFairyCircle", "BigSpike" ]]
|
||||
- [ 0x00c6, 6, ["Bumper", "AntiFairyCircle", "BigSpike" ]]
|
||||
- [ 0x00cb, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots
|
||||
- [ 0x00cb, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 1"
|
||||
- [ 0x00cb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 2"
|
||||
|
||||
@@ -58,17 +58,17 @@ class DataTables:
|
||||
self.bush_sprite_table = {}
|
||||
|
||||
# enemizer conditions
|
||||
self.uw_enemy_denials = {}
|
||||
self.ow_enemy_denials = {}
|
||||
self.uw_enemy_drop_denials = {}
|
||||
self.uw_enemy_denials = defaultdict(set)
|
||||
self.ow_enemy_denials = defaultdict(set)
|
||||
self.uw_enemy_drop_denials = defaultdict(set)
|
||||
self.sheet_choices = []
|
||||
denial_data = load_cached_yaml(['source', 'enemizer', 'enemy_deny.yaml'])
|
||||
for denial in denial_data['UwGeneralDeny']:
|
||||
self.uw_enemy_denials[denial[0], denial[1]] = {sprite_translation[x] for x in denial[2]}
|
||||
self.uw_enemy_denials[denial[0], denial[1]] |= {sprite_translation[x] for x in denial[2]}
|
||||
for denial in denial_data['OwGeneralDeny']:
|
||||
self.ow_enemy_denials[denial[0], denial[1]] = {sprite_translation[x] for x in denial[2]}
|
||||
self.ow_enemy_denials[denial[0], denial[1]] |= {sprite_translation[x] for x in denial[2]}
|
||||
for denial in denial_data['UwEnemyDrop']:
|
||||
self.uw_enemy_drop_denials[denial[0], denial[1]] = {sprite_translation[x] for x in denial[2]}
|
||||
self.uw_enemy_drop_denials[denial[0], denial[1]] |= {sprite_translation[x] for x in denial[2]}
|
||||
weights = load_cached_yaml(['source', 'enemizer', 'enemy_weight.yaml'])
|
||||
self.uw_weights = {sprite_translation[k]: v for k, v in weights['UW'].items()}
|
||||
self.ow_weights = {sprite_translation[k]: v for k, v in weights['OW'].items()}
|
||||
|
||||
Reference in New Issue
Block a user