From 418568df60fd22ef681565c775c0253969be49b8 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 10 Aug 2019 19:37:26 -0400 Subject: [PATCH] Add Item functionality setting and progressive bow Progressive bows still have issue for silvers hint --- BaseClasses.py | 19 ++++++++++++------- EntranceRandomizer.py | 7 +++++++ ItemList.py | 35 +++++++++++++++++++++++++++++------ Main.py | 6 +++--- README.md | 6 ++++++ Rom.py | 30 +++++++++++++++++++----------- 6 files changed, 76 insertions(+), 27 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index cdee826a..1195010c 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -8,13 +8,14 @@ from Utils import int16_as_bytes class World(object): - def __init__(self, players, shuffle, logic, mode, swords, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray, boss_shuffle, hints): + def __init__(self, players, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, place_dungeon_items, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray, boss_shuffle, hints): self.players = players self.shuffle = shuffle self.logic = logic self.mode = mode self.swords = swords self.difficulty = difficulty + self.difficulty_adjustments = difficulty_adjustments self.timer = timer self.progressive = progressive self.goal = goal @@ -166,9 +167,9 @@ class World(object): elif 'Bow' in item.name: if ret.has('Silver Arrows', item.player): pass - elif ret.has('Bow', item.player): + elif ret.has('Bow', item.player) and self.difficulty_requirements.progressive_bow_limit >= 2: ret.prog_items.add(('Silver Arrows', item.player)) - else: + elif self.difficulty_requirements.progressive_bow_limit >= 1: ret.prog_items.add(('Bow', item.player)) elif item.name.startswith('Bottle'): if ret.bottle_count(item.player) < self.difficulty_requirements.progressive_bottle_limit: @@ -403,10 +404,11 @@ class CollectionState(object): def heart_count(self, player): # Warning: This only considers items that are marked as advancement items + diff = self.world.difficulty_requirements return ( - self.item_count('Boss Heart Container', player) + min(self.item_count('Boss Heart Container', player), diff.boss_heart_container_limit) + self.item_count('Sanctuary Heart Container', player) - + self.item_count('Piece of Heart', player) // 4 + + min(self.item_count('Piece of Heart', player), diff.heart_piece_limit) // 4 + 3 # starting hearts ) @@ -420,9 +422,9 @@ class CollectionState(object): elif self.has('Half Magic', player): basemagic = 16 if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player): - if self.world.difficulty == 'hard' and not fullrefill: + if self.world.difficulty_adjustments == 'hard' and not fullrefill: basemagic = basemagic + int(basemagic * 0.5 * self.bottle_count(player)) - elif self.world.difficulty == 'expert' and not fullrefill: + elif self.world.difficulty_adjustments == 'expert' and not fullrefill: basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count(player)) else: basemagic = basemagic + basemagic * self.bottle_count(player) @@ -1019,6 +1021,7 @@ class Spoiler(object): 'shuffle': self.world.shuffle, 'algorithm': self.world.algorithm, 'difficulty': self.world.difficulty, + 'difficulty_mode': self.world.difficulty_adjustments, 'timer': self.world.timer, 'progressive': self.world.progressive, 'accessibility': self.world.accessibility, @@ -1052,6 +1055,8 @@ class Spoiler(object): outfile.write('Logic: %s\n' % self.metadata['logic']) outfile.write('Mode: %s\n' % self.metadata['mode']) outfile.write('Goal: %s\n' % self.metadata['goal']) + outfile.write('Difficulty: %s\n' % self.metadata['difficulty']) + outfile.write('Item Functionality: %s\n' % self.metadata['difficulty_mode']) outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle']) outfile.write('Filling Algorithm: %s\n' % self.metadata['algorithm']) outfile.write('Accessibility: %s\n' % self.metadata['accessibility']) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 1803dd73..71c739e7 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -74,6 +74,13 @@ def start(): Hard: A harder setting with less equipment and reduced health. Expert: A harder yet setting with minimum equipment and health. ''') + parser.add_argument('--item_functionality', default='normal', const='normal', nargs='?', choices=['normal', 'hard', 'expert'], + help='''\ + Select limits on item functionality to increase difficulty. (default: %(default)s) + Normal: Normal functionality. + Hard: Reduced functionality. + Expert: Greatly reduced functionality. + ''') parser.add_argument('--timer', default='none', const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'], help='''\ Select game timer setting. Affects available itempool. (default: %(default)s) diff --git a/ItemList.py b/ItemList.py index f72d2179..d8df5fbd 100644 --- a/ItemList.py +++ b/ItemList.py @@ -13,7 +13,7 @@ from Items import ItemFactory #This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space. #Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided. -alwaysitems = ['Bombos', 'Book of Mudora', 'Bow', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp', +alwaysitems = ['Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp', 'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Bug Catching Net', 'Cane of Byrna', 'Blue Boomerang', 'Red Boomerang'] progressivegloves = ['Progressive Glove'] * 2 basicgloves = ['Power Glove', 'Titans Mitts'] @@ -21,7 +21,7 @@ basicgloves = ['Power Glove', 'Titans Mitts'] normalbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)'] hardbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)', 'Bottle (Good Bee)'] -normalbaseitems = (['Silver Arrows', 'Magic Upgrade (1/2)', 'Single Arrow', 'Sanctuary Heart Container', 'Arrows (10)', 'Bombs (10)'] + +normalbaseitems = (['Magic Upgrade (1/2)', 'Single Arrow', 'Sanctuary Heart Container', 'Arrows (10)', 'Bombs (10)'] + ['Rupees (300)'] * 4 + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24) normalfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Arrows (10)'] * 6 + ['Bombs (3)'] * 6 normalsecond15extra = ['Bombs (3)'] * 10 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)'] @@ -32,10 +32,11 @@ normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2 Difficulty = namedtuple('Difficulty', ['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield', 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', - 'progressivesword', 'basicsword', 'timedohko', 'timedother', + 'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother', 'triforcehunt', 'triforce_pieces_required', 'retro', 'extras', 'progressive_sword_limit', 'progressive_shield_limit', - 'progressive_armor_limit', 'progressive_bottle_limit']) + 'progressive_armor_limit', 'progressive_bottle_limit', + 'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit']) total_items_to_place = 153 @@ -52,6 +53,7 @@ difficulties = { swordless = ['Rupees (20)'] * 4, progressivesword = ['Progressive Sword'] * 3, basicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword'], + basicbow = ['Bow', 'Silver Arrows'], timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, triforcehunt = ['Triforce Piece'] * 30, @@ -61,7 +63,10 @@ difficulties = { progressive_sword_limit = 4, progressive_shield_limit = 3, progressive_armor_limit = 2, + progressive_bow_limit = 2, progressive_bottle_limit = 4, + boss_heart_container_limit = 255, + heart_piece_limit = 255, ), 'hard': Difficulty( baseitems = normalbaseitems, @@ -71,10 +76,11 @@ difficulties = { progressiveshield = ['Progressive Shield'] * 3, basicshield = ['Blue Shield', 'Red Shield', 'Red Shield'], progressivearmor = ['Progressive Armor'] * 2, - basicarmor = ['Progressive Armor'] * 2, #only the first one will upgrade, making this equivalent to two blue mail + basicarmor = ['Progressive Armor'] * 2, # neither will count swordless = ['Rupees (20)'] * 4, progressivesword = ['Progressive Sword'] * 3, basicsword = ['Master Sword', 'Master Sword', 'Tempered Sword'], + basicbow = ['Bow'] * 2, timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, triforcehunt = ['Triforce Piece'] * 30, @@ -83,8 +89,11 @@ difficulties = { extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 3, progressive_shield_limit = 2, - progressive_armor_limit = 1, + progressive_armor_limit = 0, + progressive_bow_limit = 1, progressive_bottle_limit = 4, + boss_heart_container_limit = 6, + heart_piece_limit = 16, ), 'expert': Difficulty( baseitems = normalbaseitems, @@ -98,6 +107,7 @@ difficulties = { swordless = ['Rupees (20)'] * 4, progressivesword = ['Progressive Sword'] * 3, basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'], + basicbow = ['Bow'] * 2, timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, triforcehunt = ['Triforce Piece'] * 30, @@ -107,7 +117,10 @@ difficulties = { progressive_sword_limit = 2, progressive_shield_limit = 1, progressive_armor_limit = 0, + progressive_bow_limit = 1, progressive_bottle_limit = 4, + boss_heart_container_limit = 2, + heart_piece_limit = 8, ), } @@ -366,8 +379,18 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r else: pool.extend(diff.basicarmor) + if swords != 'swordless': + if want_progressives(): + pool.extend(['Progressive Bow'] * 2) + else: + pool.extend(diff.basicbow) + if swords == 'swordless': pool.extend(diff.swordless) + if want_progressives(): + pool.extend(['Progressive Bow'] * 2) + else: + pool.extend(['Bow', 'Silver Arrows']) elif swords == 'assured': precollected_items.append('Fighter Sword') if want_progressives(): diff --git a/Main.py b/Main.py index 81096152..69cb831c 100644 --- a/Main.py +++ b/Main.py @@ -24,7 +24,7 @@ def main(args, seed=None): start = time.clock() # initialize the world - world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints) + world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints) logger = logging.getLogger('') if seed is None: random.seed(None) @@ -117,7 +117,7 @@ def main(args, seed=None): else: sprite = None - outfilebase = 'ER_%s_%s-%s-%s%s_%s-%s%s%s%s%s_%s' % (world.logic, world.difficulty, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-nohints" if not world.hints else "", world.seed) + outfilebase = 'ER_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s_%s' % (world.logic, world.difficulty, world.difficulty_adjustments, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-nohints" if not world.hints else "", world.seed) use_enemizer = args.enemizercli and (args.shufflebosses != 'none' or args.shuffleenemies or args.enemy_health != 'default' or args.enemy_health != 'default' or args.enemy_damage or args.shufflepalette or args.shufflepots) @@ -180,7 +180,7 @@ def gt_filler(world): def copy_world(world): # ToDo: Not good yet - ret = World(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints) + ret = World(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints) ret.required_medallions = world.required_medallions.copy() ret.swamp_patch_required = world.swamp_patch_required.copy() ret.ganon_at_pyramid = world.ganon_at_pyramid.copy() diff --git a/README.md b/README.md index d1be5fc0..b0628d14 100644 --- a/README.md +++ b/README.md @@ -348,6 +348,12 @@ Select the game completion goal. (default: ganon) Select the game difficulty. Affects available itempool. (default: normal) +``` +--item_functionality [{normal,hard,expert}] +``` + +Select limits on item functionality to increase difficulty. (default: normal) + ``` --timer [{none,display,timed,timed-ohko,ohko,timed-countdown}] ``` diff --git a/Rom.py b/Rom.py index 95f22fb1..f5bca52c 100644 --- a/Rom.py +++ b/Rom.py @@ -580,8 +580,9 @@ def patch_rom(world, player, rom): GREEN_CLOCK = ItemFactory('Green Clock', player).code rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on - # handle difficulty - if world.difficulty == 'hard': + + # handle difficulty_adjustments + if world.difficulty_adjustments == 'hard': # Powdered Fairies Prize rom.write_byte(0x36DD0, 0xD8) # One Heart # potion heal amount @@ -599,9 +600,7 @@ def patch_rom(world, player, rom): rom.write_int16(0x180036, world.rupoor_cost) # Set stun items rom.write_byte(0x180180, 0x02) # Hookshot only - # Make silver arrows only usable against Ganon - rom.write_byte(0x180181, 0x01) - elif world.difficulty == 'expert': + elif world.difficulty_adjustments == 'expert': # Powdered Fairies Prize rom.write_byte(0x36DD0, 0xD8) # One Heart # potion heal amount @@ -619,8 +618,6 @@ def patch_rom(world, player, rom): rom.write_int16(0x180036, world.rupoor_cost) # Set stun items rom.write_byte(0x180180, 0x00) # Nothing - # Make silver arrows only usable against Ganon - rom.write_byte(0x180181, 0x01) else: # Powdered Fairies Prize rom.write_byte(0x36DD0, 0xE3) # fairy @@ -638,20 +635,28 @@ def patch_rom(world, player, rom): rom.write_int16(0x180036, world.rupoor_cost) # Set stun items rom.write_byte(0x180180, 0x03) # All standard items - # Make silver arrows freely usable - rom.write_byte(0x180181, 0x00) #Set overflow items for progressive equipment if world.timer in ['timed', 'timed-countdown', 'timed-ohko']: overflow_replacement = GREEN_CLOCK else: overflow_replacement = GREEN_TWENTY_RUPEES + rom.write_byte(0x180181, 0x00) # Make silver arrows freely usable rom.write_byte(0x180182, 0x01) # auto equip silvers on pickup #Byrna residual magic cost rom.write_bytes(0x45C42, [0x04, 0x02, 0x01]) difficulty = world.difficulty_requirements + + if difficulty.progressive_bow_limit < 2 and world.swords == 'swordless': + # TODO: write 2 to progressive bow limit byte + rom.write_byte(0x180181, 0x01) # Make silver arrows work on on ganon + else: + # TODO: write difficulty.progressive_bow_limit to progressive bow limit byte + pass + + #Set overflow items for progressive equipment rom.write_bytes(0x180090, [difficulty.progressive_sword_limit, overflow_replacement, @@ -686,7 +691,7 @@ def patch_rom(world, player, rom): random.shuffle(packs) prizes[:56] = [drop for pack in packs for drop in pack] - if world.difficulty in ['hard', 'expert']: + if world.difficulty_adjustments in ['hard', 'expert']: prize_replacements = {0xE0: 0xDF, # Fairy -> heart 0xE3: 0xD8} # Big magic -> small magic prizes = [prize_replacements.get(prize, prize) for prize in prizes] @@ -735,6 +740,9 @@ def patch_rom(world, player, rom): 0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees 0x51, 0x06, 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade 0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade + 0x58, 0x01, 0x36 if world.retro else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) + 0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20 + 0x17, difficulty.heart_piece_limit, 0x47, 0xff, # piece of heart -> green 20 0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel ]) @@ -804,7 +812,7 @@ def patch_rom(world, player, rom): rom.write_int32(0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32) rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32) rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32) - if world.difficulty == 'normal': + if world.difficulty_adjustments == 'normal': rom.write_int32(0x18020C, (10 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32) else: rom.write_int32(0x18020C, int((5 + ERtimeincrease / 2) * 60 * 60)) # starting time (in frames, sint32)