From 2b6d11bef3e28173199f710a0e11b9f094fc669d Mon Sep 17 00:00:00 2001 From: AmazingAmpharos Date: Tue, 14 Nov 2017 04:25:12 -0600 Subject: [PATCH 01/19] Credits fix The "Flute Boy plays again" text is supposed to refer to the item buried by the shovel, not the item given in the Dark World. Easy fix. --- Rom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 593ee6cf..c3c3b8ac 100644 --- a/Rom.py +++ b/Rom.py @@ -620,7 +620,7 @@ def write_strings(rom, world): magicshopitem = world.get_location('Potion Shop').item magicshopitem_text = random.choice(MagicShop_texts) if magicshopitem is None or magicshopitem.magicshop_credit_text is None else magicshopitem.magicshop_credit_text - fluteboyitem = world.get_location('Stumpy').item + fluteboyitem = world.get_location('Flute Spot').item fluteboyitem_text = random.choice(FluteBoy_texts) if fluteboyitem is None or fluteboyitem.fluteboy_credit_text is None else fluteboyitem.fluteboy_credit_text credits.update_credits_line('castle', 0, random.choice(KingsReturn_texts)) From b15cd2072e3df821a93e69cfa6d8044dc9cadfd9 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 18 Nov 2017 18:21:31 -0500 Subject: [PATCH 02/19] Refactored ItemList It now has much less duplicated code, and a useful difficulty abstraction. No changes to pool results were made, and this was verified with automated testing. --- ItemList.py | 718 ++++++++++++++++++---------------------------------- 1 file changed, 250 insertions(+), 468 deletions(-) diff --git a/ItemList.py b/ItemList.py index 804ba392..e897d10b 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1,5 +1,6 @@ from Items import ItemFactory from Fill import fill_restrictive +from collections import namedtuple import random #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. @@ -20,36 +21,17 @@ normalsecond15extra = ['Bombs (3)'] * 10 + ['Rupees (50)'] * 2 + ['Arrows (10)'] normalthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Arrows (10)', 'Rupee (1)', 'Rupees (5)'] normalfourth5extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2 + ['Rupees (5)'] normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2 -normaltimedohko = ['Green Clock'] * 25 -normaltimedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10 -normaltriforcehunt = ['Triforce Piece'] * 30 -normalprogressivesword = ['Progressive Sword'] * 3 -normalbasicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword'] -normalswordless = ['Rupees (20)'] * 4 -normalprogressiveshield = ['Progressive Shield'] * 3 -normalbasicshield = ['Blue Shield', 'Red Shield', 'Mirror Shield'] -normalprogressivearmor = ['Progressive Armor'] * 2 -normalbasicarmor = ['Blue Mail', 'Red Mail'] + easybaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows'] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 + ['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12) easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)'] -easylimitedextra = ['Boss Heart Container'] * 3 +easylimitedextra = ['Boss Heart Container'] * 3 # collapsing down the 12 peices of heart easyfirst15extra = ['Rupees (100)'] + ['Rupees (50)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6 + ['Bombs (3)'] easysecond10extra = ['Bombs (3)'] * 9 + ['Rupee (1)'] easythird5extra = ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupees (5)'] easyfinal25extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 14 + ['Rupee (1)'] + ['Arrows (10)'] * 3 + ['Rupees (5)'] * 3 -easytimedohko = ['Green Clock'] * 25 -easytimedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 5 easytimedotherextra = ['Red Clock'] * 5 -easytriforcehunt = ['Triforce Piece'] * 30 -easyprogressivesword = ['Progressive Sword'] * 7 -easybasicsword = ['Fighter Sword', 'Master Sword', 'Master Sword', 'Tempered Sword', 'Tempered Sword', 'Golden Sword', 'Golden Sword'] -easyswordless = ['Rupees (20)'] * 8 -easyprogressiveshield = ['Progressive Shield'] * 6 -easybasicshield = ['Blue Shield', 'Blue Shield', 'Red Shield', 'Red Shield', 'Mirror Shield', 'Mirror Shield'] -easyprogressivearmor = ['Progressive Armor'] * 4 -easybasicarmor = ['Blue Mail', 'Blue Mail', 'Red Mail', 'Red Mail'] hardbaseitems = (['Silver Arrows', 'Single Arrow'] + ['Rupees (300)'] + ['Rupees (100)'] * 2 + ['Rupees (50)'] + ['Bombs (3)'] + ['Boss Heart Container'] * 5 + ['Piece of Heart'] * 24) @@ -57,15 +39,6 @@ hardfirst20extra = ['Bombs (3)'] * 4 + ['Single Bomb'] * 4 + ['Rupees (5)'] * 5 hardsecond20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 10 + ['Rupees (20)'] * 2 + ['Rupee (1)'] * 3 + ['Arrows (10)'] hardthird20extra = ['Arrows (10)'] * 4 + ['Rupees (20)'] * 3 + ['Rupees (5)'] * 3 + ['Single Bomb'] * 5 + ['Single Arrow'] * 5 hardfinal20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 2 + ['Single Arrow'] * 14 -hardtimedohko = ['Green Clock'] * 20 -hardtimedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10 -hardtriforcehunt = ['Triforce Piece'] * 40 -hardprogressivesword = ['Progressive Sword'] * 3 -hardbasicsword = ['Master Sword', 'Master Sword', 'Tempered Sword'] -hardswordless = ['Rupees (20)'] * 4 -hardprogressiveshield = ['Progressive Shield'] * 3 -hardbasicshield = ['Blue Shield', 'Red Shield', 'Red Shield'] -hardarmor = ['Progressive Armor', 'Progressive Armor'] expertbaseitems = (['Single Arrow', 'Rupees (300)', 'Rupees (100)', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 4 + ['Rupees (5)'] * 5 + ['Rupees (20)'] + ['Single Bomb'] * 2 + ['Piece of Heart'] * 24) @@ -73,12 +46,6 @@ expertfirst15extra = ['Single Bomb'] * 13 + ['Rupees (20)'] * 2 expertsecond25extra = ['Single Bomb'] * 8 + ['Single Arrow'] * 9 + ['Rupees (20)'] * 3 + ['Rupee (1)'] * 5 expertthird15extra = ['Rupees (5)'] * 5 + ['Single Bomb'] * 3 + ['Rupees (20)'] * 2 + ['Single Arrow'] * 5 expertfinal25extra = ['Single Bomb'] * 4 + ['Rupees (20)'] * 3 + ['Single Arrow'] * 18 -experttimedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5 -experttimedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10 -experttriforcehunt = ['Triforce Piece'] * 40 -expertprogressivesword = ['Progressive Sword'] * 3 -expertbasicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'] -expertswordless = ['Rupees (20)'] * 3 + ['Silver Arrows'] insanebaseitems = (['Single Arrow', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 3 + ['Rupees (5)'] * 10 + ['Rupees (300)'] * 4 + ['Rupees (100)'] * 3 + ['Rupee (1)'] * 4 + ['Single Bomb'] * 4) @@ -87,12 +54,127 @@ insanesecond25extra = ['Single Bomb'] * 7 + ['Single Arrow'] * 7 + ['Rupee (1)'] insanethird10extra = ['Single Bomb'] * 3 + ['Single Arrow'] * 3 + ['Rupee (1)'] * 3 + ['Rupees (20)'] insanefourth15extra = ['Single Bomb'] * 5 + ['Single Arrow'] * 5 + ['Rupee (1)'] * 5 insanefinal25extra = ['Single Bomb'] * 2 + ['Single Arrow'] * 10 + ['Rupee (1)'] * 7 + ['Rupees (20)'] * 6 -insanetimedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5 -insanetimedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10 -insanetriforcehunt = ['Triforce Piece'] * 50 -insaneprogressivesword = ['Progressive Sword'] * 3 -insanebasicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'] -insaneswordless = ['Rupees (20)'] * 3 + ['Silver Arrows'] + +Difficulty = namedtuple('Difficulty', + ['baseitems', 'bottles', 'bottle_count','same_bottle', 'progressiveshield', + 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', + 'progressivesword', 'basicsword', 'timedohko', 'timedother', + 'triforcehunt', 'triforce_pieces_required', 'conditional_extras', + 'extras']) + +TotalItemsToPlace = 153 + +def easy_conditional_extras(timer, goal, mode, pool, placed_items): + extraitems = TotalItemsToPlace - len(pool) - len(placed_items) + if extraitems < len(easyextra): + return easylimitedextra + if timer in ['timed', 'timed-countdown']: + return easytimedotherextra + return [] + +def no_conditonal_extras(*args): + return [] + +#def Difficulty(**kwargs): +# protodifficulty._replace(**kwargs) + +difficulties= { + 'normal': Difficulty( + baseitems = normalbaseitems, + bottles = normalbottles, + bottle_count = 4, + same_bottle = False, + progressiveshield = ['Progressive Shield'] * 3, + basicshield = ['Blue Shield', 'Red Shield', 'Mirror Shield'], + progressivearmor = ['Progressive Armor'] * 2, + basicarmor = ['Blue Mail', 'Red Mail'], + swordless = ['Rupees (20)'] * 4, + progressivesword = ['Progressive Sword'] * 3, + basicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword'], + timedohko = ['Green Clock'] * 25, + timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, + triforcehunt = ['Triforce Piece'] * 30, + triforce_pieces_required = 20, + conditional_extras = no_conditonal_extras, + extras = [normalfirst15extra,normalsecond15extra,normalthird10extra,normalfourth5extra,normalfinal25extra] + ), + 'easy': Difficulty( + baseitems = easybaseitems, + bottles = normalbottles, + bottle_count = 8, + same_bottle = False, + progressiveshield = ['Progressive Shield'] * 6, + basicshield = ['Blue Shield', 'Red Shield', 'Mirror Shield'] * 2, + progressivearmor = ['Progressive Armor'] * 4, + basicarmor = ['Blue Mail', 'Red Mail'] * 2, + swordless = ['Rupees (20)'] * 8, + progressivesword = ['Progressive Sword'] * 7, + basicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword'] *2 + ['Fighter Sword'], + timedohko = ['Green Clock'] * 25, + timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 5, # +5 more Red Clocks if there is room + triforcehunt = ['Triforce Piece'] * 30, + triforce_pieces_required = 10, + conditional_extras = easy_conditional_extras, + extras = [easyextra, easyfirst15extra, easysecond10extra, easythird5extra, easyfinal25extra], + ), + 'hard': Difficulty( + baseitems = hardbaseitems, + bottles = hardbottles, + bottle_count = 4, + same_bottle = False, + 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 equivlent to two blue mail + swordless = ['Rupees (20)'] * 4, + progressivesword = ['Progressive Sword'] * 3, + basicsword = ['Master Sword', 'Master Sword', 'Tempered Sword'], + timedohko = ['Green Clock'] * 20, + timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, + triforcehunt = ['Triforce Piece'] * 40, + triforce_pieces_required = 30, + conditional_extras = no_conditonal_extras, + extras = [hardfirst20extra, hardsecond20extra, hardthird20extra, hardfinal20extra], + ), + 'expert': Difficulty( + baseitems = expertbaseitems, + bottles = hardbottles, + bottle_count = 4, + same_bottle = True, + progressiveshield = [], + basicshield = [], + progressivearmor = [], + basicarmor = [], + swordless = ['Rupees (20)'] * 3 + ['Silver Arrows'], + progressivesword = ['Progressive Sword'] * 3, + basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'], + timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5, + timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, + triforcehunt = ['Triforce Piece'] * 40, + triforce_pieces_required = 40, + conditional_extras = no_conditonal_extras, + extras = [expertfirst15extra, expertsecond25extra, expertthird15extra, expertfinal25extra], + ), + 'insane': Difficulty( + baseitems = insanebaseitems, + bottles = hardbottles, + bottle_count = 4, + same_bottle = True, + progressiveshield = [], + basicshield = [], + progressivearmor = [], + basicarmor = [], + swordless = ['Rupees (20)'] * 3 + ['Silver Arrows'], + progressivesword = ['Progressive Sword'] * 3, + basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'], + timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5, + timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, + triforcehunt = ['Triforce Piece'] * 50, + triforce_pieces_required = 50, + conditional_extras = no_conditonal_extras, + extras = [insanefirst15extra, insanesecond25extra, insanethird10extra, insanefourth15extra, insanefinal25extra], + ), +} def generate_itempool(world): if (world.difficulty not in ['easy', 'normal', 'hard', 'expert', 'insane'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'] @@ -107,431 +189,17 @@ def generate_itempool(world): world.get_location('Agahnim 2').event = True # set up item pool - world.itempool = ItemFactory(alwaysitems) - if world.progressive == 'on': - world.itempool.extend(ItemFactory(progressivegloves)) - elif world.progressive == 'off': - world.itempool.extend(ItemFactory(basicgloves)) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.itempool.extend(ItemFactory(progressivegloves)) - else: - world.itempool.extend(ItemFactory(basicgloves)) - - # insanity shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start - if world.shuffle == 'insanity': - world.push_item('Link\'s House', ItemFactory('Magic Mirror'), False) - world.get_location('Link\'s House').event = True - world.push_item('Sanctuary', ItemFactory('Moon Pearl'), False) - world.get_location('Sanctuary').event = True - else: - world.itempool.extend(ItemFactory(['Magic Mirror', 'Moon Pearl'])) - - if world.timer == 'display': - world.clock_mode = 'stopwatch' - - if world.difficulty == 'normal': - world.itempool.extend(ItemFactory(normalbaseitems)) - for i in range (0, 4): - thisbottle = normalbottles[random.randint(0, 6)] - world.itempool.append(ItemFactory(thisbottle)) - extraitems = 70 - if world.timer in ['timed', 'timed-countdown']: - world.itempool.extend(ItemFactory(normaltimedother)) - extraitems = extraitems - 40 - world.clock_mode = 'stopwatch' if world.timer == 'timed' else 'countdown' - elif world.timer == 'timed-ohko': - world.itempool.extend(ItemFactory(normaltimedohko)) - extraitems = extraitems - 25 - world.clock_mode = 'ohko' - if world.goal == 'triforcehunt': - world.itempool.extend(ItemFactory(normaltriforcehunt)) - extraitems = extraitems - 30 - world.treasure_hunt_count = 20 - world.treasure_hunt_icon = 'Triforce Piece' - if extraitems > 0: - world.itempool.extend(ItemFactory(normalfirst15extra)) - extraitems = extraitems - 15 - if extraitems > 0: - world.itempool.extend(ItemFactory(normalsecond15extra)) - extraitems = extraitems - 15 - if extraitems > 0: - world.itempool.extend(ItemFactory(normalthird10extra)) - extraitems = extraitems - 10 - if extraitems > 0: - world.itempool.extend(ItemFactory(normalfourth5extra)) - extraitems = extraitems - 5 - if extraitems > 0: - world.itempool.extend(ItemFactory(normalfinal25extra)) - extraitems = extraitems - 25 - if world.progressive == 'on': - world.itempool.extend(ItemFactory(normalprogressiveshield)) - world.itempool.extend(ItemFactory(normalprogressivearmor)) - elif world.progressive == 'off': - world.itempool.extend(ItemFactory(normalbasicshield)) - world.itempool.extend(ItemFactory(normalbasicarmor)) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.itempool.extend(ItemFactory(normalprogressiveshield)) - else: - world.itempool.extend(ItemFactory(normalbasicshield)) - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.itempool.extend(ItemFactory(normalprogressivearmor)) - else: - world.itempool.extend(ItemFactory(normalbasicarmor)) - if world.mode == 'swordless': - world.itempool.extend(ItemFactory(normalswordless)) - elif world.mode == 'standard': - if world.progressive == 'on': - world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(normalprogressivesword)) - elif world.progressive == 'off': - world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(normalbasicsword)) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(normalprogressivesword)) - else: - world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(normalbasicsword)) - else: - if world.progressive == 'on': - world.itempool.extend(ItemFactory(normalprogressivesword)) - world.itempool.extend(ItemFactory(['Progressive Sword'])) - elif world.progressive == 'off': - world.itempool.extend(ItemFactory(normalbasicsword)) - world.itempool.extend(ItemFactory(['Fighter Sword'])) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.itempool.extend(ItemFactory(normalprogressivesword)) - world.itempool.extend(ItemFactory(['Progressive Sword'])) - else: - world.itempool.extend(ItemFactory(normalbasicsword)) - world.itempool.extend(ItemFactory(['Fighter Sword'])) - - elif world.difficulty == 'easy': - world.itempool.extend(ItemFactory(easybaseitems)) - for i in range (0, 8): - thisbottle = normalbottles[random.randint(0, 6)] - world.itempool.append(ItemFactory(thisbottle)) - extraitems = 70 - if world.timer in ['timed', 'timed-countdown']: - world.itempool.extend(ItemFactory(easytimedother)) - extraitems = extraitems - 40 - world.clock_mode = 'stopwatch' if world.timer == 'timed' else 'countdown' - elif world.timer == 'timed-ohko': - world.itempool.extend(ItemFactory(easytimedohko)) - extraitems = extraitems - 25 - world.clock_mode = 'ohko' - if world.goal == 'triforcehunt': - world.itempool.extend(ItemFactory(easytriforcehunt)) - extraitems = extraitems - 30 - world.treasure_hunt_count = 10 - world.treasure_hunt_icon = 'Triforce Piece' - if extraitems == 0: - world.itempool.extend(ItemFactory(easylimitedextra)) - else: - world.itempool.extend(ItemFactory(easyextra)) - if world.timer in ['timed', 'timed-countdown']: - world.itempool.extend(ItemFactory(easytimedotherextra)) - extraitems = extraitems - 15 - if extraitems > 0: - world.itempool.extend(ItemFactory(easyfirst15extra)) - extraitems = extraitems - 15 - if extraitems > 0: - world.itempool.extend(ItemFactory(easysecond10extra)) - extraitems = extraitems - 10 - if extraitems > 0: - world.itempool.extend(ItemFactory(easythird5extra)) - extraitems = extraitems - 5 - if extraitems > 0: - world.itempool.extend(ItemFactory(easyfinal25extra)) - extraitems = extraitems - 25 - if world.progressive == 'on': - world.itempool.extend(ItemFactory(easyprogressiveshield)) - world.itempool.extend(ItemFactory(easyprogressivearmor)) - elif world.progressive == 'off': - world.itempool.extend(ItemFactory(easybasicshield)) - world.itempool.extend(ItemFactory(easybasicarmor)) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.itempool.extend(ItemFactory(easyprogressiveshield)) - else: - world.itempool.extend(ItemFactory(easybasicshield)) - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.itempool.extend(ItemFactory(easyprogressivearmor)) - else: - world.itempool.extend(ItemFactory(easybasicarmor)) - if world.mode == 'swordless': - world.itempool.extend(ItemFactory(easyswordless)) - elif world.mode == 'standard': - if world.progressive == 'on': - world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(easyprogressivesword)) - elif world.progressive == 'off': - world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(easybasicsword)) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(easyprogressivesword)) - else: - world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(easybasicsword)) - else: - if world.progressive == 'on': - world.itempool.extend(ItemFactory(easyprogressivesword)) - world.itempool.extend(ItemFactory(['Progressive Sword'])) - elif world.progressive == 'off': - world.itempool.extend(ItemFactory(easybasicsword)) - world.itempool.extend(ItemFactory(['Fighter Sword'])) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.itempool.extend(ItemFactory(easyprogressivesword)) - world.itempool.extend(ItemFactory(['Progressive Sword'])) - else: - world.itempool.extend(ItemFactory(easybasicsword)) - world.itempool.extend(ItemFactory(['Fighter Sword'])) - - elif world.difficulty == 'hard': - world.itempool.extend(ItemFactory(hardbaseitems)) - for i in range (0, 4): - thisbottle = hardbottles[random.randint(0, 5)] - world.itempool.append(ItemFactory(thisbottle)) - extraitems = 80 - if world.timer in ['timed', 'timed-countdown']: - world.itempool.extend(ItemFactory(hardtimedother)) - extraitems = extraitems - 40 - world.clock_mode = 'stopwatch' if world.timer == 'timed' else 'countdown' - elif world.timer == 'timed-ohko': - world.itempool.extend(ItemFactory(hardtimedohko)) - extraitems = extraitems - 25 - world.clock_mode = 'ohko' - if world.goal == 'triforcehunt': - world.itempool.extend(ItemFactory(hardtriforcehunt)) - extraitems = extraitems - 40 - world.treasure_hunt_count = 30 - world.treasure_hunt_icon = 'Triforce Piece' - if extraitems > 0: - world.itempool.extend(ItemFactory(hardfirst20extra)) - extraitems = extraitems - 20 - if extraitems > 0: - world.itempool.extend(ItemFactory(hardsecond20extra)) - extraitems = extraitems - 20 - if extraitems > 0: - world.itempool.extend(ItemFactory(hardthird20extra)) - extraitems = extraitems - 20 - if extraitems > 0: - world.itempool.extend(ItemFactory(hardfinal20extra)) - extraitems = extraitems - 20 - world.itempool.extend(ItemFactory(hardarmor)) - if world.progressive == 'on': - world.itempool.extend(ItemFactory(hardprogressiveshield)) - elif world.progressive == 'off': - world.itempool.extend(ItemFactory(hardbasicshield)) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.itempool.extend(ItemFactory(hardprogressiveshield)) - else: - world.itempool.extend(ItemFactory(hardbasicshield)) - if world.mode == 'swordless': - world.itempool.extend(ItemFactory(hardswordless)) - elif world.mode == 'standard': - if world.progressive == 'on': - world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(hardprogressivesword)) - elif world.progressive == 'off': - world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(hardbasicsword)) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(hardprogressivesword)) - else: - world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(hardbasicsword)) - else: - if world.progressive == 'on': - world.itempool.extend(ItemFactory(hardprogressivesword)) - world.itempool.extend(ItemFactory(['Progressive Sword'])) - elif world.progressive == 'off': - world.itempool.extend(ItemFactory(hardbasicsword)) - world.itempool.extend(ItemFactory(['Fighter Sword'])) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.itempool.extend(ItemFactory(hardprogressivesword)) - world.itempool.extend(ItemFactory(['Progressive Sword'])) - else: - world.itempool.extend(ItemFactory(hardbasicsword)) - world.itempool.extend(ItemFactory(['Fighter Sword'])) - - elif world.difficulty == 'expert': - world.itempool.extend(ItemFactory(expertbaseitems)) - thisbottle = hardbottles[random.randint(0, 5)] - for i in range (0, 4): - world.itempool.append(ItemFactory(thisbottle)) - extraitems = 80 - if world.timer in ['timed', 'timed-countdown']: - world.itempool.extend(ItemFactory(experttimedother)) - extraitems = extraitems - 40 - world.clock_mode = 'stopwatch' if world.timer == 'timed' else 'countdown' - elif world.timer == 'timed-ohko': - world.itempool.extend(ItemFactory(experttimedohko)) - extraitems = extraitems - 25 - world.clock_mode = 'ohko' - if world.goal == 'triforcehunt': - world.itempool.extend(ItemFactory(experttriforcehunt)) - extraitems = extraitems - 40 - world.treasure_hunt_count = 40 - world.treasure_hunt_icon = 'Triforce Piece' - if extraitems > 0: - world.itempool.extend(ItemFactory(expertfirst15extra)) - extraitems = extraitems - 15 - if extraitems > 0: - world.itempool.extend(ItemFactory(expertsecond25extra)) - extraitems = extraitems - 25 - if extraitems > 0: - world.itempool.extend(ItemFactory(expertthird15extra)) - extraitems = extraitems - 15 - if extraitems > 0: - world.itempool.extend(ItemFactory(expertfinal25extra)) - extraitems = extraitems - 25 - if world.mode == 'swordless': - world.itempool.extend(ItemFactory(expertswordless)) - elif world.mode == 'standard': - if world.progressive == 'on': - world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(expertprogressivesword)) - elif world.progressive == 'off': - world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(expertbasicsword)) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(expertprogressivesword)) - else: - world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(expertbasicsword)) - else: - if world.progressive == 'on': - world.itempool.extend(ItemFactory(expertprogressivesword)) - world.itempool.extend(ItemFactory(['Progressive Sword'])) - elif world.progressive == 'off': - world.itempool.extend(ItemFactory(expertbasicsword)) - world.itempool.extend(ItemFactory(['Fighter Sword'])) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.itempool.extend(ItemFactory(expertprogressivesword)) - world.itempool.extend(ItemFactory(['Progressive Sword'])) - else: - world.itempool.extend(ItemFactory(expertbasicsword)) - world.itempool.extend(ItemFactory(['Fighter Sword'])) - - elif world.difficulty == 'insane': - world.itempool.extend(ItemFactory(insanebaseitems)) - thisbottle = hardbottles[random.randint(0, 5)] - for i in range (0, 4): - world.itempool.append(ItemFactory(thisbottle)) - extraitems = 90 - if world.timer in ['timed', 'timed-countdown']: - world.itempool.extend(ItemFactory(insanetimedother)) - extraitems = extraitems - 40 - world.clock_mode = 'stopwatch' if world.timer == 'timed' else 'countdown' - elif world.timer == 'timed-ohko': - world.itempool.extend(ItemFactory(insanetimedohko)) - extraitems = extraitems - 25 - world.clock_mode = 'ohko' - if world.goal == 'triforcehunt': - world.itempool.extend(ItemFactory(insanetriforcehunt)) - extraitems = extraitems - 50 - world.treasure_hunt_count = 50 - world.treasure_hunt_icon = 'Triforce Piece' - if extraitems > 0: - world.itempool.extend(ItemFactory(insanefirst15extra)) - extraitems = extraitems - 15 - if extraitems > 0: - world.itempool.extend(ItemFactory(insanesecond25extra)) - extraitems = extraitems - 25 - if extraitems > 0: - world.itempool.extend(ItemFactory(insanethird10extra)) - extraitems = extraitems - 10 - if extraitems > 0: - world.itempool.extend(ItemFactory(insanefourth15extra)) - extraitems = extraitems - 15 - if extraitems > 0: - world.itempool.extend(ItemFactory(insanefinal25extra)) - extraitems = extraitems - 25 - if world.mode == 'swordless': - world.itempool.extend(ItemFactory(insaneswordless)) - elif world.mode == 'standard': - if world.progressive == 'on': - world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(insaneprogressivesword)) - elif world.progressive == 'off': - world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(insanebasicsword)) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(insaneprogressivesword)) - else: - world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False) - world.get_location('Link\'s Uncle').event = True - world.itempool.extend(ItemFactory(insanebasicsword)) - else: - if world.progressive == 'on': - world.itempool.extend(ItemFactory(insaneprogressivesword)) - world.itempool.extend(ItemFactory(['Progressive Sword'])) - elif world.progressive == 'off': - world.itempool.extend(ItemFactory(insanebasicsword)) - world.itempool.extend(ItemFactory(['Fighter Sword'])) - else: - randvalue = random.randint(0, 1) - if (randvalue == 0): - world.itempool.extend(ItemFactory(insaneprogressivesword)) - world.itempool.extend(ItemFactory(['Progressive Sword'])) - else: - world.itempool.extend(ItemFactory(insanebasicsword)) - world.itempool.extend(ItemFactory(['Fighter Sword'])) - - if world.goal == 'pedestal': - world.push_item('Master Sword Pedestal', ItemFactory('Triforce'), False) - world.get_location('Master Sword Pedestal').event = True + (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode) + world.itempool = ItemFactory(pool) + for (location, item) in placed_items: + world.push_item(location, ItemFactory(item), False) + world.get_location(location).event = True + if clock_mode is not None: + world.clock_mode = clock_mode + if treasure_hunt_count is not None: + world.treasure_hunt_count = treasure_hunt_count + if treasure_hunt_icon is not None: + wrold.treasure_hunt_icon = treasure_hunt_icon # shuffle medallions mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] @@ -546,4 +214,118 @@ def generate_itempool(world): random.shuffle(crystal_locations) - fill_restrictive(world, world.get_all_state(keys=True), crystal_locations, crystals) \ No newline at end of file + fill_restrictive(world, world.get_all_state(keys=True), crystal_locations, crystals) + + +def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode): + pool=[] + placed_items=[] + clock_mode=None + treasure_hunt_count=None + treasure_hunt_icon=None + + pool.extend(alwaysitems) + + def wantProgressives(): + return random.choice([True, False]) if progressive == 'random' else progressive=='on' + + if wantProgressives(): + pool.extend(progressivegloves) + else: + pool.extend(basicgloves) + + # insanity shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start + if shuffle == 'insanity': + placed_items.append(('Link\'s House', 'Magic Mirror')) + placed_items.append(('Sanctuary', 'Moon Pearl')) + else: + pool.extend(['Magic Mirror', 'Moon Pearl']) + + if timer == 'display': + clock_mode = 'stopwatch' + + diff = difficulties[difficulty] + pool.extend(diff.baseitems) + + # expert+ difficulties produce the same contents for + # all bottles, since only one bottle is available + if diff.same_bottle: + thisbottle = random.choice(diff.bottles) + for i in range (diff.bottle_count): + if not diff.same_bottle: + thisbottle = random.choice(diff.bottles) + pool.append(thisbottle) + + if wantProgressives(): + pool.extend(diff.progressiveshield) + else: + pool.extend(diff.basicshield) + + if wantProgressives(): + pool.extend(diff.progressivearmor) + else: + pool.extend(diff.basicarmor) + + if mode == 'swordless': + pool.extend(diff.swordless) + elif mode == 'standard': + if wantProgressives(): + placed_items.append(('Link\'s Uncle', 'Progressive Sword')) + pool.extend(diff.progressivesword) + else: + placed_items.append(('Link\'s Uncle', 'Fighter Sword')) + pool.extend(diff.basicsword) + else: + if wantProgressives(): + pool.extend(diff.progressivesword) + pool.extend(['Progressive Sword']) + else: + pool.extend(diff.basicsword) + pool.extend(['Fighter Sword']) + + extraitems = TotalItemsToPlace - len(pool) - len(placed_items) + + if timer in ['timed', 'timed-countdown']: + pool.extend(diff.timedother) + extraitems -= len(diff.timedother) + clock_mode = 'stopwatch' if timer == 'timed' else 'countdown' + elif timer == 'timed-ohko': + pool.extend(diff.timedohko) + extraitems -= len(diff.timedohko) + clock_mode = 'ohko' + if goal == 'triforcehunt': + pool.extend(diff.triforcehunt) + extraitems -= len(diff.triforcehunt) + treasure_hunt_count = diff.triforce_pieces_required + treasure_hunt_icon = 'Triforce Piece' + + cond_extras = diff.conditional_extras(timer, goal, mode, pool, placed_items) + pool.extend(cond_extras) + extraitems -= len(cond_extras) + + for extra in diff.extras: + if(extraitems > 0): + pool.extend(extra ) + extraitems -= len(extra) + + if goal == 'pedestal': + placed_items.append(('Master Sword Pedestal', 'Triforce')) + return (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon) + +# A quick test to ensure all combinations generate the correct amount of items. +if __name__ == '__main__': + for difficulty in ['easy', 'normal', 'hard', 'expert', 'insane']: + for goal in ['ganon', 'triforcehunt', 'pedestal']: + for timer in ['none', 'display', 'timed', 'timed-ohko', 'timed-countdown']: + for mode in ['open', 'standard', 'swordless']: + for progressive in ['on','off']: + for shuffle in ['full','insane']: + out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode) + count = len(out[0]) + len(out[1]) + + correct_count = TotalItemsToPlace + if goal in ['pedestal']: + # pedestal goals generate one extra item + correct_count += 1 + + assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode)) From 3e31502fc0e16971ba9b89ae6ca5b5d3dd925566 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 18 Nov 2017 20:36:42 -0500 Subject: [PATCH 03/19] Add untimed OHKO mode --- BaseClasses.py | 2 +- EntranceRandomizer.py | 4 +++- Gui.py | 2 +- ItemList.py | 8 +++++--- README.md | 20 +++++++++++++------- Rom.py | 42 ++++++++++++++++++++++++------------------ 6 files changed, 47 insertions(+), 31 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 66631f27..9a241504 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -232,7 +232,7 @@ class World(object): goal = ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'].index(self.goal) shuffle = ['vanilla', 'simple', 'restricted', 'full', 'madness', 'insanity', 'dungeonsfull', 'dungeonssimple'].index(self.shuffle) difficulty = ['easy', 'normal', 'hard', 'expert', 'insane'].index(self.difficulty) - timer = ['none', 'display', 'timed', 'timed-ohko', 'timed-countdown'].index(self.timer) + timer = ['none', 'display', 'timed', 'timed-ohko', 'timed-countdown','ohko'].index(self.timer) progressive = ['on', 'off', 'random'].index(self.progressive) algorithm = ['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'].index(self.algorithm) beatableonly = 1 if self.check_beatable_only else 0 diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 97b28df3..8ca0e098 100644 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -60,7 +60,7 @@ if __name__ == '__main__': Expert: A harder yet setting with minimum equipment and health. Insane: A setting with the absolute minimum in equipment and no extra health. ''') - parser.add_argument('--timer', default='none', const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'timed-countdown'], + 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) None: No timer. @@ -74,6 +74,8 @@ if __name__ == '__main__': Timed OHKO: Starts clock at 10 minutes. Green Clocks add 5 minutes (Total: 25). As long as clock is at 0, Link will die in one hit. + OHKO: Like Timed OHKO, but no clock items are present + and the clock is permenantly at zero. Timed Countdown: Starts with clock at 40 minutes. Same clocks as Timed mode. If time runs out, you lose (but can still keep playing). diff --git a/Gui.py b/Gui.py index 788d143c..8c86856b 100644 --- a/Gui.py +++ b/Gui.py @@ -113,7 +113,7 @@ def guiMain(args=None): timerFrame = Frame(drowDownFrame) timerVar = StringVar() timerVar.set('none') - timerOptionMenu = OptionMenu(timerFrame, timerVar, 'none', 'display', 'timed', 'timed-ohko', 'timed-countdown') + timerOptionMenu = OptionMenu(timerFrame, timerVar, 'none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown') timerOptionMenu.pack(side=RIGHT) timerLabel = Label(timerFrame, text='Timer setting') timerLabel.pack(side=LEFT) diff --git a/ItemList.py b/ItemList.py index e897d10b..693dd87b 100644 --- a/ItemList.py +++ b/ItemList.py @@ -178,7 +178,7 @@ difficulties= { def generate_itempool(world): if (world.difficulty not in ['easy', 'normal', 'hard', 'expert', 'insane'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'] - or world.mode not in ['open', 'standard', 'swordless'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']): + or world.mode not in ['open', 'standard', 'swordless'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']): raise NotImplementedError('Not supported yet') world.push_item('Ganon', ItemFactory('Triforce'), False) @@ -243,6 +243,8 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode): if timer == 'display': clock_mode = 'stopwatch' + elif timer == 'ohko': + clock_mode = 'ohko' diff = difficulties[difficulty] pool.extend(diff.baseitems) @@ -292,7 +294,7 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode): elif timer == 'timed-ohko': pool.extend(diff.timedohko) extraitems -= len(diff.timedohko) - clock_mode = 'ohko' + clock_mode = 'countdown-ohko' if goal == 'triforcehunt': pool.extend(diff.triforcehunt) extraitems -= len(diff.triforcehunt) @@ -316,7 +318,7 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode): if __name__ == '__main__': for difficulty in ['easy', 'normal', 'hard', 'expert', 'insane']: for goal in ['ganon', 'triforcehunt', 'pedestal']: - for timer in ['none', 'display', 'timed', 'timed-ohko', 'timed-countdown']: + for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']: for mode in ['open', 'standard', 'swordless']: for progressive in ['on','off']: for shuffle in ['full','insane']: diff --git a/README.md b/README.md index 9611d8c4..e29dabbf 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,13 @@ Output a Spoiler File (default: False) --logic [{noglitches,minorglitches}] ``` -Select Enforcement of Item Requirements. +Select Enforcement of Item Requirements. ### No Glitches The game can be completed without knowing how to perform glitches of any kind. -### Minor Glitches +### Minor Glitches May require Fake Flippers, Bunny Revival. (default: noglitches) @@ -54,7 +54,7 @@ Gives lightcone in Hyrule Castle Sewers even without the Lamp. ### Open -This mode starts with the option to start in your house or the sanctuary, you are free to explore. +This mode starts with the option to start in your house or the sanctuary, you are free to explore. Special notes: @@ -84,11 +84,11 @@ Select completion goal. Standard game completion requiring you to collect the 7 crystals, defeat Agahnim 2 and then beat Ganon. -### Pedestal +### Pedestal Places the Triforce at the Master Sword Pedestal. Ganon cannot be damaged. -### All Dungeons +### All Dungeons Ganon cannot be damaged until all dungeons (including Hyrule Castle Tower and Ganons Tower) are cleared. @@ -143,7 +143,7 @@ This setting is a modest step up from Expert. The main difference is that the pl additional health. ``` ---timer [{none,display,timed,timed-ohko,timed-countdown}] +--timer [{none,display,timed,timed-ohko,ohko,timed-countdown}] ``` Select the timer setting. @@ -168,10 +168,16 @@ Displays a countdown timer on screen that, when it hits zero, will put the playe knockout state until more time is added to the clock via some of the Green Clocks that will be added to the itempool. +### OHKO + +The player into a one hit state the entire game. This is the same as Timed-OHKO, +except that the clock starts at zero, and there are no Clock items, so it will +always stay at zero, resulting in a permanent one hit knockout state. + ### Timed-countdown Displays a countdown timer on screen that can be increased with Green Clocks and Blue Clocks or -increased with Red Clocks found in chests that will be added to the itempool. The goal of this mode +decreased with Red Clocks found in chests that will be added to the itempool. The goal of this mode is to finish the game without the timer reaching zero, but the game will continue uninterrupted if the player runs out of time. diff --git a/Rom.py b/Rom.py index c3c3b8ac..e1bf18ce 100644 --- a/Rom.py +++ b/Rom.py @@ -391,33 +391,39 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None): # set up clocks for timed modes if world.clock_mode == 'off': rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode - rom.write_bytes(0x180200, [0x00, 0x00, 0x00, 0x00]) # red clock adjustment time (in frames, sint32) - rom.write_bytes(0x180204, [0x00, 0x00, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32) - rom.write_bytes(0x180208, [0x00, 0x00, 0x00, 0x00]) # green clock adjustment time (in frames, sint32) - rom.write_bytes(0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, sint32) + rom.write_int32_to_rom(0x180200, 0) # red clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x180204, 0) # blue clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x180208, 0) # green clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x18020C, 0) # starting time (in frames, sint32) elif world.clock_mode == 'ohko': rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality - rom.write_bytes(0x180200, [0x00, 0x00, 0x00, 0x81]) # red clock adjustment time (in frames, sint32) - rom.write_bytes(0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32) - rom.write_bytes(0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x180200, 0) # red clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x180204, 0) # blue clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x180208, 0) # green clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x18020C, 0) # starting time (in frames, sint32) + elif world.clock_mode == 'countdown-ohko': + rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality + rom.write_int32_to_rom(0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32) if world.difficulty == 'easy': - rom.write_bytes(0x18020C, [0x40, 0x19, 0x01, 0x00]) # starting time (in frames, sint32) + rom.write_int32_to_rom(0x18020C, 20 * 60 * 60) # starting time (in frames, sint32) elif world.difficulty == 'normal': - rom.write_bytes(0x18020C, [0xA0, 0x8C, 0x00, 0x00]) # starting time (in frames, sint32) + rom.write_int32_to_rom(0x18020C, 10 * 60 * 60) # starting time (in frames, sint32) else: - rom.write_bytes(0x18020C, [0x50, 0x46, 0x00, 0x00]) # starting time (in frames, sint32) + rom.write_int32_to_rom(0x18020C, 5 * 60 * 60) # starting time (in frames, sint32) if world.clock_mode == 'stopwatch': rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode - rom.write_bytes(0x180200, [0xE0, 0xE3, 0xFF, 0xFF]) # red clock adjustment time (in frames, sint32) - rom.write_bytes(0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32) - rom.write_bytes(0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32) - rom.write_bytes(0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, sint32) + rom.write_int32_to_rom(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x18020C, 0) # starting time (in frames, sint32) if world.clock_mode == 'countdown': rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available - rom.write_bytes(0x180200, [0xE0, 0xE3, 0xFF, 0xFF]) # red clock adjustment time (in frames, sint32) - rom.write_bytes(0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32) - rom.write_bytes(0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32) - rom.write_bytes(0x18020C, [0x80, 0x32, 0x02, 0x00]) # starting time (in frames, sint32) + rom.write_int32_to_rom(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32) + rom.write_int32_to_rom(0x18020C, 40 * 60 * 60) # starting time (in frames, sint32) # set up goals for treasure hunt rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon == 'Triforce Piece' else [0x0D, 0x28]) From 9d4d3b8456a0d76c125b7548064af8d08598be21 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 18 Nov 2017 20:43:37 -0500 Subject: [PATCH 04/19] Whitespace fixes Removing trailing whitepace. --- BaseClasses.py | 4 ++-- EntranceRandomizer.py | 40 ++++++++++++++++++++-------------------- Fill.py | 2 +- Main.py | 2 +- README.md | 10 +++++----- Rom.py | 8 ++++---- Rules.py | 24 ++++++++++++------------ Text.py | 10 +++++----- 8 files changed, 50 insertions(+), 50 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 9a241504..b271abd2 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -185,7 +185,7 @@ class World(object): return True return False - + def has_beaten_game(self, state): if state.has('Triforce'): return True if self.goal in ['triforcehunt']: @@ -329,7 +329,7 @@ class CollectionState(object): return item in self.prog_items else: return self.item_count(item) >= count - + def item_count(self, item): return len([pritem for pritem in self.prog_items if pritem == item]) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 8ca0e098..54d7288c 100644 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -20,9 +20,9 @@ if __name__ == '__main__': parser.add_argument('--logic', default='noglitches', const='noglitches', nargs='?', choices=['noglitches', 'minorglitches'], help='''\ Select Enforcement of Item Requirements. (default: %(default)s) - No Glitches: + No Glitches: Minor Glitches: May require Fake Flippers, Bunny Revival - and Dark Room Navigation. + and Dark Room Navigation. ''') parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open', 'swordless'], help='''\ @@ -37,19 +37,19 @@ if __name__ == '__main__': Agahnim\'s Tower barrier can be destroyed with hammer. Misery Mire and Turtle Rock can be opened without a sword. Hammer damages Ganon. Ether and - Bombos Tablet can be activated with Hammer (and Book). + Bombos Tablet can be activated with Hammer (and Book). ''') parser.add_argument('--goal', default='ganon', const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'], help='''\ Select completion goal. (default: %(default)s) - Ganon: Collect all crystals, beat Agahnim 2 then + Ganon: Collect all crystals, beat Agahnim 2 then defeat Ganon. Crystals: Collect all crystals then defeat Ganon. Pedestal: Places the Triforce at the Master Sword Pedestal. All Dungeons: Collect all crystals, pendants, beat both Agahnim fights and then defeat Ganon. - Triforce Hunt: Places 30 Triforce Pieces in the world, collect - 20 of them to beat the game. + Triforce Hunt: Places 30 Triforce Pieces in the world, collect + 20 of them to beat the game. ''') parser.add_argument('--difficulty', default='normal', const='normal', nargs='?', choices=['easy', 'normal', 'hard', 'expert', 'insane'], help='''\ @@ -58,7 +58,7 @@ if __name__ == '__main__': Normal: Normal difficulty. Hard: A harder setting with less equipment and reduced health. Expert: A harder yet setting with minimum equipment and health. - Insane: A setting with the absolute minimum in equipment and no extra health. + Insane: A setting with the absolute minimum in equipment and no extra health. ''') parser.add_argument('--timer', default='none', const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'], help='''\ @@ -78,7 +78,7 @@ if __name__ == '__main__': and the clock is permenantly at zero. Timed Countdown: Starts with clock at 40 minutes. Same clocks as Timed mode. If time runs out, you lose (but can - still keep playing). + still keep playing). ''') parser.add_argument('--progressive', default='on', const='normal', nargs='?', choices=['on', 'off', 'random'], help='''\ @@ -92,7 +92,7 @@ if __name__ == '__main__': be found at any time. Downgrades are not possible. Random: Swords, Shields, Armor, and Gloves will, per category, be randomly progressive or not. - Link will die in one hit. + Link will die in one hit. ''') parser.add_argument('--algorithm', default='balanced', const='balanced', nargs='?', choices=['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'], help='''\ @@ -115,7 +115,7 @@ if __name__ == '__main__': them the more often they were found unreachable. Flood: Push out items starting from Link\'s House and slightly biased to placing progression items with - less restrictions. + less restrictions. ''') parser.add_argument('--shuffle', default='full', const='full', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'madness', 'insanity', 'dungeonsfull', 'dungeonssimple'], help='''\ @@ -137,7 +137,7 @@ if __name__ == '__main__': discretion. Experimental. The dungeon variants only mix up dungeons and keep the rest of - the overworld vanilla. + the overworld vanilla. ''') parser.add_argument('--rom', default='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', help='Path to an ALttP JAP(1.0) rom to use as a base.') parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.') @@ -147,7 +147,7 @@ if __name__ == '__main__': If --seed is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with --seed given will produce the same 10 (different) roms each - time). + time). ''', type=int) parser.add_argument('--fastmenu', help='Enable instant menu', action='store_true') parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') @@ -157,33 +157,33 @@ if __name__ == '__main__': ''', action='store_true') parser.add_argument('--nodungeonitems', help='''\ Remove Maps and Compasses from Itempool, replacing them by - empty slots. + empty slots. ''', action='store_true') parser.add_argument('--beatableonly', help='''\ Only check if the game is beatable with placement. Do not ensure all locations are reachable. This only has an effect - on the restrictive algorithm currently. + on the restrictive algorithm currently. ''', action='store_true') parser.add_argument('--shuffleganon', help='''\ - If set, include the Pyramid Hole and Ganon's Tower in the - entrance shuffle pool. + If set, include the Pyramid Hole and Ganon's Tower in the + entrance shuffle pool. ''', action='store_true') parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', choices=['normal', 'half', 'quarter', 'off'], help='''\ Select the rate at which the heart beep sound is played at - low health. (default: %(default)s) + low health. (default: %(default)s) ''') parser.add_argument('--sprite', help='''\ Path to a sprite sheet to use for Link. Needs to be in - binary format and have a length of 0x7000 (28672) bytes, + binary format and have a length of 0x7000 (28672) bytes, or 0x7078 (28792) bytes including palette data. Alternatively, can be a ALttP Rom patched with a Link - sprite that will be extracted. + sprite that will be extracted. ''') parser.add_argument('--suppress_rom', help='Do not create an output rom file.', action='store_true') parser.add_argument('--gui', help='Launch the GUI', action='store_true') parser.add_argument('--jsonout', action='store_true', help='''\ - Output .json patch to stdout instead of a patched rom. Used + Output .json patch to stdout instead of a patched rom. Used for VT site integration, do not use otherwise. ''') args = parser.parse_args() diff --git a/Fill.py b/Fill.py index 351c7d8d..6ed0e3da 100644 --- a/Fill.py +++ b/Fill.py @@ -167,7 +167,7 @@ def fill_restrictive(world, base_state, locations, itempool): while itempool and locations: item_to_place = itempool.pop() maximum_exploration_state = sweep_from_pool() - + if world.check_beatable_only: can_beat_without = world.has_beaten_game(maximum_exploration_state) diff --git a/Main.py b/Main.py index a1b05e5b..feb615cf 100644 --- a/Main.py +++ b/Main.py @@ -228,4 +228,4 @@ def create_playthrough(world): old_world.required_locations = [location.name for sphere in collection_spheres for location in sphere] # we can finally output our playthrough - old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)]) \ No newline at end of file + old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)]) diff --git a/README.md b/README.md index e29dabbf..ecd16f53 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ randomized. --algorithm [{freshness,flood,vt21,vt22,vt25,vt26,balanced}] ``` -Select item filling algorithm. +Select item filling algorithm. ### Balanced (Default) This is a variation of vt26 that aims to strike a balance between the overworld heavy vt25 and the dungeon heavy vt26 algorithm. @@ -220,7 +220,7 @@ Items and locations are shuffled like in VT25, and dungeon items are now placed shuffled it includes a slight deliberate bias against having too many desireable items in Ganon's Tower to help counterbalance the sheer number of chests in that single location. -### VT25 +### VT25 Items and locations are shuffled and placed from the top of the lists. The only thing preventing an item from being placed into a spot is if is absolutely impossible to be there given the previous made placement choices. Leads to very uniform but guaranteed solvable distributions. @@ -243,7 +243,7 @@ staleness, decreasing the likelihood of receiving a progress item. --shuffle [{default,simple,restricted,full,madness,insanity,dungeonsfull,dungeonssimple}] ``` -Select Entrance Shuffling Algorithm. +Select Entrance Shuffling Algorithm. ### Default @@ -256,7 +256,7 @@ on the overworld. On Death Mountain, entrances are connected more freely. ### Full (Default) -Mixes cave and dungeon entrances freely. +Mixes cave and dungeon entrances freely. ### Restricted @@ -296,7 +296,7 @@ Define seed number to generate. (default: None) Using the same seed with same se --count COUNT ``` -Use to batch generate multiple seeds with same settings. +Use to batch generate multiple seeds with same settings. If --seed is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with --seed given will produce the same 10 (different) roms each time). (default: None) ``` diff --git a/Rom.py b/Rom.py index e1bf18ce..2f47d956 100644 --- a/Rom.py +++ b/Rom.py @@ -21,10 +21,10 @@ class JsonRom(object): def write_bytes(self, startaddress, values): self.patches[str(startaddress)] = list(values) - + def write_int16_to_rom(self, address, value): self.write_bytes(address, int16_as_bytes(value)) - + def write_int32_to_rom(self, address, value): self.write_bytes(address, int32_as_bytes(value)) @@ -47,7 +47,7 @@ class LocalRom(object): def write_int16_to_rom(self, address, value): self.write_bytes(address, int16_as_bytes(value)) - + def write_int32_to_rom(self, address, value): self.write_bytes(address, int32_as_bytes(value)) @@ -86,7 +86,7 @@ class LocalRom(object): def int16_as_bytes(value): value = value & 0xFFFF return [value & 0xFF, (value >> 8) & 0xFF] - + def int32_as_bytes(value): value = value & 0xFFFFFFFF return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF] diff --git a/Rules.py b/Rules.py index d7fe41c8..f4f2011d 100644 --- a/Rules.py +++ b/Rules.py @@ -301,7 +301,7 @@ def global_rules(world): set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (state.world.get_location('Palace of Darkness - Big Key Chest').item is not None and (state.world.get_location('Palace of Darkness - Big Key Chest').item.name 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 (state.world.get_location('Palace of Darkness - Harmless Hellway').item is not None and (state.world.get_location('Palace of Darkness - Harmless Hellway').item.name in ['Small Key (Palace of Darkness)']))) set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (state.world.get_location('Palace of Darkness - Big Key Chest').item is not None and (state.world.get_location('Palace of Darkness - Big Key Chest').item.name in ['Small Key (Palace of Darkness)']))) @@ -310,7 +310,7 @@ def global_rules(world): set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (state.world.get_location('Palace of Darkness - Harmless Hellway').item is not None and (state.world.get_location('Palace of Darkness - Harmless Hellway').item.name in ['Small Key (Palace of Darkness)']))) set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (state.world.get_location('Palace of Darkness - Big Key Chest').item is not None and (state.world.get_location('Palace of Darkness - Big Key Chest').item.name in ['Small Key (Palace of Darkness)']))) 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']: forbid_item(world.get_location(location), 'Big Key (Palace of Darkness)') @@ -320,7 +320,7 @@ def global_rules(world): # these key rules are conservative, you might be able to get away with more lenient rules randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'] compass_room_chests = ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right'] - + set_rule(world.get_location('Ganons Tower - Bob\'s Torch'), lambda state: state.has_Boots()) set_rule(world.get_entrance('Ganons Tower (Tile Room)'), lambda state: state.has('Cane of Somaria')) set_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hammer')) @@ -328,24 +328,24 @@ def global_rules(world): set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has('Small Key (Ganons Tower)', 4) or (state.world.get_location('Ganons Tower - Map Chest').item is not None and state.world.get_location('Ganons Tower - Map Chest').item.name == 'Big Key (Ganons Tower)' and state.has('Small Key (Ganons Tower)', 3)) or (state.world.get_location('Ganons Tower - Map Chest').item is not None and state.world.get_location('Ganons Tower - Map Chest').item.name == 'Small Key (Ganons Tower)')) else: set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has('Small Key (Ganons Tower)', 3) or (state.world.get_location('Ganons Tower - Map Chest').item is not None and state.world.get_location('Ganons Tower - Map Chest').item.name == 'Small Key (Ganons Tower)')) - + # It is possible to need more than 2 keys to get through this entance if you spend keys elsewhere We reflect this in the chest requirements. # However we need to leave these at the lower values derive that with 3 keys it is always possible to reach Bob and Ice Armos. set_rule(world.get_entrance('Ganons Tower (Double Switch Room)'), lambda state: state.has('Small Key (Ganons Tower)', 2)) # It is possible to need more than 3 keys .... set_rule(world.get_entrance('Ganons Tower (Firesnake Room)'), lambda state: state.has('Small Key (Ganons Tower)', 3)) - + #The actual requirements for these rooms to avoid key-lock set_rule(world.get_location('Ganons Tower - Firesnake Room'), lambda state: state.has('Small Key (Ganons Tower)', 3) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has('Small Key (Ganons Tower)', 2))) for location in randomizer_room_chests: set_rule(world.get_location(location), lambda state: state.has('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has('Small Key (Ganons Tower)', 3))) - + # Once again it is possible to need more than 3 keys... set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door'), lambda state: state.has('Small Key (Ganons Tower)', 3) and state.has('Fire Rod')) # Actual requirements for location in compass_room_chests: set_rule(world.get_location(location), lambda state: state.has('Fire Rod') and (state.has('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', compass_room_chests) and state.has('Small Key (Ganons Tower)', 3)))) - + set_rule(world.get_location('Ganons Tower - Big Chest'), lambda state: state.has('Big Key (Ganons Tower)')) set_rule(world.get_location('Ganons Tower - Big Key Room - Left'), lambda state: state.has('Bow') or state.has_blunt_weapon()) set_rule(world.get_location('Ganons Tower - Big Key Chest'), lambda state: state.has('Bow') or state.has_blunt_weapon()) @@ -429,7 +429,7 @@ def open_rules(world): # softlock protection as you can reach the sewers small key door with a guard drop key forbid_item(world.get_location('Hyrule Castle - Boomerang Chest'), 'Small Key (Escape)') forbid_item(world.get_location('Hyrule Castle - Zelda\'s Chest'), 'Small Key (Escape)') - + # to prevent key-lock in keysanity we need to prevent these chests from having an item that # blocks the small key if (world.keysanity): @@ -443,7 +443,7 @@ def swordless_rules(world): # should there ever be fixes that apply to open mode but not swordless, this # can be revisited. open_rules(world) - + set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has('Hammer') or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has('Hammer') or state.has('Bug Catching Net') and state.has('Small Key (Agahnims Tower)', 2)) set_rule(world.get_location('Ether Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer')) @@ -479,7 +479,7 @@ def set_trock_key_rules(world): # if we have backdoor access we can waste a key on the trinexx door, then have no lamp to reverse traverse the maze room. We simply require an additional key just to be super safe then. The backdoor access to the chest is otherwise free if not can_reach_back: - set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has('Small Key (Turtle Rock)', 1)) + set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has('Small Key (Turtle Rock)', 1)) else: set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has('Small Key (Turtle Rock)', 2)) @@ -496,8 +496,8 @@ def set_trock_key_rules(world): # however in keysanity being able to reach all other chests while only having three keys does not imply this contains # a key, so we again need all four keys unless it contains the big key if can_reach_back: - set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 4) or (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Small Key (Turtle Rock)']))) - elif world.keysanity: + set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 4) or (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Small Key (Turtle Rock)']))) + elif world.keysanity: set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 2) if (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Big Key (Turtle Rock)'])) else state.has('Small Key (Turtle Rock)', 4) or (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Small Key (Turtle Rock)']))) else: set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 2) if (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Big Key (Turtle Rock)'])) else state.has('Small Key (Turtle Rock)', 3) or (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Small Key (Turtle Rock)']))) diff --git a/Text.py b/Text.py index 148eccba..f1f308a8 100644 --- a/Text.py +++ b/Text.py @@ -141,11 +141,11 @@ class Credits(object): class CreditLine(object): """Base class of credit lines""" - + def __init__(self, text, align='center'): self.text = text self.align = align - + @property def x(self): x = 0 @@ -157,7 +157,7 @@ class CreditLine(object): x = (32 - len(self.text)) // 2 return x - + class SceneCreditLine(CreditLine): """Base class for credit lines for the scene portion of the credits""" def __init__(self, y, text, align='center'): @@ -214,7 +214,7 @@ class SceneLargeCreditLine(SceneCreditLine): buf += LargeCreditBottomMapper.convert(self.text) return buf - + def string_to_alttp_text(s, maxbytes=256): lines = s.upper().split('\n') outbuf = bytearray() @@ -489,7 +489,7 @@ class GreenCreditMapper(TextMapper): char_map = {' ': 0x9F, '.': 0x52} alpha_offset = -0x29 - + class RedCreditMapper(TextMapper): char_map = {' ': 0x9F} #fixme alpha_offset= -0x61 From 8158f5793d008f5767d7758bbd5f702fea74c085 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 18 Nov 2017 20:56:45 -0500 Subject: [PATCH 05/19] Rename Bottles for better display in spoiler logs --- BaseClasses.py | 2 +- ItemList.py | 4 ++-- Items.py | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index b271abd2..21cb8c99 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -337,7 +337,7 @@ class CollectionState(object): return self.has('Power Glove') or self.has('Titans Mitts') def has_bottle(self): - return self.has('Bottle') or self.has('BottleRedPotion') or self.has('BottleGreenPotion') or self.has('BottleBluePotion') or self.has('BottleFairy') or self.has('BottleBee') or self.has('BottleGoodBee') + 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)') def can_lift_heavy_rocks(self): return self.has('Titans Mitts') diff --git a/ItemList.py b/ItemList.py index 693dd87b..91e8fa53 100644 --- a/ItemList.py +++ b/ItemList.py @@ -11,8 +11,8 @@ alwaysitems = ['Bombos', 'Book of Mudora', 'Bow', 'Cane of Somaria', 'Ether', 'F progressivegloves = ['Progressive Glove'] * 2 basicgloves = ['Power Glove', 'Titans Mitts'] -normalbottles = ['Bottle', 'BottleRedPotion', 'BottleGreenPotion', 'BottleBluePotion', 'BottleFairy', 'BottleBee', 'BottleGoodBee'] -hardbottles = ['Bottle', 'BottleRedPotion', 'BottleGreenPotion', 'BottleBluePotion', 'BottleBee', 'BottleGoodBee'] +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 = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows', 'Magic Upgrade (1/2)'] + ['Rupees (300)'] * 4 + ['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24) diff --git a/Items.py b/Items.py index b93d778f..37a83e52 100644 --- a/Items.py +++ b/Items.py @@ -47,12 +47,12 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla 'Bombos': (True, False, None, 0x0F, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'boy hides coin again'), 'Quake': (True, False, None, 0x11, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'boy hides coin again'), 'Bottle': (True, False, None, 0x16, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'boy stores things again'), - 'BottleRedPotion': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy drinks again'), - 'BottleGreenPotion': (True, False, None, 0x2C, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy drinks again'), - 'BottleBluePotion': (True, False, None, 0x2D, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy stores drinks again'), - 'BottleFairy': (True, False, None, 0x3D, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'boy revives again'), - 'BottleBee': (True, False, None, 0x3C, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'boy is stung again'), - 'BottleGoodBee': (True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'boy is stung again'), + 'Bottle (Red Potion)': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy drinks again'), + 'Bottle (Green Potion)': (True, False, None, 0x2C, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy drinks again'), + 'Bottle (Blue Potion)': (True, False, None, 0x2D, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy stores drinks again'), + 'Bottle (Fairy)': (True, False, None, 0x3D, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'boy revives again'), + 'Bottle (Bee)': (True, False, None, 0x3C, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'boy is stung again'), + 'Bottle (Good Bee)': (True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'boy is stung again'), 'Master Sword': (True, False, None, 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'boy fights again'), 'Tempered Sword': (True, False, None, 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'boy fights again'), 'Fighter Sword': (True, False, None, 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'boy fights again'), From d4052ab60a983674d71a6cf41a483ac66edc3535 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sun, 19 Nov 2017 16:00:26 -0500 Subject: [PATCH 06/19] Use sys.exit instead of exit The global exit function is designed only for the interpreter, so use the correct one. Also fix the plandomizer so it does not immediately error when run. --- EntranceRandomizer.py | 5 +++-- Plando.py | 16 +++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 54d7288c..17fb6896 100644 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -3,6 +3,7 @@ import os import logging import random import textwrap +import sys from Main import main from Gui import guiMain @@ -191,11 +192,11 @@ if __name__ == '__main__': # ToDo: Validate files further than mere existance if not args.jsonout and not os.path.isfile(args.rom): input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom) - exit(1) + sys.exit(1) if args.sprite is not None and not os.path.isfile(args.sprite): if not args.jsonout: input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite) - exit(1) + sys.exit(1) else: raise IOError('Cannot find sprite file at %s' % args.sprite) diff --git a/Plando.py b/Plando.py index a013c398..ddcc4d8d 100644 --- a/Plando.py +++ b/Plando.py @@ -1,7 +1,7 @@ from BaseClasses import World from Regions import create_regions from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit -from Rom import patch_rom, LocalRom, write_string_to_rom, write_credits_string_to_rom +from Rom import patch_rom, LocalRom, write_string_to_rom from Rules import set_rules from Dungeons import create_dungeons from Items import ItemFactory @@ -12,6 +12,7 @@ import logging import argparse import os import hashlib +import sys __version__ = '0.2-dev' @@ -161,9 +162,10 @@ def fill_world(world, plando, text_patches): elif line.startswith('!text_'): textname, text = line.split(':', 1) text_patches.append([textname.lstrip('!text_').strip(), 'text', text.strip()]) - elif line.startswith('!credits_'): - textname, text = line.split(':', 1) - text_patches.append([textname.lstrip('!credits_').strip(), 'credits', text.strip()]) + #temporarilly removed. New credits system not ready to handle this. + #elif line.startswith('!credits_'): + # textname, text = line.split(':', 1) + # text_patches.append([textname.lstrip('!credits_').strip(), 'credits', text.strip()]) continue locationstr, itemstr = line.split(':', 1) @@ -214,13 +216,13 @@ if __name__ == '__main__': # ToDo: Validate files further than mere existance if not os.path.isfile(args.rom): input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom) - exit(1) + sys.exit(1) if not os.path.isfile(args.plando): input('Could not find Plandomizer distribution at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.plando) - exit(1) + sys.exit(1) if args.sprite is not None and not os.path.isfile(args.rom): input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite) - exit(1) + sys.exit(1) # set up logger loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel] From 424fc3e6c6fa5c1a70a50b03c62c6ed2c1e7361d Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Thu, 23 Nov 2017 12:38:28 -0500 Subject: [PATCH 07/19] Create data subfolder in preperation for new features Move base2current.json to this new subfolder --- Rom.py | 2 +- base2current.json => data/base2current.json | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename base2current.json => data/base2current.json (100%) diff --git a/Rom.py b/Rom.py index 2f47d956..b9dcb776 100644 --- a/Rom.py +++ b/Rom.py @@ -66,7 +66,7 @@ class LocalRom(object): self.buffer.extend(bytearray([0x00] * (2097152 - len(self.buffer)))) # load randomizer patches - patches = json.load(open('base2current.json', 'r')) + patches = json.load(open('data/base2current.json', 'r')) for patch in patches: if isinstance(patch, dict): for baseaddress, values in patch.items(): diff --git a/base2current.json b/data/base2current.json similarity index 100% rename from base2current.json rename to data/base2current.json From 572cffc2644bbe38141fc826f5a10e9d17f7d8a1 Mon Sep 17 00:00:00 2001 From: AmazingAmpharos Date: Tue, 28 Nov 2017 04:54:14 -0600 Subject: [PATCH 08/19] Add disable music feature This is a feature the vt rando has that we haven't had until now. It was a fairly basic feature... though I confess I tested it minimally. --- BaseClasses.py | 4 +++- EntranceRandomizer.py | 1 + Gui.py | 5 +++++ Main.py | 4 ++-- Plando.py | 3 ++- README.md | 12 ++++++++--- Rom.py | 47 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 69 insertions(+), 7 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 21cb8c99..0508d758 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -6,7 +6,7 @@ from collections import OrderedDict class World(object): - def __init__(self, shuffle, logic, mode, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, check_beatable_only, shuffle_ganon, quickswap, fastmenu, keysanity): + def __init__(self, shuffle, logic, mode, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, check_beatable_only, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity): self.shuffle = shuffle self.logic = logic self.mode = mode @@ -51,6 +51,7 @@ class World(object): self.can_access_trock_eyebridge = None self.quickswap = quickswap self.fastmenu = fastmenu + self.disable_music = disable_music self.keysanity = keysanity self.spoiler = Spoiler(self) @@ -672,6 +673,7 @@ class Spoiler(object): 'dungeonitems': self.world.place_dungeon_items, 'quickswap': self.world.quickswap, 'fastmenu': self.world.fastmenu, + 'disable_music': self.world.disable_music, 'keysanity': self.world.keysanity} def to_json(self): diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 17fb6896..2a6f7193 100644 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -152,6 +152,7 @@ if __name__ == '__main__': ''', type=int) parser.add_argument('--fastmenu', help='Enable instant menu', action='store_true') parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') + parser.add_argument('--disablemusic', help='Disables game music.', action='store_true') parser.add_argument('--keysanity', help='''\ Keys (and other dungeon items) are no longer restricted to their dungeons, but can be anywhere diff --git a/Gui.py b/Gui.py index 8c86856b..feee07c7 100644 --- a/Gui.py +++ b/Gui.py @@ -27,6 +27,8 @@ def guiMain(args=None): dungeonItemsCheckbutton = Checkbutton(checkBoxFrame, text="Place Dungeon Items (Compasses/Maps)", onvalue=0, offvalue=1, variable=dungeonItemsVar) beatableOnlyVar = IntVar() beatableOnlyCheckbutton = Checkbutton(checkBoxFrame, text="Only ensure seed is beatable, not all items must be reachable", variable=beatableOnlyVar) + disableMusicVar = IntVar() + disableMusicCheckbutton = Checkbutton(checkBoxFrame, text="Disable game music", variable=disableMusicVar) shuffleGanonVar = IntVar() shuffleGanonCheckbutton = Checkbutton(checkBoxFrame, text="Include Ganon's Tower and Pyramid Hole in shuffle pool", variable=shuffleGanonVar) @@ -37,6 +39,7 @@ def guiMain(args=None): keysanityCheckbutton.pack(expand=True, anchor=W) dungeonItemsCheckbutton.pack(expand=True, anchor=W) beatableOnlyCheckbutton.pack(expand=True, anchor=W) + disableMusicCheckbutton.pack(expand=True, anchor=W) shuffleGanonCheckbutton.pack(expand=True, anchor=W) fileDialogFrame = Frame(rightHalfFrame) @@ -189,6 +192,7 @@ def guiMain(args=None): guiargs.beatableonly = bool(beatableOnlyVar.get()) guiargs.fastmenu = bool(fastMenuVar.get()) guiargs.quickswap = bool(quickSwapVar.get()) + guiargs.disablemusic = bool(disableMusicVar.get()) guiargs.shuffleganon = bool(shuffleGanonVar.get()) guiargs.rom = romVar.get() guiargs.jsonout = None @@ -229,6 +233,7 @@ def guiMain(args=None): beatableOnlyVar.set(int(args.beatableonly)) fastMenuVar.set(int(args.fastmenu)) quickSwapVar.set(int(args.quickswap)) + disableMusicVar.set(int(args.disablemusic)) if args.count: countVar.set(str(args.count)) if args.seed: diff --git a/Main.py b/Main.py index feb615cf..8b49044e 100644 --- a/Main.py +++ b/Main.py @@ -29,7 +29,7 @@ def main(args, seed=None): start = time.clock() # initialize the world - world = World(args.shuffle, args.logic, args.mode, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.beatableonly, args.shuffleganon, args.quickswap, args.fastmenu, args.keysanity) + world = World(args.shuffle, args.logic, args.mode, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.beatableonly, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity) logger = logging.getLogger('') if seed is None: random.seed(None) @@ -117,7 +117,7 @@ def main(args, seed=None): def copy_world(world): # ToDo: Not good yet - ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.keysanity) + ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity) ret.required_medallions = list(world.required_medallions) ret.swamp_patch_required = world.swamp_patch_required ret.ganon_at_pyramid = world.ganon_at_pyramid diff --git a/Plando.py b/Plando.py index ddcc4d8d..a0e07edb 100644 --- a/Plando.py +++ b/Plando.py @@ -30,7 +30,7 @@ def main(args, seed=None): start = time.clock() # initialize the world - world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, False) + world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False) logger = logging.getLogger('') hasher = hashlib.md5() @@ -207,6 +207,7 @@ if __name__ == '__main__': parser.add_argument('--seed', help='Define seed number to generate.', type=int) parser.add_argument('--fastmenu', help='Enable instant menu', action='store_true') parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') + parser.add_argument('--disablemusic', help='Disables game music.', action='store_true') parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', choices=['normal', 'half', 'quarter', 'off'], help='Select the rate at which the heart beep sound is played at low health.') parser.add_argument('--sprite', help='Path to a sprite sheet to use for Link. Needs to be in binary format and have a length of 0x7000 (28672) bytes.') diff --git a/README.md b/README.md index ecd16f53..06009eb8 100644 --- a/README.md +++ b/README.md @@ -170,9 +170,8 @@ to the itempool. ### OHKO -The player into a one hit state the entire game. This is the same as Timed-OHKO, -except that the clock starts at zero, and there are no Clock items, so it will -always stay at zero, resulting in a permanent one hit knockout state. +The player will be in a one hit knockout state the entire game. This is the same as Timed-OHKO except +without the Clock items and the timer permanently at zero. ### Timed-countdown @@ -311,6 +310,13 @@ Use to enable quick item swap with L/R buttons. (default: False) As an alternative to quickswap, opens menu instantly. (default: False) + +``` +--disablemusic +``` + +Disables game music, resulting in the game sound being just the SFX. (default: False) + ``` --keysanity ``` diff --git a/Rom.py b/Rom.py index b9dcb776..bc27fa32 100644 --- a/Rom.py +++ b/Rom.py @@ -534,6 +534,53 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None): write_strings(rom, world) + if world.disable_music: + volumeaddresses = [0xD373B, 0xD375B, 0xD90F8, 0xDA710, 0xDA7A4, 0xDA7BB, 0xDA7D2, 0xD5954, 0xD653B, 0xDA736, 0xDA752, 0xDA772, 0xDA792, + 0xD5B47, 0xD5B5E, 0xD4306, 0xD6878, 0xD6883, 0xD6E48, 0xD6E76, 0xD6EFB, 0xD6F2D, 0xDA211, 0xDA35B, 0xDA37B, 0xDA38E, + 0xDA39F, 0xDA5C3, 0xDA691, 0xDA6A8, 0xDA6DF, 0xD2349, 0xD3F45, 0xD42EB, 0xD48B9, 0xD48FF, 0xD543F, 0xD5817, 0xD5957, + 0xD5ACB, 0xD5AE8, 0xD5B4A, 0xDA5DE, 0xDA608, 0xDA635, 0xDA662, 0xDA71F, 0xDA7AF, 0xDA7C6, 0xDA7DD, 0xD2F00, 0xDA3D5, + 0xD249C, 0xD24CD, 0xD2C09, 0xD2C53, 0xD2CAF, 0xD2CEB, 0xD2D91, 0xD2EE6, 0xD38ED, 0xD3C91, 0xD3CD3, 0xD3CE8, 0xD3F0C, + 0xD3F82, 0xD405F, 0xD4139, 0xD4198, 0xD41D5, 0xD41F6, 0xD422B, 0xD4270, 0xD42B1, 0xD4334, 0xD4371, 0xD43A6, 0xD43DB, + 0xD441E, 0xD4597, 0xD4B3C, 0xD4BAB, 0xD4C03, 0xD4C53, 0xD4C7F, 0xD4D9C, 0xD5424, 0xD65D2, 0xD664F, 0xD6698, 0xD66FF, + 0xD6985, 0xD6C5C, 0xD6C6F, 0xD6C8E, 0xD6CB4, 0xD6D7D, 0xD827D, 0xD960C, 0xD9828, 0xDA233, 0xDA3A2, 0xDA49E, 0xDA72B, + 0xDA745, 0xDA765, 0xDA785, 0xDABF6, 0xDAC0D, 0xDAEBE, 0xDAFAC, 0xD9A02, 0xD9BD6, 0xD21CD, 0xD2279, 0xD2E66, 0xD2E70, + 0xD2EAB, 0xD3B97, 0xD3BAC, 0xD3BE8, 0xD3C0D, 0xD3C39, 0xD3C68, 0xD3C9F, 0xD3CBC, 0xD401E, 0xD4290, 0xD443E, 0xD456F, + 0xD47D3, 0xD4D43, 0xD4DCC, 0xD4EBA, 0xD4F0B, 0xD4FE5, 0xD5012, 0xD54BC, 0xD54D5, 0xD54F0, 0xD5509, 0xD57D8, 0xD59B9, + 0xD5A2F, 0xD5AEB, 0xD5E5E, 0xD5FE9, 0xD658F, 0xD674A, 0xD6827, 0xD69D6, 0xD69F5, 0xD6A05, 0xD6AE9, 0xD6DCF, 0xD6E20, + 0xD6ECB, 0xD71D4, 0xD71E6, 0xD7203, 0xD721E, 0xD8724, 0xD8732, 0xD9652, 0xD9698, 0xD9CBC, 0xD9DC0, 0xD9E49, 0xDAA68, + 0xDAA77, 0xDAA88, 0xDAA99, 0xDAF04, 0xD1D28, 0xD1D41, 0xD1D5C, 0xD1D77, 0xD1EEE, 0xD311D, 0xD31D1, 0xD4148, 0xD5543, + 0xD5B6F, 0xD65B3, 0xD6760, 0xD6B6B, 0xD6DF6, 0xD6E0D, 0xD73A1, 0xD814C, 0xD825D, 0xD82BE, 0xD8340, 0xD8394, 0xD842C, + 0xD8796, 0xD8903, 0xD892A, 0xD91E8, 0xD922B, 0xD92E0, 0xD937E, 0xD93C1, 0xDA958, 0xDA971, 0xDA98C, 0xDA9A7, 0xD1D92, + 0xD1DBD, 0xD1DEB, 0xD1F5D, 0xD1F9F, 0xD1FBD, 0xD1FDC, 0xD1FEA, 0xD20CA, 0xD21BB, 0xD22C9, 0xD2754, 0xD284C, 0xD2866, + 0xD2887, 0xD28A0, 0xD28BA, 0xD28DB, 0xD28F4, 0xD293E, 0xD2BF3, 0xD2C1F, 0xD2C69, 0xD2CA1, 0xD2CC5, 0xD2D05, 0xD2D73, + 0xD2DAF, 0xD2E3D, 0xD2F36, 0xD2F46, 0xD2F6F, 0xD2FCF, 0xD2FDF, 0xD302B, 0xD3086, 0xD3099, 0xD30A5, 0xD30CD, 0xD30F6, + 0xD3154, 0xD3184, 0xD333A, 0xD33D9, 0xD349F, 0xD354A, 0xD35E5, 0xD3624, 0xD363C, 0xD3672, 0xD3691, 0xD36B4, 0xD36C6, + 0xD3724, 0xD3767, 0xD38CB, 0xD3B1D, 0xD3B2F, 0xD3B55, 0xD3B70, 0xD3B81, 0xD3BBF, 0xD3D34, 0xD3D55, 0xD3D6E, 0xD3DC6, + 0xD3E04, 0xD3E38, 0xD3F65, 0xD3FA6, 0xD404F, 0xD4087, 0xD417A, 0xD41A0, 0xD425C, 0xD4319, 0xD433C, 0xD43EF, 0xD440C, + 0xD4452, 0xD4494, 0xD44B5, 0xD4512, 0xD45D1, 0xD45EF, 0xD4682, 0xD46C3, 0xD483C, 0xD4848, 0xD4855, 0xD4862, 0xD486F, + 0xD487C, 0xD4A1C, 0xD4A3B, 0xD4A60, 0xD4B27, 0xD4C7A, 0xD4D12, 0xD4D81, 0xD4E90, 0xD4ED6, 0xD4EE2, 0xD5005, 0xD502E, + 0xD503C, 0xD5081, 0xD51B1, 0xD51C7, 0xD51CF, 0xD51EF, 0xD520C, 0xD5214, 0xD5231, 0xD5257, 0xD526D, 0xD5275, 0xD52AF, + 0xD52BD, 0xD52CD, 0xD52DB, 0xD549C, 0xD5801, 0xD58A4, 0xD5A68, 0xD5A7F, 0xD5C12, 0xD5D71, 0xD5E10, 0xD5E9A, 0xD5F8B, + 0xD5FA4, 0xD651A, 0xD6542, 0xD65ED, 0xD661D, 0xD66D7, 0xD6776, 0xD68BD, 0xD68E5, 0xD6956, 0xD6973, 0xD69A8, 0xD6A51, + 0xD6A86, 0xD6B96, 0xD6C3E, 0xD6D4A, 0xD6E9C, 0xD6F80, 0xD717E, 0xD7190, 0xD71B9, 0xD811D, 0xD8139, 0xD816B, 0xD818A, + 0xD819E, 0xD81BE, 0xD829C, 0xD82E1, 0xD8306, 0xD830E, 0xD835E, 0xD83AB, 0xD83CA, 0xD83F0, 0xD83F8, 0xD844B, 0xD8479, + 0xD849E, 0xD84CB, 0xD84EB, 0xD84F3, 0xD854A, 0xD8573, 0xD859D, 0xD85B4, 0xD85CE, 0xD862A, 0xD8681, 0xD87E3, 0xD87FF, + 0xD887B, 0xD88C6, 0xD88E3, 0xD8944, 0xD897B, 0xD8C97, 0xD8CA4, 0xD8CB3, 0xD8CC2, 0xD8CD1, 0xD8D01, 0xD917B, 0xD918C, + 0xD919A, 0xD91B5, 0xD91D0, 0xD91DD, 0xD9220, 0xD9273, 0xD9284, 0xD9292, 0xD92AD, 0xD92C8, 0xD92D5, 0xD9311, 0xD9322, + 0xD9330, 0xD934B, 0xD9366, 0xD9373, 0xD93B6, 0xD97A6, 0xD97C2, 0xD97DC, 0xD97FB, 0xD9811, 0xD98FF, 0xD996F, 0xD99A8, + 0xD99D5, 0xD9A30, 0xD9A4E, 0xD9A6B, 0xD9A88, 0xD9AF7, 0xD9B1D, 0xD9B43, 0xD9B7C, 0xD9BA9, 0xD9C84, 0xD9C8D, 0xD9CAC, + 0xD9CE8, 0xD9CF3, 0xD9CFD, 0xD9D46, 0xDA35E, 0xDA37E, 0xDA391, 0xDA478, 0xDA4C3, 0xDA4D7, 0xDA4F6, 0xDA515, 0xDA6E2, + 0xDA9C2, 0xDA9ED, 0xDAA1B, 0xDAA57, 0xDABAF, 0xDABC9, 0xDABE2, 0xDAC28, 0xDAC46, 0xDAC63, 0xDACB8, 0xDACEC, 0xDAD08, + 0xDAD25, 0xDAD42, 0xDAD5F, 0xDAE17, 0xDAE34, 0xDAE51, 0xDAF2E, 0xDAF55, 0xDAF6B, 0xDAF81, 0xDB14F, 0xDB16B, 0xDB180, + 0xDB195, 0xDB1AA, 0xD2B88, 0xD364A, 0xD369F, 0xD3747, 0xD213F, 0xD2174, 0xD229E, 0xD2426, 0xD4731, 0xD4753, 0xD4774, + 0xD4795, 0xD47B6, 0xD4AA5, 0xD4AE4, 0xD4B96, 0xD4CA5, 0xD5477, 0xD5A3D, 0xD6566, 0xD672C, 0xD67C0, 0xD69B8, 0xD6AB1, + 0xD6C05, 0xD6DB3, 0xD71AB, 0xD8E2D, 0xD8F0D, 0xD94E0, 0xD9544, 0xD95A8, 0xD9982, 0xD9B56, 0xDA694, 0xDA6AB, 0xDAE88, + 0xDAEC8, 0xDAEE6, 0xDB1BF, 0xD210A, 0xD22DC, 0xD2447, 0xD5A4D, 0xD5DDC, 0xDA251, 0xDA26C, 0xD945E, 0xD967D, 0xD96C2, + 0xD9C95, 0xD9EE6, 0xDA5C6, 0xD2047, 0xD24C2, 0xD24EC, 0xD25A4, 0xD3DAA, 0xD51A8, 0xD51E6, 0xD524E, 0xD529E, 0xD6045, + 0xD81DE, 0xD821E, 0xD94AA, 0xD9A9E, 0xD9AE4, 0xDA289, 0xD2085, 0xD21C5, 0xD5F28] + for address in volumeaddresses: + rom.write_byte(address, 0x00) + # set rom name # 21 bytes rom.write_bytes(0x7FC0, bytearray('ER_050_%09d\0' % world.seed, 'utf8') + world.option_identifier.to_bytes(4, 'big')) From 97c7fdaf59658ecddc45a47958b5edf10bddb1e9 Mon Sep 17 00:00:00 2001 From: AmazingAmpharos Date: Sat, 2 Dec 2017 00:18:38 -0600 Subject: [PATCH 09/19] PoD mislabel/softlock fix Two addresses for PoD item locations were reversed, causing items to be placed with incorrect assumptions. This fixes that problem. --- Regions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Regions.py b/Regions.py index cfab146d..e1e504d2 100644 --- a/Regions.py +++ b/Regions.py @@ -458,10 +458,10 @@ location_table = {'Mushroom': (0x180013, False, 'in the woods'), 'Turtle Rock - Eye Bridge - Top Right': (0xEA28, False, 'in Turtle Rock'), 'Turtle Rock - Trinexx': (0x180159, False, 'with Trinexx'), 'Palace of Darkness - Shooter Room': (0xEA5B, False, 'in Palace of Darkness'), - 'Palace of Darkness - The Arena - Bridge': (0xEA3A, False, 'in Palace of Darkness'), + 'Palace of Darkness - The Arena - Bridge': (0xEA3D, False, 'in Palace of Darkness'), 'Palace of Darkness - Stalfos Basement': (0xEA49, False, 'in Palace of Darkness'), 'Palace of Darkness - Big Key Chest': (0xEA37, False, 'in Palace of Darkness'), - 'Palace of Darkness - The Arena - Ledge': (0xEA3D, False, 'in Palace of Darkness'), + 'Palace of Darkness - The Arena - Ledge': (0xEA3A, False, 'in Palace of Darkness'), 'Palace of Darkness - Map Chest': (0xEA52, False, 'in Palace of Darkness'), 'Palace of Darkness - Compass Chest': (0xEA43, False, 'in Palace of Darkness'), 'Palace of Darkness - Dark Basement - Left': (0xEA4C, False, 'in Palace of Darkness'), From 619ad5a7169460560ea2f7036fcdbfe6d55ca600 Mon Sep 17 00:00:00 2001 From: AmazingAmpharos Date: Sat, 2 Dec 2017 02:30:48 -0600 Subject: [PATCH 10/19] Typo fix Fix three typos, two in comments and one that broke triforce hunt mode. --- ItemList.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ItemList.py b/ItemList.py index 91e8fa53..45fdb1ef 100644 --- a/ItemList.py +++ b/ItemList.py @@ -26,7 +26,7 @@ normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2 easybaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows'] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 + ['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12) easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)'] -easylimitedextra = ['Boss Heart Container'] * 3 # collapsing down the 12 peices of heart +easylimitedextra = ['Boss Heart Container'] * 3 # collapsing down the 12 pieces of heart easyfirst15extra = ['Rupees (100)'] + ['Rupees (50)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6 + ['Bombs (3)'] easysecond10extra = ['Bombs (3)'] * 9 + ['Rupee (1)'] easythird5extra = ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupees (5)'] @@ -125,7 +125,7 @@ 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 equivlent to two blue mail + basicarmor = ['Progressive Armor'] * 2, #only the first one will upgrade, making this equivalent to two blue mail swordless = ['Rupees (20)'] * 4, progressivesword = ['Progressive Sword'] * 3, basicsword = ['Master Sword', 'Master Sword', 'Tempered Sword'], @@ -199,7 +199,7 @@ def generate_itempool(world): if treasure_hunt_count is not None: world.treasure_hunt_count = treasure_hunt_count if treasure_hunt_icon is not None: - wrold.treasure_hunt_icon = treasure_hunt_icon + world.treasure_hunt_icon = treasure_hunt_icon # shuffle medallions mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] From 4271b4430a04b98aeca1edf1cd6cb967327ab2c8 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Wed, 15 Nov 2017 22:06:53 -0500 Subject: [PATCH 11/19] Add pyinstaller bundle support Will allow creating generating pre-made builds for windows that do not require python to be already installed. --- EntranceRandomizer.py | 8 ++++++++ bundle/EntranceRandomizer.spec | 33 +++++++++++++++++++++++++++++++++ bundle/_rt_hook.py | 4 ++++ 3 files changed, 45 insertions(+) create mode 100644 bundle/EntranceRandomizer.spec create mode 100644 bundle/_rt_hook.py diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 2a6f7193..80b7e213 100644 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -190,6 +190,14 @@ if __name__ == '__main__': ''') args = parser.parse_args() + if hasattr(sys, 'frozen') and len(sys.argv) == 1 : + # for the Precompiled windows build, if we have no arguments, the user + # probably wants the gui. Users of the windows build who want the command line + # interface shouuld specify at least one option, possibly setting a value to a + # default if they like all the defaults + guiMain() + sys.exit(0) + # ToDo: Validate files further than mere existance if not args.jsonout and not os.path.isfile(args.rom): input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom) diff --git a/bundle/EntranceRandomizer.spec b/bundle/EntranceRandomizer.spec new file mode 100644 index 00000000..975255e6 --- /dev/null +++ b/bundle/EntranceRandomizer.spec @@ -0,0 +1,33 @@ +# -*- mode: python -*- + +block_cipher = None + + +a = Analysis(['..\\EntranceRandomizer.py'], + pathex=['bundle'], + binaries=[], + datas=[('../data/', 'data/'), ('../README.html', '.'), ('../LICENSE.txt', '.')], + hiddenimports=[], + hookspath=[], + runtime_hooks=['bundle/_rt_hook.py'], + excludes=['lzma', 'bz2'], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + exclude_binaries=True, + name='EntranceRandomizer', + debug=False, + strip=False, + upx=False, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name='EntranceRandomizer') diff --git a/bundle/_rt_hook.py b/bundle/_rt_hook.py new file mode 100644 index 00000000..17ea446b --- /dev/null +++ b/bundle/_rt_hook.py @@ -0,0 +1,4 @@ +import sys +import os + +sys.path.append(os.path.join(sys._MEIPASS, "ext")) From 06dbbce2be3d363d914f51a31e762860547fc9ff Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Wed, 15 Nov 2017 23:52:26 -0500 Subject: [PATCH 12/19] Add Appveyor integration --- appveyor.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..eec0e1ef --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,32 @@ +version: '{build}' +pull_requests: + do_not_increment_build_number: true +environment: + ProjectVersion: build$(APPVEYOR_BUILD_VERSION) + matrix: + - PYTHON: C:\PYTHON36 +install: + - ps: 'if(Test-Path env:APPVEYOR_REPO_TAG_NAME) {$env:ProjectVersion=$env:APPVEYOR_REPO_TAG_NAME}' + - '%PYTHON%\python.exe --version' + - '%PYTHON%\Scripts\pip install pyinstaller' + - '%PYTHON%\Scripts\pip install markdown' + - '%PYTHON%\python.exe -m markdown README.md > README.html' + - 'copy LICENSE LICENSE.txt' + - '%PYTHON%\Scripts\pyinstaller bundle\EntranceRandomizer.spec' + - 'mkdir dist\EntranceRandomizer\ext' + - 'move dist\EntranceRandomizer\*.pyd dist\EntranceRandomizer\ext' + - 'move dist\EntranceRandomizer\tcl*.dll dist\EntranceRandomizer\ext' + - 'move dist\EntranceRandomizer\tk*.dll dist\EntranceRandomizer\ext' +build: off +artifacts: +- path: dist/EntranceRandomizer/ + name: EntranceRandomizer-$(ProjectVersion)-win32 +deploy: +- provider: GitHub + tag: $(APPVEYOR_REPO_TAG_NAME) + auth_token: + secure: wQH+KnogyjYcDdo/srOQeoYEVIbH1uoYA5Iajdy/sR0Tbme7gOt15u2FBIkTg9/x + artifact: /.*-win32.zip/ + force_update: false + on: + appveyor_repo_tag: true From 91c7fdaf2d3789b9018aeeab29911f221d710f80 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Tue, 21 Nov 2017 08:03:48 -0500 Subject: [PATCH 13/19] Update Spec File --- bundle/EntranceRandomizer.spec | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bundle/EntranceRandomizer.spec b/bundle/EntranceRandomizer.spec index 975255e6..2e73e6ab 100644 --- a/bundle/EntranceRandomizer.spec +++ b/bundle/EntranceRandomizer.spec @@ -1,9 +1,9 @@ # -*- mode: python -*- - +from PyInstaller.compat import is_win block_cipher = None - -a = Analysis(['..\\EntranceRandomizer.py'], +# Todo: the runtime hooks should only be installed on windows +a = Analysis(['../EntranceRandomizer.py'], pathex=['bundle'], binaries=[], datas=[('../data/', 'data/'), ('../README.html', '.'), ('../LICENSE.txt', '.')], @@ -16,6 +16,7 @@ a = Analysis(['..\\EntranceRandomizer.py'], cipher=block_cipher) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + exe = EXE(pyz, a.scripts, exclude_binaries=True, @@ -23,7 +24,7 @@ exe = EXE(pyz, debug=False, strip=False, upx=False, - console=True ) + console=is_win ) coll = COLLECT(exe, a.binaries, a.zipfiles, @@ -31,3 +32,7 @@ coll = COLLECT(exe, strip=False, upx=False, name='EntranceRandomizer') +app = BUNDLE(coll, + name ='EntranceRandomizer.app', + icon = None, + bundle_identifier = None) From 0de4a5857c4633c7fc9ed06ed583ac283dfdf417 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 25 Nov 2017 21:49:36 -0500 Subject: [PATCH 14/19] Preliminary msi support Added a proper icon Fix output directory for packaged builds Added a button to open the ouput directory, and a button to open documentation for packaged builds. --- .gitignore | 5 +++- Gui.py | 41 +++++++++++++++++++++++---- Main.py | 43 +++++++++++++++++++++++++++-- appveyor.yml | 10 ++++--- bundle/EntranceRandomizer.spec | 5 ++-- bundle/components.xslt | 35 +++++++++++++++++++++++ bundle/installer.wxs | 49 +++++++++++++++++++++++++++++++++ data/ER.icns | Bin 0 -> 177490 bytes data/ER.ico | Bin 0 -> 39223 bytes data/ER16.gif | Bin 0 -> 123 bytes data/ER32.gif | Bin 0 -> 370 bytes data/ER48.gif | Bin 0 -> 882 bytes 12 files changed, 174 insertions(+), 14 deletions(-) create mode 100644 bundle/components.xslt create mode 100644 bundle/installer.wxs create mode 100644 data/ER.icns create mode 100644 data/ER.ico create mode 100644 data/ER16.gif create mode 100644 data/ER32.gif create mode 100644 data/ER48.gif diff --git a/.gitignore b/.gitignore index 5dd284f9..04bc2055 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ dist build .idea *.sfc -*_Spoiler.txt \ No newline at end of file +*_Spoiler.txt +bundle/components.wxs +*.wixobj +README.html diff --git a/Gui.py b/Gui.py index feee07c7..6ddc29ca 100644 --- a/Gui.py +++ b/Gui.py @@ -1,14 +1,18 @@ -from Main import main, __version__ as ESVersion +from Main import main, __version__ as ESVersion, get_output_path from argparse import Namespace import random - -from tkinter import Checkbutton, OptionMenu, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, Entry, Spinbox, Button, filedialog, messagebox +import subprocess +import os +import sys +from tkinter import Checkbutton, OptionMenu, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, Entry, Spinbox, Button, filedialog, messagebox, PhotoImage def guiMain(args=None): mainWindow = Tk() mainWindow.wm_title("Entrance Shuffle %s" % ESVersion) + set_icon(mainWindow) + topFrame = Frame(mainWindow) rightHalfFrame = Frame(topFrame) checkBoxFrame = Frame(rightHalfFrame) @@ -164,6 +168,7 @@ def guiMain(args=None): heartbeepFrame.pack(expand=True, anchor=E) bottomFrame = Frame(mainWindow) + farBottomFrame = Frame(mainWindow) seedLabel = Label(bottomFrame, text='Seed #') seedVar = StringVar() @@ -212,15 +217,29 @@ def guiMain(args=None): generateButton = Button(bottomFrame, text='Generate Patched Rom', command=generateRom) + def open_output(): + open_file(get_output_path()) + + openOutputButton = Button(farBottomFrame, text='Open Output Directory', command=open_output) + + if os.path.exists('README.html'): + def open_readme(): + open_file('README.html') + openReadmeButton = Button(farBottomFrame, text='Open Documentation', command=open_readme) + openReadmeButton.pack(side=LEFT) + seedLabel.pack(side=LEFT) seedEntry.pack(side=LEFT) - countLabel.pack(side=LEFT) + countLabel.pack(side=LEFT, padx=(5,0)) countSpinbox.pack(side=LEFT) - generateButton.pack(side=LEFT) + generateButton.pack(side=LEFT, padx=(5,0)) + + openOutputButton.pack(side=RIGHT) drowDownFrame.pack(side=LEFT) rightHalfFrame.pack(side=RIGHT) topFrame.pack(side=TOP) + farBottomFrame.pack(side=BOTTOM, fill=X, padx=5, pady=5) bottomFrame.pack(side=BOTTOM) if args is not None: @@ -254,6 +273,18 @@ def guiMain(args=None): mainWindow.mainloop() +def open_file(filename): + if sys.platform == 'win32': + os.startfile(filename) + else: + open_Command = 'open' if sys.plaform == 'darwin' else 'xdg-open' + subprocess.call([open_command, filename]) + +def set_icon(window): + er16 = PhotoImage(file='data/ER16.gif') + er32 = PhotoImage(file='data/ER32.gif') + er48 = PhotoImage(file='data/ER32.gif') + window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) if __name__ == '__main__': guiMain() diff --git a/Main.py b/Main.py index 8b49044e..3cbc8758 100644 --- a/Main.py +++ b/Main.py @@ -12,6 +12,8 @@ import random import time import logging import json +import sys +import os __version__ = '0.5.0-dev' @@ -105,16 +107,53 @@ def main(args, seed=None): if args.jsonout: print(json.dumps({'patch': rom.patches, 'spoiler': world.spoiler.to_json()})) else: - rom.write_to_file(args.jsonout or '%s.sfc' % outfilebase) + rom.write_to_file(args.jsonout or os.path.join(get_output_path(),'%s.sfc' % outfilebase)) if args.create_spoiler and not args.jsonout: - world.spoiler.to_file('%s_Spoiler.txt' % outfilebase) + world.spoiler.to_file(os.path.join(get_output_path(),'%s_Spoiler.txt' % outfilebase)) logger.info('Done. Enjoy.') logger.debug('Total Time: %s' % (time.clock() - start)) return world +def get_output_path(): + if get_output_path.cached_path is not None: + return get_output_path.cached_path + + if not hasattr(sys, 'frozen'): + get_output_path.cached_path = '.' + return get_output_path.cached_path + else: + # has been packaged, so cannot use CWD for output. + if sys.platform == 'win32': + #windows + import ctypes.wintypes + CSIDL_PERSONAL = 5 # My Documents + SHGFP_TYPE_CURRENT = 0 # Get current, not default value + + buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) + ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf) + + documents = buf.value + + elif sys.platform == 'darwin': + from AppKit import NSSearchPathForDirectoriesInDomains + # http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains + NSDocumentDirectory = 9 + NSUserDomainMask = 1 + # True for expanding the tilde into a fully qualified path + documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0] + else: + raise NotImplementedError('Not supported yet') + + get_output_path.cached_path = os.path.join(documents, 'ALttPEntranceRandomizer') + if not os.path.exists(get_output_path.cached_path): + os.mkdir(get_output_path.cached_path) + return get_output_path.cached_path + +get_output_path.cached_path = None + def copy_world(world): # ToDo: Not good yet ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity) diff --git a/appveyor.yml b/appveyor.yml index eec0e1ef..19d5b530 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,22 +11,24 @@ install: - '%PYTHON%\Scripts\pip install pyinstaller' - '%PYTHON%\Scripts\pip install markdown' - '%PYTHON%\python.exe -m markdown README.md > README.html' - - 'copy LICENSE LICENSE.txt' - '%PYTHON%\Scripts\pyinstaller bundle\EntranceRandomizer.spec' - 'mkdir dist\EntranceRandomizer\ext' - 'move dist\EntranceRandomizer\*.pyd dist\EntranceRandomizer\ext' - 'move dist\EntranceRandomizer\tcl*.dll dist\EntranceRandomizer\ext' - 'move dist\EntranceRandomizer\tk*.dll dist\EntranceRandomizer\ext' + - '"%WIX%\bin\heat.exe" dir "dist\EntranceRandomizer" -sfrag -srd -suid -dr INSTALLDIR -cg ERFiles -ag -template fragment -t bundle\components.xslt -out build\components.wxs' + - '"%WIX%\bin\candle.exe" -out build\ bundle\*.wxs build\*.wxs' + - '"%WIX%\bin\light.exe" -ext WixUIExtension build\*.wixobj -o dist\EntranceRandomizer.msi -b dist\EntranceRandomizer' build: off artifacts: -- path: dist/EntranceRandomizer/ - name: EntranceRandomizer-$(ProjectVersion)-win32 +- path: dist/EntranceRandomizer.msi + name: EntranceRandomizer-$(ProjectVersion)-win32.msi deploy: - provider: GitHub tag: $(APPVEYOR_REPO_TAG_NAME) auth_token: secure: wQH+KnogyjYcDdo/srOQeoYEVIbH1uoYA5Iajdy/sR0Tbme7gOt15u2FBIkTg9/x - artifact: /.*-win32.zip/ + artifact: /.*-win32.*/ force_update: false on: appveyor_repo_tag: true diff --git a/bundle/EntranceRandomizer.spec b/bundle/EntranceRandomizer.spec index 2e73e6ab..c01d6155 100644 --- a/bundle/EntranceRandomizer.spec +++ b/bundle/EntranceRandomizer.spec @@ -6,7 +6,7 @@ block_cipher = None a = Analysis(['../EntranceRandomizer.py'], pathex=['bundle'], binaries=[], - datas=[('../data/', 'data/'), ('../README.html', '.'), ('../LICENSE.txt', '.')], + datas=[('../data/', 'data/'), ('../README.html', '.')], hiddenimports=[], hookspath=[], runtime_hooks=['bundle/_rt_hook.py'], @@ -24,6 +24,7 @@ exe = EXE(pyz, debug=False, strip=False, upx=False, + icon='data/ER.ico', console=is_win ) coll = COLLECT(exe, a.binaries, @@ -34,5 +35,5 @@ coll = COLLECT(exe, name='EntranceRandomizer') app = BUNDLE(coll, name ='EntranceRandomizer.app', - icon = None, + icon = 'data/ER.icns', bundle_identifier = None) diff --git a/bundle/components.xslt b/bundle/components.xslt new file mode 100644 index 00000000..1b4c7132 --- /dev/null +++ b/bundle/components.xslt @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundle/installer.wxs b/bundle/installer.wxs new file mode 100644 index 00000000..c6ef2185 --- /dev/null +++ b/bundle/installer.wxs @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + INSTALLDIR + + + + + 1 + 1 + + + diff --git a/data/ER.icns b/data/ER.icns new file mode 100644 index 0000000000000000000000000000000000000000..e3a6fbfa5031212c6c017f61349c0a84c04d1fe3 GIT binary patch literal 177490 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�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 literal 0 HcmV?d00001 diff --git a/data/ER.ico b/data/ER.ico new file mode 100644 index 0000000000000000000000000000000000000000..b9b60360b8532828378f2f1f64815004f4b991bd GIT binary patch literal 39223 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 W1IPP!YBKhJ(gxrVJP-Zz^8W#w*G=01 literal 0 HcmV?d00001 diff --git a/data/ER16.gif b/data/ER16.gif new file mode 100644 index 0000000000000000000000000000000000000000..5806c8fd834bc0bf1b248a17c10d5489cfcf8854 GIT binary patch literal 123 zcmZ?wbhEHb6krfwIK;r9W27^uDx@UJWzW1E1|*>PlZBCifrUW_BnDE;!0f=W>yEa8 z*N-L+iKOgYf^pSei`yqx3%E)&J&F*EH;rVE+@)?K7ty`%(f6l29KyHF&h#jm zSTj$CsWHlIZj&n0785n@;JXXI-pt#2hhKQ=oVMC@*{HioTsD#M*S*&lAHJ*-eVBdD z$-1ok+L|W&_*k8?Qk?{cj`pJBD38XBi6zq#J0_WwOf8R|)x3ydZrucj>hfms<>~FS z7qg0Mluyg3ThX~jK~!UX@rwRMd);IA#PxU0J26@O=+a%wmQ6jEZNETst?t#Eld>;;Of*C$wM2GQx+^Z*v!FuAb49q$Uz1H zWv`Zq4T)=f_&GD~oG^57wN_vdNjl-7(BRgg?jI&I^TL9Yll1-feYvqo&9l$K`xY=5 FtpUb#m#+W- literal 0 HcmV?d00001 diff --git a/data/ER48.gif b/data/ER48.gif new file mode 100644 index 0000000000000000000000000000000000000000..06d7af558584d5678e04f345926333009da271dd GIT binary patch literal 882 zcmZ?wbhEHbG+;1bXklRB;o~vs$`Q#DsW~(!MownSET1V^Dm+d+K4v0Ec1__Cc=EM`dmlj5q0tdJp){4oWWZJaha+U18Rn4zsB_%H?57ZYSY_r^LEJV2hHV|-)zk4I6Hk+*&eBn9v7Z1Xe{UZ7ryX`Yij7` zUIlwQ=ZXm_R&^UIj%t^!2t3TrAtJ*vLG#?n`GICqnht#zkN0V{t93XZTW*%99cfs~ z@^od8`E)*CrQn2x)?ruX*gWnmow~Kb`-aKF#|bhg)~wY#=22$;V0umQ$t@4rjw>bOk;6l%ZT z{m6cR>Ej!Ag$Bki;tv@ZjZXB{@UAeAV+>KaaJNi2NMcckaM+K9W$Y;u2ij!ky?9V> z5cEQ+OVjLt5(Cef6>{}FA0(3cU!C1?my!L>jKzJ9Y&#tocvw~}>C_XdT->hiq`743 zUB}A%6;GC4zc>ALCi}A)cR2&(r*rE`KCTaVw?nN#{Z@GEED^7Wq)z>Nnu>F7az1#@ z6spj0psBp?hRVE>t{?G}Yref)zJS4D){EISeI6^P_VY#En Date: Sat, 25 Nov 2017 21:55:48 -0500 Subject: [PATCH 15/19] Update Appveyer to use better naming for msi and zip. --- appveyor.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 19d5b530..3d9ebe17 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,11 +18,13 @@ install: - 'move dist\EntranceRandomizer\tk*.dll dist\EntranceRandomizer\ext' - '"%WIX%\bin\heat.exe" dir "dist\EntranceRandomizer" -sfrag -srd -suid -dr INSTALLDIR -cg ERFiles -ag -template fragment -t bundle\components.xslt -out build\components.wxs' - '"%WIX%\bin\candle.exe" -out build\ bundle\*.wxs build\*.wxs' - - '"%WIX%\bin\light.exe" -ext WixUIExtension build\*.wixobj -o dist\EntranceRandomizer.msi -b dist\EntranceRandomizer' + - '"%WIX%\bin\light.exe" -ext WixUIExtension build\*.wixobj -o dist\EntranceRandomizer-Installer-%ProjectVersion%-win32.msi -b dist\EntranceRandomizer' build: off artifacts: -- path: dist/EntranceRandomizer.msi - name: EntranceRandomizer-$(ProjectVersion)-win32.msi +- path: dist/EntranceRandomizer*.msi + name: EntranceRandomizer-Installer-$(ProjectVersion)-win32.msi +- path: dist/EntranceRandomizer/ + name: EntranceRandomizer-Raw-$(ProjectVersion)-win32.zip deploy: - provider: GitHub tag: $(APPVEYOR_REPO_TAG_NAME) From 7b7dcc12c6be944634f66dbe035db64279c1f7fd Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Mon, 27 Nov 2017 22:03:48 -0500 Subject: [PATCH 16/19] Restructure README.md Make the readme less command-line focused, since non-technical users are encouraged to use the GUI. --- README.md | 234 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 150 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 06009eb8..7c9f5a2f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is a entrance randomizer for _The Legend of Zelda: A Link to the Past_ for the SNES. See http://vt.alttp.run for more details on the normal randomizer. -## Installation +# Installation Clone this repository and then run ```EntranceRandomizer.py``` (requires Python 3). @@ -11,40 +11,9 @@ Alternatively, run ```Gui.py``` for a simple graphical user interface. For releases, a Windows standalone executable is available for users without Python 3. -## Options +# Settings - -``` --h, --help -``` - -Show the help message and exit. - -``` ---create_spoiler -``` - -Output a Spoiler File (default: False) - -``` ---logic [{noglitches,minorglitches}] -``` - -Select Enforcement of Item Requirements. - -### No Glitches - -The game can be completed without knowing how to perform glitches of any kind. - -### Minor Glitches - -May require Fake Flippers, Bunny Revival. (default: noglitches) - -``` ---mode [{standard,open,swordless}] -``` - -Select game mode. (default: open) +## Game Mode ### Standard @@ -74,13 +43,20 @@ Special notes: - The magic barrier to Hyrule Castle Tower can be broken with a Hammer. - The Hammer can be used to activate the Ether and Bombos tablets. -``` ---goal [{ganon,pedestal,dungeons,triforcehunt,crystals}] -``` +## Game Logic +This determines the Item Requirements for each location. -Select completion goal. +### No Glitches -### Ganon (Default) +The game can be completed without knowing how to perform glitches of any kind. + +### Minor Glitches + +May require Fake Flippers, Bunny Revival. + +## Game Goal + +### Ganon Standard game completion requiring you to collect the 7 crystals, defeat Agahnim 2 and then beat Ganon. @@ -109,18 +85,14 @@ Standard game completion requiring you to collect the 7 crystals and then beat G This is only noticeably different if the --shuffleganon option is enabled. -``` ---difficulty [{easy,normal,hard,expert,insane}] -``` - -Select game difficulty. Affects available itempool. (default: normal) +## Game Difficulty ### Easy This setting doubles the number of swords, shields, armors, and bottles in the item pool. Within dungeons, the number of items found will be displayed on screen if there is no timer. -### Normal (Default) +### Normal This is the default setting that has an item pool most similar to the original The Legend of Zelda: A Link to the Past. @@ -142,13 +114,9 @@ pool is less helpful, and the player can find no armor, only a Master Sword, and This setting is a modest step up from Expert. The main difference is that the player will never find any additional health. -``` ---timer [{none,display,timed,timed-ohko,ohko,timed-countdown}] -``` +## Timer Setting -Select the timer setting. - -### None (Default) +### None Does not invoke a timer. @@ -180,11 +148,9 @@ decreased with Red Clocks found in chests that will be added to the itempool. Th is to finish the game without the timer reaching zero, but the game will continue uninterrupted if the player runs out of time. -``` ---progressive [{on,off,random}] -``` +## Progressive equipment -Select the setting for progressive equipment. +Determines if Sword, Shield, and gloves are progressive (upgrading in sequence) or not. ### On (Default) @@ -204,14 +170,12 @@ will simply do nothing. This setting makes swords, shields, armor, and gloves randomly either progressive or not. Each category is independently randomized. -``` ---algorithm [{freshness,flood,vt21,vt22,vt25,vt26,balanced}] -``` +## Item Distribution Algorithm -Select item filling algorithm. +Determines how the items are shuffled. -### Balanced (Default) -This is a variation of vt26 that aims to strike a balance between the overworld heavy vt25 and the dungeon heavy vt26 algorithm. +### Balanced +This is a variation of VT26 that aims to strike a balance between the overworld heavy VT25 and the dungeon heavy VT26 algorithm. It does this by reshuffling the remaining locations after placing dungeon items. ### VT26 @@ -223,14 +187,14 @@ the sheer number of chests in that single location. Items and locations are shuffled and placed from the top of the lists. The only thing preventing an item from being placed into a spot is if is absolutely impossible to be there given the previous made placement choices. Leads to very uniform but guaranteed solvable distributions. +### VT22 +The ordinary VT v8.22 algorithm. Fixes issues in placement in VT21 by discarding all previously skipped and unfilled locations +after 2/3 of the progression items were placed to prevent stale late game locations from soaking up the same items all the time. + ### VT21 The ordinary VT v8.21 algorithm. Unbiased placement of items into unlocked locations, placing items that unlock new locations first. May lead to distributions that seem a bit wonky (high likelyhood of ice rod in Turtle Rock, for instance) -### VT22 -The ordinary VT v8.21 algorithm. Fixes issues in placement in VT21 by discarding all previously skipped and unfilled locations -after 2/3 of the progression items were placed to prevent stale late game locations from soaking up the same items all the time. - ### Flood Pushes out items starting from Link's House and is slightly biased to placing progression items with less restrictions. Use for relatively simple distributions. @@ -238,11 +202,9 @@ Pushes out items starting from Link's House and is slightly biased to placing pr Alternative approach to VT22 to improve on VT21 flaws. Locations that are skipped because they are currently unreachable increase in staleness, decreasing the likelihood of receiving a progress item. -``` ---shuffle [{default,simple,restricted,full,madness,insanity,dungeonsfull,dungeonssimple}] -``` +## Entrance Shuffle Algorithm -Select Entrance Shuffling Algorithm. +Determines how locations are shuffled. ### Default @@ -253,7 +215,7 @@ Is the Vanilla layout. Shuffles Dungeon Entrances/Exits between each other and keeps all 4-entrance dungeons confined to one location. Outside Light World Death Mountain, interiors are shuffled but still connect the same points on the overworld. On Death Mountain, entrances are connected more freely. -### Full (Default) +### Full Mixes cave and dungeon entrances freely. @@ -273,6 +235,118 @@ Madness, but without the light/dark world restrictions. Gives access to Mirror a The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla. +## Heartbeep Sound Rate + +Select frequency of beeps when on low health. Can completely disable them. + +## Create Spoiler Log + +Output a Spoiler File. + +## Do not Create Patched Rom + +If set, will not produce a patched rom as output. Useful in conjunction with the spoiler log option to batch +generate spoilers for statistical analysis. + +## Enable L/R button quickswapping + +Use to enable quick item swap with L/R buttons + +## Instant Menu + +As an alternative to quickswap, opens menu instantly. + +## Keysanity + +This setting allows dungeon specific items (Small Key, Big Key, Map, Compass) to be distributed anywhere in the world and not just +in their native dungeon. Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that +is traditionally a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but +the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell +for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize. + +## Place Dungeon Items + +If not set, Compasses and Maps are removed from the dungeon item pools and replaced by empty chests that may end up anywhere in the world. +This may lead to different amount of itempool items being placed in a dungeon than you are used to. + +## Only Ensure Seed Beatable + +If set, will only ensure the goal can be achieved, but not necessarily that all locations are reachable. Currently only affects VT25, VT26 and balanced algorithms. + +## Include Ganon's Tower and Pyramid Hole in Shuffle pool + +If set, Ganon's Tower is included in the dungeon shuffle pool and the Pyramid Hole/Exit pair is included in the Holes shuffle pool. Ganon can not be defeated until the primary goal is fulfilled. This setting removes any bias against Ganon's Tower that some algorithms may have. + +## Seed + +Can be used to set a seed number to generate. Using the same seed with same settings on the same version of the entrance randomizer will always yield an identical output. + +## Count + +Use to batch generate multiple seeds with same settings. If a seed number is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with the same seed number given will produce the same 10 (different) roms each time). + +# Command Line Options + +``` +-h, --help +``` + +Show the help message and exit. + +``` +--create_spoiler +``` + +Output a Spoiler File (default: False) + +``` +--logic [{noglitches,minorglitches}] +``` + +Select the game logic (default: noglitches) + +``` +--mode [{standard,open,swordless}] +``` + +Select the game mode. (default: open) + +``` +--goal [{ganon,pedestal,dungeons,triforcehunt,crystals}] +``` + +Select the game completion goal. (default: ganon) + +``` +--difficulty [{easy,normal,hard,expert,insane}] +``` + +Select the game difficulty. Affects available itempool. (default: normal) + +``` +--timer [{none,display,timed,timed-ohko,ohko,timed-countdown}] +``` + +Select the timer setting. (default: none) + +``` +--progressive [{on,off,random}] +``` + +Select the setting for progressive equipment. (default: on) + +``` +--algorithm [{freshness,flood,vt21,vt22,vt25,vt26,balanced}] +``` + +Select item distribution algorithm. (default: balanced) + +``` +--shuffle [{default,simple,restricted,full,madness,insanity,dungeonsfull,dungeonssimple}] +``` + +Select entrance shuffle algorithm. (default: full) + ``` --rom ROM ``` @@ -289,14 +363,13 @@ Select level of logging for output. (default: info) --seed SEED ``` -Define seed number to generate. (default: None) Using the same seed with same settings on the same version of the entrance randomizer will always yield an identical output. +Define seed number to generate. (default: None) ``` --count COUNT ``` -Use to batch generate multiple seeds with same settings. -If --seed is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with --seed given will produce the same 10 (different) roms each time). (default: None) +Set the count option (default: None) ``` --quickswap @@ -321,11 +394,7 @@ Disables game music, resulting in the game sound being just the SFX. (default: F --keysanity ``` -This setting allows dungeon specific items (Small Key, Big Key, Map, Compass) to be distributed anywhere in the world and not just -in their native dungeon. Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that -is traditionally a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but -the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell -for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize. +Enable Keysanity (default: False) ``` --nodungeonitems @@ -350,22 +419,19 @@ Use to select a different sprite sheet to use for Link. Path to a binary file of --beatableonly ``` -If set, will only ensure the goal can be achieved, but not necessarily that all locations are reachable. Currently only affects VT25, VT26 and balanced algorithms. +Enables the "Only Ensure Seed Beatable" option (default: False) ``` --shuffleganon ``` -If set, Ganon's Tower is included in the dungeon shuffle pool and the Pyramid Hole/Exit pair is included in the Holes shuffle pool. Ganon can not be defeated until the primary goal is fulfilled. -This setting removes any bias against Ganon's Tower that some algorithms may have. - -Note: This option is under development and may sometimes lead to dungeon and crystal distributions that cannot be solved. If this is the case, the generation will fail. Simply retry with a different seed number if you run into this issue. +Enables the "Include Ganon's Tower and Pyramid Hole in Shuffle pool" option. (default: false) ``` --suppress_rom ``` -If set, will not produce a patched rom as output. Useful to batch generate spoilers for statistical analysis. +Enables the "Do not Create Patched Rom" option. (default: False) ``` --gui From 793eaeed6586d0e52fc3671e9f9b329268b0a487 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Tue, 28 Nov 2017 09:36:32 -0500 Subject: [PATCH 17/19] Create a utils file for low level helpers like path utilities --- EntranceRandomizer.py | 7 +++-- Gui.py | 22 ++++++--------- Main.py | 44 ++--------------------------- Rom.py | 3 +- Utils.py | 64 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 59 deletions(-) create mode 100644 Utils.py diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 80b7e213..7b195486 100644 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -7,6 +7,7 @@ import sys from Main import main from Gui import guiMain +from Utils import is_bundled class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): @@ -190,9 +191,9 @@ if __name__ == '__main__': ''') args = parser.parse_args() - if hasattr(sys, 'frozen') and len(sys.argv) == 1 : - # for the Precompiled windows build, if we have no arguments, the user - # probably wants the gui. Users of the windows build who want the command line + if is_bundled and len(sys.argv) == 1 : + # for the bundled builds, if we have no arguments, the user + # probably wants the gui. Users of the bundled build who want the command line # interface shouuld specify at least one option, possibly setting a value to a # default if they like all the defaults guiMain() diff --git a/Gui.py b/Gui.py index 6ddc29ca..17214273 100644 --- a/Gui.py +++ b/Gui.py @@ -1,4 +1,5 @@ -from Main import main, __version__ as ESVersion, get_output_path +from Main import main, __version__ as ESVersion +from Utils import is_bundled, local_path, output_path, open_file from argparse import Namespace import random import subprocess @@ -218,13 +219,13 @@ def guiMain(args=None): generateButton = Button(bottomFrame, text='Generate Patched Rom', command=generateRom) def open_output(): - open_file(get_output_path()) + open_file(output_path('')) openOutputButton = Button(farBottomFrame, text='Open Output Directory', command=open_output) - if os.path.exists('README.html'): + if os.path.exists(local_path('README.html')): def open_readme(): - open_file('README.html') + open_file(local_path('README.html')) openReadmeButton = Button(farBottomFrame, text='Open Documentation', command=open_readme) openReadmeButton.pack(side=LEFT) @@ -273,17 +274,10 @@ def guiMain(args=None): mainWindow.mainloop() -def open_file(filename): - if sys.platform == 'win32': - os.startfile(filename) - else: - open_Command = 'open' if sys.plaform == 'darwin' else 'xdg-open' - subprocess.call([open_command, filename]) - def set_icon(window): - er16 = PhotoImage(file='data/ER16.gif') - er32 = PhotoImage(file='data/ER32.gif') - er48 = PhotoImage(file='data/ER32.gif') + er16 = PhotoImage(file=local_path('data/ER16.gif')) + er32 = PhotoImage(file=local_path('data/ER32.gif')) + er48 = PhotoImage(file=local_path('data/ER32.gif')) window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) if __name__ == '__main__': diff --git a/Main.py b/Main.py index 3cbc8758..e98db109 100644 --- a/Main.py +++ b/Main.py @@ -8,12 +8,11 @@ from Items import ItemFactory from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, fill_restrictive, flood_items from collections import OrderedDict from ItemList import generate_itempool +from Utils import output_path import random import time import logging import json -import sys -import os __version__ = '0.5.0-dev' @@ -107,53 +106,16 @@ def main(args, seed=None): if args.jsonout: print(json.dumps({'patch': rom.patches, 'spoiler': world.spoiler.to_json()})) else: - rom.write_to_file(args.jsonout or os.path.join(get_output_path(),'%s.sfc' % outfilebase)) + rom.write_to_file(args.jsonout or output_path('%s.sfc' % outfilebase)) if args.create_spoiler and not args.jsonout: - world.spoiler.to_file(os.path.join(get_output_path(),'%s_Spoiler.txt' % outfilebase)) + world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) logger.info('Done. Enjoy.') logger.debug('Total Time: %s' % (time.clock() - start)) return world -def get_output_path(): - if get_output_path.cached_path is not None: - return get_output_path.cached_path - - if not hasattr(sys, 'frozen'): - get_output_path.cached_path = '.' - return get_output_path.cached_path - else: - # has been packaged, so cannot use CWD for output. - if sys.platform == 'win32': - #windows - import ctypes.wintypes - CSIDL_PERSONAL = 5 # My Documents - SHGFP_TYPE_CURRENT = 0 # Get current, not default value - - buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) - ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf) - - documents = buf.value - - elif sys.platform == 'darwin': - from AppKit import NSSearchPathForDirectoriesInDomains - # http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains - NSDocumentDirectory = 9 - NSUserDomainMask = 1 - # True for expanding the tilde into a fully qualified path - documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0] - else: - raise NotImplementedError('Not supported yet') - - get_output_path.cached_path = os.path.join(documents, 'ALttPEntranceRandomizer') - if not os.path.exists(get_output_path.cached_path): - os.mkdir(get_output_path.cached_path) - return get_output_path.cached_path - -get_output_path.cached_path = None - def copy_world(world): # ToDo: Not good yet ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity) diff --git a/Rom.py b/Rom.py index bc27fa32..24fda4a6 100644 --- a/Rom.py +++ b/Rom.py @@ -2,6 +2,7 @@ from Dungeons import dungeon_music_addresses 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 +from Utils import local_path import random import json import hashlib @@ -66,7 +67,7 @@ class LocalRom(object): self.buffer.extend(bytearray([0x00] * (2097152 - len(self.buffer)))) # load randomizer patches - patches = json.load(open('data/base2current.json', 'r')) + patches = json.load(open(local_path('data/base2current.json'), 'r')) for patch in patches: if isinstance(patch, dict): for baseaddress, values in patch.items(): diff --git a/Utils.py b/Utils.py new file mode 100644 index 00000000..cfc53aa0 --- /dev/null +++ b/Utils.py @@ -0,0 +1,64 @@ +import os +import sys + +def is_bundled(): + return getattr(sys, 'frozen', False) + +def local_path(path): + if local_path.cached_path is not None: + return os.path.join(local_path.cached_path, path) + + if is_bundled(): + # we are running in a bundle + local_path.cached_path = sys._MEIPASS + else: + # we are running in a normal Python environment + local_path.cached_path = os.path.dirname(os.path.abspath(__file__)) + + return os.path.join(local_path.cached_path, path) + +local_path.cached_path = None + +def output_path(path): + if output_path.cached_path is not None: + return os.path.join(output_path.cached_path, path) + + if not is_bundled(): + output_path.cached_path = '.' + return os.path.join(output_path.cached_path, path) + else: + # has been packaged, so cannot use CWD for output. + if sys.platform == 'win32': + #windows + import ctypes.wintypes + CSIDL_PERSONAL = 5 # My Documents + SHGFP_TYPE_CURRENT = 0 # Get current, not default value + + buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) + ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf) + + documents = buf.value + + elif sys.platform == 'darwin': + from AppKit import NSSearchPathForDirectoriesInDomains + # http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains + NSDocumentDirectory = 9 + NSUserDomainMask = 1 + # True for expanding the tilde into a fully qualified path + documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0] + else: + raise NotImplementedError('Not supported yet') + + output_path.cached_path = os.path.join(documents, 'ALttPEntranceRandomizer') + if not os.path.exists(output_path.cached_path): + os.mkdir(output_path.cached_path) + return os.path.join(output_path.cached_path, path) + +output_path.cached_path = None + +def open_file(filename): + if sys.platform == 'win32': + os.startfile(filename) + else: + open_Command = 'open' if sys.platform == 'darwin' else 'xdg-open' + subprocess.call([open_command, filename]) From c760ac1766f2032ee1685eda6e91770fbf29d3d4 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 2 Dec 2017 09:21:04 -0500 Subject: [PATCH 18/19] Add versioning to MSI installer. Also close console window when not using CLI. Remove deployment section of appveyor.yml for the time being. --- EntranceRandomizer.py | 3 ++- Utils.py | 9 +++++++++ appveyor.yml | 10 +--------- bundle/installer.wxs | 5 ++++- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 7b195486..f44dee52 100644 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -7,7 +7,7 @@ import sys from Main import main from Gui import guiMain -from Utils import is_bundled +from Utils import is_bundled, close_console class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): @@ -196,6 +196,7 @@ if __name__ == '__main__': # probably wants the gui. Users of the bundled build who want the command line # interface shouuld specify at least one option, possibly setting a value to a # default if they like all the defaults + close_console() guiMain() sys.exit(0) diff --git a/Utils.py b/Utils.py index cfc53aa0..30703287 100644 --- a/Utils.py +++ b/Utils.py @@ -62,3 +62,12 @@ def open_file(filename): else: open_Command = 'open' if sys.platform == 'darwin' else 'xdg-open' subprocess.call([open_command, filename]) + +def close_console(): + if sys.platform == 'win32': + #windows + import ctypes.wintypes + try: + ctypes.windll.kernel32.FreeConsole() + except: + pass diff --git a/appveyor.yml b/appveyor.yml index 3d9ebe17..a36537ac 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,6 +16,7 @@ install: - 'move dist\EntranceRandomizer\*.pyd dist\EntranceRandomizer\ext' - 'move dist\EntranceRandomizer\tcl*.dll dist\EntranceRandomizer\ext' - 'move dist\EntranceRandomizer\tk*.dll dist\EntranceRandomizer\ext' + - ps: '$env:ER_Version= &"$env:PYTHON\python.exe" -c "import Main; import re; print(re.match(''[0-9]+\\.[0-9]+\\.[0-9]+'',Main.__version__).group(0))"' - '"%WIX%\bin\heat.exe" dir "dist\EntranceRandomizer" -sfrag -srd -suid -dr INSTALLDIR -cg ERFiles -ag -template fragment -t bundle\components.xslt -out build\components.wxs' - '"%WIX%\bin\candle.exe" -out build\ bundle\*.wxs build\*.wxs' - '"%WIX%\bin\light.exe" -ext WixUIExtension build\*.wixobj -o dist\EntranceRandomizer-Installer-%ProjectVersion%-win32.msi -b dist\EntranceRandomizer' @@ -25,12 +26,3 @@ artifacts: name: EntranceRandomizer-Installer-$(ProjectVersion)-win32.msi - path: dist/EntranceRandomizer/ name: EntranceRandomizer-Raw-$(ProjectVersion)-win32.zip -deploy: -- provider: GitHub - tag: $(APPVEYOR_REPO_TAG_NAME) - auth_token: - secure: wQH+KnogyjYcDdo/srOQeoYEVIbH1uoYA5Iajdy/sR0Tbme7gOt15u2FBIkTg9/x - artifact: /.*-win32.*/ - force_update: false - on: - appveyor_repo_tag: true diff --git a/bundle/installer.wxs b/bundle/installer.wxs index c6ef2185..f5d111d7 100644 --- a/bundle/installer.wxs +++ b/bundle/installer.wxs @@ -1,7 +1,10 @@ - + + + From d9182a4adb3798a582f4c167f56f61f4b855849d Mon Sep 17 00:00:00 2001 From: AmazingAmpharos Date: Sat, 2 Dec 2017 16:15:25 -0600 Subject: [PATCH 19/19] Updated version number and logic hash Pretty self explanatory --- Main.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Main.py b/Main.py index e98db109..e39df18b 100644 --- a/Main.py +++ b/Main.py @@ -14,16 +14,16 @@ import time import logging import json -__version__ = '0.5.0-dev' +__version__ = '0.5.1-dev' -logic_hash = [217, 163, 29, 168, 46, 16, 56, 85, 183, 60, 44, 118, 98, 125, 64, 42, 161, 36, 131, 95, 247, 37, 127, 164, 47, 14, 19, 40, 96, 174, 67, 200, - 240, 119, 189, 4, 243, 155, 162, 32, 159, 186, 84, 180, 233, 99, 86, 242, 105, 26, 216, 196, 249, 214, 45, 70, 72, 224, 78, 87, 93, 182, 38, 248, - 173, 109, 30, 205, 73, 7, 193, 113, 241, 251, 52, 62, 171, 43, 41, 222, 138, 49, 145, 170, 103, 48, 21, 235, 74, 110, 176, 201, 253, 114, 68, 117, - 89, 207, 82, 54, 211, 61, 53, 88, 158, 226, 218, 177, 50, 213, 25, 9, 104, 140, 203, 169, 166, 116, 152, 2, 33, 149, 20, 220, 165, 108, 254, 179, - 107, 6, 22, 128, 69, 250, 231, 94, 92, 97, 252, 160, 172, 148, 237, 81, 77, 199, 35, 215, 184, 187, 136, 28, 129, 71, 210, 178, 102, 195, 198, 121, - 80, 135, 111, 151, 17, 223, 228, 238, 51, 147, 133, 79, 55, 12, 122, 1, 100, 120, 225, 202, 144, 63, 185, 208, 181, 204, 134, 142, 188, 146, 126, 27, - 153, 91, 191, 13, 157, 5, 59, 234, 83, 141, 23, 15, 18, 236, 137, 31, 143, 209, 229, 34, 132, 57, 75, 0, 230, 190, 90, 115, 76, 123, 197, 39, - 3, 206, 255, 112, 244, 167, 212, 154, 65, 124, 219, 221, 106, 139, 175, 10, 101, 239, 150, 227, 11, 246, 24, 156, 8, 130, 245, 66, 194, 58, 232, 192] +logic_hash = [117, 227, 77, 12, 94, 219, 67, 70, 58, 42, 7, 75, 132, 55, 130, 97, 235, 46, 206, 185, 243, 64, 109, 161, 107, 91, 224, 142, 25, 84, 4, 78, + 160, 245, 143, 18, 251, 114, 165, 157, 13, 26, 119, 92, 188, 216, 27, 39, 76, 238, 152, 113, 231, 193, 191, 103, 118, 182, 213, 134, 41, 90, 246, 82, + 57, 225, 150, 139, 99, 151, 184, 11, 85, 209, 144, 147, 47, 56, 129, 247, 121, 177, 79, 1, 215, 207, 126, 136, 105, 100, 180, 5, 2, 14, 153, 6, + 163, 192, 198, 88, 98, 174, 149, 201, 249, 200, 158, 116, 196, 80, 220, 31, 111, 214, 194, 248, 221, 167, 250, 115, 38, 10, 32, 218, 133, 19, 253, 122, + 239, 16, 52, 48, 156, 205, 127, 3, 138, 237, 234, 190, 37, 112, 189, 86, 223, 236, 195, 54, 71, 181, 43, 49, 226, 255, 0, 135, 186, 203, 175, 87, + 21, 229, 120, 124, 145, 171, 252, 155, 22, 62, 199, 51, 35, 179, 159, 44, 69, 30, 172, 242, 140, 74, 9, 83, 183, 93, 202, 137, 108, 241, 173, 23, + 164, 45, 222, 232, 166, 176, 230, 63, 154, 96, 170, 34, 66, 50, 17, 211, 95, 53, 208, 244, 36, 123, 81, 187, 106, 131, 169, 29, 104, 72, 101, 141, + 68, 24, 168, 125, 217, 240, 15, 162, 148, 8, 40, 102, 33, 89, 128, 61, 210, 204, 73, 228, 59, 146, 28, 110, 233, 178, 254, 65, 197, 20, 212, 60] def main(args, seed=None):