Rules changes

Implement Big Magic/Chicken/Fairy spawn items
This commit is contained in:
aerinon
2022-10-03 16:12:05 -06:00
parent b71c7aa2b4
commit a5fc4dd7a6
5 changed files with 82 additions and 79 deletions

68
Fill.py
View File

@@ -361,54 +361,6 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
# get items to distribute # get items to distribute
classify_major_items(world) classify_major_items(world)
# handle pot/drop shuffle # 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) random.shuffle(world.itempool)
progitempool = [item for item in world.itempool if item.advancement] 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'} if (loc.item.name in {'Arrows (5)', 'Nothing'}
and (loc.type != LocationType.Pot or loc.item.player != loc.player)): and (loc.type != LocationType.Pot or loc.item.player != loc.player)):
loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.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 # do the arrow retro check
if world.bow_mode[loc.item.player].startswith('retro') and loc.item.name in {'Arrows (5)', 'Arrows (10)'}: 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) loc.item = ItemFactory('Rupees (5)', loc.item.player)
# don't write out all pots to spoiler # don't write out all pots to spoiler
# todo: skip uninteresting enemy drops
if write_skips: if write_skips:
if loc.type == LocationType.Pot and loc.item.name in valid_pot_items: if loc.type == LocationType.Pot and loc.item.name in valid_pot_items:
loc.skip = True loc.skip = True

2
Rom.py
View File

@@ -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'] colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery']
and (world.colorizepots[player] and (world.colorizepots[player]
or world.pottery[player] in ['reduced', 'clustered'])) 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) write_strings(rom, world, player, team)

View File

@@ -9,7 +9,7 @@ from Dungeons import dungeon_table
from RoomData import DoorKind from RoomData import DoorKind
from OverworldGlitchRules import overworld_glitches_rules 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): def set_rules(world, player):
@@ -781,6 +781,9 @@ def drop_rules(world, player):
if enemy.location: if enemy.location:
# could handle odd health rules here? assume harder variant for now # could handle odd health rules here? assume harder variant for now
verbose_rule = defeat_rules[enemy.kind] 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 enemy.location.verbose_rule = verbose_rule
add_rule(enemy.location, verbose_rule.rule_lambda) add_rule(enemy.location, verbose_rule.rule_lambda)

View File

@@ -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, 0x08, 0x14, 'Mimic Cave')
create_sprite(0x010c, EnemySprite.GreenEyegoreMimic, 0x00, 0, 0x0c, 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(0x010c, EnemySprite.GreenEyegoreMimic, 0x00, 0, 0x0c, 0x1a, 'Mimic Cave')
create_sprite(0x010d, EnemySprite.SparkCW, 0x00, 0, 0x05, 0x16, 'Mimic Cave') create_sprite(0x010d, EnemySprite.SparkCW, 0x00, 0, 0x05, 0x16, 'Mire Shed')
create_sprite(0x010d, EnemySprite.SparkCCW, 0x00, 0, 0x0a, 0x16, 'Mimic Cave') 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, 0x06, 0x06)
create_sprite(0x010e, EnemySprite.DarkWorldHintNpc, 0x00, 0, 0x18, 0x06) create_sprite(0x010e, EnemySprite.DarkWorldHintNpc, 0x00, 0, 0x18, 0x06)
create_sprite(0x010f, EnemySprite.Shopkeeper, 0x00, 0, 0x07, 0x15) 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), bomb=2, silver=3, fire=None),
EnemySprite.MiniMoldorm: defeat_rule(world, player, h(EnemySprite.MiniMoldorm), ice=1, hook=True), EnemySprite.MiniMoldorm: defeat_rule(world, player, h(EnemySprite.MiniMoldorm), ice=1, hook=True),
EnemySprite.Sluggula: defeat_rule(world, player, h(EnemySprite.Sluggula), bomb=None), EnemySprite.Sluggula: defeat_rule(world, player, h(EnemySprite.Sluggula), bomb=None),
# too hard to hit red biris with arrows EnemySprite.RedBari: or_rule(has('Fire Rod', player), and_rule(has_sword(player), has('Bombos', player))),
EnemySprite.RedBari: defeat_rule(world, player, h(EnemySprite.RedBari) * 3, arrow=None, ice=2, hook=True),
EnemySprite.BlueBari: defeat_rule(world, player, h(EnemySprite.BlueBari), ice=2, hook=True), EnemySprite.BlueBari: defeat_rule(world, player, h(EnemySprite.BlueBari), ice=2, hook=True),
EnemySprite.HardhatBeetle: defeat_rule(world, player, h(EnemySprite.HardhatBeetle), EnemySprite.HardhatBeetle: defeat_rule(world, player, h(EnemySprite.HardhatBeetle),
arrow=None, bomb=None, fire=None), arrow=None, bomb=None, fire=None),
@@ -2101,18 +2100,22 @@ class EnemyTable:
def setup_enemy_locations(world, player): def setup_enemy_locations(world, player):
for super_tile, enemy_list in world.data_tables[player].uw_enemy_table.room_map.items(): for super_tile, enemy_list in world.data_tables[player].uw_enemy_table.room_map.items():
for index, sprite in enumerate(enemy_list): 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) 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 world.dropshuffle[player] == 'underworld':
if sprite.drops_item and sprite.drop_item_kind == 0xe4: if sprite.drops_item and sprite.drop_item_kind == 0xe4:
# already has a location # already has a location
return False return False
elif sprite.sub_type != SpriteType.Overlord: elif sprite.sub_type != SpriteType.Overlord:
stat = world.data_tables[player].enemy_stats[sprite.kind] 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): 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) address = drop_address(index, super_tile)
region_name = sprite.region region_name = sprite.region
parent = world.get_region(region_name, player) 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', 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'} '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) drop_location = Location(player, f'{region_name} {descriptor}', address, hint_text=hint_text, parent=parent)
world.dynamic_locations.append(drop_location) world.dynamic_locations.append(drop_location)
drop_location.drop = sprite drop_location.drop = sprite
@@ -2272,6 +2276,21 @@ def can_bow_kill(world, player, damage, silver_damage, health):
return can_shoot_arrows(world, player) 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 # main enemy types
def defeat_rule(world, player, health, class1=1, def defeat_rule(world, player, health, class1=1,
arrow: typing.Optional[int] = 1, silver=1, arrow: typing.Optional[int] = 1, silver=1,
@@ -2321,6 +2340,49 @@ def can_shoot_arrows(world, player):
def can_use_bombs(world, player): def can_use_bombs(world, player):
return or_rule(RuleFactory.static_rule(not world.bombbag[player]), has('Bomb Upgrade (+10)', 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 = { enemy_names = {
0x00: 'Raven', 0x00: 'Raven',
0x01: 'Vulture', 0x01: 'Vulture',

View File

@@ -312,11 +312,15 @@ class RoomHeader:
self.room_id = room_id self.room_id = room_id
# todo: the rest of the header # 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): def write_to_rom(self, rom, base_address):
room_offest = self.room_id*14 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 + 3, self.sprite_sheet)
rom.write_byte(base_address + room_offest + 4, self.effect)
def init_room_headers(): def init_room_headers():