diff --git a/BaseClasses.py b/BaseClasses.py index b5495b4a..60cc1c74 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -139,6 +139,7 @@ class World(object): set_player_attr('treasure_hunt_total', 0) set_player_attr('potshuffle', False) set_player_attr('pot_contents', None) + set_player_attr('fakeboots', False) set_player_attr('shopsanity', False) set_player_attr('keydropshuffle', False) @@ -2430,7 +2431,7 @@ access_mode = {"items": 0, "locations": 1, "none": 2} # byte 6: BSMC BBEE (big, small, maps, compass, bosses, enemies) boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3, "chaos": 3} -enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2} +enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2, "legacy": 3} # byte 7: HHHD DP?? (enemy_health, enemy_dmg, potshuffle, ?) e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4} diff --git a/CLI.py b/CLI.py index 467ccf0d..1b6fae40 100644 --- a/CLI.py +++ b/CLI.py @@ -98,7 +98,7 @@ def parse_cli(argv, no_defaults=False): 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', - 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', + 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'fakeboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', @@ -149,6 +149,7 @@ def parse_settings(): "ow_fluteshuffle": "vanilla", "shuffle": "vanilla", "shufflelinks": False, + "fakeboots": False, "shufflepots": False, "shuffleenemies": "none", diff --git a/EntranceShuffle.py b/EntranceShuffle.py index ef0d7711..54608e08 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1482,14 +1482,12 @@ def link_entrances(world, player): blacksmith_hut = blacksmith_doors.pop() connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) doors.remove(blacksmith_hut) - exit_pool.remove(blacksmith_hut) # place dam and pyramid fairy, have limited options random.shuffle(bomb_shop_doors) bomb_shop = bomb_shop_doors.pop() connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) doors.remove(bomb_shop) - exit_pool.remove(bomb_shop) # handle remaining caves for cave in caves: diff --git a/Fill.py b/Fill.py index 9a9ebd0f..5e1176a3 100644 --- a/Fill.py +++ b/Fill.py @@ -3,7 +3,7 @@ import logging from BaseClasses import CollectionState from Items import ItemFactory -from Regions import shop_to_location_table +from Regions import shop_to_location_table, retro_shops class FillError(RuntimeError): @@ -496,6 +496,10 @@ def balance_multiworld_progression(world): new_location = replacement_locations.pop() new_location.item, old_location.item = old_location.item, new_location.item + if world.shopsanity[new_location.player]: + check_shop_swap(new_location) + if world.shopsanity[old_location.player]: + check_shop_swap(old_location) new_location.event, old_location.event = True, False state.collect(new_location.item, True, new_location) replaced_items = True @@ -516,6 +520,18 @@ def balance_multiworld_progression(world): raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') +def check_shop_swap(l): + if l.parent_region.name in shop_to_location_table: + if l.name in shop_to_location_table[l.parent_region.name]: + idx = shop_to_location_table[l.parent_region.name].index(l.name) + inv_slot = l.parent_region.shop.inventory[idx] + inv_slot['item'] = l.item.name + elif l.parent_region in retro_shops: + idx = retro_shops[l.parent_region.name].index(l.name) + inv_slot = l.parent_region.shop.inventory[idx] + inv_slot['item'] = l.item.name + + def balance_money_progression(world): logger = logging.getLogger('') state = CollectionState(world) @@ -557,7 +573,6 @@ def balance_money_progression(world): for player in range(1, world.players+1): logger.debug(f'Money balance for P{player}: Needed: {total_price[player]} Available: {available_money[player]}') - def get_sphere_locations(sphere_state, locations): sphere_state.sweep_for_events(key_only=True, locations=locations) return [loc for loc in locations if sphere_state.can_reach(loc) and sphere_state.not_flooding_a_key(sphere_state.world, loc)] @@ -672,6 +687,7 @@ def balance_money_progression(world): logger.debug(f'Upgrading {best_target.item.name} @ {best_target.name} for 300 Rupees') best_target.item = ItemFactory('Rupees (300)', best_target.item.player) best_target.item.location = best_target + check_shop_swap(best_target.item.location) else: old_item = best_target.item logger.debug(f'Swapping {best_target.item.name} @ {best_target.name} for {best_swap.item.name} @ {best_swap.name}') @@ -679,6 +695,8 @@ def balance_money_progression(world): best_target.item.location = best_target best_swap.item = old_item best_swap.item.location = best_swap + check_shop_swap(best_target.item.location) + check_shop_swap(best_swap.item.location) increase = best_value - old_value difference -= increase wallet[target_player] += increase diff --git a/Main.py b/Main.py index 7faef477..b185c128 100644 --- a/Main.py +++ b/Main.py @@ -28,7 +28,7 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names -__version__ = '0.4.0.6-u' +__version__ = '0.4.0.8-u' class EnemizerError(RuntimeError): @@ -93,6 +93,7 @@ def main(args, seed=None, fish=None): world.treasure_hunt_count = args.triforce_goal.copy() world.treasure_hunt_total = args.triforce_pool.copy() world.shufflelinks = args.shufflelinks.copy() + world.fakeboots = args.fakeboots.copy() world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} @@ -263,12 +264,10 @@ def main(args, seed=None, fish=None): or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default' or sprite_random_on_hit) - if use_enemizer: - base_patch = LocalRom(args.rom) # update base2current.json - rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom) if use_enemizer and (args.enemizercli or not args.jsonout): + base_patch = LocalRom(args.rom) # update base2current.json (side effect) if args.rom and not(os.path.isfile(args.rom)): raise RuntimeError("Could not find valid base rom for enemizing at expected path %s." % args.rom) if os.path.exists(args.enemizercli): diff --git a/Mystery.py b/Mystery.py index 9fb6155a..a614702b 100644 --- a/Mystery.py +++ b/Mystery.py @@ -150,6 +150,7 @@ def roll_settings(weights): ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle == 'on' else 'off' ret.shufflelinks = get_choice('shufflelinks') == 'on' + ret.fakeboots = get_choice('fakeboots') == 'on' ret.shopsanity = get_choice('shopsanity') == 'on' ret.keydropshuffle = get_choice('keydropshuffle') == 'on' ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent' @@ -202,15 +203,17 @@ def roll_settings(weights): boss_choice = old_style_bosses[boss_choice] ret.shufflebosses = boss_choice - ret.shuffleenemies = {'none': 'none', - 'shuffled': 'shuffled', - 'random': 'chaos' - }[get_choice('enemy_shuffle')] + enemy_choice = get_choice('enemy_shuffle') + if enemy_choice == 'chaos': + enemy_choice = 'random' + ret.shuffleenemies = enemy_choice - ret.enemy_damage = {'default': 'default', - 'shuffled': 'shuffled', - 'random': 'chaos' - }[get_choice('enemy_damage')] + old_style_damage = {'none': 'default', + 'chaos': 'random'} + damage_choice = get_choice('enemy_damage') + if damage_choice in old_style_damage: + damage_choice = old_style_damage[damage_choice] + ret.enemy_damage = damage_choice ret.enemy_health = get_choice('enemy_health') diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8097eff2..dd2ce1fc 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -14,6 +14,35 @@ Thanks to qadan, cheuer, & compiling # Bug Fixes and Notes. +* 0.4.0.8 + * Ganon jokes added for when silvers aren't available + * Some text updated (Blind jokes, uncle text) + * Fixed some enemizer Mystery settings + * Added a setting that's random enemy shuffle without Unkillable Thieves possible + * Fixed shop spoiler when money balancing/multiworld balancing + * Fixed a problem with insanity + * Fixed an issue with the credit stats specific to DR (e.g. collection rate total) + * More helpful error message when bps is missing? + * Minor generation issues involving enemizer and the link sprite + * Baserom updates (from Bonta, kan, qwertymodo, ardnaxelark) + * Boss icon on dungeon map (if you have a compass) + * Progressive bow sprite replacement + * Quickswap - consecutive special swaps + * Bonk Counter + * One mind + * MSU fix + * Chest turn tracking (not yet in credits) + * Damaged and magic stats in credits (gt bk removed) + * Fix for infinite bombs + * Fake boots option + * Always allowed medallions for swordless (no option yet) +* 0.4.0.7 + * Reduce flashing option added + * Sprite author credit added + * Ranged Crystal switch rules tweaked + * Baserom update: includes Credits Speedup, reduced flashing option, msu resume (but turned off by default) + * Create link sprite's zspr from local ROM and no longer attempts to download it from website + * Some minor bug fixes * 0.4.0.6 * Hints now default to off * The maiden gives you a hint to the attic if you bring her to the unlit boss room diff --git a/Rom.py b/Rom.py index 3f46f8d9..bfddd7cf 100644 --- a/Rom.py +++ b/Rom.py @@ -9,17 +9,20 @@ import random import struct import sys import subprocess -import bps.apply -import bps.io +try: + import bps.apply + import bps.io +except ImportError: + raise Exception('Could not load BPS module') from BaseClasses import CollectionState, ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, PotItem from DoorShuffle import compass_data, DROptions, boss_indicator from Dungeons import dungeon_music_addresses -from KeyDoorShuffle import count_locations_exclude_logic from Regions import location_table, shop_to_location_table, retro_shops from RoomData import DoorKind from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable -from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts +from Text import Uncle_texts, Ganon1_texts, Ganon_Phase_3_No_Silvers_texts, TavernMan_texts, Sahasrahla2_texts +from Text import Triforce_texts, Blind_texts, BombShop2_texts, junk_texts from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc from Items import ItemFactory @@ -28,7 +31,7 @@ from OverworldShuffle import default_flute_connections, flute_data JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '5d2041f4387123c2de98dd41e6e5c4c6' +RANDOMIZERBASEHASH = 'f0a6138148c13414ff4dc89dc0101de6' class JsonRom(object): @@ -201,7 +204,7 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, random_sprite_ options = { 'RandomizeEnemies': world.enemy_shuffle[player] != 'none', 'RandomizeEnemiesType': 3, - 'RandomizeBushEnemyChance': world.enemy_shuffle[player] == 'random', + 'RandomizeBushEnemyChance': world.enemy_shuffle[player] in ['random', 'legacy'], 'RandomizeEnemyHealthRange': world.enemy_health[player] != 'default', 'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[world.enemy_health[player]], 'OHKO': False, @@ -247,9 +250,9 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, random_sprite_ 'SwordGraphics': "sword_gfx/normal.gfx", 'BeeMizer': False, 'BeesLevel': 0, - 'RandomizeTileTrapPattern': world.enemy_shuffle[player] == 'random', + 'RandomizeTileTrapPattern': world.enemy_shuffle[player] in ['random', 'legacy'], 'RandomizeTileTrapFloorTile': False, - 'AllowKillableThief': bool(random.randint(0, 1)) if world.enemy_shuffle[player] == 'random' else world.enemy_shuffle[player] != 'none', + 'AllowKillableThief': bool(random.randint(0, 1)) if world.enemy_shuffle[player] == 'legacy' else world.enemy_shuffle[player] != 'none', 'RandomizeSpriteOnHit': random_sprite_on_hit, 'DebugMode': False, 'DebugForceEnemy': False, @@ -891,7 +894,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): write_int16(rom, 0x187010, credits_total) # dynamic credits if credits_total != 216: # collection rate address: - cr_address = 0x2391CC + cr_address = 0x2391C4 cr_pc = cr_address - 0x120000 # convert to pc mid_top, mid_bot = credits_digit((credits_total // 10) % 10) last_top, last_bot = credits_digit(credits_total % 10) @@ -902,25 +905,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(cr_pc+0x3a, mid_bot) rom.write_byte(cr_pc+0x3b, last_bot) - if world.keydropshuffle[player] or world.doorShuffle[player] != 'vanilla': - gt = world.dungeon_layouts[player]['Ganons Tower'] - gt_logic = world.key_logic[player]['Ganons Tower'] - total = 0 - for region in gt.master_sector.regions: - total += count_locations_exclude_logic(region.locations, gt_logic) - # rom.write_byte(0x187012, total) # dynamic credits - # gt big key address: - gtbk_address = 0x2390EE - gtbk_pc = gtbk_address - 0x120000 # convert to pc - mid_top, mid_bot = credits_digit(total // 10) - last_top, last_bot = credits_digit(total % 10) - # top half - rom.write_byte(gtbk_pc+0x1c, mid_top) - rom.write_byte(gtbk_pc+0x1d, last_top) - # bottom half - rom.write_byte(gtbk_pc+0x3a, mid_bot) - rom.write_byte(gtbk_pc+0x3b, last_bot) - # patch medallion requirements if world.required_medallions[player][0] == 'Bombos': rom.write_byte(0x180022, 0x00) # requirement @@ -1264,6 +1248,9 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles # Starting equipment + if world.fakeboots[player]: + rom.write_byte(0x18008E, 0x01) + equip = [0] * (0x340 + 0x4F) equip[0x36C] = 0x18 equip[0x36D] = 0x18 @@ -1724,7 +1711,8 @@ def hud_format_text(text): def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, ow_palettes, uw_palettes, reduce_flashing): - if not os.path.exists("data/sprites/official/001.link.1.zspr"): + + if not os.path.exists("data/sprites/official/001.link.1.zspr") and rom.orig_buffer: dump_zspr(rom.orig_buffer[0x80000:0x87000], rom.orig_buffer[0xdd308:0xdd380], rom.orig_buffer[0xdedf5:0xdedf9], "data/sprites/official/001.link.1.zspr", "Nintendo", "Link") @@ -1812,7 +1800,6 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr if reduce_flashing: rom.write_byte(0x18017f, 1) - default_ow_palettes(rom) if ow_palettes == 'random': randomize_ow_palettes(rom) @@ -1883,6 +1870,61 @@ def dump_zspr(basesprite, basepalette, baseglove, outfilename, author_name, spri with open('%s' % outfilename, "wb") as zspr_file: zspr_file.write(write_buffer) +# .zspr file dumping logic copied with permission from SpriteSomething: +# https://github.com/Artheau/SpriteSomething/blob/master/source/meta/classes/spritelib.py#L443 (thanks miketrethewey!) +def dump_zspr(basesprite, basepalette, baseglove, outfilename, author_name, sprite_name): + palettes = basepalette + # Add glove data + palettes.extend(baseglove) + HEADER_STRING = b"ZSPR" + VERSION = 0x01 + SPRITE_TYPE = 0x01 # this format has "1" for the player sprite + RESERVED_BYTES = b'\x00\x00\x00\x00\x00\x00' + QUAD_BYTE_NULL_CHAR = b'\x00\x00\x00\x00' + DOUBLE_BYTE_NULL_CHAR = b'\x00\x00' + SINGLE_BYTE_NULL_CHAR = b'\x00' + + write_buffer = bytearray() + + write_buffer.extend(HEADER_STRING) + write_buffer.extend(struct.pack('B', VERSION)) # as_u8 + checksum_start = len(write_buffer) + write_buffer.extend(QUAD_BYTE_NULL_CHAR) # checksum + sprite_sheet_pointer = len(write_buffer) + write_buffer.extend(QUAD_BYTE_NULL_CHAR) + write_buffer.extend(struct.pack('= 2 or world.swords[player] == 'swordless' if distinguished_prog_bow_loc: prog_bow_locs.remove(distinguished_prog_bow_loc) - silverarrow_hint = (' %s?' % hint_text(distinguished_prog_bow_loc).replace('Ganon\'s', 'my')) if progressive_silvers else '?\nI think not!' - tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint - + hint_phrase = hint_text(distinguished_prog_bow_loc).replace("Ganon's", "my") + silverarrow_hint = f'Did you find the silver arrows {hint_phrase}?' if progressive_silvers else no_silver_text + tt['ganon_phase_3_no_silvers'] = silverarrow_hint if any(prog_bow_locs): - silverarrow_hint = (' %s?' % hint_text(random.choice(prog_bow_locs)).replace('Ganon\'s', 'my')) if progressive_silvers else '?\nI think not!' - tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint + hint_phrase = hint_text(random.choice(prog_bow_locs)).replace("Ganon's", "my") + silverarrow_hint = f'Did you find the silver arrows {hint_phrase}?' if progressive_silvers else no_silver_text + tt['ganon_phase_3_no_silvers_alt'] = silverarrow_hint crystal5 = world.find_items('Crystal 5', player)[0] crystal6 = world.find_items('Crystal 6', player)[0] diff --git a/Text.py b/Text.py index 3bf78af3..c84a202c 100644 --- a/Text.py +++ b/Text.py @@ -21,13 +21,14 @@ text_addresses = {'Pedestal': (0x180300, 256), Uncle_texts = [ + # these ones are er specific 'Good Luck!\nYou will need it.', 'Forward this message to 10 other people or this seed will be awful.', 'I hope you like your seeds bootless and fluteless.', '10\n9\n8\n7\n6\n5\n4\n3\n2\n1\nGo!', 'I\'m off to visit cousin Fritzl.', 'Don\'t forget to check Antlion Cave.' -] * 2 + [ + # these ones are from web randomizer "We're out of\nWeetabix. To\nthe store!", "This seed is\nbootless\nuntil boots.", "Why do we only\nhave one bed?", @@ -66,15 +67,32 @@ Uncle_texts = [ "Get to the\nchop...\ncastle!", "Come with me\nif you want\nto live", "I must go\nmy planet\nneeds me", + "Are we in\ngo mode yet?", + "Darn, I\nthought this\nwas combo.", + "Don't check\nanything I\nwouldn't!", + "I know where\nthe bow is!\n", + "This message\nwill self\ndestruct.", + "Time to cast\nMeteo on\nGanon!", + "I have a\nlong, full\nlife ahead!", + "Why did that\nsoda have a\nskull on it?", + "Something\nrandom just\ncame up.", + "I'm bad at\nthis. Can you\ndo it for me?", + "Link!\n Wake up!\n ... Bye!", + "Text me when\nyou hit\ngo mode.", + "Turn off the\nstove before\nyou leave.", + "It's raining.\nI'm taking\nthe umbrella.", + "Count to 30.\nThen come\nfind me.", + "Gonna shuffle\nall the items\nreal quick." ] Triforce_texts = [ + # these ones are er specific 'Product has Hole in center. Bad seller, 0 out of 5.', 'Who stole the fourth triangle?', 'Trifource?\nMore Like Tritrice, am I right?' '\n Well Done!', 'You just wasted 2 hours of your life.', - 'This was meant to be a trapezoid' -] * 2 + [ + 'This was meant to be a trapezoid', + # these ones are from web randomizer "\n G G", "All your base\nare belong\nto us.", "You have ended\nthe domination\nof Dr. Wily", @@ -107,6 +125,13 @@ Triforce_texts = [ "You get one\nwish. Choose\nwisely, hero!", "Can you please\nbreak us three\nup? Thanks.", " Pick us up\n before we\n get dizzy!", + "Thank you,\nMikey. You’re\n2 minutes late", + "This was a\n7000 series\ntrain.", + " I'd buy\n that for\n a rupee!", + " Did you like\n that bow\n placement?", + "I promise the\nnext seed will\nbe better.", + "\n Honk.", + "Breakfast\nis served!", ] BombShop2_texts = ['Bombs!\nBombs!\nBiggest!\nBestest!\nGreatest!\nBoomest!'] Sahasrahla2_texts = ['You already got my item, idiot.', 'Why are you still talking to me?', 'This text won\'t change.', 'Have you met my brother, Hasarahshla?'] @@ -145,6 +170,34 @@ Blind_texts = [ "I tried to\ncatch fog,\nbut I mist.", "Winter is a\ngreat time\nto chill.", "Do you think\nthe Ice Rod\nis cool?", + "Pyramids?\nI never saw\nthe point.", + "Stone golems\nare created as\nblank slates.", + "Desert humor\nis often dry.\n", + "Ganon is a\nbacon of\ndespair!", + "Butchering\ncows means\nhigh steaks.", + "I can't search\nthe web...\nToo many links", + "I can whistle\nMost pitches\nbut I can't C", + "The Blinds\nStore is\ncurtain death", + "Dark Aga Rooms\nare not a\nbright idea.", + "Best advice\nfor a Goron?\nBe Boulder.", + "Equestrian\nservices are\na stable job.", + "Do I like\ndrills? Just\na bit.", + "I'd shell out\ngood rupees\nfor a conch.", + "Current\naffairs are\nshocking!", + "A lying Goron\ndeals in\nboulderdash.", + "A bread joke?\nEh, it'd be\nhalf baked.", + "I could take\na stab at a\nsword pun.", + "Gloves open\na handful\nof checks", + "Red mail?\nReturn to\nsender.", + "For sale:\nBaby boots,\nNever found", + "SRL or rtGG?\nI prefer the\nLadder", + "Ladders are\nalways up\nto something", + "Zelda's\nfashion is\nvery chic", + "Zombie geese\nare waterfoul.\n", + "I bought some\ncuccos for a\npoultry sum.", + "The stratus of\nclouds is up\nin the air.", + "Tie two ropes\ntogether?!\nI think knot!", + "Time for you\nto go on a\nBlind date!" ] Ganon1_texts = [ "Start your day\nsmiling with a\ndelicious\nwhole grain\nbreakfast\ncreated for\nyour\nincredible\ninsides.", @@ -171,6 +224,40 @@ Ganon1_texts = [ "Life, dreams,\nhope...\nWhere'd they\ncome from? And\nwhere are they\nheaded? These\nthings... I am\ngoing to\ndestroy!", "My minions all\nfailed to\nguard those\nitems?!\n\nWhy am I\nsurrounded by\nincompetent\nfools?!", ] + +Ganon_Phase_3_No_Silvers_texts = [ + "Did you find\nthe arrows on\nPlanet Zebes?", + "Did you find\nthe arrows?\nI think not.", + "Silver arrows?\nI have never\nheard of them", + "Did you find\nthe arrows on\nThe Moon?", + "Did you find\nthe arrows\nIn dev null?", + "I have sold\nthe arrows for\na green big 20", + "Did you find\nThe arrows in\nCount Dracula?", + "Error 404\nSilver arrows\nnot found.", + "No arrows for\nYou today,\nSorry", + "No arrows?\nCheck your\njunk mail." + "Careful, all\nthat spinning\nmakes me dizzy", + "Did you find\nthe arrows in\nJabu's belly?", + "Silver is not\nan appropriate\narrow material", + "Did you find\nthe arrows in\nNarnia?", + "Are you ready\nTo spin\nTo win?", + "DID YOU FIND\nTHE ARROWS IN\nKEFKA'S TOWER", + "Did you find\nthe arrows in\nRecycle Bin?", + "Silver Arrows?\n\nLUL", + "Imagine\nfinding the\narrows", + "Did you find\nsilvers in\nscenic Ohio?", + "Did you find\nThe arrows in\n*mumblemumble*", + "\nSpin To Win!\n", + "did you find\nthe arrows in\nthe hourglass?", + "Silver Arrows\nare so v30", + "OH, NO, THEY\nACTUALLY SAID\nSILVER MARROW", + "SURELY THE\nLEFTMOST TILES\nWILL STAY UP", + "Did you find\nthe arrows in\nWorld 4-2?", + "You Spin Me\nRight Round\nLike A Record", + "SILLY HERO,\nSILVER IS FOR\nWEREWOLVES!", + "Did you find\nthe silvers in\nganti's ears", +] + TavernMan_texts = [ "What do you\ncall a blind\ndinosaur?\na doyouthink-\nhesaurus.", "A blind man\nwalks into\na bar.\nAnd a table.\nAnd a chair.", diff --git a/asm/owrando.asm b/asm/owrando.asm index 03a4443f..f290ba0e 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -11,6 +11,8 @@ dw 0 ;Hooks org $02a999 jsl OWEdgeTransition : nop #4 ;LDA $02A4E3,X : ORA $7EF3CA +;org $02e238 ;LDX #$9E : - DEX : DEX : CMP $DAEE,X : BNE - +;jsl OWSpecialTransition : nop #5 ; flute menu cancel org $0ab7af ;LDA $F2 : ORA $F0 : AND #$C0 @@ -285,7 +287,8 @@ OWNewDestination: ++ lda $84 : !add 1,s : sta $84 : pla : pla .adjustMainAxis - LDA $84 : SEC : SBC #$0400 : AND #$0F00 : ASL : XBA : STA $88 ; vram + ;LDA $84 : SEC : SBC #$0400 : AND #$0F80 : ASL : XBA : STA $88 ; vram + LDA $84 : SEC : SBC #$0400 : AND #$0F00 : ASL : XBA : STA $88 LDA $84 : SEC : SBC #$0010 : AND #$003E : LSR : STA $86 pla : pla : sep #$10 : ldy $418 @@ -346,7 +349,7 @@ OWNewDestination: ; turn into bunny lda $5d : cmp #$17 : beq .return lda #$17 : sta $5d - lda #$01 : sta $02e0 + lda #$01 : sta $2e0 bra .return .nobunny lda $5d : cmp #$17 : bne .return @@ -356,6 +359,11 @@ OWNewDestination: lda $05 : sta $8a rep #$30 : rts } +OWSpecialTransition: +{ + LDX #$9E + - DEX : DEX : CMP $DAEE,X : BNE - +} ;Data org $aaa000 diff --git a/data/base2current.bps b/data/base2current.bps index 2d526a6a..71df5da8 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 96d1f21c..782d37af 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -354,6 +354,10 @@ "action": "store_true", "type": "bool" }, + "fakeboots": { + "action": "store_true", + "type": "bool" + }, "calc_playthrough": { "action": "store_false", "type": "bool" @@ -384,7 +388,8 @@ "choices": [ "none", "shuffled", - "random" + "random", + "legacy" ] }, "enemy_health": { diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 2a1fb25e..10c823a3 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -290,6 +290,7 @@ "Keys are universal, shooting arrows costs rupees,", "and a few other little things make this more like Zelda-1. (default: %(default)s)" ], + "fakeboots": [ " Players starts with fake boots that allow dashing but no item checks (default: %(default)s"], "startinventory": [ "Specifies a list of items that will be in your starting inventory (separated by commas). (default: %(default)s)" ], "usestartinventory": [ "Toggle usage of Starting Inventory." ], "custom": [ "Not supported." ], diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index d9c56e59..7bb13444 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -2,6 +2,7 @@ "gui": { "adjust.nobgm": "Disable Music & MSU-1", "adjust.quickswap": "L/R Quickswapping", + "adjust.reduce_flashing": "Reduce Flashing", "adjust.heartcolor": "Heart Color", "adjust.heartcolor.red": "Red", @@ -86,6 +87,7 @@ "randomizer.enemizer.enemyshuffle.none": "None", "randomizer.enemizer.enemyshuffle.shuffled": "Shuffled", "randomizer.enemizer.enemyshuffle.random": "Random", + "randomizer.enemizer.enemyshuffle.legacy": "Random (including Thieves)", "randomizer.enemizer.bossshuffle": "Boss Shuffle", "randomizer.enemizer.bossshuffle.none": "None", @@ -204,6 +206,7 @@ "randomizer.item.hints": "Include Helpful Hints", "randomizer.item.retro": "Retro mode (universal keys)", + "randomizer.item.fakeboots": "Start with Fake Boots", "randomizer.item.worldstate": "World State", "randomizer.item.worldstate.standard": "Standard", diff --git a/resources/app/gui/randomize/enemizer/widgets.json b/resources/app/gui/randomize/enemizer/widgets.json index bb731cc2..d987d41a 100644 --- a/resources/app/gui/randomize/enemizer/widgets.json +++ b/resources/app/gui/randomize/enemizer/widgets.json @@ -5,7 +5,8 @@ "options": [ "none", "shuffled", - "random" + "random", + "legacy" ] }, "bossshuffle": { diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index 1871dcaf..1f5eb19a 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -4,7 +4,8 @@ "shopsanity": { "type": "checkbox" }, "hints": { "type": "checkbox" - } + }, + "fakeboots": { "type": "checkbox" } }, "leftItemFrame": { "worldstate": { diff --git a/source/classes/constants.py b/source/classes/constants.py index daec53e3..c9740675 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -58,6 +58,7 @@ SETTINGSTOPROCESS = { "hints": "hints", "retro": "retro", "shopsanity": "shopsanity", + "fakeboots": "fakeboots", "worldstate": "mode", "logiclevel": "logic", "goal": "goal", diff --git a/source/gui/randomize/item.py b/source/gui/randomize/item.py index b01892ab..81c957ce 100644 --- a/source/gui/randomize/item.py +++ b/source/gui/randomize/item.py @@ -1,4 +1,4 @@ -from tkinter import ttk, Frame, E, W, LEFT, RIGHT +from tkinter import ttk, Frame, E, W, LEFT, RIGHT, Label import source.gui.widgets as widgets import json import os @@ -17,6 +17,9 @@ def item_page(parent): self.frames["checkboxes"] = Frame(self) self.frames["checkboxes"].pack(anchor=W) + various_options = Label(self.frames["checkboxes"], text="") + various_options.pack(side=LEFT) + self.frames["leftItemFrame"] = Frame(self) self.frames["rightItemFrame"] = Frame(self) self.frames["leftItemFrame"].pack(side=LEFT) @@ -34,7 +37,7 @@ def item_page(parent): self.widgets[key] = dictWidgets[key] packAttrs = {"anchor":E} if self.widgets[key].type == "checkbox": - packAttrs["anchor"] = W + packAttrs["side"] = LEFT self.widgets[key].pack(packAttrs) return self