diff --git a/BaseClasses.py b/BaseClasses.py index 26d87250..f0b4e184 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -54,6 +54,7 @@ class World(object): self.disable_music = disable_music self.keysanity = keysanity self.can_take_damage = True + self.difficulty_requirements = None self.spoiler = Spoiler(self) def intialize_regions(self): @@ -106,13 +107,13 @@ class World(object): if 'Sword' in item.name: if ret.has('Golden Sword'): pass - elif ret.has('Tempered Sword'): + elif ret.has('Tempered Sword') and self.difficulty_requirements.progressive_sword_limit >= 4: ret.prog_items.append('Golden Sword') - elif ret.has('Master Sword'): + elif ret.has('Master Sword') and self.difficulty_requirements.progressive_sword_limit >= 3: ret.prog_items.append('Tempered Sword') - elif ret.has('Fighter Sword'): + elif ret.has('Fighter Sword') and self.difficulty_requirements.progressive_sword_limit >= 2: ret.prog_items.append('Master Sword') - else: + elif self.difficulty_requirements.progressive_sword_limit >= 1: ret.prog_items.append('Fighter Sword') elif 'Glove' in item.name: if ret.has('Titans Mitts'): @@ -124,13 +125,15 @@ class World(object): elif 'Shield' in item.name: if ret.has('Mirror Shield'): pass - elif ret.has('Red Shield'): + elif ret.has('Red Shield') and self.difficulty_requirements.progressive_shield_limit >= 3: ret.prog_items.append('Mirror Shield') - elif ret.has('Blue Shield'): + elif ret.has('Blue Shield') and self.difficulty_requirements.progressive_shield_limit >= 2: ret.prog_items.append('Red Shield') - else: + elif self.difficulty_requirements.progressive_shield_limit >= 1: ret.prog_items.append('Blue Shield') - + elif item.name.startswith('Bottle'): + if ret.bottle_count() < self.difficulty_requirements.progressive_bottle_limit: + ret.prog_items.append(item.name) elif item.advancement or item.key: ret.prog_items.append(item.name) @@ -360,7 +363,10 @@ class CollectionState(object): return self.has('Power Glove') or self.has('Titans Mitts') def has_bottle(self): - return self.has('Bottle') or self.has('Bottle (Red Potion)') or self.has('Bottle (Green Potion)') or self.has('Bottle (Blue Potion)') or self.has('Bottle (Fairy)') or self.has('Bottle (Bee)') or self.has('Bottle (Good Bee)') + return self.bottle_count() > 0 + + def bottle_count(self): + return len([pritem for pritem in self.prog_items if pritem.startswith('Bottle')]) def can_lift_heavy_rocks(self): return self.has('Titans Mitts') @@ -411,16 +417,16 @@ class CollectionState(object): if 'Sword' in item.name: if self.has('Golden Sword'): pass - elif self.has('Tempered Sword'): + elif self.has('Tempered Sword') and self.world.difficulty_requirements.progressive_sword_limit >= 4: self.prog_items.append('Golden Sword') changed = True - elif self.has('Master Sword'): + elif self.has('Master Sword') and self.world.difficulty_requirements.progressive_sword_limit >= 3: self.prog_items.append('Tempered Sword') changed = True - elif self.has('Fighter Sword'): + elif self.has('Fighter Sword') and self.world.difficulty_requirements.progressive_sword_limit >= 2: self.prog_items.append('Master Sword') changed = True - else: + elif self.world.difficulty_requirements.progressive_sword_limit >= 1: self.prog_items.append('Fighter Sword') changed = True elif 'Glove' in item.name: @@ -435,16 +441,19 @@ class CollectionState(object): elif 'Shield' in item.name: if self.has('Mirror Shield'): pass - elif self.has('Red Shield'): + elif self.has('Red Shield') and self.world.difficulty_requirements.progressive_shield_limit >= 3: self.prog_items.append('Mirror Shield') changed = True - elif self.has('Blue Shield'): + elif self.has('Blue Shield') and self.world.difficulty_requirements.progressive_shield_limit >= 2: self.prog_items.append('Red Shield') changed = True - else: + elif self.world.difficulty_requirements.progressive_shield_limit >= 1: self.prog_items.append('Blue Shield') changed = True - + elif item.name.startswith('Bottle'): + if self.bottle_count() < self.world.difficulty_requirements.progressive_bottle_limit: + self.prog_items.append(item.name) + changed = True elif event or item.advancement: self.prog_items.append(item.name) changed = True @@ -758,5 +767,4 @@ class Spoiler(object): path_lines.append(region) path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines))) - #["%s: \n %s" % (location, "\n => ".join(zip_longest(*[iter(path)]*2))) for location, path in self.paths.items()] outfile.write('\n'.join(path_listings)) diff --git a/ItemList.py b/ItemList.py index 10880d7a..877d332e 100644 --- a/ItemList.py +++ b/ItemList.py @@ -62,7 +62,8 @@ Difficulty = namedtuple('Difficulty', 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'progressivesword', 'basicsword', 'timedohko', 'timedother', 'triforcehunt', 'triforce_pieces_required', 'conditional_extras', - 'extras']) + 'extras', 'progressive_sword_limit', 'progressive_shield_limit', + 'progressive_armor_limit', 'progressive_bottle_limit']) total_items_to_place = 153 @@ -77,7 +78,7 @@ def easy_conditional_extras(timer, _goal, _mode, pool, placed_items): def no_conditonal_extras(*_args): return [] -# pylint: disable= + difficulties = { 'normal': Difficulty( baseitems = normalbaseitems, @@ -96,7 +97,11 @@ difficulties = { triforcehunt = ['Triforce Piece'] * 30, triforce_pieces_required = 20, conditional_extras = no_conditonal_extras, - extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra] + extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], + progressive_sword_limit = 4, + progressive_shield_limit = 3, + progressive_armor_limit = 2, + progressive_bottle_limit = 4, ), 'easy': Difficulty( baseitems = easybaseitems, @@ -116,6 +121,10 @@ difficulties = { triforce_pieces_required = 10, conditional_extras = easy_conditional_extras, extras = [easyextra, easyfirst15extra, easysecond10extra, easythird5extra, easyfinal25extra], + progressive_sword_limit = 4, + progressive_shield_limit = 3, + progressive_armor_limit = 2, + progressive_bottle_limit = 4, ), 'hard': Difficulty( baseitems = hardbaseitems, @@ -135,6 +144,10 @@ difficulties = { triforce_pieces_required = 30, conditional_extras = no_conditonal_extras, extras = [hardfirst20extra, hardsecond20extra, hardthird20extra, hardfinal20extra], + progressive_sword_limit = 3, + progressive_shield_limit = 2, + progressive_armor_limit = 1, + progressive_bottle_limit = 2, ), 'expert': Difficulty( baseitems = expertbaseitems, @@ -154,6 +167,10 @@ difficulties = { triforce_pieces_required = 40, conditional_extras = no_conditonal_extras, extras = [expertfirst15extra, expertsecond25extra, expertthird15extra, expertfinal25extra], + progressive_sword_limit = 2, + progressive_shield_limit = 0, + progressive_armor_limit = 0, + progressive_bottle_limit = 1, ), 'insane': Difficulty( baseitems = insanebaseitems, @@ -173,6 +190,10 @@ difficulties = { triforce_pieces_required = 50, conditional_extras = no_conditonal_extras, extras = [insanefirst15extra, insanesecond25extra, insanethird10extra, insanefourth15extra, insanefinal25extra], + progressive_sword_limit = 2, + progressive_shield_limit = 0, + progressive_armor_limit = 0, + progressive_bottle_limit = 1, ), } diff --git a/Main.py b/Main.py index 307fdadf..fbdc0e8e 100644 --- a/Main.py +++ b/Main.py @@ -11,7 +11,7 @@ from Rom import patch_rom, Sprite, LocalRom, JsonRom from Rules import set_rules from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items -from ItemList import generate_itempool +from ItemList import generate_itempool, difficulties from Utils import output_path __version__ = '0.5.1-dev' @@ -41,6 +41,8 @@ def main(args, seed=None): logger.info('ALttP Entrance Randomizer Version %s - Seed: %s\n\n', __version__, world.seed) + world.difficulty_requirements = difficulties[world.difficulty] + create_regions(world) create_dungeons(world) @@ -134,6 +136,7 @@ def copy_world(world): ret.seed = world.seed ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge ret.can_take_damage = world.can_take_damage + ret.difficulty_requirements = world.difficulty_requirements create_regions(ret) create_dungeons(ret) diff --git a/Plando.py b/Plando.py index d91ec44a..6170b2dc 100755 --- a/Plando.py +++ b/Plando.py @@ -14,6 +14,7 @@ from Rom import patch_rom, LocalRom, Sprite, write_string_to_rom from Rules import set_rules from Dungeons import create_dungeons from Items import ItemFactory +from ItemList import difficulties from Main import create_playthrough __version__ = '0.2-dev' @@ -45,6 +46,8 @@ def main(args): logger.info('ALttP Plandomizer Version %s - Seed: %s\n\n', __version__, args.plando) + world.difficulty_requirements = difficulties[world.difficulty] + create_regions(world) create_dungeons(world) diff --git a/Rom.py b/Rom.py index 03040ac2..b08d5dea 100644 --- a/Rom.py +++ b/Rom.py @@ -11,6 +11,7 @@ from Text import string_to_alttp_text, text_addresses, Credits from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names from Utils import local_path +from Items import ItemFactory JAP10HASH = '03a63945398191337e896e5771f77173' @@ -345,6 +346,10 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None): rom.write_byte(0x180039, 0x01 if world.light_world_light_cone else 0x00) rom.write_byte(0x18003A, 0x01 if world.dark_world_light_cone else 0x00) + GREEN_TWENTY_RUPEES = 0x47 + TRIFORCE_PIECE = ItemFactory('Triforce Piece').code + GREEN_CLOCK = ItemFactory('Green Clock').code + # handle difficulty if world.difficulty == 'hard': # Powdered Fairies Prize @@ -359,8 +364,7 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None): rom.write_bytes(0x45C42, [0x08, 0x08, 0x08]) #Disable catching fairies rom.write_byte(0x34FD6, 0x80) - #Set overflow items for progressive equipment - rom.write_bytes(0x180090, [0x03, 0x47, 0x02, 0x47, 0x01, 0x47, 0x02, 0x47]) + overflow_replacement = GREEN_TWENTY_RUPEES # Rupoor negative value rom.write_int16_to_rom(0x180036, 10) #Make Blue Shield more expensive @@ -394,8 +398,7 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None): rom.write_bytes(0x45C42, [0x08, 0x08, 0x08]) #Disable catching fairies rom.write_byte(0x34FD6, 0x80) - #Set overflow items for progressive equipment - rom.write_bytes(0x180090, [0x02, 0x47, 0x00, 0x47, 0x00, 0x47, 0x01, 0x47]) + overflow_replacement = GREEN_TWENTY_RUPEES # Rupoor negative value rom.write_int16_to_rom(0x180036, 20) #Make Blue Shield more expensive @@ -429,8 +432,7 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None): rom.write_bytes(0x45C42, [0x08, 0x08, 0x08]) #Disable catching fairies rom.write_byte(0x34FD6, 0x80) - #Set overflow items for progressive equipment - rom.write_bytes(0x180090, [0x02, 0x47, 0x00, 0x47, 0x00, 0x47, 0x01, 0x47]) + overflow_replacement = GREEN_TWENTY_RUPEES # Rupoor negative value rom.write_int16_to_rom(0x180036, 9999) #Make Blue Shield more expensive @@ -466,11 +468,19 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None): rom.write_byte(0x34FD6, 0xF0) #Set overflow items for progressive equipment if world.goal == 'triforcehunt': - rom.write_bytes(0x180090, [0x04, 0x6C, 0x03, 0x6C, 0x02, 0x6C, 0x04, 0x6C]) + overflow_replacement = TRIFORCE_PIECE elif world.timer in ['timed', 'timed-countdown', 'timed-ohko']: - rom.write_bytes(0x180090, [0x04, 0x5D, 0x03, 0x5D, 0x02, 0x5D, 0x04, 0x5D]) + overflow_replacement = GREEN_CLOCK else: - rom.write_bytes(0x180090, [0x04, 0x47, 0x03, 0x47, 0x02, 0x47, 0x04, 0x47]) + overflow_replacement = GREEN_TWENTY_RUPEES + + difficulty = world.difficulty_requirements + #Set overflow items for progressive equipment + rom.write_bytes(0x180090, + [difficulty.progressive_sword_limit, overflow_replacement, + difficulty.progressive_shield_limit, overflow_replacement, + difficulty.progressive_armor_limit, overflow_replacement, + difficulty.progressive_bottle_limit, overflow_replacement]) # set up game internal RNG seed for i in range(1024): diff --git a/Rules.py b/Rules.py index 4fd3e0d6..9b6d6bc5 100644 --- a/Rules.py +++ b/Rules.py @@ -187,7 +187,7 @@ def global_rules(world): set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)'), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling set_rule(world.get_location('Spike Cave'), lambda state: state.has('Hammer') and state.can_lift_rocks() and (state.has('Cane of Byrna') or state.has('Cape')) and state.can_extend_magic()) # TODO: Current-VT logic is: hammer and lift_rocks and ((cape and extend) or (byrna and (can-take-damage OR canextend))) - # Is that really good enough? Can you really get through with byrna, single magic w/o refills and only 3 hearts? (answer: probnably but seems to requires tas-like timing.) + # Is that really good enough? Can you really get through with byrna, single magic w/o refills and only 3 hearts? (answer: probably but seems to requires tas-like timing.) set_rule(world.get_location('Hookshot Cave - Top Right'), lambda state: state.has('Hookshot')) set_rule(world.get_location('Hookshot Cave - Top Left'), lambda state: state.has('Hookshot')) set_rule(world.get_location('Hookshot Cave - Bottom Right'), lambda state: state.has('Hookshot') or state.has('Pegasus Boots')) @@ -298,7 +298,6 @@ def global_rules(world): set_rule(world.get_entrance('Turtle Rock Dark Room Staircase'), lambda state: state.has('Small Key (Turtle Rock)', 3)) set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)'), lambda state: state.has('Cane of Somaria')) set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)'), lambda state: state.has('Cane of Somaria')) - # FIXME: should shield overflow count check to the progrssive logic stuff, so we don't get false mirror shields which would cause problems here set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) @@ -312,17 +311,20 @@ def global_rules(world): set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop'), lambda state: state.has('Hammer')) set_rule(world.get_entrance('Palace of Darkness Bridge Room'), lambda state: state.has('Small Key (Palace of Darkness)', 1)) # If we can reach any other small key door, we already have back door access to this area set_rule(world.get_entrance('Palace of Darkness Big Key Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) and state.has('Big Key (Palace of Darkness)') and state.has('Bow') and state.has('Hammer')) - set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)'])) set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has('Small Key (Palace of Darkness)', 4)) set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)')) if world.keysanity: set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)'])) + # TODO: add an always_allow rule for this to permit key for a key set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)'])) + # TODO: add an always_allow rule for this to permit key for a key set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6)) else: set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)'])) + # TODO: add an always_allow rule for this to permit key for a key set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)'])) + # TODO: add an always_allow rule for this to permit key for a key set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5)) for location in ['Palace of Darkness - Big Chest', 'Palace of Darkness - Helmasaur']: