Simplify ShopSlot Shuffling
This commit is contained in:
		
							parent
							
								
									52d5b96435
								
							
						
					
					
						commit
						f12259dd7d
					
				| 
						 | 
					@ -981,7 +981,7 @@ class Dungeon(object):
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
 | 
					        return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Boss(object):
 | 
					class Boss():
 | 
				
			||||||
    def __init__(self, name, enemizer_name, defeat_rule, player: int):
 | 
					    def __init__(self, name, enemizer_name, defeat_rule, player: int):
 | 
				
			||||||
        self.name = name
 | 
					        self.name = name
 | 
				
			||||||
        self.enemizer_name = enemizer_name
 | 
					        self.enemizer_name = enemizer_name
 | 
				
			||||||
| 
						 | 
					@ -991,7 +991,12 @@ class Boss(object):
 | 
				
			||||||
    def can_defeat(self, state) -> bool:
 | 
					    def can_defeat(self, state) -> bool:
 | 
				
			||||||
        return self.defeat_rule(state, self.player)
 | 
					        return self.defeat_rule(state, self.player)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Location(object):
 | 
					
 | 
				
			||||||
 | 
					class Location():
 | 
				
			||||||
 | 
					    shop_slot: bool = False
 | 
				
			||||||
 | 
					    event: bool = False
 | 
				
			||||||
 | 
					    locked: bool = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, player: int, name: str = '', address=None, crystal: bool = False,
 | 
					    def __init__(self, player: int, name: str = '', address=None, crystal: bool = False,
 | 
				
			||||||
                 hint_text: Optional[str] = None, parent=None,
 | 
					                 hint_text: Optional[str] = None, parent=None,
 | 
				
			||||||
                 player_address=None):
 | 
					                 player_address=None):
 | 
				
			||||||
| 
						 | 
					@ -1004,8 +1009,6 @@ class Location(object):
 | 
				
			||||||
        self.spot_type = 'Location'
 | 
					        self.spot_type = 'Location'
 | 
				
			||||||
        self.hint_text: str = hint_text if hint_text else name
 | 
					        self.hint_text: str = hint_text if hint_text else name
 | 
				
			||||||
        self.recursion_count = 0
 | 
					        self.recursion_count = 0
 | 
				
			||||||
        self.event = False
 | 
					 | 
				
			||||||
        self.locked = False
 | 
					 | 
				
			||||||
        self.always_allow = lambda item, state: False
 | 
					        self.always_allow = lambda item, state: False
 | 
				
			||||||
        self.access_rule = lambda state: True
 | 
					        self.access_rule = lambda state: True
 | 
				
			||||||
        self.item_rule = lambda item: True
 | 
					        self.item_rule = lambda item: True
 | 
				
			||||||
