diff --git a/Fill.py b/Fill.py index 6ae73dcf..4d59a35d 100644 --- a/Fill.py +++ b/Fill.py @@ -361,54 +361,6 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # get items to distribute classify_major_items(world) # handle pot/drop shuffle - chicken_pool = collections.defaultdict(list) - big_magic_pool = collections.defaultdict(list) - fairy_pool = collections.defaultdict(list) - - for item in world.itempool: - # can only fill these in that players world - if item.name == 'Chicken': - chicken_pool[item.player].append(item) - elif item.name == 'Big Magic': - big_magic_pool[item.player].append(item) - elif item.name == 'Fairy': - fairy_pool[item.player].append(item) - - def fast_fill_certain_items(item_pool, location_types, filter_locations, matcher): - flag = False - for player, pool in item_pool.items(): - if pool: - for certain_item in pool: - world.itempool.remove(certain_item) - cand_locations = [location for location in fill_locations - if location.type in location_types and location.player == player] - locations = filter_locations(cand_locations, world, matcher) - fast_fill_helper(world, pool, locations) - flag = True - return flag - - def chicken_matcher(l): - return l.pot and l.pot.item == PotItem.Chicken - - def magic_matcher(l): - if l.drop: - pack = enemy_stats[l.drop.kind].prize_pack - return pack in {3,7} if isinstance(pack, int) else (3 in pack or 7 in pack) - return l.pot and l.pot.item == PotItem.BigMagic - - def fairy_matcher(l): - if l.drop: - pack = enemy_stats[l.drop.kind].prize_pack - return pack == 7 if isinstance(pack, int) else 7 in pack - return False - - reshuffle = fast_fill_certain_items(chicken_pool, [LocationType.Pot], filter_special_locations, chicken_matcher) - reshuffle |= fast_fill_certain_items(fairy_pool, [LocationType.Drop], filter_special_locations, fairy_matcher) - reshuffle |= fast_fill_certain_items(big_magic_pool, [LocationType.Pot, LocationType.Drop], - filter_special_locations, magic_matcher) - if reshuffle: - fill_locations = world.get_unfilled_locations() - random.shuffle(fill_locations) random.shuffle(world.itempool) progitempool = [item for item in world.itempool if item.advancement] @@ -524,29 +476,11 @@ def ensure_good_items(world, write_skips=False): if (loc.item.name in {'Arrows (5)', 'Nothing'} and (loc.type != LocationType.Pot or loc.item.player != loc.player)): loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player) - # can be placed here by multiworld balancing or shop balancing - # change it to something normal for the player it got swapped to - elif loc.item.name == 'Chicken' and (loc.type != LocationType.Pot or loc.item.player != loc.player): - if loc.type == LocationType.Pot: - loc.item.player = loc.player - else: - loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player) - elif (loc.item.name == 'Big Magic' - and (loc.type not in [LocationType.Pot, LocationType.Drop] or loc.item.player != loc.player)): - if loc.type in [LocationType.Pot, LocationType.Drop]: - loc.item.player = loc.player - else: - loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player) - elif (loc.item.name == 'Fairy' - and (loc.type != LocationType.Drop or loc.item.player != loc.player)): - if loc.type == LocationType.Drop: - loc.item.player = loc.player - else: - loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player) # do the arrow retro check if world.bow_mode[loc.item.player].startswith('retro') and loc.item.name in {'Arrows (5)', 'Arrows (10)'}: loc.item = ItemFactory('Rupees (5)', loc.item.player) # don't write out all pots to spoiler + # todo: skip uninteresting enemy drops if write_skips: if loc.type == LocationType.Pot and loc.item.name in valid_pot_items: loc.skip = True diff --git a/Rom.py b/Rom.py index 27fc8f24..dc08f9d3 100644 --- a/Rom.py +++ b/Rom.py @@ -1534,7 +1534,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery'] and (world.colorizepots[player] or world.pottery[player] in ['reduced', 'clustered'])) - world.data_tables[player].write_to_rom(rom) + world.data_tables[player].write_to_rom(rom, colorize_pots) write_strings(rom, world, player, team) diff --git a/Rules.py b/Rules.py index 83512aaf..2dbfd1b0 100644 --- a/Rules.py +++ b/Rules.py @@ -9,7 +9,7 @@ from Dungeons import dungeon_table from RoomData import DoorKind from OverworldGlitchRules import overworld_glitches_rules -from source.dungeon.EnemyList import kill_rules +from source.dungeon.EnemyList import kill_rules, special_rules_check, special_rules_for_region def set_rules(world, player): @@ -781,6 +781,9 @@ def drop_rules(world, player): if enemy.location: # could handle odd health rules here? assume harder variant for now verbose_rule = defeat_rules[enemy.kind] + if enemy.location.parent_region.name in special_rules_check: + verbose_rule = special_rules_for_region(world, player, enemy.location.parent_region.name, + enemy.location, verbose_rule, enemy) enemy.location.verbose_rule = verbose_rule add_rule(enemy.location, verbose_rule.rule_lambda) diff --git a/source/dungeon/EnemyList.py b/source/dungeon/EnemyList.py index a0944556..295e6762 100644 --- a/source/dungeon/EnemyList.py +++ b/source/dungeon/EnemyList.py @@ -1945,8 +1945,8 @@ def init_vanilla_sprites(): create_sprite(0x010c, EnemySprite.GreenEyegoreMimic, 0x00, 0, 0x08, 0x14, 'Mimic Cave') create_sprite(0x010c, EnemySprite.GreenEyegoreMimic, 0x00, 0, 0x0c, 0x14, 'Mimic Cave') create_sprite(0x010c, EnemySprite.GreenEyegoreMimic, 0x00, 0, 0x0c, 0x1a, 'Mimic Cave') - create_sprite(0x010d, EnemySprite.SparkCW, 0x00, 0, 0x05, 0x16, 'Mimic Cave') - create_sprite(0x010d, EnemySprite.SparkCCW, 0x00, 0, 0x0a, 0x16, 'Mimic Cave') + create_sprite(0x010d, EnemySprite.SparkCW, 0x00, 0, 0x05, 0x16, 'Mire Shed') + create_sprite(0x010d, EnemySprite.SparkCCW, 0x00, 0, 0x0a, 0x16, 'Mire Shed') create_sprite(0x010e, EnemySprite.DarkWorldHintNpc, 0x00, 0, 0x06, 0x06) create_sprite(0x010e, EnemySprite.DarkWorldHintNpc, 0x00, 0, 0x18, 0x06) create_sprite(0x010f, EnemySprite.Shopkeeper, 0x00, 0, 0x07, 0x15) @@ -2005,8 +2005,7 @@ def kill_rules(world, player, stats): bomb=2, silver=3, fire=None), EnemySprite.MiniMoldorm: defeat_rule(world, player, h(EnemySprite.MiniMoldorm), ice=1, hook=True), EnemySprite.Sluggula: defeat_rule(world, player, h(EnemySprite.Sluggula), bomb=None), - # too hard to hit red biris with arrows - EnemySprite.RedBari: defeat_rule(world, player, h(EnemySprite.RedBari) * 3, arrow=None, ice=2, hook=True), + EnemySprite.RedBari: or_rule(has('Fire Rod', player), and_rule(has_sword(player), has('Bombos', player))), EnemySprite.BlueBari: defeat_rule(world, player, h(EnemySprite.BlueBari), ice=2, hook=True), EnemySprite.HardhatBeetle: defeat_rule(world, player, h(EnemySprite.HardhatBeetle), arrow=None, bomb=None, fire=None), @@ -2101,18 +2100,22 @@ class EnemyTable: def setup_enemy_locations(world, player): for super_tile, enemy_list in world.data_tables[player].uw_enemy_table.room_map.items(): for index, sprite in enumerate(enemy_list): - if valid_drop_location(sprite, world, player): + if valid_drop_location(sprite, index, world, player): create_drop_location(sprite, index, super_tile, world, player) -def valid_drop_location(sprite, world, player): +exceptions = {0xf1: [4, 5]} # these keese cannot be lured away + + +def valid_drop_location(sprite, index, world, player): if world.dropshuffle[player] == 'underworld': if sprite.drops_item and sprite.drop_item_kind == 0xe4: # already has a location return False elif sprite.sub_type != SpriteType.Overlord: stat = world.data_tables[player].enemy_stats[sprite.kind] - return not stat.static and stat.drop_flag + return stat.drop_flag and (sprite.super_tile not in exceptions + or index not in exceptions[sprite.super_tile]) def create_drop_location(sprite, index, super_tile, world, player): @@ -2120,10 +2123,11 @@ def create_drop_location(sprite, index, super_tile, world, player): address = drop_address(index, super_tile) region_name = sprite.region parent = world.get_region(region_name, player) - descriptor = f'Enemy #{index+1}' + enemy_name = enemy_names[sprite.kind] + descriptor = f'{enemy_name} #{index+1}' modifier = parent.hint_text not in {'a storyteller', 'fairies deep in a cave', 'a spiky hint', 'a bounty of five items', 'the sick kid', 'Sahasrahla'} - hint_text = f'{"held by an enemy"} {"in" if modifier else "near"} {parent.hint_text}' + hint_text = f'held by a {enemy_name} {"in" if modifier else "near"} {parent.hint_text}' drop_location = Location(player, f'{region_name} {descriptor}', address, hint_text=hint_text, parent=parent) world.dynamic_locations.append(drop_location) drop_location.drop = sprite @@ -2272,6 +2276,21 @@ def can_bow_kill(world, player, damage, silver_damage, health): return can_shoot_arrows(world, player) +def can_quake_kill(world, player, damage, health): + magic_needed = math.ceil(health / damage) * 2 + if magic_needed > 8: + return and_rule(has('Quake', player), has_sword(player), can_extend_magic(world, player, magic_needed)) + else: + return and_rule(has('Quake', player), has_sword(player)) + + +def can_ether_kill(world, player, damage, health): + magic_needed = math.ceil(health / damage) * 2 + if magic_needed > 8: + return and_rule(has('Ether', player), has_sword(player), can_extend_magic(world, player, magic_needed)) + else: + return and_rule(has('Ether', player), has_sword(player)) + # main enemy types def defeat_rule(world, player, health, class1=1, arrow: typing.Optional[int] = 1, silver=1, @@ -2321,6 +2340,49 @@ def can_shoot_arrows(world, player): def can_use_bombs(world, player): return or_rule(RuleFactory.static_rule(not world.bombbag[player]), has('Bomb Upgrade (+10)', player)) + +special_rules_check = { + 'Swamp Waterway': None, + 'Hera Back': [5, 6], + 'GT Petting Zoo': [1, 4, 5, 7], + 'Mimic Cave': [3, 4], + 'Ice Hookshot Ledge': None, + 'TR Hub Ledges': [3, 4, 5, 6, 7], + 'Old Man Cave': None, + 'Old Man House Back': [4, 5, 6], + 'Death Mountain Return Cave (left)': None, + 'Death Mountain Return Cave (right)': [1, 2, 3, 6, 7] + +} + + +def special_rules_for_region(world, player, region_name, location, original_rule, enemy): + if region_name == 'Swamp Waterway': + stats = world.data_tables[player].enemy_stats[enemy.kind] + return or_rule(can_quake_kill(world, player, 64, stats.health), can_ether_kill(world, player, 64, stats.health)) + elif region_name in ['Hera Back', 'GT Petting Zoo', 'Mimic Cave']: + enemy_number = int(location.name.split('#')[1]) + if enemy_number in special_rules_check[region_name]: + return and_rule(original_rule, has_boomerang(player)) + else: + return original_rule + elif region_name == 'Ice Hookshot Ledge': # enemizer has these hardcoded to red baris for now + return and_rule(or_rule(has('Fire Rod', player), and_rule(has_sword(player), has('Bombos', player))), + or_rule(has_boomerang(player), has('Hookshot', player))) + elif region_name in ['TR Hub Ledges', 'Old Man Cave', 'Old Man House Back', + 'Death Mountain Return Cave (left)', 'Death Mountain Return Cave (right)']: + enemy_number = int(location.name.split('#')[1]) + if special_rules_check[region_name] is None or enemy_number in special_rules_check[region_name]: + return and_rule(original_rule, or_rule(has_boomerang(player), has('Hookshot', player))) + else: + return original_rule + return original_rule + + + + + + enemy_names = { 0x00: 'Raven', 0x01: 'Vulture', diff --git a/source/dungeon/RoomHeader.py b/source/dungeon/RoomHeader.py index 37272724..5dc0bef7 100644 --- a/source/dungeon/RoomHeader.py +++ b/source/dungeon/RoomHeader.py @@ -312,11 +312,15 @@ class RoomHeader: self.room_id = room_id # todo: the rest of the header - self.sprite_sheet = byte_array[3] + self.byte_0 = byte_array[0] # bg2, collision, lights out + self.sprite_sheet = byte_array[3] # sprite gfx # + self.effect = byte_array[4] def write_to_rom(self, rom, base_address): room_offest = self.room_id*14 + rom.write_byte(base_address + room_offest + 0, self.byte_0) rom.write_byte(base_address + room_offest + 3, self.sprite_sheet) + rom.write_byte(base_address + room_offest + 4, self.effect) def init_room_headers():