From 907639b9848690f08249899fd94e78084906e2a4 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 10 Apr 2023 09:07:01 -0600 Subject: [PATCH] any_enemy_logic option added --- BaseClasses.py | 4 +++ CLI.py | 3 ++- Main.py | 2 ++ mystery_example.yml | 4 +++ mystery_testsuite.yml | 1 + resources/app/cli/args.json | 7 +++++ resources/app/cli/lang/en.json | 6 +++++ resources/app/gui/lang/en.json | 5 ++++ .../app/gui/randomize/enemizer/widgets.json | 14 ++++++++++ source/classes/CustomSettings.py | 2 ++ source/classes/constants.py | 3 ++- source/enemizer/Enemizer.py | 26 +++++++++++++------ source/enemizer/SpriteSheets.py | 4 +-- source/gui/randomize/enemizer.py | 4 ++- source/tools/MysteryUtils.py | 1 + 15 files changed, 73 insertions(+), 13 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 64e2385c..f07e76e5 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -128,6 +128,7 @@ class World(object): set_player_attr('enemy_shuffle', 'none') set_player_attr('enemy_health', 'default') set_player_attr('enemy_damage', 'default') + set_player_attr('any_enemy_logic', 'allow_all') set_player_attr('beemizer', 0) set_player_attr('escape_assist', []) set_player_attr('crystals_needed_for_ganon', 7) @@ -2497,6 +2498,7 @@ class Spoiler(object): 'enemy_shuffle': self.world.enemy_shuffle, 'enemy_health': self.world.enemy_health, 'enemy_damage': self.world.enemy_damage, + 'any_enemy_logic': self.world.any_enemy_logic, 'players': self.world.players, 'teams': self.world.teams, 'experimental': self.world.experimental, @@ -2701,6 +2703,8 @@ class Spoiler(object): outfile.write('Enemy shuffle: %s\n' % self.metadata['enemy_shuffle'][player]) outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player]) outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player]) + if self.metadata['enemy_shuffle'][player] != 'none': + outfile.write(f"Enemy logic: {self.metadata['any_enemy_logic'][player]}\n") outfile.write(f"Hints: {yn(self.metadata['hints'][player])}\n") outfile.write('Race: %s\n' % ('Yes' if self.world.settings.world_rep['meta']['race'] else 'No')) diff --git a/CLI.py b/CLI.py index 1acebccc..fcb769f0 100644 --- a/CLI.py +++ b/CLI.py @@ -141,7 +141,7 @@ def parse_cli(argv, no_defaults=False): 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', - 'trap_door_mode', 'key_logic_algorithm']: + 'trap_door_mode', 'key_logic_algorithm', 'any_enemy_logic']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) @@ -199,6 +199,7 @@ def parse_settings(): "shufflebosses": "none", "enemy_damage": "default", "enemy_health": "default", + 'any_enemy_logic': 'allow_all', "shopsanity": False, 'keydropshuffle': False, diff --git a/Main.py b/Main.py index 0d3716b4..87b1461f 100644 --- a/Main.py +++ b/Main.py @@ -112,6 +112,7 @@ def main(args, seed=None, fish=None): world.enemy_shuffle = args.shuffleenemies.copy() world.enemy_health = args.enemy_health.copy() world.enemy_damage = args.enemy_damage.copy() + world.any_enemy_logic = args.any_enemy_logic.copy() world.beemizer = args.beemizer.copy() world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} world.door_type_mode = args.door_type_mode.copy() @@ -454,6 +455,7 @@ def copy_world(world): ret.enemy_shuffle = world.enemy_shuffle.copy() ret.enemy_health = world.enemy_health.copy() ret.enemy_damage = world.enemy_damage.copy() + ret.any_enemy_logic = world.any_enemy_logic.copy() ret.beemizer = world.beemizer.copy() ret.intensity = world.intensity.copy() ret.decoupledoors = world.decoupledoors.copy() diff --git a/mystery_example.yml b/mystery_example.yml index b56f38d8..c3fba1e4 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -162,6 +162,10 @@ none: 3 shuffled: 2 random: 1 + any_enemy_logic: + none: 1 + allow_drops: 2 + allow_all: 3 hints: on: 1 off: 1 diff --git a/mystery_testsuite.yml b/mystery_testsuite.yml index 5d7291b7..403e93f5 100644 --- a/mystery_testsuite.yml +++ b/mystery_testsuite.yml @@ -146,6 +146,7 @@ enemy_shuffle: # shouldn't affect generation shuffled: 1 random: 1 legacy: 0 +any_enemy_logic: allow_all # more interesting logic hints: on: 1 off: 1 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index c6829d68..2e9606a6 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -494,6 +494,13 @@ "random" ] }, + "any_enemy_logic": { + "choices": [ + "none", + "allow_drops", + "allow_all" + ] + }, "remote_items": { "action": "store_true", "type": "bool" diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 588a565a..d36dbc16 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -335,6 +335,12 @@ ], "pseudoboots": [ " Players starts with pseudo boots that allow dashing but no item checks (default: %(default)s"], "bombbag": ["Start with 0 bomb capacity. Two capacity upgrades (+10) are added to the pool (default: %(default)s)" ], + "any_enemy_logic": [ + "How to handle potential traversal between dungeon in Crossed door shuffle", + "None: Enemies that required unusual weaponry are not allowed to logical protect doors, chests, or key drops.", + "Allow_Drops: Drops are okay for those enemies, and a correct weapon is logically required", + "Allow_All: All enemies allowed anywhere. (Logic will be adapted to the placement)" + ], "startinventory": [ "Specifies a list of items that will be in your starting inventory (separated by commas). (default: %(default)s)" ], "usestartinventory": [ "Toggle usage of Starting Inventory." ], "customizer": ["Path to a customizer file."], diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 12b36fca..faa4b73e 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -130,6 +130,11 @@ "randomizer.enemizer.enemyhealth.hard": "Hard", "randomizer.enemizer.enemyhealth.expert": "Expert", + "randomizer.enemizer.enemylogic": "Enemy Logic", + "randomizer.enemizer.enemylogic.none": "Forbid special enemies", + "randomizer.enemizer.enemylogic.allow_drops": "Item drops may have special enemies", + "randomizer.enemizer.enemylogic.allow_all": "Allow special enemies anywhere", + "randomizer.entrance.openpyramid": "Pre-open Pyramid Hole", "randomizer.entrance.openpyramid.auto": "Auto", "randomizer.entrance.openpyramid.yes": "Yes", diff --git a/resources/app/gui/randomize/enemizer/widgets.json b/resources/app/gui/randomize/enemizer/widgets.json index 7fa097a2..0c78cb2d 100644 --- a/resources/app/gui/randomize/enemizer/widgets.json +++ b/resources/app/gui/randomize/enemizer/widgets.json @@ -39,5 +39,19 @@ "expert" ] } + }, + "bottomEnemizerFrame": { + "enemylogic": { + "type": "selectbox", + "default": "allow_all", + "options": [ + "none", + "allow_drops", + "allow_all" + ], + "config": { + "width": 32 + } + } } } diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index c16c93e7..91bcf371 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -134,6 +134,7 @@ class CustomSettings(object): args.shuffleenemies[p] = get_setting(settings['enemy_shuffle'], args.shuffleenemies[p]) args.enemy_health[p] = get_setting(settings['enemy_health'], args.enemy_health[p]) args.enemy_damage[p] = get_setting(settings['enemy_damage'], args.enemy_damage[p]) + args.any_enemy_logic[p] = get_setting(settings['any_enemy_logic'], args.any_enemy_logic[p]) args.shufflepots[p] = get_setting(settings['shufflepots'], args.shufflepots[p]) args.bombbag[p] = get_setting(settings['bombbag'], args.bombbag[p]) args.shufflelinks[p] = get_setting(settings['shufflelinks'], args.shufflelinks[p]) @@ -254,6 +255,7 @@ class CustomSettings(object): settings_dict[p]['shuffleenemies'] = world.enemy_shuffle[p] settings_dict[p]['enemy_health'] = world.enemy_health[p] settings_dict[p]['enemy_damage'] = world.enemy_damage[p] + settings_dict[p]['any_enemy_logic'] = world.any_enemy_logic[p] settings_dict[p]['shufflepots'] = world.potshuffle[p] settings_dict[p]['bombbag'] = world.bombbag[p] settings_dict[p]['shufflelinks'] = world.shufflelinks[p] diff --git a/source/classes/constants.py b/source/classes/constants.py index 3f0fb620..b7f034d1 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -114,7 +114,8 @@ SETTINGSTOPROCESS = { "enemyshuffle": "shuffleenemies", "bossshuffle": "shufflebosses", "enemydamage": "enemy_damage", - "enemyhealth": "enemy_health" + "enemyhealth": "enemy_health", + "enemylogic": "any_enemy_logic" }, "gameoptions": { "nobgm": "disablemusic", diff --git a/source/enemizer/Enemizer.py b/source/enemizer/Enemizer.py index 82fc05f2..d97c0cd3 100644 --- a/source/enemizer/Enemizer.py +++ b/source/enemizer/Enemizer.py @@ -289,16 +289,12 @@ def randomize_underworld_rooms(data_tables, world, player): if wallmaster_chosen: candidate_sprites = [x for x in candidate_sprites if x.sprite != EnemySprite.Wallmaster] if sprite.drops_item: - choice_list = [x for x in candidate_sprites if x.good_for_key_drop()] + forbidden = determine_forbidden(any_enemy_logic == 'none', room_id, True) + choice_list = [x for x in candidate_sprites if x.good_for_key_drop(forbidden)] # terrorpin, deadrock, buzzblob, lynel, redmimic/eyegore elif room_id in shutter_sprites and i in shutter_sprites[room_id]: - forbidden_set = set() - if not any_enemy_logic: - forbidden_set.update({EnemySprite.Terrorpin, EnemySprite.Deadrock, EnemySprite.Buzzblob, - EnemySprite.Lynel}) - if room_id not in {0x6b, 0x4b, 0x1b, 0xd8}: # mimics/eyegore are allowed in vanilla - forbidden_set.add(EnemySprite.RedEyegoreMimic) - choice_list = [x for x in candidate_sprites if x.good_for_shutter(forbidden_set)] + forbidden = determine_forbidden(any_enemy_logic != 'allow_all', room_id) + choice_list = [x for x in candidate_sprites if x.good_for_shutter(forbidden)] else: choice_list = [x for x in candidate_sprites if not x.water_only] choice_list = filter_choices(choice_list, room_id, i, data_tables.uw_enemy_denials) @@ -317,6 +313,20 @@ def randomize_underworld_rooms(data_tables, world, player): # done with rooms +def determine_forbidden(forbid, room_id, drop_flag=False): + forbidden_set = set() + if forbid: + forbidden_set.update({EnemySprite.Terrorpin, EnemySprite.Deadrock, EnemySprite.Buzzblob, + EnemySprite.Lynel}) + if drop_flag: + forbidden_set.add(EnemySprite.RedBari) # requires FireRod to Drop + # else: Not yet able to protect triggers, would change default GT tile room behavior + # forbidden_set.add(EnemySprite.AntiFairy) # can't drop anyway + if room_id not in {0x6b, 0x4b, 0x1b, 0xd8}: # mimics/eyegore are allowed in vanilla rooms + forbidden_set.add(EnemySprite.RedEyegoreMimic) + return forbidden_set + + def filter_choices(options, room_id, sprite_idx, denials): key = room_id, sprite_idx return [x for x in options if key not in denials or x.sprite not in denials[key]] diff --git a/source/enemizer/SpriteSheets.py b/source/enemizer/SpriteSheets.py index 4ecd664d..97baf0a1 100644 --- a/source/enemizer/SpriteSheets.py +++ b/source/enemizer/SpriteSheets.py @@ -96,8 +96,8 @@ class SpriteRequirement: return False return self.killable and not self.static and not self.dont_use and self.uw_valid - def good_for_key_drop(self): - return self.good_for_shutter() and self.can_drop + def good_for_key_drop(self, forbidden): + return self.good_for_shutter(forbidden) and self.can_drop def __str__(self): return f'Req for {enemy_names[self.sprite]}' diff --git a/source/gui/randomize/enemizer.py b/source/gui/randomize/enemizer.py index a5cfe686..40bf055c 100644 --- a/source/gui/randomize/enemizer.py +++ b/source/gui/randomize/enemizer.py @@ -26,7 +26,7 @@ def enemizer_page(parent,settings): self.frames["selectOptionsFrame"].pack(fill=X) self.frames["leftEnemizerFrame"].pack(side=LEFT) self.frames["rightEnemizerFrame"].pack(side=RIGHT) - self.frames["bottomEnemizerFrame"].pack(fill=X) + self.frames["bottomEnemizerFrame"].pack(fill=X, padx=(12, 0)) # Load Enemizer option widgets as defined by JSON file # Defns include frame name, widget type, widget options, widget placement attributes @@ -40,6 +40,8 @@ def enemizer_page(parent,settings): packAttrs = {"anchor":E} if self.widgets[key].type == "checkbox": packAttrs["anchor"] = W + if framename == 'bottomEnemizerFrame': + packAttrs["anchor"] = W self.widgets[key].pack(packAttrs) return self, settings diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index af20b5e7..6229fac1 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -182,6 +182,7 @@ def roll_settings(weights): ret.enemy_damage = damage_choice ret.enemy_health = get_choice('enemy_health') + ret.any_enemy_logic = get_choice('any_enemy_logic') ret.beemizer = get_choice('beemizer') if 'beemizer' in weights else '0'