| 
						 | 
					@ -1152,7 +1155,8 @@ class Shop():
 | 
				
			||||||
        self.inventory = [None] * self.slots
 | 
					        self.inventory = [None] * self.slots
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def add_inventory(self, slot: int, item: str, price: int, max: int = 0,
 | 
					    def add_inventory(self, slot: int, item: str, price: int, max: int = 0,
 | 
				
			||||||
                      replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False):
 | 
					                      replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False,
 | 
				
			||||||
 | 
					                      player: int = 0):
 | 
				
			||||||
        self.inventory[slot] = {
 | 
					        self.inventory[slot] = {
 | 
				
			||||||
            'item': item,
 | 
					            'item': item,
 | 
				
			||||||
            'price': price,
 | 
					            'price': price,
 | 
				
			||||||
| 
						 | 
					@ -1160,10 +1164,10 @@ class Shop():
 | 
				
			||||||
            'replacement': replacement,
 | 
					            'replacement': replacement,
 | 
				
			||||||
            'replacement_price': replacement_price,
 | 
					            'replacement_price': replacement_price,
 | 
				
			||||||
            'create_location': create_location,
 | 
					            'create_location': create_location,
 | 
				
			||||||
            'player': 0
 | 
					            'player': player
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def push_inventory(self, slot: int, item: str, price: int, max: int = 1):
 | 
					    def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0):
 | 
				
			||||||
        if not self.inventory[slot]:
 | 
					        if not self.inventory[slot]:
 | 
				
			||||||
            raise ValueError("Inventory can't be pushed back if it doesn't exist")
 | 
					            raise ValueError("Inventory can't be pushed back if it doesn't exist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1174,9 +1178,11 @@ class Shop():
 | 
				
			||||||
            'replacement': self.inventory[slot]["item"],
 | 
					            'replacement': self.inventory[slot]["item"],
 | 
				
			||||||
            'replacement_price': self.inventory[slot]["price"],
 | 
					            'replacement_price': self.inventory[slot]["price"],
 | 
				
			||||||
            'create_location': self.inventory[slot]["create_location"],
 | 
					            'create_location': self.inventory[slot]["create_location"],
 | 
				
			||||||
            'player': self.inventory[slot]["player"]
 | 
					            'player': player
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def can_push_inventory(self, slot: int):
 | 
				
			||||||
 | 
					        return self.inventory[slot] and not self.inventory[slot]["replacement"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TakeAny(Shop):
 | 
					class TakeAny(Shop):
 | 
				
			||||||
    type = ShopType.TakeAny
 | 
					    type = ShopType.TakeAny
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -590,6 +590,7 @@ def create_dynamic_shop_locations(world, player):
 | 
				
			||||||
                    world.clear_location_cache()
 | 
					                    world.clear_location_cache()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    world.push_item(loc, ItemFactory(item['item'], player), False)
 | 
					                    world.push_item(loc, ItemFactory(item['item'], player), False)
 | 
				
			||||||
 | 
					                    loc.shop_slot = True
 | 
				
			||||||
                    loc.event = True
 | 
					                    loc.event = True
 | 
				
			||||||
                    loc.locked = True
 | 
					                    loc.locked = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										74
									
								
								Main.py
								
								
								
								
							
							
						
						
									
										74
									
								
								Main.py
								
								
								
								
							| 
						 | 
					@ -9,7 +9,7 @@ import time
 | 
				
			||||||
import zlib
 | 
					import zlib
 | 
				
			||||||
import concurrent.futures
 | 
					import concurrent.futures
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from BaseClasses import World, CollectionState, Item, Region, Location, PlandoItem
 | 
					from BaseClasses import World, CollectionState, Item, Region, Location, Shop
 | 
				
			||||||
from Items import ItemFactory, item_table, item_name_groups
 | 
					from Items import ItemFactory, item_table, item_name_groups
 | 
				
			||||||
from Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance, SHOP_ID_START
 | 
					from Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance, SHOP_ID_START
 | 
				
			||||||
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
 | 
					from InvertedRegions import create_inverted_regions, mark_dark_world_regions
 | 
				
			||||||
| 
						 | 
					@ -215,69 +215,47 @@ def main(args, seed=None):
 | 
				
			||||||
        blacklist_word in item_name for blacklist_word in blacklist_words)}
 | 
					        blacklist_word in item_name for blacklist_word in blacklist_words)}
 | 
				
			||||||
    blacklist_words.add("Bee")
 | 
					    blacklist_words.add("Bee")
 | 
				
			||||||
    candidates = [location for location in world.get_locations() if
 | 
					    candidates = [location for location in world.get_locations() if
 | 
				
			||||||
                  not location.locked and not location.item.name in blacklist_words]
 | 
					                  not location.locked and
 | 
				
			||||||
 | 
					                  not location.shop_slot and
 | 
				
			||||||
 | 
					                  not location.item.name in blacklist_words]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    world.random.shuffle(candidates)
 | 
					    world.random.shuffle(candidates)
 | 
				
			||||||
    shop_slots = [item for sublist in [shop.region.locations for shop in world.shops] for item in sublist if
 | 
					    shop_slots = [item for sublist in (shop.region.locations for shop in world.shops) for item in sublist if
 | 
				
			||||||
                  item.name != 'Potion Shop']  # TODO: "w" in shop_shuffle options?
 | 
					                  item.shop_slot]
 | 
				
			||||||
    shop_slots_adjusted = []
 | 
					 | 
				
			||||||
    shop_items = []
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for location in shop_slots:
 | 
					    for location in shop_slots:
 | 
				
			||||||
        slot_num = int(location.name[-1]) - 1
 | 
					        slot_num = int(location.name[-1]) - 1
 | 
				
			||||||
        shop_item = location.parent_region.shop.inventory[slot_num]
 | 
					        shop: Shop = location.parent_region.shop
 | 
				
			||||||
        item = location.item
 | 
					        if shop.can_push_inventory(slot_num):
 | 
				
			||||||
        # if item is a rupee or single bee, or identical, swap it out
 | 
					 | 
				
			||||||
        if (shop_item is not None and shop_item['item'] == item.name) or 'Rupee' in item.name or (item.name in ['Bee']):
 | 
					 | 
				
			||||||
            for c in candidates:  # chosen item locations
 | 
					            for c in candidates:  # chosen item locations
 | 
				
			||||||
                if c.parent_region.shop or c is location: continue
 | 
					 | 
				
			||||||
                if (shop_item is not None and shop_item['item'] == c.item.name): continue
 | 
					 | 
				
			||||||
                if c.item_rule(location.item):  # if rule is good...
 | 
					                if c.item_rule(location.item):  # if rule is good...
 | 
				
			||||||
                    logging.debug('Swapping {} with {}:: {} ||| {}'.format(c, location, c.item, location.item))
 | 
					                    logging.debug('Swapping {} with {}:: {} ||| {}'.format(c, location, c.item, location.item))
 | 
				
			||||||
                    c.item, location.item = location.item, c.item
 | 
					                    c.item, location.item = location.item, c.item
 | 
				
			||||||
                    if not world.can_beat_game():
 | 
					                    if not world.can_beat_game():
 | 
				
			||||||
                        c.item, location.item = location.item, c.item
 | 
					                        c.item, location.item = location.item, c.item
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        shop_slots_adjusted.append(location)
 | 
					                        # we use this candidate
 | 
				
			||||||
 | 
					                        candidates.remove(c)
 | 
				
			||||||
                        break
 | 
					                        break
 | 
				
			||||||
        # update table to location data
 | 
					 | 
				
			||||||
        item = location.item
 | 
					 | 
				
			||||||
        if (shop_item and shop_item['item'] == item.name) or 'Rupee' in item.name or item.name == 'Bee':
 | 
					 | 
				
			||||||
            # this will filter items that match the item in the shop or Rupees, or single bees
 | 
					 | 
				
			||||||
            # really not a way for the player to know a renewable item from a player pool item
 | 
					 | 
				
			||||||
            # bombs can be sitting on top of arrows or a potion refill, but dunno if that's a big deal
 | 
					 | 
				
			||||||
            # this should rarely happen with the above code in place, and could be an option in config if necessary
 | 
					 | 
				
			||||||
            logging.debug('skipping item shop {}'.format(item.name))
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            if shop_item is None:
 | 
					 | 
				
			||||||
                location.parent_region.shop.add_inventory(slot_num, 'None', 0)
 | 
					 | 
				
			||||||
                shop_item = location.parent_region.shop.inventory[slot_num]
 | 
					 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                shop_item['replacement'] = shop_item['item']
 | 
					                logging.warning("Ran out of ShopShuffle Item candidate locations.")
 | 
				
			||||||
                shop_item['replacement_price'] = shop_item['price']
 | 
					                break  # we ran out of candidates
 | 
				
			||||||
            shop_item['item'] = item.name
 | 
					
 | 
				
			||||||
            if any(x in shop_item['item'] for x in ['Single Bomb', 'Single Arrow']):
 | 
					            # update table to location data
 | 
				
			||||||
                shop_item['price'] = world.random.randrange(5, 35)
 | 
					            item_name = location.item.name
 | 
				
			||||||
            elif any(x in shop_item['item'] for x in ['Arrows', 'Bombs', 'Clock']):
 | 
					            if any(x in item_name for x in ['Single Bomb', 'Single Arrow']):
 | 
				
			||||||
                shop_item['price'] = world.random.randrange(20, 120)
 | 
					                price = world.random.randrange(1, 7)
 | 
				
			||||||
            elif any(x in shop_item['item'] for x in ['Compass', 'Map', 'Small Key', 'Piece of Heart']):
 | 
					            elif any(x in item_name for x in ['Arrows', 'Bombs', 'Clock']):
 | 
				
			||||||
                shop_item['price'] = world.random.randrange(50, 150)
 | 
					                price = world.random.randrange(4, 24)
 | 
				
			||||||
 | 
					            elif any(x in item_name for x in ['Compass', 'Map', 'Small Key', 'Piece of Heart']):
 | 
				
			||||||
 | 
					                price = world.random.randrange(10, 30)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                shop_item['price'] = world.random.randrange(50,300)
 | 
					                price = world.random.randrange(10, 60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            shop_item['max'] = 1
 | 
					            price *= 5
 | 
				
			||||||
            shop_item['player'] = item.player if item.player != location.player else 0
 | 
					 | 
				
			||||||
            shop_items.append(shop_item)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if len(shop_items) > 0:
 | 
					            shop.push_inventory(slot_num, item_name, price, 1,
 | 
				
			||||||
        my_prices = [my_item['price'] for my_item in shop_items]
 | 
					                                location.item.player if location.item.player != location.player else 0)
 | 
				
			||||||
        price_scale = (80*max(8, len(my_prices)+2))/sum(my_prices)
 | 
					 | 
				
			||||||
        for i in shop_items:
 | 
					 | 
				
			||||||
            i['price'] *= price_scale
 | 
					 | 
				
			||||||
            if i['price'] < 5: i['price'] = 5
 | 
					 | 
				
			||||||
            else: i['price'] = int((i['price']//5)*5)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    logging.debug('Adjusting {} of {} shop slots'.format(len(shop_slots_adjusted), len(shop_slots)))
 | 
					 | 
				
			||||||
    logging.debug('Adjusted {} into shops'.format([x.item.name for x in shop_slots_adjusted]))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.info('Patching ROM.')
 | 
					    logger.info('Patching ROM.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -415,9 +415,11 @@ def create_shops(world, player: int):
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                if my_shop_slots.pop():
 | 
					                if my_shop_slots.pop():
 | 
				
			||||||
                    additional_item = world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
 | 
					                    additional_item = world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
 | 
				
			||||||
                    world.itempool.append(ItemFactory(additional_item, player))
 | 
					 | 
				
			||||||
                    slot_name = "{} Slot {}".format(shop.region.name, index + 1)
 | 
					                    slot_name = "{} Slot {}".format(shop.region.name, index + 1)
 | 
				
			||||||
                    loc = Location(player, slot_name, address=shop_table_by_location[slot_name], parent=shop.region)
 | 
					                    loc = Location(player, slot_name, address=shop_table_by_location[slot_name], parent=shop.region)
 | 
				
			||||||
 | 
					                    loc.shop_slot = True
 | 
				
			||||||
 | 
					                    loc.locked = True
 | 
				
			||||||
 | 
					                    loc.item = ItemFactory(additional_item, player)
 | 
				
			||||||
                    shop.region.locations.append(loc)
 | 
					                    shop.region.locations.append(loc)
 | 
				
			||||||
                    world.dynamic_locations.append(loc)
 | 
					                    world.dynamic_locations.append(loc)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								Rom.py
								
								
								
								
							
							
						
						
									
										8
									
								
								Rom.py
								
								
								
								
							| 
						 | 
					@ -683,17 +683,11 @@ def patch_rom(world, rom, player, team, enemized):
 | 
				
			||||||
    # patch items
 | 
					    # patch items
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for location in world.get_locations():
 | 
					    for location in world.get_locations():
 | 
				
			||||||
        if location.player != player:
 | 
					        if location.player != player or location.address is None or location.shop_slot:
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        itemid = location.item.code if location.item is not None else 0x5A
 | 
					        itemid = location.item.code if location.item is not None else 0x5A
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if location.address is None:
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if location.parent_region.shop and 'Slot' in location.name:
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not location.crystal:
 | 
					        if not location.crystal:
 | 
				
			||||||
            if location.item is not None:
 | 
					            if location.item is not None:
 | 
				
			||||||
                # Keys in their native dungeon should use the orignal item code for keys
 | 
					                # Keys in their native dungeon should use the orignal item code for keys
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue