diff --git a/.gitignore b/.gitignore index 7eaa21a1..eaa59db1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ __pycache__/ *.py[cod] $py.class *-objective.json -*-flag.json \ No newline at end of file +*-flag.json +wip/ \ No newline at end of file diff --git a/README.md b/README.md index c126f7fc..24a8aa70 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ Complete objectives while searching the worlds for characters, espers, and items ## Links -###### Start playing: [ff6wc.com](https://www.ff6wc.com) -###### More Info: [wiki.ff6wc.com](https://wiki.ff6wc.com) +###### Start playing: [ff6worldscollide.com](https://www.ff6worldscollide.com) +###### More Info: [wiki.ff6worldscollide.com](https://wiki.ff6worldscollide.com) ###### Community: [discord](https://discord.gg/5MPeng5) ## Usage diff --git a/args/auction_house.py b/args/auction_house.py index 8df8b3ff..562398ba 100644 --- a/args/auction_house.py +++ b/args/auction_house.py @@ -31,10 +31,10 @@ def flags(args): def options(args): return [ - ("Randomize Items", args.auction_random_items), - ("No Chocobo/Airship", args.auction_no_chocobo_airship), - ("Door Esper Hint", args.auction_door_esper_hint), - ("Max Espers", args.auction_max_espers), + ("Randomize Items", args.auction_random_items, "auction_random_items"), + ("No Chocobo/Airship", args.auction_no_chocobo_airship, "auction_no_chocobo_airship"), + ("Door Esper Hint", args.auction_door_esper_hint, "auction_door_esper_hint"), + ("Max Espers", args.auction_max_espers, "auction_max_espers"), ] def menu(args): diff --git a/args/blitzes.py b/args/blitzes.py index 52660c78..f537c0fd 100644 --- a/args/blitzes.py +++ b/args/blitzes.py @@ -24,8 +24,8 @@ def flags(args): def options(args): return [ - ("Bum Rush Last", args.bum_rush_last), - ("Everyone Learns", args.blitzes_everyone_learns), + ("Bum Rush Last", args.bum_rush_last, "bum_rush_last"), + ("Everyone Learns", args.blitzes_everyone_learns, "blitzes_everyone_learns"), ] def menu(args): diff --git a/args/boss_ai.py b/args/boss_ai.py index d5fdb0a1..cbafc86d 100644 --- a/args/boss_ai.py +++ b/args/boss_ai.py @@ -31,10 +31,10 @@ def flags(args): def options(args): return [ - ("Doom Gaze No Escape", args.doom_gaze_no_escape), - ("Wrexsoul No Zinger", args.wrexsoul_no_zinger), - ("MagiMaster No Ultima", args.magimaster_no_ultima), - ("Chadarnook More Demon", args.chadarnook_more_demon), + ("Doom Gaze No Escape", args.doom_gaze_no_escape, "doom_gaze_no_escape"), + ("Wrexsoul No Zinger", args.wrexsoul_no_zinger, "wrexsoul_no_zinger"), + ("MagiMaster No Ultima", args.magimaster_no_ultima, "magimaster_no_ultima"), + ("Chadarnook More Demon", args.chadarnook_more_demon, "chadarnook_more_demon"), ] def menu(args): diff --git a/args/bosses.py b/args/bosses.py index 9a287fd7..2854fc1c 100644 --- a/args/bosses.py +++ b/args/bosses.py @@ -92,25 +92,25 @@ def options(args): statue_battles = args.statue_boss_location.capitalize() return [ - ("Boss Battles", boss_battles), - ("Dragons", dragon_battles), - ("Statues", statue_battles), - ("Shuffle/Random Phunbaba 3", args.shuffle_random_phunbaba3), - ("Normalize & Distort Stats", args.boss_normalize_distort_stats), - ("Boss Experience", args.boss_experience), - ("No Undead", args.boss_no_undead), - ("Marshal Keep Lobos", args.boss_marshal_keep_lobos), + ("Boss Battles", boss_battles, "boss_battles"), + ("Dragons", dragon_battles, "dragon_battles"), + ("Statues", statue_battles, "statue_battles"), + ("Shuffle/Random Phunbaba 3", args.shuffle_random_phunbaba3, "shuffle_random_phunbaba3"), + ("Normalize & Distort Stats", args.boss_normalize_distort_stats, "boss_normalize_distort_stats"), + ("Boss Experience", args.boss_experience, "boss_experience"), + ("No Undead", args.boss_no_undead, "boss_no_undead"), + ("Marshal Keep Lobos", args.boss_marshal_keep_lobos, "boss_marshal_keep_lobos"), ] def menu(args): entries = options(args) for index, entry in enumerate(entries): - key, value = entry + key, value, unique_name = entry if key == "Shuffle/Random Phunbaba 3": - entries[index] = ("Mix Phunbaba 3", value) + entries[index] = ("Mix Phunbaba 3", value, unique_name) elif key == "Normalize & Distort Stats": - entries[index] = ("Normalize & Distort", value) + entries[index] = ("Normalize & Distort", value, unique_name) return (name(), entries) def log(args): diff --git a/args/bug_fixes.py b/args/bug_fixes.py index 3fcccc28..80b5e85e 100644 --- a/args/bug_fixes.py +++ b/args/bug_fixes.py @@ -47,14 +47,14 @@ def flags(args): def options(args): return [ - ("Sketch", args.fix_sketch), - ("Evade", args.fix_evade), - ("Vanish/Doom", args.fix_vanish_doom), - ("Retort", args.fix_retort), - ("Jump", args.fix_jump), - ("Boss Skip", args.fix_boss_skip), - ("Enemy Damage Counter", args.fix_enemy_damage_counter), - ("Capture", args.fix_capture), + ("Sketch", args.fix_sketch, "fix_sketch"), + ("Evade", args.fix_evade, "fix_evade"), + ("Vanish/Doom", args.fix_vanish_doom, "fix_vanish_doom"), + ("Retort", args.fix_retort, "fix_retort"), + ("Jump", args.fix_jump, "fix_jump"), + ("Boss Skip", args.fix_boss_skip, "fix_boss_skip"), + ("Enemy Damage Counter", args.fix_enemy_damage_counter, "fix_enemy_damage_counter"), + ("Capture", args.fix_capture, "fix_capture"), ] def menu(args): diff --git a/args/challenges.py b/args/challenges.py index fd2e6a94..d70a9d6f 100644 --- a/args/challenges.py +++ b/args/challenges.py @@ -24,6 +24,8 @@ def parse(parser): help = "Life spells cannot be learned. Fenix Downs unavailable (except from starting items). Buckets/inns/tents/events do not revive characters. Phoenix casts Life 3 on party instead of Life") challenges.add_argument("-rls", "--remove-learnable-spells", type = str, help = "Remove spells from learnable sources: Items, Espers, Natural Magic, and Objectives") + challenges.add_argument("-nosaves", "--no-saves", action = "store_true", + help = "Ironmog Mode: You cannot save (but save points still work for Tents/Sleeping Bags)") def process(args): from constants.spells import black_magic_ids, white_magic_ids, gray_magic_ids, spell_id @@ -89,6 +91,8 @@ def flags(args): flags += " -pd" if args.remove_learnable_spells: flags += f" -rls {args.remove_learnable_spells}" + if args.no_saves: + flags += " -nosaves" return flags @@ -100,23 +104,24 @@ def options(args): ultima = "254 MP" return [ - ("No Moogle Charms", args.no_moogle_charms), - ("No Exp Eggs", args.no_exp_eggs), - ("No Illuminas", args.no_illuminas), - ("No Sprint Shoes", args.no_sprint_shoes), - ("No Free Paladin Shields", args.no_free_paladin_shields), - ("No Free Characters/Espers", args.no_free_characters_espers), - ("Permadeath", args.permadeath), - ("Ultima", ultima), - ("Remove Learnable Spells", args.remove_learnable_spell_ids), + ("No Moogle Charms", args.no_moogle_charms, "no_moogle_charms"), + ("No Exp Eggs", args.no_exp_eggs, "no_exp_eggs"), + ("No Illuminas", args.no_illuminas, "no_illuminas"), + ("No Sprint Shoes", args.no_sprint_shoes, "no_sprint_shoes"), + ("No Free Paladin Shields", args.no_free_paladin_shields, "no_free_paladin_shields"), + ("No Free Characters/Espers", args.no_free_characters_espers, "no_free_characters_espers"), + ("Permadeath", args.permadeath, "permadeath"), + ("Ultima", ultima, "ultima"), + ("Remove Learnable Spells", args.remove_learnable_spell_ids, "remove_learnable_spell_ids"), + ("No Saves", args.no_saves, "no_saves"), ] return opts def _format_spells_log_entries(spell_ids): from constants.spells import id_spell spell_entries = [] - for spell_id in spell_ids: - spell_entries.append(("", id_spell[spell_id])) + for i, spell_id in enumerate(spell_ids): + spell_entries.append(("", id_spell[spell_id], f"rls_{i}")) return spell_entries def _format_spells_log_entries(spell_ids): @@ -131,13 +136,13 @@ def menu(args): entries = options(args) for index, entry in enumerate(entries): - key, value = entry + key, value, unique_name = entry if key == "No Free Paladin Shields": - entries[index] = ("No Free Paladin Shlds", entry[1]) + entries[index] = ("No Free Paladin Shlds", entry[1], unique_name) elif key == "No Free Characters/Espers": - entries[index] = ("No Free Chars/Espers", entry[1]) + entries[index] = ("No Free Chars/Espers", entry[1], unique_name) elif key == "Remove Learnable Spells": - entries[index] = ("Remove L. Spells", FlagsRemoveLearnableSpells(value)) # flags sub-menu + entries[index] = ("Remove L. Spells", FlagsRemoveLearnableSpells(value), unique_name) # flags sub-menu return (name(), entries) @@ -147,12 +152,12 @@ def log(args): entries = options(args) for entry in entries: - key, value = entry + key, value, unique_name = entry if key == "Remove Learnable Spells": if len(value) == 0: - entry = (key, "None") + entry = (key, "None", unique_name) else: - entry = (key, "") # The entries will show up on subsequent lines + entry = (key, "", unique_name) # The entries will show up on subsequent lines log.append(format_option(*entry)) for spell_entry in _format_spells_log_entries(value): log.append(format_option(*spell_entry)) diff --git a/args/characters.py b/args/characters.py index 4cb482fa..40c788ac 100644 --- a/args/characters.py +++ b/args/characters.py @@ -39,19 +39,19 @@ def options(args): character_stats = f"{args.character_stat_random_percent_min}-{args.character_stat_random_percent_max}%" return [ - ("Start Average Level", args.start_average_level), - ("Start Level", args.start_level), - ("Start Naked", args.start_naked), - ("Equipable Umaro", args.equipable_umaro), - ("Character Stats", character_stats), + ("Start Average Level", args.start_average_level, "start_average_level"), + ("Start Level", args.start_level, "start_level"), + ("Start Naked", args.start_naked, "start_naked"), + ("Equipable Umaro", args.equipable_umaro, "equipable_umaro"), + ("Character Stats", character_stats, "character_stats"), ] def menu(args): entries = options(args) for index, entry in enumerate(entries): - key, value = entry + key, value, unique_name = entry if key == "Character Stats": - entries[index] = ("Stats", entry[1]) + entries[index] = ("Stats", entry[1], unique_name) return (name(), entries) def log(args): diff --git a/args/chests.py b/args/chests.py index 8d581a8b..a366c74e 100644 --- a/args/chests.py +++ b/args/chests.py @@ -63,15 +63,15 @@ def options(args): elif args.chest_contents_empty: contents_value = "Empty" - result.append(("Contents", contents_value)) + result.append(("Contents", contents_value, "contents_value")) if args.chest_contents_shuffle_random: - result.append(("Random Percent", f"{args.chest_contents_shuffle_random_percent}%")) + result.append(("Random Percent", f"{args.chest_contents_shuffle_random_percent}%", "chest_contents_shuffle_random_percent")) if args.chest_random_monsters: - result.append(("MIAB Percent", f"{args.chest_random_monsters_enemy}%")) - result.append((" Boss Percent", f"{args.chest_random_monsters_boss}%")) + result.append(("MIAB Percent", f"{args.chest_random_monsters_enemy}%", "chest_random_monsters_enemy")) + result.append((" Boss Percent", f"{args.chest_random_monsters_boss}%", "chest_random_monsters_boss")) - result.append(("MIAB Shuffled", args.chest_monsters_shuffle)) + result.append(("MIAB Shuffled", args.chest_monsters_shuffle, "chest_monsters_shuffle")) return result diff --git a/args/coliseum.py b/args/coliseum.py index 2d5180f9..9fe9ce27 100644 --- a/args/coliseum.py +++ b/args/coliseum.py @@ -80,11 +80,11 @@ def options(args): rewards_visible = f"{args.coliseum_rewards_visible_random_min}-{args.coliseum_rewards_visible_random_max}" return [ - ("Opponents", opponents), - ("Rewards", rewards), - ("Rewards Visible", rewards_visible), - ("No Exp. Eggs", args.coliseum_no_exp_eggs), - ("No Illuminas", args.coliseum_no_illuminas), + ("Opponents", opponents, "opponents"), + ("Rewards", rewards, "rewards"), + ("Rewards Visible", rewards_visible, "rewards_visible"), + ("No Exp. Eggs", args.coliseum_no_exp_eggs, "coliseum_no_exp_eggs"), + ("No Illuminas", args.coliseum_no_illuminas, "coliseum_no_illuminas"), ] def menu(args): diff --git a/args/commands.py b/args/commands.py index 9fe525da..49535e7a 100644 --- a/args/commands.py +++ b/args/commands.py @@ -79,22 +79,22 @@ def options(args): result = [] if args.commands is not None: for index, command_string in enumerate(args.command_strings): - result.append((COMMAND_OPTIONS[index], command_string)) + result.append((COMMAND_OPTIONS[index], command_string, COMMAND_OPTIONS[index])) else: for option in COMMAND_OPTIONS: - result.append((option, option)) + result.append((option, option, option)) - result.append(("", "")) - result.append(("Shuffle Commands", args.shuffle_commands)) + result.append(("", "", "")) + result.append(("Shuffle Commands", args.shuffle_commands, "shuffle_commands")) - add_exclude_command = lambda command : result.append(("Random Exclude", "None" if command == NONE_COMMAND else id_name[command])) + add_exclude_command = lambda command, i : result.append(("Random Exclude", "None" if command == NONE_COMMAND else id_name[command], f"random_exclude_command{i}")) - add_exclude_command(args.random_exclude_command1) - add_exclude_command(args.random_exclude_command2) - add_exclude_command(args.random_exclude_command3) - add_exclude_command(args.random_exclude_command4) - add_exclude_command(args.random_exclude_command5) - add_exclude_command(args.random_exclude_command6) + add_exclude_command(args.random_exclude_command1, 1) + add_exclude_command(args.random_exclude_command2, 2) + add_exclude_command(args.random_exclude_command3, 3) + add_exclude_command(args.random_exclude_command4, 4) + add_exclude_command(args.random_exclude_command5, 5) + add_exclude_command(args.random_exclude_command6, 6) return result diff --git a/args/dances.py b/args/dances.py index 1bbce5e7..904b8f7f 100644 --- a/args/dances.py +++ b/args/dances.py @@ -48,11 +48,11 @@ def options(args): start_dances = f"Random {args.start_dances_random_min}-{args.start_dances_random_max}" return [ - ("Start Dances", start_dances), - ("Shuffle Abilities", args.dances_shuffle), - ("Display Abilities", args.dances_display_abilities), - ("No Stumble", args.dances_no_stumble), - ("Everyone Learns", args.dances_everyone_learns), + ("Start Dances", start_dances, "start_dances"), + ("Shuffle Abilities", args.dances_shuffle, "dances_shuffle"), + ("Display Abilities", args.dances_display_abilities, "dances_display_abilities"), + ("No Stumble", args.dances_no_stumble, "dances_no_stumble"), + ("Everyone Learns", args.dances_everyone_learns, "dances_everyone_learns"), ] def menu(args): diff --git a/args/encounters.py b/args/encounters.py index c8d3a91d..2f3d6c41 100644 --- a/args/encounters.py +++ b/args/encounters.py @@ -57,33 +57,33 @@ def options(args): elif args.random_encounters_chupon: random_encounters = "Chupon" - result.append(("Random Encounters", random_encounters)) + result.append(("Random Encounters", random_encounters, "random_encounters")) if args.random_encounters_random is not None: - result.append(("Boss Percent", f"{args.random_encounters_random}%")) + result.append(("Boss Percent", f"{args.random_encounters_random}%", "random_encounters_random")) fixed_encounters = "Original" if args.fixed_encounters_random is not None: fixed_encounters = "Random" - result.append(("Fixed Encounters", fixed_encounters)) + result.append(("Fixed Encounters", fixed_encounters, "fixed_encounters")) if args.fixed_encounters_random is not None: - result.append(("Boss Percent", f"{args.fixed_encounters_random}%")) + result.append(("Boss Percent", f"{args.fixed_encounters_random}%", "fixed_encounters_random")) escapable = "Original" if args.encounters_escapable_random is not None: escapable = f"{args.encounters_escapable_random}%" - result.append(("Escapable", escapable)) + result.append(("Escapable", escapable, "escapable")) return result def menu(args): entries = options(args) for index, entry in enumerate(entries): - key, value = entry + key, value, unique_name = entry if key == "Random Encounters": - entries[index] = ("Random", value) + entries[index] = ("Random", value, unique_name) elif key == "Fixed Encounters": - entries[index] = ("Fixed", value) + entries[index] = ("Fixed", value, unique_name) return (name(), entries) def log(args): diff --git a/args/espers.py b/args/espers.py index d1e0a15b..eb10d0d7 100644 --- a/args/espers.py +++ b/args/espers.py @@ -17,22 +17,28 @@ def parse(parser): esper_start = espers.add_mutually_exclusive_group() esper_start.add_argument("-stesp", "--starting-espers", default = [0, 0], type = int, - nargs = 2, metavar = ("MIN", "MAX"), choices = range(MAX_STARTING_ESPERS + 1), - help = "Party starts with %(metavar) random espers") + nargs = 2, metavar = ("MIN", "MAX"), choices = range(MAX_STARTING_ESPERS + 1), + help = "Party starts with %(metavar) random espers") esper_spells = espers.add_mutually_exclusive_group() esper_spells.add_argument("-esrr", "--esper-spells-random-rates", action = "store_true", - help = "Original esper spells with random learn rates") + help = "(DEPRECATED) Original esper spells with random learn rates") esper_spells.add_argument("-ess", "--esper-spells-shuffle", action = "store_true", - help = "Esper spells shuffled with original learn rates") + help = "Esper spells shuffled") esper_spells.add_argument("-essrr", "--esper-spells-shuffle-random-rates", action = "store_true", - help = "Esper spells shuffled with random learn rates") + help = "(DEPRECATED) Esper spells shuffled with random learn rates") esper_spells.add_argument("-esr", "--esper-spells-random", default = None, type = int, nargs = 2, metavar = ("MIN", "MAX"), choices = range(Esper.SPELL_COUNT + 1), - help = "Esper spells and learn rates randomized") + help = "Esper spells randomized") esper_spells.add_argument("-esrt", "--esper-spells-random-tiered", action = "store_true", - help = "Esper spells and learn rates randomized by tier") + help = "Esper spells randomized by tier") + + esper_learnrates = espers.add_mutually_exclusive_group() + esper_learnrates.add_argument("-elr", "--esper-learnrates-random", action = "store_true", + help = "Esper learn rates randomized") + esper_learnrates.add_argument("-elrt", "--esper-learnrates-random-tiered", action="store_true", + help="Esper learn rates randomized by tier") esper_bonuses = espers.add_mutually_exclusive_group() esper_bonuses.add_argument("-ebs", "--esper-bonuses-shuffle", action = "store_true", @@ -74,6 +80,18 @@ def process(args): args._process_min_max("esper_mp_random_percent") args._process_min_max("esper_equipable_random") + # Forces random learnrates if espers are not original/shuffled and learnrates are not set to tiered + randomized_espers = args.esper_spells_random or args.esper_spells_random_tiered + if randomized_espers and args.esper_learnrates_random_tiered != True: + args.esper_learnrates_random = True + + # Converts deprecated combined esper/learnrate flags to separate args + if args.esper_spells_random_rates: + args.esper_learnrates_random = True + elif args.esper_spells_shuffle_random_rates: + args.esper_spells_shuffle = True + args.esper_learnrates_random = True + if args.esper_bonuses_random is not None: args.esper_bonuses_random_percent = args.esper_bonuses_random args.esper_bonuses_random = True @@ -88,17 +106,18 @@ def flags(args): if args.starting_espers_min or args.starting_espers_max: flags += f" -stesp {args.starting_espers_min} {args.starting_espers_max}" - if args.esper_spells_random_rates: - flags += " -esrr" - elif args.esper_spells_shuffle: + if args.esper_spells_shuffle: flags += " -ess" - elif args.esper_spells_shuffle_random_rates: - flags += " -essrr" elif args.esper_spells_random: flags += f" -esr {args.esper_spells_random_min} {args.esper_spells_random_max}" elif args.esper_spells_random_tiered: flags += " -esrt" + if args.esper_learnrates_random: + flags += " -elr" + elif args.esper_learnrates_random_tiered: + flags += " -elrt" + if args.esper_bonuses_shuffle: flags += " -ebs" elif args.esper_bonuses_random: @@ -126,17 +145,19 @@ def flags(args): def options(args): spells = "Original" - if args.esper_spells_random_rates: - spells = "Original (Random Rates)" - elif args.esper_spells_shuffle: + if args.esper_spells_shuffle: spells = "Shuffle" - elif args.esper_spells_shuffle_random_rates: - spells = "Shuffle (Random Rates)" elif args.esper_spells_random: spells = f"Random {args.esper_spells_random_min}-{args.esper_spells_random_max}" elif args.esper_spells_random_tiered: spells = "Random Tiered" + learnrates = "Original" + if args.esper_learnrates_random: + learnrates = "Random" + elif args.esper_learnrates_random_tiered: + learnrates = "Random Tiered" + bonuses = "Original" if args.esper_bonuses_shuffle: bonuses = "Shuffle" @@ -158,21 +179,22 @@ def options(args): equipable = f"Balanced Random {args.esper_equipable_balanced_random_value}" result = [] - result.append(("Starting Espers", f"{args.starting_espers_min}-{args.starting_espers_max}")) - result.append(("Spells", spells)) - result.append(("Bonuses", bonuses)) + result.append(("Starting Espers", f"{args.starting_espers_min}-{args.starting_espers_max}", "starting_espers")) + result.append(("Spells", spells, "spells")) + result.append(("Rates", learnrates, "esper_learn_rates")) + result.append(("Bonuses", bonuses, "bonuses")) if args.esper_bonuses_random: - result.append(("Bonus Chance", f"{args.esper_bonuses_random_percent}%")) - result.append(("MP", mp)) - result.append(("Equipable", equipable)) - result.append(("Multi Summon", args.esper_multi_summon)) - result.append(("Mastered Icon", args.esper_mastered_icon)) + result.append(("Bonus Chance", f"{args.esper_bonuses_random_percent}%", "esper_bonus_chance")) + result.append(("MP", mp, "esper_mp")) + result.append(("Equipable", equipable, "esper_equipable")) + result.append(("Multi Summon", args.esper_multi_summon, "esper_multi_summon")) + result.append(("Mastered Icon", args.esper_mastered_icon, "esper_mastered_icon")) return result def menu(args): entries = options(args) for index, entry in enumerate(entries): - key, value = entry + key, value, unique_name = entry try: value = value.replace("Original (Random Rates)", "Random Rates") value = value.replace("Shuffle (Random Rates)", "Shuffle R Rates") @@ -181,7 +203,7 @@ def menu(args): value = value.replace("Balanced Random", "Balanced") if key == "Equipable": value = value.replace("Random", "") - entries[index] = (key, value) + entries[index] = (key, value, unique_name) except: pass return (name(), entries) diff --git a/args/graphics.py b/args/graphics.py index deb39926..7f3f7b13 100644 --- a/args/graphics.py +++ b/args/graphics.py @@ -132,7 +132,7 @@ def _sprite_palettes_log(args): log = ["Sprite Palettes"] for index, palette, in enumerate(args.palettes): - log.append(format_option(f"Palette {index}", _truncated_name(palette))) + log.append(format_option(f"Palette {index}", _truncated_name(palette), f"palette_{index}")) return log @@ -147,12 +147,12 @@ def _other_portraits_sprites_log(args): name = other_names[character - Characters.CHARACTER_COUNT] if character in PORTRAIT_CHARACTERS: - log.append(format_option(name, _truncated_name(args.portraits[portrait_index]))) + log.append(format_option(name, _truncated_name(args.portraits[portrait_index]), f"portraits_{portrait_index}")) portrait_index += 1 name = "" # do not have name show up in front of sprite also if character in SPRITE_CHARACTERS: - log.append(format_option(name, _truncated_name(args.sprites[sprite_index]))) - log.append(format_option("", f"Palette {args.sprite_palettes[sprite_index]}")) + log.append(format_option(name, _truncated_name(args.sprites[sprite_index]), f"sprite_{sprite_index}")) + log.append(format_option("", f"Palette {args.sprite_palettes[sprite_index]}", f"palette_{sprite_index}")) sprite_index += 1 return log @@ -163,9 +163,9 @@ def _character_customization_log(args): for index in range(Characters.CHARACTER_COUNT): log_name = f"{Characters.DEFAULT_NAME[index]:<6} -> {args.names[index]}" - log.append(format_option(log_name, _truncated_name(args.portraits[index]))) - log.append(format_option("", _truncated_name(args.sprites[index]))) - log.append(format_option("", f"Palette {args.sprite_palettes[index]}")) + log.append(format_option(log_name, _truncated_name(args.portraits[index]), f"char_name_{index}")) + log.append(format_option("", _truncated_name(args.sprites[index]), f"char_sprite_{index}")) + log.append(format_option("", f"Palette {args.sprite_palettes[index]}", f"char_palette_{index}")) return log @@ -188,9 +188,9 @@ def _other_options_log(args): healing_text = "Blue" entries = [ - ("Remove Flashes", remove_flashes), - ("World Minimap", world_minimap), - ("Healing Text", healing_text), + ("Remove Flashes", remove_flashes, "remove_flashes"), + ("World Minimap", world_minimap, "world_minimap"), + ("Healing Text", healing_text, "healing_text"), ] for entry in entries: diff --git a/args/items.py b/args/items.py index eed7a77f..15cb75c7 100644 --- a/args/items.py +++ b/args/items.py @@ -1,57 +1,69 @@ def name(): return "Items" + def parse(parser): from data.characters import Characters items = parser.add_argument_group("Items") items_equipable = items.add_mutually_exclusive_group() items_equipable.add_argument("-ier", "--item-equipable-random", - default = None, type = int, nargs = 2, metavar = ("MIN", "MAX"), - choices = range(Characters.CHARACTER_COUNT + 1), - help = "Each item equipable by between %(metavar)s random characters") + default=None, type=int, nargs=2, metavar=("MIN", "MAX"), + choices=range(Characters.CHARACTER_COUNT + 1), + help="Each item equipable by between %(metavar)s random characters") items_equipable.add_argument("-iebr", "--item-equipable-balanced-random", default = None, type = int, metavar = "VALUE", choices = range(Characters.CHARACTER_COUNT + 1), - help = "Each item equipable by %(metavar)s random characters. Total number of items equipable by each character is balanced") + help="Each item equipable by %(metavar)s random characters. Total number of items equipable by each character is balanced") + items_equipable.add_argument("-ietr", "--item-equipable-tiered-random",action = "store_true", + help = "Equipment is categorized by tier and chance of being equipable by a character is chosen at random. Higher tier equipment is less likely to be equipable.") items_equipable.add_argument("-ieor", "--item-equipable-original-random", - default = None, type = int, metavar = "PERCENT", choices = range(-100, 101), - help = "Characters have a %(metavar)s chance of being able to equip each item they could not previously equip. If %(metavar)s negative, characters have a -%(metavar)s chance of not being able to equip each item they could previously equip") + default=None, type=int, metavar="PERCENT", choices=range(-100, 101), + help="Characters have a %(metavar)s chance of being able to equip each item they could not previously equip. If %(metavar)s negative, characters have a -%(metavar)s chance of not being able to equip each item they could previously equip") items_equipable.add_argument("-iesr", "--item-equipable-shuffle-random", - default = None, type = int, metavar = "PERCENT", choices = range(-100, 101), - help = "Shuffle character equipment. After randomization, characters have a %(metavar)s chance of being able to equip each item they could not previously equip. If %(metavar)s negative, characters have a -%(metavar)s chance of not being able to equip each item they could previously equip") + default=None, type=int, metavar="PERCENT", choices=range(-100, 101), + help="Shuffle character equipment. After randomization, characters have a %(metavar)s chance of being able to equip each item they could not previously equip. If %(metavar)s negative, characters have a -%(metavar)s chance of not being able to equip each item they could previously equip") items_equipable_relic = items.add_mutually_exclusive_group() items_equipable_relic.add_argument("-ierr", "--item-equipable-relic-random", - default = None, type = int, nargs = 2, metavar = ("MIN", "MAX"), - choices = range(Characters.CHARACTER_COUNT + 1), - help = "Each relic equipable by between %(metavar)s random characters") + default=None, type=int, nargs=2, metavar=("MIN", "MAX"), + choices=range(Characters.CHARACTER_COUNT + 1), + help="Each relic equipable by between %(metavar)s random characters") items_equipable_relic.add_argument("-ierbr", "--item-equipable-relic-balanced-random", default = None, type = int, metavar = "VALUE", choices = range(Characters.CHARACTER_COUNT + 1), help = "Each relic equipable by %(metavar)s random characters. Total number of relics equipable by each character is balanced") + items_equipable_relic.add_argument("-iertr", "--item-equipable-relic-tiered-random", action="store_true", + help="Relics are categorized by tier and chance of being equipable by a character is chosen at random. Higher tier relics are less likely to be equipable.") items_equipable_relic.add_argument("-ieror", "--item-equipable-relic-original-random", - default = None, type = int, metavar = "PERCENT", choices = range(-100, 101), - help = "Characters have a %(metavar)s chance of being able to equip each relic they could not previously equip. If %(metavar)s negative, characters have a -%(metavar)s chance of not being able to equip each relic they could previously equip") + default=None, type=int, metavar="PERCENT", choices=range(-100, 101), + help="Characters have a %(metavar)s chance of being able to equip each relic they could not previously equip. If %(metavar)s negative, characters have a -%(metavar)s chance of not being able to equip each relic they could previously equip") items_equipable_relic.add_argument("-iersr", "--item-equipable-relic-shuffle-random", - default = None, type = int, metavar = "PERCENT", choices = range(-100, 101), - help = "Shuffle character relics. After randomization, characters have a %(metavar)s chance of being able to equip each item they could not previously equip. If %(metavar)s negative, characters have a -%(metavar)s chance of not being able to equip each item they could previously equip") + default=None, type=int, metavar="PERCENT", choices=range(-100, 101), + help="Shuffle character relics. After randomization, characters have a %(metavar)s chance of being able to equip each item they could not previously equip. If %(metavar)s negative, characters have a -%(metavar)s chance of not being able to equip each item they could previously equip") - items.add_argument("-csb", "--cursed-shield-battles", default = [256, 256], type = int, - nargs = 2, metavar = ("MIN", "MAX"), choices = range(257), - help = "Number of battles required to uncurse the cursed shield") + items.add_argument("-ir", "--item-rewards", type=str, + help="Choose which items will be received as check rewards") - items.add_argument("-mca", "--moogle-charm-all", action = "store_true", - help = "All characters can wear Moogle Charm relics which prevent random battles. Overrides Equipable option") - items.add_argument("-stra", "--swdtech-runic-all", action = "store_true", - help = "All weapons enable swdtech and runic") + items.add_argument("-csb", "--cursed-shield-battles", default=[256, 256], type=int, + nargs=2, metavar=("MIN", "MAX"), choices=range(257), + help="Number of battles required to uncurse the cursed shield") + + items.add_argument("-mca", "--moogle-charm-all", action="store_true", + help="All characters can wear Moogle Charm relics which prevent random battles. Overrides Equipable option") + items.add_argument("-stra", "--swdtech-runic-all", action="store_true", + help="All weapons enable swdtech and runic") + + items.add_argument("-saw", "--stronger-atma-weapon", action="store_true", + help="Atma Weapon moved to higher tier and divisor reduced from 64 to 32") - items.add_argument("-saw", "--stronger-atma-weapon", action = "store_true", - help = "Atma Weapon moved to higher tier and divisor reduced from 64 to 32") def process(args): + from constants.items import good_items, better_items + from constants.items import id_name, name_id + args._process_min_max("item_equipable_random") if args.item_equipable_balanced_random is not None: args.item_equipable_balanced_random_value = args.item_equipable_balanced_random @@ -74,10 +86,58 @@ def process(args): args.item_equipable_relic_shuffle_random_percent = args.item_equipable_relic_shuffle_random args.item_equipable_relic_shuffle_random = True + args.item_rewards_ids = [] + + if not args.item_rewards: + args.item_rewards = 'standard' + + # Split the comma-separated string + for a_item_id in args.item_rewards.split(','): + # look for strings first + a_item_id = a_item_id.lower().strip() + if a_item_id == 'standard': + args.item_rewards_ids = [name_id[name] for name in good_items] + elif a_item_id == 'premium': + args.item_rewards_ids = [name_id[name] for name in better_items] + else: + item_ids_lower = {k.lower(): v for k, v in name_id.items()} + if a_item_id in item_ids_lower: + args.item_rewards_ids.append(item_ids_lower[a_item_id]) + else: + # assuming it's a number... it'll error out if not + args.item_rewards_ids.append(int(a_item_id)) + + # remove duplicates and sort + args.item_rewards_ids = list(set(args.item_rewards_ids)) + args.item_rewards_ids.sort() + + # Remove Atma Weapon is it's not Stronger and we're in standard/premium + if not args.stronger_atma_weapon and name_id["Atma Weapon"] in args.item_rewards_ids \ + and any(s.lower().strip() in args.item_rewards.split(',') for s in ('standard', 'premium')): + args.item_rewards_ids.remove(name_id["Atma Weapon"]) + + # Remove excluded items + if args.no_free_paladin_shields and name_id["Paladin Shld"] in args.item_rewards_ids: + args.item_rewards_ids.remove(name_id["Paladin Shld"]) + if args.no_exp_eggs and name_id["Exp. Egg"] in args.item_rewards_ids: + args.item_rewards_ids.remove(name_id["Exp. Egg"]) + if args.no_illuminas and name_id["Illumina"] in args.item_rewards_ids: + args.item_rewards_ids.remove(name_id["Illumina"]) + if args.no_sprint_shoes and name_id["Sprint Shoes"] in args.item_rewards_ids: + args.item_rewards_ids.remove(name_id["Sprint Shoes"]) + if args.no_moogle_charms and name_id["Moogle Charm"] in args.item_rewards_ids: + args.item_rewards_ids.remove(name_id["Moogle Charm"]) + + # Make dead checks award "empty" if the item reward list is empty (e.g. all items were supposed to be Illuminas and + # the No Illumina flag is on) + if len(args.item_rewards_ids) < 1: + args.item_rewards_ids.append(name_id["Empty"]) + args._process_min_max("cursed_shield_battles") - args.cursed_shield_battles_original = args.cursed_shield_battles_min == 256 and\ + args.cursed_shield_battles_original = args.cursed_shield_battles_min == 256 and \ args.cursed_shield_battles_max == 256 + def flags(args): flags = "" @@ -85,6 +145,8 @@ def flags(args): flags += f" -ier {args.item_equipable_random_min} {args.item_equipable_random_max}" elif args.item_equipable_balanced_random: flags += f" -iebr {args.item_equipable_balanced_random_value}" + elif args.item_equipable_tiered_random: + flags += f" -ietr" elif args.item_equipable_original_random: flags += f" -ieor {args.item_equipable_original_random_percent}" elif args.item_equipable_shuffle_random: @@ -94,11 +156,16 @@ def flags(args): flags += f" -ierr {args.item_equipable_relic_random_min} {args.item_equipable_relic_random_max}" elif args.item_equipable_relic_balanced_random: flags += f" -ierbr {args.item_equipable_relic_balanced_random_value}" + elif args.item_equipable_relic_tiered_random: + flags += f" -iertr" elif args.item_equipable_relic_original_random: flags += f" -ieror {args.item_equipable_relic_original_random_percent}" elif args.item_equipable_relic_shuffle_random: flags += f" -iersr {args.item_equipable_relic_shuffle_random_percent}" + if args.item_rewards: + flags += f" -ir {args.item_rewards}" + if args.cursed_shield_battles_min != 256 or args.cursed_shield_battles_max != 256: flags += f" -csb {args.cursed_shield_battles_min} {args.cursed_shield_battles_max}" @@ -113,12 +180,15 @@ def flags(args): return flags + def options(args): equipable = "Original" if args.item_equipable_random: equipable = f"Random {args.item_equipable_random_min}-{args.item_equipable_random_max}" elif args.item_equipable_balanced_random: equipable = f"Balanced Random {args.item_equipable_balanced_random_value}" + elif args.item_equipable_tiered_random: + equipable = f"Tiered Random" elif args.item_equipable_original_random: equipable = f"Original + Random {args.item_equipable_original_random_percent}%" elif args.item_equipable_shuffle_random: @@ -129,6 +199,8 @@ def options(args): equipable_relics = f"Random {args.item_equipable_relic_random_min}-{args.item_equipable_relic_random_max}" elif args.item_equipable_relic_balanced_random: equipable_relics = f"Balanced Random {args.item_equipable_relic_balanced_random_value}" + elif args.item_equipable_relic_tiered_random: + equipable_relics = f"Tiered Random" elif args.item_equipable_relic_original_random: equipable_relics = f"Original + Random {args.item_equipable_relic_original_random_percent}%" elif args.item_equipable_relic_shuffle_random: @@ -137,39 +209,69 @@ def options(args): cursed_shield_battles = f"{args.cursed_shield_battles_min}-{args.cursed_shield_battles_max}" return [ - ("Equipable", equipable), - ("Equipable Relics", equipable_relics), - ("Cursed Shield Battles", cursed_shield_battles), - ("Moogle Charm All", args.moogle_charm_all), - ("SwdTech Runic All", args.swdtech_runic_all), - ("Stronger Atma Weapon", args.stronger_atma_weapon), + ("Equipable", equipable, "items_equipable"), + ("Equipable Relics", equipable_relics, "relics_equipable"), + ("Cursed Shield Battles", cursed_shield_battles, "cursed_shield_battles"), + ("Moogle Charm All", args.moogle_charm_all, "moogle_charm_all"), + ("SwdTech Runic All", args.swdtech_runic_all, "swdtech_runic_all"), + ("Stronger Atma Weapon", args.stronger_atma_weapon, "stronger_atma_weapon"), + ("Item Rewards", args.item_rewards_ids, "item_rewards"), ] + +def _format_items_log_entries(item_ids): + from constants.items import id_name + item_entries = [] + for item_id in item_ids: + item_entries.append(("", id_name[item_id])) + return item_entries + + def menu(args): + from menus.flags_reward_items import FlagsRewardItems + entries = options(args) for index, entry in enumerate(entries): - key, value = entry + key, value, unique_name = entry try: if key == "Equipable": key = "Equip" elif key == "Equipable Relics": key = "EquipR" + elif key == "Item Rewards": + entries[index] = ("Item Rewards", FlagsRewardItems(value)) # flags sub-menu elif key == "Cursed Shield Battles": key = "Cursed Shield" value = value.replace("Balanced Random", "Balanced") + value = value.replace("Tiered Random", "Tiered") value = value.replace("Original + Random", "Original + ") value = value.replace("Shuffle + Random", "Shuffle + ") - entries[index] = (key, value) + entries[index] = (key, value, unique_name) except: pass + return (name(), entries) + def log(args): from log import format_option log = [name()] entries = options(args) - for entry in entries: + '''for entry in entries: log.append(format_option(*entry)) + ''' + for entry in entries: + key, value, unique_name = entry + if key == "Item Rewards": + if len(value) == 0: + entry = (key, "None") + else: + entry = (key, "") # The entries will show up on subsequent lines + log.append(format_option(*entry)) + for item_entry in _format_items_log_entries(value): + log.append(format_option(*item_entry)) + else: + log.append(format_option(*entry)) return log diff --git a/args/lores.py b/args/lores.py index 21a431f8..0b3ef227 100644 --- a/args/lores.py +++ b/args/lores.py @@ -66,10 +66,10 @@ def options(args): lvl_x_spells = "Random" if args.lores_level_randomize else "Original" opts = [ - ("Start Lores", start_lores), - ("MP", mp), - ("Everyone Learns", args.lores_everyone_learns), - ("L.x Spells", lvl_x_spells) + ("Start Lores", start_lores, "start_lores"), + ("MP", mp, "lores_mp"), + ("Everyone Learns", args.lores_everyone_learns, "lores_everyone_learns"), + ("L.x Spells", lvl_x_spells, "lvl_x_spells") ] @@ -78,14 +78,14 @@ def options(args): def menu(args): entries = options(args) for index, entry in enumerate(entries): - key, value = entry + key, value, unique_name = entry try: if key == "Start Lores": value = value.replace("Random ", "") elif key == "MP": value = value.replace("Random Value ", "") value = value.replace("Random Percent ", "") - entries[index] = (key, value) + entries[index] = (key, value, unique_name) except: pass return (name(), entries) diff --git a/args/misc.py b/args/misc.py index 0c83c403..08789cd3 100644 --- a/args/misc.py +++ b/args/misc.py @@ -153,15 +153,15 @@ def options(args): movement = key_name[AUTO_SPRINT] return [ - ("Movement", movement), - ("Original Name Display", args.original_name_display), - ("Random RNG", args.random_rng), - ("Random Clock", args.random_clock), - ("Scan All", args.scan_all), - ("Warp All", args.warp_all), - ("Event Timers", event_timers), - ("Y NPC", y_npc), - ("NPC Tips", args.npc_dialog_tips), + ("Movement", movement, "movement"), + ("Original Name Display", args.original_name_display, "original_name_display"), + ("Random RNG", args.random_rng, "random_rng"), + ("Random Clock", args.random_clock, "random_clock"), + ("Scan All", args.scan_all, "scan_all"), + ("Warp All", args.warp_all, "warp_all"), + ("Event Timers", event_timers, "event_timers"), + ("Y NPC", y_npc, "y_npc"), + ("NPC Tips", args.npc_dialog_tips, "npc_dialog_tips"), ] def menu(args): diff --git a/args/misc_magic.py b/args/misc_magic.py index e4250d57..c886c558 100644 --- a/args/misc_magic.py +++ b/args/misc_magic.py @@ -41,18 +41,18 @@ def options(args): mp = f"Random Percent {args.magic_mp_random_percent_min}-{args.magic_mp_random_percent_max}%" return [ - ("MP", mp), + ("MP", mp, "misc_magic_mp"), ] def menu(args): entries = options(args) for index, entry in enumerate(entries): - key, value = entry + key, value, unique_name = entry try: if key == "MP": value = value.replace("Random Value ", "") value = value.replace("Random Percent ", "") - entries[index] = (key, value) + entries[index] = (key, value, unique_name) except: pass return (name(), entries) diff --git a/args/natural_magic.py b/args/natural_magic.py index ec1cb83d..fe8c1b58 100644 --- a/args/natural_magic.py +++ b/args/natural_magic.py @@ -58,15 +58,15 @@ def options(args): natural_magic2 = args.natural_magic2.capitalize() return [ - ("Natural Magic", natural_magic1), - ("Randomize Levels", args.random_natural_levels1), - ("Randomize Spells", args.random_natural_spells1), + ("Natural Magic", natural_magic1, "natural_magic1"), + ("Randomize Levels", args.random_natural_levels1, "random_natural_levels1"), + ("Randomize Spells", args.random_natural_spells1, "random_natural_spells1"), - ("Natural Magic", natural_magic2), - ("Randomize Levels", args.random_natural_levels2), - ("Randomize Spells", args.random_natural_spells2), + ("Natural Magic", natural_magic2, "natural_magic2"), + ("Randomize Levels", args.random_natural_levels2, "random_natural_levels2"), + ("Randomize Spells", args.random_natural_spells2, "random_natural_spells2"), - ("Menu Indicator", args.natural_magic_menu_indicator), + ("Menu Indicator", args.natural_magic_menu_indicator, "natural_magic_menu_indicator"), ] def menu(args): diff --git a/args/objectives.py b/args/objectives.py index a4a5170e..64412c19 100644 --- a/args/objectives.py +++ b/args/objectives.py @@ -131,7 +131,7 @@ def log(args): result_args = "Random" else: result_args = '-'.join([str(arg) for arg in objective.result.args]) - entry.append(format_option(result, result_args)) + entry.append(format_option(result, result_args, f"{objective.result.name}")) for condition in objective.conditions: if condition.min_max: @@ -143,10 +143,10 @@ def log(args): condition_args = "Random" else: condition_args = condition.string_function(*condition.args) - entry.append(format_option(" " + condition.name, condition_args)) + entry.append(format_option(" " + condition.name, condition_args, f"{objective.result.name}_{condition.name}")) conditions_required_args = f"{objective.conditions_required_min}-{objective.conditions_required_max}" - entry.append(format_option("Conditions Required", conditions_required_args)) + entry.append(format_option("Conditions Required", conditions_required_args, f"{objective.result.name}_conditions_req")) if oi % 2: rentries.append(entry) diff --git a/args/rages.py b/args/rages.py index 534e680f..02bedb6d 100644 --- a/args/rages.py +++ b/args/rages.py @@ -39,18 +39,18 @@ def options(args): start_rages = f"Random {args.start_rages_random_min}-{args.start_rages_random_max}" return [ - ("Start Rages", start_rages), - ("No Leap", args.rages_no_leap), - ("No Charm", args.rages_no_charm), + ("Start Rages", start_rages, "start_rages"), + ("No Leap", args.rages_no_leap, "rages_no_leap"), + ("No Charm", args.rages_no_charm, "rages_no_charm"), ] def menu(args): entries = options(args) for index, entry in enumerate(entries): - key, value = entry + key, value, unique_name = entry if key == "Start Rages": value = value.replace("Random ", "") - entries[index] = (key, value) + entries[index] = (key, value, unique_name) return (name(), entries) def log(args): diff --git a/args/scaling.py b/args/scaling.py index 0c8f9554..ed64f458 100644 --- a/args/scaling.py +++ b/args/scaling.py @@ -237,9 +237,9 @@ def options(args): elif args.level_scaling_time: level_scaling = "Time" - result.append(("Level Scaling", level_scaling)) + result.append(("Level Scaling", level_scaling, "level_scaling")) if args.level_scaling_factor is not None: - result.append(("Level Scaling Factor", f"{args.level_scaling_factor:g}")) + result.append(("Level Scaling Factor", f"{args.level_scaling_factor:g}", "level_scaling_factor")) hp_mp_scaling = "None" if args.hp_mp_scaling_average: @@ -257,9 +257,9 @@ def options(args): elif args.hp_mp_scaling_time: hp_mp_scaling = "Time" - result.append(("HP/MP Scaling", hp_mp_scaling)) + result.append(("HP/MP Scaling", hp_mp_scaling, "hp_mp_scaling")) if args.hp_mp_scaling_factor is not None: - result.append(("HP/MP Scaling Factor", f"{args.hp_mp_scaling_factor:g}")) + result.append(("HP/MP Scaling Factor", f"{args.hp_mp_scaling_factor:g}", "hp_mp_scaling_factor")) xp_gp_scaling = "None" if args.xp_gp_scaling_average: @@ -277,9 +277,9 @@ def options(args): elif args.xp_gp_scaling_time: xp_gp_scaling = "Time" - result.append(("Exp/GP Scaling", xp_gp_scaling)) + result.append(("Exp/GP Scaling", xp_gp_scaling, "xp_gp_scaling")) if args.xp_gp_scaling_factor is not None: - result.append(("Exp/GP Scaling Factor", f"{args.xp_gp_scaling_factor:g}")) + result.append(("Exp/GP Scaling Factor", f"{args.xp_gp_scaling_factor:g}", "xp_gp_scaling_factor")) ability_scaling = "None" if args.ability_scaling_element: @@ -287,13 +287,13 @@ def options(args): elif args.ability_scaling_random: ability_scaling = "Random" - result.append(("Ability Scaling", ability_scaling)) + result.append(("Ability Scaling", ability_scaling, "ability_scaling")) if args.ability_scaling_factor is not None: - result.append(("Ability Scaling Factor", f"{args.ability_scaling_factor:g}")) + result.append(("Ability Scaling Factor", f"{args.ability_scaling_factor:g}", "ability_scaling_factor")) - result.append(("Max Scale Level", args.max_scale_level)) - result.append(("Scale Eight Dragons", args.scale_eight_dragons)) - result.append(("Scale Final Battles", args.scale_final_battles)) + result.append(("Max Scale Level", args.max_scale_level, "max_scale_level")) + result.append(("Scale Eight Dragons", args.scale_eight_dragons, "scale_eight_dragons")) + result.append(("Scale Final Battles", args.scale_final_battles, "scale_final_battles")) return result @@ -301,7 +301,7 @@ def menu(args): entries = options(args) for index, entry in enumerate(entries): - key, value = entry + key, value, unique_name = entry try: key = key.replace(" Scaling", "") value = value.replace("Party Average", "PAverage") @@ -309,7 +309,7 @@ def menu(args): value = value.replace("Characters + Espers + Dragons", "C + E + D") value = value.replace("Characters + Espers", "C + E") value = value.replace("Bosses + Dragons", "B + D") - entries[index] = (key, value) + entries[index] = (key, value, unique_name) except: pass return (name(), entries) diff --git a/args/settings.py b/args/settings.py index d14b9bc0..e1a43db3 100644 --- a/args/settings.py +++ b/args/settings.py @@ -35,9 +35,9 @@ def options(args): game_mode = "Character Gating" return [ - ("Mode", game_mode), - ("Seed", args.seed), - ("Spoiler Log", args.spoiler_log), + ("Mode", game_mode, "game_mode"), + ("Seed", args.seed, "seed"), + ("Spoiler Log", args.spoiler_log, "spoiler_log"), ] def menu(args): @@ -45,7 +45,7 @@ def menu(args): for index, entry in enumerate(entries): if entry[0] == "Seed": if len(entry[1]) > 18: - entries[index] = (entry[0], entry[1][:15] + "...") + entries[index] = (entry[0], entry[1][:15] + "...", "seed") break return (name(), entries) diff --git a/args/shops.py b/args/shops.py index 1ab2dab7..779a1e49 100644 --- a/args/shops.py +++ b/args/shops.py @@ -143,22 +143,22 @@ def options(args): elif args.shops_expensive_super_balls: super_balls = "Expensive" - result = [("Inventory", inventory)] + result = [("Inventory", inventory, "shops_inventory")] if args.shop_inventory_shuffle_random: - result.append(("Random Percent", f"{args.shop_inventory_shuffle_random_percent}%")) + result.append(("Random Percent", f"{args.shop_inventory_shuffle_random_percent}%", "shops_random_percent")) result.extend([ - ("Price", price), - ("Sell Fraction", sell_fraction), - ("Dried Meat", args.shop_dried_meat), - ("No Priceless Items", args.no_priceless_items), - ("No Breakable Rods", args.shops_no_breakable_rods), - ("Expensive Rods", args.shops_expensive_breakable_rods), - ("No Elemental Shields", args.shops_no_elemental_shields), - ("No Super Balls", args.shops_no_super_balls), - ("Expensive Balls", args.shops_expensive_super_balls), - ("No Exp. Eggs", args.shops_no_exp_eggs), - ("No Illuminas", args.shops_no_illuminas), + ("Price", price, "price"), + ("Sell Fraction", sell_fraction, "sell_fraction"), + ("Dried Meat", args.shop_dried_meat, "shop_dried_meat"), + ("No Priceless Items", args.no_priceless_items, "no_priceless_items"), + ("No Breakable Rods", args.shops_no_breakable_rods, "shops_no_breakable_rods"), + ("Expensive Rods", args.shops_expensive_breakable_rods, "shops_expensive_breakable_rods"), + ("No Elemental Shields", args.shops_no_elemental_shields, "shops_no_elemental_shields"), + ("No Super Balls", args.shops_no_super_balls, "shops_no_super_balls"), + ("Expensive Balls", args.shops_expensive_super_balls, "shops_expensive_super_balls"), + ("No Exp. Eggs", args.shops_no_exp_eggs, "shops_no_exp_eggs"), + ("No Illuminas", args.shops_no_illuminas, "shops_no_illuminas"), ]) return result diff --git a/args/sketch_control.py b/args/sketch_control.py index f5c82cbf..2ca8ebb4 100644 --- a/args/sketch_control.py +++ b/args/sketch_control.py @@ -27,18 +27,17 @@ def options(args): accuracy = "100%" if args.sketch_control_improved_stats else "Original" stats = "Character" if args.sketch_control_improved_stats else "Original" - sketch_abilities = ("Sketch Ability", abilities) - sketch_stats = ("Sketch Stats", stats) - sketch_accuracy = ("Sketch Accuracy", accuracy) + sketch_abilities = ("Sketch Ability", abilities, "sketch_abilities") + sketch_stats = ("Sketch Stats", stats, "sketch_stats") + sketch_accuracy = ("Sketch Accuracy", accuracy, "sketch_accuracy") - control_abilities = ("Control Ability", abilities) - control_stats = ("Control Stats", stats) + control_abilities = ("Control Ability", abilities, "control_abilities") + control_stats = ("Control Stats", stats, "control_stats") return [ sketch_abilities, sketch_accuracy, sketch_stats, - ("", ""), control_abilities, control_stats, ] diff --git a/args/starting_gold_items.py b/args/starting_gold_items.py index 2faa23d9..d997a197 100644 --- a/args/starting_gold_items.py +++ b/args/starting_gold_items.py @@ -46,18 +46,17 @@ def flags(args): def options(args): opts = [ - ("Start Gold", args.gold), - ("Start Moogle Charms", args.start_moogle_charms), - ("Start Sprint Shoes", args.start_sprint_shoes), - ("Start Warp Stones", args.start_warp_stones), - ("Start Fenix Downs", args.start_fenix_downs), - ("Start Tools", args.start_tools), - ("Start Junk", args.start_junk), + ("Start Gold", args.gold, "gold"), + ("Start Moogle Charms", args.start_moogle_charms, "start_moogle_charms"), + ("Start Sprint Shoes", args.start_sprint_shoes, "start_sprint_shoes"), + ("Start Warp Stones", args.start_warp_stones, "start_warp_stones"), + ("Start Fenix Downs", args.start_fenix_downs, "start_fenix_downs"), + ("Start Tools", args.start_tools, "start_tools"), ] if args.start_junk != 0: opts += [ - ("Start Junk", args.start_junk) + ("Start Junk", args.start_junk, "start_junk") ] return opts diff --git a/args/starting_party.py b/args/starting_party.py index a4375a05..6965c781 100644 --- a/args/starting_party.py +++ b/args/starting_party.py @@ -58,20 +58,20 @@ def flags(args): def options(args): result = [] start_chars = [args.start_char1, args.start_char2, args.start_char3, args.start_char4] - for start_char in start_chars: + for i, start_char in enumerate(start_chars): value = "None" if start_char == "randomngu": value = "Random (No Gogo/Umaro)" elif start_char: value = start_char.capitalize() - result.append(("Start Character", value)) + result.append((f"Start Character {i+1}", value, f"start_char{i+1}")) return result def menu(args): entries = options(args) for index, entry in enumerate(entries): - entries[index] = (entry[1], "") + entries[index] = (entry[1], "", entry[2]) return (name(), entries) def log(args): diff --git a/args/steal.py b/args/steal.py index a809840a..1da79d62 100644 --- a/args/steal.py +++ b/args/steal.py @@ -41,11 +41,11 @@ def options(args): if args.steal_chances_always: steal_chances = "Always" - result.append(("Chances", steal_chances)) + result.append(("Chances", steal_chances, "steal_chances")) - result.append(("Shuffle", args.shuffle_steals_drops)) + result.append(("Shuffle", args.shuffle_steals_drops, "shuffle_steals_drops")) if args.shuffle_steals_drops: - result.append(("Random Percent", f"{args.shuffle_steals_drops_random_percent}%")) + result.append(("Random Percent", f"{args.shuffle_steals_drops_random_percent}%", "shuffle_steals_drops_random_percent")) return result diff --git a/args/swdtechs.py b/args/swdtechs.py index 682acc73..7042610e 100644 --- a/args/swdtechs.py +++ b/args/swdtechs.py @@ -24,8 +24,8 @@ def flags(args): def options(args): return [ - ("Fast SwdTech", args.fast_swdtech), - ("Everyone Learns", args.swdtechs_everyone_learns), + ("Fast SwdTech", args.fast_swdtech, "fast_swdtech"), + ("Everyone Learns", args.swdtechs_everyone_learns, "swdtechs_everyone_learns"), ] def menu(args): diff --git a/args/xpmpgp.py b/args/xpmpgp.py index 81f90cd3..1403c4ce 100644 --- a/args/xpmpgp.py +++ b/args/xpmpgp.py @@ -33,10 +33,10 @@ def flags(args): def options(args): return [ - ("Experience Multiplier", args.xp_mult), - ("Magic Points Multiplier", args.mp_mult), - ("Gold Multiplier", args.gp_mult), - ("No Exp Party Divide", args.no_exp_party_divide), + ("Experience Multiplier", args.xp_mult, "xp_mult"), + ("Magic Points Multiplier", args.mp_mult, "mp_mult"), + ("Gold Multiplier", args.gp_mult, "gp_mult"), + ("No Exp Party Divide", args.no_exp_party_divide, "no_exp_party_divide"), ] def menu(args): @@ -44,12 +44,12 @@ def menu(args): entries = options(args) for index in range(3): - key, value = entries[index] + key, value, unique_name = entries[index] key = key.replace(" Multiplier", "") value = str(value) + 'x' - entries[index] = (key, value) + entries[index] = (key, value, unique_name) return (short_name, entries) diff --git a/constants/items.py b/constants/items.py index c619eadc..0f59a39a 100644 --- a/constants/items.py +++ b/constants/items.py @@ -3,6 +3,24 @@ BREAKABLE_RODS = range(53, 59) ELEMENTAL_SHIELDS = range(96, 99) +DIRKS = range(0, 10) +SWORDS = range(10, 29) +LANCES = range(29, 37) +KNIVES = range(37, 43) +KATANAS = range(43, 51) +RODS = range(51, 61) +BRUSHES = range(61, 65) +STARS = range(65, 68) +SPECIAL = range(68, 77) +GAMBLER = range(77, 83) +CLAWS = range(83, 90) +SHIELDS = range(90, 105) +HELMETS = range(105, 132) +ARMORS = range(132, 163) +TOOLS = range(163, 171) +SKEANS = range(171, 176) +RELICS = range(176, 231) + id_name = { 0 : "Dirk", @@ -268,6 +286,7 @@ "ValiantKnife", "Illumina", "Ragnarok", + "Atma Weapon", "Pearl Lance", "Aura Lance", "Magus Rod", @@ -296,3 +315,19 @@ "Exp. Egg", ] +better_items = [ + "ValiantKnife", + "Illumina", + "Ragnarok", + "Atma Weapon", + "Fixed Dice", + "Flame Shld", + "Ice Shld", + "Thunder Shld", + "Paladin Shld", + "Minerva", + "Genji Glove", + "Offering", + "Exp. Egg", +] + diff --git a/constants/objectives/results.py b/constants/objectives/results.py index 0524abc9..4f5cec1b 100644 --- a/constants/objectives/results.py +++ b/constants/objectives/results.py @@ -94,6 +94,8 @@ category_types["Auto"].append(ResultType(64, "Auto Dark", "Auto Dark", None)) category_types["Auto"].append(ResultType(65, "Auto Clear", "Auto Clear", None)) category_types["Auto"].append(ResultType(66, "Auto Imp", "Auto Imp", None)) +category_types["Item"].append(ResultType(67, "Throwables", "Throwables", None)) +category_types["Item"].append(ResultType(68, "Restoratives", "Restoratives", None)) categories = list(category_types.keys()) diff --git a/constants/standard_flags.py b/constants/standard_flags.py new file mode 100644 index 00000000..eeeece7e --- /dev/null +++ b/constants/standard_flags.py @@ -0,0 +1,252 @@ +# Standard Flags +# Last updated to match Ultros League Season 4 + +standard_flags = { + "game_mode": "Character Gating", + "spoiler_log": "False", + "Unlock Final Kefka": "", + "Unlock Final Kefka_Characters": "6-6", + "Unlock Final Kefka_Espers": "9-9", + "Unlock Final Kefka_conditions_req": "2-2", + "Unlock KT Skip": "", + "Unlock KT Skip_Characters": "9-9", + "Unlock KT Skip_Espers": "12-12", + "Unlock KT Skip_conditions_req": "1-1", + "Learn SwdTechs": "8-8", + "Learn SwdTechs_Check": "Doma Dream Awaken", + "Learn SwdTechs_conditions_req": "1-1", + "Magitek Upgrade": "", + "Magitek Upgrade_Check": "Magitek Factory Finish", + "Magitek Upgrade_conditions_req": "1-1", + "start_char1": "Random", + "start_char2": "Random", + "start_char3": "Random", + "start_char4": "None", + "fast_swdtech": "True", + "swdtechs_everyone_learns": "False", + "bum_rush_last": "True", + "blitzes_everyone_learns": "False", + "start_lores": "Random 3-5", + "lores_mp": "Random Percent 75-125%", + "lores_everyone_learns": "True", + "lvl_x_spells": "Original", + "start_rages": "Random 25-35", + "rages_no_leap": "True", + "rages_no_charm": "True", + "start_dances": "Random 1-2", + "dances_shuffle": "True", + "dances_display_abilities": "True", + "dances_no_stumble": "True", + "dances_everyone_learns": "False", + "steal_chances": "Higher", + "shuffle_steals_drops": "None", + "sketch_abilities": "Original", + "sketch_accuracy": "100%", + "sketch_stats": "Character", + "control_abilities": "Original", + "control_stats": "Character", + "start_average_level": "True", + "start_level": "3", + "start_naked": "False", + "equipable_umaro": "True", + "character_stats": "80-125%", + "Morph": "Random Unique", + "Steal": "Random Unique", + "SwdTech": "Random Unique", + "Throw": "Random Unique", + "Tools": "Random Unique", + "Blitz": "Random Unique", + "Runic": "Random Unique", + "Lore": "Random Unique", + "Sketch": "Random Unique", + "Slot": "Random Unique", + "Dance": "Random Unique", + "Rage": "Random Unique", + "Leap": "Random Unique", + "": "", + "shuffle_commands": "False", + "random_exclude_command1": "Possess", + "random_exclude_command2": "Shock", + "random_exclude_command3": "None", + "random_exclude_command4": "None", + "random_exclude_command5": "None", + "random_exclude_command6": "None", + "xp_mult": "3", + "mp_mult": "5", + "gp_mult": "5", + "no_exp_party_divide": "True", + "boss_battles": "Shuffle", + "dragon_battles": "Shuffle", + "statue_battles": "Mix", + "shuffle_random_phunbaba3": "False", + "boss_normalize_distort_stats": "False", + "boss_experience": "True", + "boss_no_undead": "True", + "boss_marshal_keep_lobos": "False", + "doom_gaze_no_escape": "True", + "wrexsoul_no_zinger": "True", + "magimaster_no_ultima": "True", + "chadarnook_more_demon": "True", + "level_scaling": "Characters + Espers + Dragons", + "level_scaling_factor": "2", + "hp_mp_scaling": "Characters + Espers + Dragons", + "hp_mp_scaling_factor": "2", + "xp_gp_scaling": "Characters + Espers + Dragons", + "xp_gp_scaling_factor": "2", + "ability_scaling": "Element", + "ability_scaling_factor": "2", + "max_scale_level": "40", + "scale_eight_dragons": "True", + "scale_final_battles": "False", + "random_encounters": "Shuffle", + "fixed_encounters": "Random", + "fixed_encounters_random": "0%", + "escapable": "100%", + "starting_espers": "0-0", + "spells": "Random 2-5", + "bonuses": "Random", + "esper_bonus_chance": "82%", + "esper_mp": "Random Percent 75-125%", + "esper_equipable": "All", + "esper_multi_summon": "False", + "esper_mastered_icon": "False", + "misc_magic_mp": "Random Percent 75-125%", + "natural_magic1": "Random", + "random_natural_levels1": "True", + "random_natural_spells1": "True", + "natural_magic2": "Random", + "random_natural_levels2": "True", + "random_natural_spells2": "True", + "natural_magic_menu_indicator": "True", + "gold": "5000", + "start_moogle_charms": "3", + "start_sprint_shoes": "0", + "start_warp_stones": "0", + "start_fenix_downs": "0", + "start_tools": "1", + "items_equipable": "Original + Random 33%", + "relics_equipable": "Original + Random 33%", + "cursed_shield_battles": "3-14", + "moogle_charm_all": "True", + "swdtech_runic_all": "True", + "stronger_atma_weapon": "True", + "shops_inventory": "Shuffle + Random", + "shops_random_percent": "20%", + "price": "Random Percent 75-125%", + "sell_fraction": "1/2", + "shop_dried_meat": "5", + "no_priceless_items": "True", + "shops_no_breakable_rods": "False", + "shops_expensive_breakable_rods": "True", + "shops_no_elemental_shields": "False", + "shops_no_super_balls": "False", + "shops_expensive_super_balls": "True", + "shops_no_exp_eggs": "False", + "shops_no_illuminas": "False", + "contents_value": "Shuffle + Random", + "chest_contents_shuffle_random_percent": "20%", + "chest_random_monsters_enemy": "0%", + "chest_random_monsters_boss": "0%", + "chest_monsters_shuffle": "True", + "palette_0": "Original 0", + "palette_1": "Original 1", + "palette_2": "Original 2", + "palette_3": "Original 3", + "palette_4": "Original 4", + "palette_5": "Original 5", + "palette_6": "Original 6", + "sprite_14": "Soldier", + "palette_14": "Palette 1", + "portraits_14": "Imp", + "sprite_15": "Imp", + "palette_15": "Palette 0", + "sprite_16": "General Leo", + "palette_16": "Palette 6", + "sprite_17": "Banon-Duncan", + "palette_17": "Palette 1", + "sprite_18": "Esper Terra", + "palette_18": "Palette 0", + "sprite_19": "Merchant", + "palette_19": "Palette 3", + "remove_flashes": "Worst", + "world_minimap": "High Contrast", + "healing_text": "Original", + "char_name_0": "Terra", + "char_sprite_0": "Terra", + "char_palette_0": "Palette 2", + "char_name_1": "Locke", + "char_sprite_1": "Locke", + "char_palette_1": "Palette 1", + "char_name_2": "Cyan", + "char_sprite_2": "Cyan", + "char_palette_2": "Palette 4", + "char_name_3": "Shadow", + "char_sprite_3": "Shadow", + "char_palette_3": "Palette 4", + "char_name_4": "Edgar", + "char_sprite_4": "Edgar", + "char_palette_4": "Palette 0", + "char_name_5": "Sabin", + "char_sprite_5": "Sabin", + "char_palette_5": "Palette 0", + "char_name_6": "Celes", + "char_sprite_6": "Celes", + "char_palette_6": "Palette 0", + "char_name_7": "Strago", + "char_sprite_7": "Strago", + "char_palette_7": "Palette 3", + "char_name_8": "Relm", + "char_sprite_8": "Relm", + "char_palette_8": "Palette 3", + "char_name_9": "Setzer", + "char_sprite_9": "Setzer", + "char_palette_9": "Palette 4", + "char_name_10": "Mog", + "char_sprite_10": "Mog", + "char_palette_10": "Palette 5", + "char_name_11": "Gau", + "char_sprite_11": "Gau", + "char_palette_11": "Palette 3", + "char_name_12": "Gogo", + "char_sprite_12": "Gogo", + "char_palette_12": "Palette 3", + "char_name_13": "Umaro", + "char_sprite_13": "Umaro", + "char_palette_13": "Palette 5", + "opponents": "Random", + "rewards": "Random", + "rewards_visible": "80-100", + "coliseum_no_exp_eggs": "False", + "coliseum_no_illuminas": "False", + "auction_random_items": "True", + "auction_no_chocobo_airship": "True", + "auction_door_esper_hint": "True", + "auction_max_espers": "1", + "movement": "AUTO_SPRINT", + "original_name_display": "True", + "random_rng": "True", + "random_clock": "False", + "scan_all": "False", + "warp_all": "False", + "event_timers": "None", + "y_npc": "None", + "npc_dialog_tips": "False", + "no_moogle_charms": "True", + "no_exp_eggs": "False", + "no_illuminas": "False", + "no_sprint_shoes": "True", + "no_free_paladin_shields": "True", + "no_free_characters_espers": "False", + "permadeath": "False", + "ultima": "N/A", + "remove_learnable_spell_ids": "", + "rls_0": "Ultima", + "fix_sketch": "True", + "fix_evade": "True", + "fix_vanish_doom": "True", + "fix_retort": "True", + "fix_jump": "True", + "fix_boss_skip": "True", + "fix_enemy_damage_counter": "True", + "fix_capture": "True", +} \ No newline at end of file diff --git a/data/esper.py b/data/esper.py index 96d9f62a..e61c5fe0 100644 --- a/data/esper.py +++ b/data/esper.py @@ -131,6 +131,27 @@ def randomize_rates(self): for spell_index in range(self.spell_count): self.spells[spell_index].rate = random.choice(self.LEARN_RATES) + def randomize_rates_tiered(self): + import random + from data.esper_spell_tiers import tiers + for spell_index in range(self.spell_count): + if self.spells[spell_index].id in tiers[0]: + self.spells[spell_index].rate = random.choice([10, 15, 16, 20]) + elif self.spells[spell_index].id in tiers[1]: + self.spells[spell_index].rate = random.choice([5, 6, 7, 8]) + elif self.spells[spell_index].id in tiers[2]: + self.spells[spell_index].rate = random.choice([1, 2, 3, 4]) + elif self.spells[spell_index].id in tiers[3]: + self.spells[spell_index].rate = random.choice([10, 15, 16, 20]) + elif self.spells[spell_index].id in tiers[4]: + self.spells[spell_index].rate = random.choice([6, 7, 8, 10, 15]) + elif self.spells[spell_index].id in tiers[5]: + self.spells[spell_index].rate = random.choice([4, 5, 6, 7, 8]) + elif self.spells[spell_index].id in tiers[6]: + self.spells[spell_index].rate = random.choice([2, 3, 4]) + elif self.spells[spell_index].id in tiers[7]: + self.spells[spell_index].rate = 1 + def randomize_bonus(self): import random # exclude lvl percent bonuses diff --git a/data/espers.py b/data/espers.py index c09a5b12..11f25ee0 100644 --- a/data/espers.py +++ b/data/espers.py @@ -172,6 +172,10 @@ def randomize_rates(self): for esper in self.espers: esper.randomize_rates() + def randomize_rates_tiered(self): + for esper in self.espers: + esper.randomize_rates_tiered() + def shuffle_bonuses(self): bonuses = [] for esper in self.espers: @@ -273,12 +277,6 @@ def multi_summon(self): def mod(self, dialogs): self.receive_dialogs_mod(dialogs) - if self.args.esper_spells_random_rates or self.args.esper_spells_shuffle_random_rates: - self.randomize_rates() - - if len(self.starting_espers): - self.randomize_rates() - if self.args.esper_spells_shuffle or self.args.esper_spells_shuffle_random_rates: self.shuffle_spells() elif self.args.esper_spells_random: @@ -286,6 +284,19 @@ def mod(self, dialogs): elif self.args.esper_spells_random_tiered: self.randomize_spells_tiered() + if self.args.esper_spells_random or self.args.esper_spells_random_tiered: + # if random, replace the spells + self.replace_flagged_learnables() + else: + # otherwise (original or shuffled), remove them + self.remove_flagged_learnables() + + if self.args.esper_learnrates_random: + self.randomize_rates() + + if self.args.esper_learnrates_random_tiered: + self.randomize_rates_tiered() + if self.args.esper_bonuses_shuffle: self.shuffle_bonuses() elif self.args.esper_bonuses_random: @@ -310,16 +321,10 @@ def mod(self, dialogs): if self.args.permadeath: self.phoenix_life3() - if self.args.esper_spells_random or self.args.esper_spells_random_tiered: - # if random, replace the spells - self.replace_flagged_learnables() - else: - # otherwise (original or shuffled), remove them - self.remove_flagged_learnables() - if self.args.esper_multi_summon: self.multi_summon() + def write(self): if self.args.spoiler_log: self.log() diff --git a/data/items.py b/data/items.py index 172295b6..092a3cdc 100644 --- a/data/items.py +++ b/data/items.py @@ -2,7 +2,7 @@ from data.item import Item from data.structures import DataList -from constants.items import good_items +from constants.items import good_items, better_items from constants.items import id_name, name_id import data.items_asm as items_asm @@ -10,7 +10,7 @@ class Items(): ITEM_COUNT = 256 - EMPTY = 0xff # item 255 is empty + EMPTY = 0xff # item 255 is empty BREAKABLE_RODS = range(53, 59) ELEMENTAL_SHIELDS = range(96, 99) @@ -21,15 +21,7 @@ class Items(): DESC_START = 0x2d6400 DESC_END = 0x2d779f - GOOD = [name_id[name] for name in good_items] - if args.stronger_atma_weapon: - GOOD.append(name_id["Atma Weapon"]) - if args.no_free_paladin_shields: - GOOD.remove(name_id["Paladin Shld"]) - if args.no_exp_eggs: - GOOD.remove(name_id["Exp. Egg"]) - if args.no_illuminas: - GOOD.remove(name_id["Illumina"]) + GOOD = args.item_rewards_ids def __init__(self, rom, args, dialogs, characters): self.rom = rom @@ -45,8 +37,8 @@ def __init__(self, rom, args, dialogs, characters): def read(self): self.items = [] - self.type_items = {Item.TOOL : [], Item.WEAPON : [], Item.ARMOR : [], - Item.SHIELD : [], Item.HELMET : [], Item.RELIC : [], Item.ITEM : []} + self.type_items = {Item.TOOL: [], Item.WEAPON: [], Item.ARMOR: [], + Item.SHIELD: [], Item.HELMET: [], Item.RELIC: [], Item.ITEM: []} for item_index in range(self.ITEM_COUNT): item = Item(item_index, self.rom, self.desc_data[item_index]) @@ -81,8 +73,8 @@ def equipable_balanced_random(self, type_condition, characters_per_item): if len(possible_characters) < characters_per_item: # fewer possibilities left than number of characters needed for each item - character_group = possible_characters # add remaining possible characters to current group - possible_characters = list(range(Characters.CHARACTER_COUNT)) # add all characters back into pool + character_group = possible_characters # add remaining possible characters to current group + possible_characters = list(range(Characters.CHARACTER_COUNT)) # add all characters back into pool # select characters at random from possible pool until # character_group contains characters_per_item unique characters @@ -101,6 +93,34 @@ def equipable_balanced_random(self, type_condition, characters_per_item): possible_characters.remove(character) item.add_equipable_character(self.characters.playable[character]) + def equipable_tiered(self, type_condition): + from data.chest_item_tiers import tiers + + tier_mins = [13, 11, 7, 4, 1] + tier_maxes = [14, 12, 10, 6, 3] + + for item in self.items: + if item.is_equipable() and item.id != self.EMPTY and item.id != 102 and type_condition(item.type): + for i, tier in enumerate(tiers): + if item.id in tier: + item_tier = i - 5 + break + + item.remove_all_equipable_characters() + + num_chars = random.randint(tier_mins[item_tier], tier_maxes[item_tier]) + rand_chars = random.sample(self.characters.playable, num_chars) + + # if Paladin Shld is only equipable by Gogo and/or Umaro, instead reroll for 3 characters + if item.id == 103 and all(obj.id in [13, 14] for obj in rand_chars): + rand_chars = random.sample(self.characters.playable, 3) + + for character in rand_chars: + item.add_equipable_character(character) + + # force Cursed Shld equips to match Paladin Shld equips + self.items[102].equipable_characters = self.items[103].equipable_characters + def equipable_original_random(self, type_condition, percent): if percent == 0: return @@ -165,7 +185,7 @@ def random_prices_percent(self): price_percent = random.randint(self.args.shop_prices_random_percent_min, self.args.shop_prices_random_percent_max) / 100.0 value = int(item.price * price_percent) - item.price = max(min(value, 2**16 - 1), 0) + item.price = max(min(value, 2 ** 16 - 1), 0) def expensive_breakable_rods(self): self.items[name_id["Poison Rod"]].scale_price(3) @@ -200,23 +220,27 @@ def moogle_starting_equipment(self): self.characters.characters[index].init_head = random.choice(tiers[Item.HELMET][1]) def mod(self): - not_relic_condition = lambda x : x != Item.RELIC + not_relic_condition = lambda x: x != Item.RELIC if self.args.item_equipable_random: self.equipable_random(not_relic_condition, self.args.item_equipable_random_min, - self.args.item_equipable_random_max) + self.args.item_equipable_random_max) elif self.args.item_equipable_balanced_random: self.equipable_balanced_random(not_relic_condition, self.args.item_equipable_balanced_random_value) + elif self.args.item_equipable_tiered_random: + self.equipable_tiered(not_relic_condition) elif self.args.item_equipable_original_random: self.equipable_original_random(not_relic_condition, self.args.item_equipable_original_random_percent) elif self.args.item_equipable_shuffle_random: self.equipable_shuffle_random(not_relic_condition, self.args.item_equipable_shuffle_random_percent) - relic_condition = lambda x : x == Item.RELIC + relic_condition = lambda x: x == Item.RELIC if self.args.item_equipable_relic_random: self.equipable_random(relic_condition, self.args.item_equipable_relic_random_min, - self.args.item_equipable_relic_random_max) + self.args.item_equipable_relic_random_max) elif self.args.item_equipable_relic_balanced_random: self.equipable_balanced_random(relic_condition, self.args.item_equipable_relic_balanced_random_value) + elif self.args.item_equipable_relic_tiered_random: + self.equipable_tiered(relic_condition) elif self.args.item_equipable_relic_original_random: self.equipable_original_random(relic_condition, self.args.item_equipable_relic_original_random_percent) elif self.args.item_equipable_relic_shuffle_random: @@ -294,7 +318,7 @@ def get_name(self, id): def get_type(self, id): return self.items[id].type - def get_items(self, exclude = None, item_types = None): + def get_items(self, exclude=None, item_types=None): if exclude is None: exclude = [] exclude.append(self.EMPTY) @@ -303,12 +327,12 @@ def get_items(self, exclude = None, item_types = None): item_list = [item.id for item in self.items] else: try: - assert(item_types >= 0 and item_types < Item.ITEM_TYPE_COUNT) + assert (item_types >= 0 and item_types < Item.ITEM_TYPE_COUNT) item_list = [item.id for item in self.type_items[item_types]] except ValueError: item_list = [] for item_type in item_types: - assert(item_type >= 0 and item_type < Item.ITEM_TYPE_COUNT) + assert (item_type >= 0 and item_type < Item.ITEM_TYPE_COUNT) item_list.extend([item.id for item in self.type_items[item_type]]) item_list = [item_id for item_id in item_list if item_id not in exclude] @@ -336,7 +360,7 @@ def get_excluded(self): return exclude - def get_random(self, exclude = None, item_types = None): + def get_random(self, exclude=None, item_types=None): if exclude is None: exclude = [] exclude.extend(self.get_excluded()) diff --git a/data/maps.py b/data/maps.py index 216312cf..eeec6175 100644 --- a/data/maps.py +++ b/data/maps.py @@ -14,6 +14,9 @@ import data.world_map_event_modifications as world_map_event_modifications from data.world_map import WorldMap +import instruction.asm as asm +from memory.space import Reserve + class Maps(): MAP_COUNT = 416 @@ -264,6 +267,14 @@ def _fix_Cid_timer_glitch(self): new_le.event_address = space.start_address - EVENT_CODE_START self.add_long_event(map_id, new_le) + def _disable_saves(self): + # Ironmog mode -- disable saves + space = Reserve(0x32ead, 0x32eae, asm.NOP()) + space.add_label("DISABLE SAVE", 0x32ebf) + space.write( + asm.BRA("DISABLE SAVE") # replace the vanilla BPL $2EBF to always branch) + ) + def mod(self, characters): self.npcs.mod(characters) self.chests.mod() @@ -271,6 +282,8 @@ def mod(self, characters): self._fix_imperial_camp_boxes() self._fix_Cid_timer_glitch() + if self.args.no_saves: + self._disable_saves() def write(self): self.npcs.write() diff --git a/data/text/text2.py b/data/text/text2.py index 8833a2d5..f8383c27 100644 --- a/data/text/text2.py +++ b/data/text/text2.py @@ -27,7 +27,7 @@ '' : 0xd8, '' : 0xd9, '' : 0xda, - '' : 0xdb, + '' : 0xdb, '' : 0xdc, '' : 0xdd, '' : 0xde, diff --git a/event/debug_room.py b/event/debug_room.py index 483c5f47..87b58724 100644 --- a/event/debug_room.py +++ b/event/debug_room.py @@ -1,5 +1,6 @@ from event.event import * from data.npc import NPC +from music.song_utils import get_character_theme class DebugRoom(Event): # Using the 3 Scenarios room as our debug map @@ -21,6 +22,7 @@ def _add_recruit_npc(self, character, x, y, direction): src = [ field.RecruitCharacter(character), field.PlaySoundEffect(150), + field.StartSong(get_character_theme(character)), field.Return(), ] space = Write(Bank.CC, src, "Recruit NPC") diff --git a/event/event_reward.py b/event/event_reward.py index f1063e2e..ce722994 100644 --- a/event/event_reward.py +++ b/event/event_reward.py @@ -51,6 +51,15 @@ def choose_reward(possible_types, characters, espers, items): assert(item_possible) return (items.get_good_random(), RewardType.ITEM) +# Documentation from AtmaTek: +# The main idea for gating is that we have y characters that we want to distribute amongst x checks with equal probability while also guaranteeing a path between them. The natural approach is to take our starting character(s), put the checks they unlock into a pool, pick a check at random from the pool, assign a character to it then put that character's checks into the pool and repeat until all characters have been assigned. This is basically the approach WC takes (and I assume what other gating randomizers do as well). +# However, there is a problem. We guaranteed a path but the x original checks no longer all have an equal probability of being assigned a character in a single seed. I think the easiest way to picture it is to imagine you have a bag with a red marble and a blue marble. You pick one at random so both red and blue have a 50/50 chance. Let's say you pick blue then take it out and replace it with yellow. Now you pick again with a 50% chance of red and a 50% chance of yellow. However, red has now had 2 chances to be picked with 50% odds while yellow has only had 1 chance. This means that checks which were unlocked by characters assigned by the algorithm earlier will have a higher chance of rewarding a character than checks unlocked by characters assigned later. In other words, the checks unlocked by the first character had many more chances to be picked than the checks unlocked by the 13th character. + +# Broadly speaking, for a randomizer with a gating mechanic and decently balanced checks (never really true, but something many believe should be a goal), a breadth-first-search is the optimal approach to unlocking gates. +# I am not sure if/how other randomizers handle this (and would be interested in learning). However, in an attempt to compensate, the WC code at line 53 in event_reward.py instead picks the "marbles" with a weighting based on how long they have been in the "bag". So instead of 50/50 red and yellow, red will now have lower odds than yellow. With the given algorithm, it is not possible to make them entirely equally weighted because a decision has already been made by the time yellow is added. Instead, I somewhat arbitrarily opted to make new "marbles" twice as likely so red will have 33% chance and yellow 66%. +# My hope is that in practice (when combined with the unequal value of some checks and real-time opportunities like checks a player is already close to) this compensation is enough to offset the breadth-first-search skew. If it ever becomes a more serious issue then the compensation can be adjusted or entirely new approaches may be possible (like possibly finding a new algorithm with a depth-first-search skew and randomly choosing between them). + + # weight reward slots based on how long they have been in the reward pool (longer means lower odds) # the first events unlocked will have more chances to be picked, this balances it somewhat by lowering their odds each time they aren't picked # specifically, when an event is added to the pool it is twice as likely to be picked as an event added in the previous iteration diff --git a/event/events.py b/event/events.py index f1d40477..d22d7e41 100644 --- a/event/events.py +++ b/event/events.py @@ -171,4 +171,4 @@ def validate(self, events): for event in events: char_esper_checks += [r for r in event.rewards if r.possible_types == (RewardType.CHARACTER | RewardType.ESPER)] - assert len(char_esper_checks) == CHARACTER_ESPER_ONLY_REWARDS, "Number of char/esper only checks changed - Check usages of CHARACTER_ESPER_ONLY_REWARDS and ensure no breaking changes" \ No newline at end of file + f"Number of char/esper only checks changed - Check usages of CHARACTER_ESPER_ONLY_REWARDS and ensure no breaking changes. Expected: {CHARACTER_ESPER_ONLY_REWARDS}, Actual: {len(char_esper_checks)}" diff --git a/event/lone_wolf.py b/event/lone_wolf.py index ce93f05b..e6393aa2 100644 --- a/event/lone_wolf.py +++ b/event/lone_wolf.py @@ -8,7 +8,11 @@ def character_gate(self): return self.characters.MOG def init_rewards(self): - self.reward1 = self.add_reward(RewardType.CHARACTER | RewardType.ESPER | RewardType.ITEM) + if self.args.no_free_characters_espers: + self.reward1 = self.add_reward(RewardType.ITEM) + else: + self.reward1 = self.add_reward(RewardType.CHARACTER | RewardType.ESPER | RewardType.ITEM) + self.reward2 = self.add_reward(RewardType.ITEM) def init_event_bits(self, space): @@ -70,7 +74,16 @@ def chase_mod(self): space = Reserve(0xcd402, 0xcd402, "lone wolf pauses before turning right") space.write(field.Pause(0.5)) # shorten from 2 seconds + def character_music_mod(self, character): + from music.song_utils import get_character_theme + src = [ + field.StartSong(get_character_theme(character)), + ] + space = Reserve(0xcd606, 0xcd607, "Play Song Mog") + space.write(src) + def character_mod(self, character): + self.character_music_mod(character) self.mog_npc.sprite = character self.mog_npc.palette = self.characters.get_palette(character) diff --git a/event/mt_zozo.py b/event/mt_zozo.py index 6b43c0b8..fe54b824 100644 --- a/event/mt_zozo.py +++ b/event/mt_zozo.py @@ -8,7 +8,10 @@ def character_gate(self): return self.characters.CYAN def init_rewards(self): - self.reward = self.add_reward(RewardType.CHARACTER | RewardType.ESPER | RewardType.ITEM) + if self.args.no_free_characters_espers: + self.reward = self.add_reward(RewardType.ITEM) + else: + self.reward = self.add_reward(RewardType.CHARACTER | RewardType.ESPER | RewardType.ITEM) def init_event_bits(self, space): space.write( diff --git a/event/owzer_mansion.py b/event/owzer_mansion.py index ae8eefd8..b0472c53 100644 --- a/event/owzer_mansion.py +++ b/event/owzer_mansion.py @@ -111,11 +111,13 @@ def chadarnook_battle_mod(self): def character_music_mod(self, character): from music.song_utils import get_character_theme - - space = Reserve(0xb4d1f, 0xb4d20, "Play Song Relm") - space.write([ + src = [ field.StartSong(get_character_theme(character)), - ]) + ] + space = Reserve(0xb4d1f, 0xb4d20, "Play Song Relm") + space.write(src) + space = Reserve(0xb4cc6, 0xb4cc7, "Play Song Relm") + space.write(src) def character_mod(self, character): self.character_music_mod(character) diff --git a/log/format.py b/log/format.py index 0c22b47b..8d031472 100644 --- a/log/format.py +++ b/log/format.py @@ -53,5 +53,11 @@ def get_separator(label): return separator -def format_option(option, value): +def format_option(option, value, unique_name = ''): + from constants.standard_flags import standard_flags + standard_flag = standard_flags.get(unique_name) + if(standard_flag != str(value)): + pass # nothing for now -- uncomment once we have new standard + # option = f"!{option}" # prepend a ! to indicate it's non standard + return f" {option:<26} {value}" diff --git a/menus/flags.py b/menus/flags.py index 35938c08..c644ab8a 100644 --- a/menus/flags.py +++ b/menus/flags.py @@ -21,7 +21,8 @@ def __init__(self): self.lines.append(scroll_area.Line(name, f0.set_blue_text_color)) for option in options: - key, value = option + key = option[0] + value = option[1] key = " " + key.replace("&", "+") diff --git a/menus/flags_reward_items.py b/menus/flags_reward_items.py new file mode 100644 index 00000000..3b8af3ee --- /dev/null +++ b/menus/flags_reward_items.py @@ -0,0 +1,85 @@ +import menus.pregame_track_scroll_area as scroll_area +from data.text.text2 import text_value +import instruction.f0 as f0 + +class FlagsRewardItems(scroll_area.ScrollArea): + MENU_NUMBER = 16 + + def __init__(self, item_ids): + self.number_items = len(item_ids) + self.lines = [] + + self.lines.append(scroll_area.Line(f"Item Rewards ({self.number_items})", f0.set_blue_text_color)) + self.lines.append(scroll_area.Line(f"Checks may reward any of:", f0.set_gray_text_color)) + + item_lines = FlagsRewardItems._format_items_menu(item_ids) + + for list_value in item_lines: + padding = scroll_area.WIDTH - (len(list_value)) + self.lines.append(scroll_area.Line(f"{' ' * padding}{list_value}", f0.set_user_text_color)) + + super().__init__() + + def _format_items_menu(item_ids): + from constants.items import id_name + COLUMN_WIDTHS = [13, 13] + item_lines = [] + + # Step through each item by the number of columns + for item_idx in range(0, len(item_ids), len(COLUMN_WIDTHS)): + current_line = '' + # Populate each column on the line + for col in range(0, len(COLUMN_WIDTHS)): + if(item_idx + col < len(item_ids)): + a_item_id = item_ids[item_idx + col] + icon = FlagsRewardItems._get_item_icon(a_item_id) + item_str = f"{icon}{id_name[a_item_id]}" + padding = COLUMN_WIDTHS[col] - len(item_str) + current_line += f"{item_str}{' ' * padding}" + else: + # No item, add padding + current_line += f"{' ' * COLUMN_WIDTHS[col]}" + # Write the line + item_lines.append(current_line) + return item_lines + + def _get_item_icon(item_id): + from constants.items import DIRKS, SWORDS, LANCES, KNIVES, KATANAS, RODS, BRUSHES, \ + STARS, SPECIAL, GAMBLER, CLAWS, SHIELDS, HELMETS, ARMORS, TOOLS, SKEANS, RELICS + from data.text.text2 import text_value + icon = '' + if item_id in DIRKS or item_id in KNIVES: + icon = chr(text_value['']) + elif item_id in SWORDS: + icon = chr(text_value['']) + elif item_id in LANCES: + icon = chr(text_value['']) + elif item_id in KATANAS: + icon = chr(text_value['']) + elif item_id in RODS: + # for some reason, the Rod icon causes submenus to not work + icon = '' + #icon = chr(text_value['']) + elif item_id in STARS: + icon = chr(text_value['']) + elif item_id in GAMBLER: + icon = chr(text_value['']) + elif item_id in CLAWS: + icon = chr(text_value['']) + elif item_id in SHIELDS: + icon = chr(text_value['']) + elif item_id in HELMETS: + icon = chr(text_value['']) + elif item_id in ARMORS: + icon = chr(text_value['']) + elif item_id in TOOLS: + icon = chr(text_value['']) + elif item_id in SKEANS: + icon = chr(text_value['']) + elif item_id in RELICS: + icon = chr(text_value['']) + return icon \ No newline at end of file diff --git a/music/song_utils.py b/music/song_utils.py index 8758346e..b671d5ff 100644 --- a/music/song_utils.py +++ b/music/song_utils.py @@ -3,7 +3,7 @@ from instruction import field character_to_song = { - TERRA: 0x05, + TERRA: 0x06, SHADOW: 0x07, STRAGO: 0x08, GAU: 0x09, diff --git a/objectives/results/restoratives.py b/objectives/results/restoratives.py new file mode 100644 index 00000000..a79755ee --- /dev/null +++ b/objectives/results/restoratives.py @@ -0,0 +1,37 @@ +from objectives.results._objective_result import * +from data.item_names import name_id as item_name_id + +RESTORATIVES = ["Tonic", "Potion", "X-Potion", "Tincture", "Ether", "X-Ether", "Elixir", "Megalixir", "Fenix Down", + "Revivify", "Antidote", "Eyedrop", "Soft", "Remedy", "Sleeping Bag", "Tent", "Green Cherry", + "Echo Screen"] +RESTORATIVE_COUNTS = [10, 10, 5, 10, 10, 5, 3, 1, 10, 5, 2, 2, 2, 3, 10, 3, 2, 2] +RESTORATIVE_IDS = [item_name_id[item_name] for item_name in RESTORATIVES] + + +class Field(field_result.Result): + def src(self): + src = [] + for item_id, count in zip(RESTORATIVE_IDS, RESTORATIVE_COUNTS): + for _ in range(count): + src += [ + field.AddItem(item_id), + ] + return src + + +class Battle(battle_result.Result): + def src(self): + src = [] + for item_id, count in zip(RESTORATIVE_IDS, RESTORATIVE_COUNTS): + for _ in range(count): + src += [ + battle_result.AddItem(item_id), + ] + return src + + +class Result(ObjectiveResult): + NAME = "Restoratives" + + def __init__(self): + super().__init__(Field, Battle) \ No newline at end of file diff --git a/objectives/results/throwables.py b/objectives/results/throwables.py new file mode 100644 index 00000000..71017575 --- /dev/null +++ b/objectives/results/throwables.py @@ -0,0 +1,36 @@ +from objectives.results._objective_result import * +from data.item_names import name_id as item_name_id + +THROWABLES = ["Shuriken", "Ninja Star", "Tack Star", "Fire Skean", "Water Edge", "Bolt Edge", "Inviz Edge", + "Shadow Edge"] +THROWABLE_COUNTS = [20, 10, 5, 10, 10, 10, 5, 5] +THROWABLE_IDS = [item_name_id[item_name] for item_name in THROWABLES] + + +class Field(field_result.Result): + def src(self): + src = [] + for item_id, count in zip(THROWABLE_IDS, THROWABLE_COUNTS): + for _ in range(count): + src += [ + field.AddItem(item_id), + ] + return src + + +class Battle(battle_result.Result): + def src(self): + src = [] + for item_id, count in zip(THROWABLE_IDS, THROWABLE_COUNTS): + for _ in range(count): + src += [ + battle_result.AddItem(item_id), + ] + return src + + +class Result(ObjectiveResult): + NAME = "Throwables" + + def __init__(self): + super().__init__(Field, Battle) \ No newline at end of file diff --git a/settings/__init__.py b/settings/__init__.py index d0355987..df73fe62 100644 --- a/settings/__init__.py +++ b/settings/__init__.py @@ -3,6 +3,7 @@ from settings.random_rng import RandomRNG from settings.permadeath import Permadeath from settings.y_npc import YNPC +from settings.less_poison_blur import LessPoisonBlur from settings.config import Config from memory.space import Reserve @@ -16,6 +17,7 @@ def __init__(self): self.random_rng = RandomRNG() self.permadeath = Permadeath() self.y_npc = YNPC() + self.less_poison_blur = LessPoisonBlur() self.config = Config() # do not auto load save file after game over diff --git a/settings/less_poison_blur.py b/settings/less_poison_blur.py new file mode 100644 index 00000000..c0ca54b5 --- /dev/null +++ b/settings/less_poison_blur.py @@ -0,0 +1,26 @@ +## Original mod from Beyond Chaos +## https://github.com/FF6BeyondChaos/BeyondChaosRandomizer/blob/main/BeyondChaos/patches.py +## look for section beginning with "nicer_poison(fout):" +from memory.space import Reserve +import instruction.asm as asm + +### reduce poison pixellation effect while walking +### does not affect poison sound effect while on overworld map +class LessPoisonBlur: + def __init__(self): + self.mod() + + def mod(self): + # make poison pixelation effect 1/10 of it's vanilla amount in dungeons/towns + space = Reserve(0x00e82, 0x00e9f, "town/dungeon poison gfx fix") + space.write( + [0x0F, 0x0F, 0x0F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, + 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, + 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x0F, 0x0F, 0x0F] + ) + + # remove poison pixelation on the overworld + space = Reserve(0x2e1864, 0x2e1865, "overworld poison gfx fix") + space.write( + asm.LDA(0x00, asm.IMM8), + ) diff --git a/version.py b/version.py index 9c73af26..edd839d1 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -__version__ = "1.3.1" +__version__ = "1.4d"