From 18533fc77d1323b6733e318817747a7868ce161b Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Thu, 22 Mar 2018 23:18:40 -0400 Subject: [PATCH] Initial Take-Any implementation --- BaseClasses.py | 5 ++++ ItemList.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++-- Items.py | 10 +++---- Main.py | 24 ++++++++++++--- 4 files changed, 108 insertions(+), 11 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 62052e90..e1449215 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -63,6 +63,8 @@ class World(object): self.can_take_damage = True self.difficulty_requirements = None self.fix_fake_world = True + self.dynamic_regions = [] + self.dynamic_locations = [] self.spoiler = Spoiler(self) self.lamps_needed_for_dark_rooms = 1 @@ -185,6 +187,9 @@ class World(object): self._cached_locations.extend(region.locations) return self._cached_locations + def clear_location_cache(self): + self._cached_locations = None + def get_unfilled_locations(self): return [location for location in self.get_locations() if location.item is None] diff --git a/ItemList.py b/ItemList.py index 0eb8b906..fac24f5e 100644 --- a/ItemList.py +++ b/ItemList.py @@ -2,9 +2,12 @@ from collections import namedtuple import logging import random -from Items import ItemFactory -from Fill import FillError, fill_restrictive +from BaseClasses import Region, RegionType, Shop, ShopType, Location from Dungeons import get_dungeon_item_pool +from EntranceShuffle import connect_entrance +from Fill import FillError, fill_restrictive +from Items import ItemFactory + #This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space. #Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided. @@ -264,9 +267,82 @@ def generate_itempool(world): set_up_shops(world) + if world.retro: + set_up_take_anys(world) + + create_dynamic_shop_locations(world) + # distribute crystals fill_prizes(world) +take_any_locations = [ + 'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut', + 'Fortune Teller (Light)', 'Lake Hylia Fortune Teller', 'Lumberjack House', 'Bonk Fairy (Light)', + 'Bonk Fairy (Dark)', 'Lake Hylia Healer Fairy', 'Swamp Healer Fairy', 'Desert Healer Fairy', + 'Dark Lake Hylia Healer Fairy', 'Dark Lake Hylia Ledge Healer Fairy', 'Dark Desert Healer Fairy', + 'Dark Death Mountain Healer Fairy', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave', + 'Kakariko Gamble Game', 'Capacity Upgrade', '50 Rupee Cave', 'Lost Woods Gamble', 'Hookshot Fairy', + 'Palace of Darkness Hint', 'East Dark World Hint', 'Archery Game', 'Dark Lake Hylia Ledge Hint', + 'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Desert Hint'] + +def set_up_take_anys(world): + regions = random.sample(take_any_locations, 5) + + old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave) + world.regions.append(old_man_take_any) + world.dynamic_regions.append(old_man_take_any) + + reg = regions.pop() + entrance = world.get_region(reg).entrances[0] + connect_entrance(world, entrance, old_man_take_any) + entrance.target = 0x58 + old_man_take_any.shop = Shop(old_man_take_any, 0x0112, ShopType.TakeAny, 0xE2, True) + world.shops.append(old_man_take_any.shop) + old_man_take_any.shop.active = True + + swords = [item for item in world.itempool if item.type == 'Sword'] + if swords: + sword = random.choice(swords) + world.itempool.remove(sword) + world.itempool.append(ItemFactory('Rupees (20)')) + old_man_take_any.shop.add_inventory(0, sword.name, 0, 1, create_location=True) + else: + old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 1) + + for num in range(4): + take_any = Region("Take-Any #{}".format(num+1), RegionType.Cave) + world.regions.append(take_any) + world.dynamic_regions.append(take_any) + + target, room_id = random.choice([(0x58, 0x0112), (0x60, 0x010F), (0x46, 0x011F)]) + reg = regions.pop() + entrance = world.get_region(reg).entrances[0] + connect_entrance(world, entrance, take_any) + entrance.target = target + take_any.shop = Shop(old_man_take_any, room_id, ShopType.TakeAny, 0xE3, True) + world.shops.append(take_any.shop) + take_any.shop.active = True + take_any.shop.add_inventory(0, 'Blue Potion', 0, 1) + take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 1) + + world.intialize_regions() + +def create_dynamic_shop_locations(world): + for shop in world.shops: + for i, item in enumerate(shop.inventory): + if item is None: + continue + if item['create_location']: + loc = Location("{} Item {}".format(shop.region.name, i+1), parent=shop.region) + shop.region.locations.append(loc) + world.dynamic_locations.append(loc) + + world.clear_location_cache() + + world.push_item(loc, ItemFactory(item['item']), False) + loc.event = True + + def fill_prizes(world, attempts=15): crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6']) crystal_locations = [world.get_location('Turtle Rock - Prize'), world.get_location('Eastern Palace - Prize'), world.get_location('Desert Palace - Prize'), world.get_location('Tower of Hera - Prize'), world.get_location('Palace of Darkness - Prize'), diff --git a/Items.py b/Items.py index 3001f5df..226b9536 100644 --- a/Items.py +++ b/Items.py @@ -52,11 +52,11 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla 'Bottle (Fairy)': (True, False, None, 0x3D, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again'), 'Bottle (Bee)': (True, False, None, 0x3C, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again'), 'Bottle (Good Bee)': (True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again'), - 'Master Sword': (True, False, None, 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again'), - 'Tempered Sword': (True, False, None, 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again'), - 'Fighter Sword': (True, False, None, 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again'), - 'Golden Sword': (True, False, None, 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again'), - 'Progressive Sword': (True, False, None, 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again'), + 'Master Sword': (True, False, 'Sword', 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again'), + 'Tempered Sword': (True, False, 'Sword', 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again'), + 'Fighter Sword': (True, False, 'Sword', 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again'), + 'Golden Sword': (True, False, 'Sword', 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again'), + 'Progressive Sword': (True, False, 'Sword', 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again'), 'Progressive Glove': (True, False, None, 0x61, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again'), 'Silver Arrows': (True, False, None, 0x58, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again'), 'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], None, None, None, None, None, None), diff --git a/Main.py b/Main.py index f480e773..bdfd96dc 100644 --- a/Main.py +++ b/Main.py @@ -6,7 +6,7 @@ import logging import random import time -from BaseClasses import World, CollectionState, Item +from BaseClasses import World, CollectionState, Item, Region, Location, Entrance, Shop from Regions import create_regions, mark_light_world_regions from EntranceShuffle import link_entrances from Rom import patch_rom, Sprite, LocalRom, JsonRom @@ -158,15 +158,13 @@ def copy_world(world): create_regions(ret) create_dungeons(ret) - #TODO: copy_dynamic_regions_and_locations() # also adds some new shops + copy_dynamic_regions_and_locations(world, ret) for shop in world.shops: copied_shop = ret.get_region(shop.region.name).shop copied_shop.active = shop.active copied_shop.inventory = copy.copy(shop.inventory) - - # connect copied world for region in world.regions: copied_region = ret.get_region(region.name) @@ -195,6 +193,24 @@ def copy_world(world): return ret +def copy_dynamic_regions_and_locations(world, ret): + for region in world.dynamic_regions: + new_reg = Region(region.name, region.type) + ret.regions.append(new_reg) + ret.dynamic_regions.append(new_reg) + + # Note: ideally exits should be copied here, but the current use case (Take anys) do not require this + + if region.shop: + new_reg.shop = Shop(new_reg, region.shop.room_id, region.shop.type, region.shop.shopkeeper_config, region.shop.replaceable) + ret.shops.append(new_reg.shop) + + for location in world.dynamic_locations: + new_loc = Location(location.name, location.address, location.crystal, location.hint_text, location.parent_region) + new_reg = ret.get_region(location.parent_region.name) + new_reg.locations.append(new_loc) + + def create_playthrough(world): # create a copy as we will modify it old_world = world