Rules changes
Implement Big Magic/Chicken/Fairy spawn items
This commit is contained in:
68
Fill.py
68
Fill.py
@@ -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
2
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']
|
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)
|
||||||
|
|
||||||
|
|||||||
5
Rules.py
5
Rules.py
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
Reference in New Issue
Block a user