shop cleanup and correctly backreference swapped items' locations

Also fixes a false reference in progression balancing from 2019 (swapped Location.item.location was not updated)
This commit is contained in:
Fabian Dill 2021-01-11 13:35:48 +01:00
parent 322feb37f0
commit 058436e47f
2 changed files with 78 additions and 52 deletions

20
Fill.py
View File

@ -1,7 +1,7 @@
import logging
import typing
from BaseClasses import CollectionState, PlandoItem
from BaseClasses import CollectionState, PlandoItem, Location
from Items import ItemFactory
from Regions import key_drop_data
@ -336,7 +336,8 @@ def balance_multiworld_progression(world):
replacement_locations.insert(0, new_location)
new_location = replacement_locations.pop()
new_location.item, old_location.item = old_location.item, new_location.item
swap_location_item(old_location, new_location)
new_location.event, old_location.event = True, False
logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, "
f"displacing {old_location.item} in {old_location}")
@ -361,6 +362,18 @@ def balance_multiworld_progression(world):
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
def swap_location_item(location_1: Location, location_2: Location, check_locked=True):
"""Swaps Items of locations. Does NOT swap flags like event, shop_slot or locked"""
if check_locked:
if location_1.locked:
logging.warning(f"Swapping {location_1}, which is marked as locked.")
if location_2.locked:
logging.warning(f"Swapping {location_2}, which is marked as locked.")
location_2.item, location_1.item = location_1.item, location_2.item
location_1.item.location = location_1
location_2.item.location = location_2
def distribute_planned(world):
world_name_lookup = {world.player_names[player_id][0]: player_id for player_id in world.player_ids}
@ -368,7 +381,8 @@ def distribute_planned(world):
placement: PlandoItem
for placement in world.plando_items[player]:
if placement.location in key_drop_data:
placement.warn(f"Can't place '{placement.item}' at '{placement.location}', as key drop shuffle locations are not supported yet.")
placement.warn(
f"Can't place '{placement.item}' at '{placement.location}', as key drop shuffle locations are not supported yet.")
continue
item = ItemFactory(placement.item, player)
target_world: int = placement.world

110
Main.py
View File

@ -8,16 +8,19 @@ import random
import time
import zlib
import concurrent.futures
import typing
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
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 EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, get_hash_string
from Rules import set_rules
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned, \
swap_location_item
from ItemPool import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple
import Patch
@ -210,56 +213,66 @@ def main(args, seed=None):
if world.players > 1:
balance_multiworld_progression(world)
blacklist_words = {"Rupee", "Pendant", "Crystal"}
blacklist_words = {item_name for item_name in item_table if any(
blacklist_word in item_name for blacklist_word in blacklist_words)}
blacklist_words.add("Bee")
candidates = [location for location in world.get_locations() if
not location.locked and
not location.shop_slot and
not location.item.name in blacklist_words]
shop_slots: typing.List[Location] = [location for shop_locations in (shop.region.locations for shop in world.shops)
for location in shop_locations if location.shop_slot]
world.random.shuffle(candidates)
shop_slots = [item for sublist in (shop.region.locations for shop in world.shops) for item in sublist if
item.shop_slot]
if shop_slots:
# TODO: allow each game to register a blacklist to be used here?
blacklist_words = {"Rupee", "Pendant", "Crystal"}
blacklist_words = {item_name for item_name in item_table if any(
blacklist_word in item_name for blacklist_word in blacklist_words)}
blacklist_words.add("Bee")
candidates: typing.List[Location] = [location for location in world.get_locations() if
not location.locked and
not location.shop_slot and
not location.item.name in blacklist_words]
for location in shop_slots:
slot_num = int(location.name[-1]) - 1
shop: Shop = location.parent_region.shop
if shop.can_push_inventory(slot_num):
for c in candidates: # chosen item locations
if c.item_rule(location.item): # if rule is good...
logging.debug('Swapping {} with {}:: {} ||| {}'.format(c, location, c.item, location.item))
c.item, location.item = location.item, c.item
if not world.can_beat_game():
c.item, location.item = location.item, c.item
else:
# we use this candidate
candidates.remove(c)
break
world.random.shuffle(candidates)
# currently special care needs to be taken so that Shop.region.locations.item is identical to Shop.inventory
# Potentially create Locations as needed and make inventory the only source, to prevent divergence
for location in shop_slots:
slot_num = int(location.name[-1]) - 1
shop: Shop = location.parent_region.shop
if shop.can_push_inventory(slot_num):
for c in candidates: # chosen item locations
if c.item_rule(location.item): # if rule is good...
logging.debug('Swapping {} with {}:: {} ||| {}'.format(c, location, c.item, location.item))
swap_location_item(c, location, check_locked=False)
# TODO: should likely be (all_state-c.item).can_reach(shop.region)
# can still be can_beat_game when beatable-only for the item-owning player
# appears to be the main source of "progression items unreachable Exception"
# in door-rando + multishop
if not world.can_beat_game():
# swap back
swap_location_item(c, location, check_locked=False)
else:
# we use this candidate
candidates.remove(c)
break
else:
# This *should* never happen. But let's fail safely just in case.
logging.warning("Ran out of ShopShuffle Item candidate locations.")
shop.region.locations.remove(location)
continue
item_name = location.item.name
if any(x in item_name for x in ['Single Bomb', 'Single Arrow']):
price = world.random.randrange(1, 7)
elif any(x in item_name for x in ['Arrows', 'Bombs', 'Clock']):
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:
price = world.random.randrange(10, 60)
price *= 5
shop.push_inventory(slot_num, item_name, price, 1,
location.item.player if location.item.player != location.player else 0)
else:
# This *should* never happen. But let's fail safely just in case.
logging.warning("Ran out of ShopShuffle Item candidate locations.")
shop.region.locations.remove(location)
continue
# update table to location data
item_name = location.item.name
if any(x in item_name for x in ['Single Bomb', 'Single Arrow']):
price = world.random.randrange(1, 7)
elif any(x in item_name for x in ['Arrows', 'Bombs', 'Clock']):
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:
price = world.random.randrange(10, 60)
price *= 5
shop.push_inventory(slot_num, item_name, price, 1,
location.item.player if location.item.player != location.player else 0)
else:
shop.region.locations.remove(location)
logger.info('Patching ROM.')
@ -302,7 +315,6 @@ def main(args, seed=None):
args.fastmenu[player], args.disablemusic[player], args.sprite[player],
palettes_options, world, player, True)
mcsb_name = ''
if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player],
world.bigkeyshuffle[player]]):