Item Plando updates (#226)

* Item Plando updates

Add True option for item count to place the number of that item that is in the item pool.
Prioritize plando blocks by location count minus item count, so that the least flexible blocks are handled first to increase likelihood of success.
True and False for Force option are coming in as bools instead of strings, so that had to be accounted for.
Several other bug fixes.
This commit is contained in:
Alchav 2022-01-22 15:03:13 -05:00 committed by GitHub
parent c7e87bc16a
commit 219bcb3521
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 28 deletions

60
Fill.py
View File

@ -409,15 +409,16 @@ def swap_location_item(location_1: Location, location_2: Location, check_locked=
location_1.item.location = location_1
location_2.item.location = location_2
location_1.event, location_2.event = location_2.event, location_1.event
def distribute_planned(world: MultiWorld):
def warn(warning: str, force):
if force in ['true', 'fail', 'failure', 'none', 'false', 'warn', 'warning']:
if force in [True, 'fail', 'failure', 'none', False, 'warn', 'warning']:
logging.warning(f'{warning}')
else:
logging.debug(f'{warning}')
def failed(warning: str, force):
if force in ['true', 'fail', 'failure']:
if force in [True, 'fail', 'failure']:
raise Exception(warning)
else:
warn(warning, force)
@ -447,10 +448,13 @@ def distribute_planned(world: MultiWorld):
if 'count' not in block:
block['count'] = 1
else:
failed("You must specify at least one item to place items with plando.", block['forced'])
failed("You must specify at least one item to place items with plando.", block['force'])
continue
if isinstance(items, dict):
item_list = []
for key, value in items.items():
if value is True:
value = world.itempool.count(world.worlds[player].create_item(key))
item_list += [key] * value
items = item_list
if isinstance(items, str):
@ -458,10 +462,12 @@ def distribute_planned(world: MultiWorld):
block['items'] = items
locations = []
if 'locations' in block:
locations.extend(block['locations'])
elif 'location' in block:
locations.extend(block['location'])
if 'location' in block:
locations = block['location'] # just allow 'location' to keep old yamls compatible
elif 'locations' in block:
locations = block['locations']
if isinstance(locations, str):
locations = [locations]
if isinstance(locations, dict):
location_list = []
@ -471,7 +477,6 @@ def distribute_planned(world: MultiWorld):
if isinstance(locations, str):
locations = [locations]
block['locations'] = locations
c = block['count']
if not block['count']:
block['count'] = (min(len(block['items']), len(block['locations'])) if len(block['locations'])
@ -489,17 +494,19 @@ def distribute_planned(world: MultiWorld):
block['count'] = len(block['items'])
if block['count']['max'] > len(block['locations']) > 0:
count = block['count']
failed(f"Plando count {count} greater than locations specified for player ", block['force'])
failed(f"Plando count {count} greater than locations specified", block['force'])
block['count'] = len(block['locations'])
block['count']['target'] = world.random.randint(block['count']['min'], block['count']['max'])
c = block['count']
if block['count']['target'] > 0:
plando_blocks.append(block)
# shuffle, but then sort blocks by number of items, so blocks with fewer items get priority
# shuffle, but then sort blocks by number of locations minus number of items,
# so less-flexible blocks get priority
world.random.shuffle(plando_blocks)
plando_blocks.sort(key=lambda block: block['count']['target'])
plando_blocks.sort(key=lambda block: (len(block['locations']) - block['count']['target']
if len(block['locations']) > 0
else len(world.get_unfilled_locations(player)) - block['count']['target']))
for placement in plando_blocks:
player = placement['player']
@ -509,13 +516,13 @@ def distribute_planned(world: MultiWorld):
items = placement['items']
maxcount = placement['count']['target']
from_pool = placement['from_pool']
if target_world is False or world.players == 1:
if target_world is False or world.players == 1: # target own world
worlds = {player}
elif target_world is True:
elif target_world is True: # target any worlds besides own
worlds = set(world.player_ids) - {player}
elif target_world is None:
elif target_world is None: # target all worlds
worlds = set(world.player_ids)
elif type(target_world) == list:
elif type(target_world) == list: # list of target worlds
worlds = []
for listed_world in target_world:
if listed_world not in world_name_lookup:
@ -524,14 +531,14 @@ def distribute_planned(world: MultiWorld):
continue
worlds.append(world_name_lookup[listed_world])
worlds = set(worlds)
elif type(target_world) == int:
elif type(target_world) == int: # target world by slot number
if target_world not in range(1, world.players + 1):
failed(
f"Cannot place item in world {target_world} as it is not in range of (1, {world.players})",
placement['forced'])
placement['force'])
continue
worlds = {target_world}
else: # find world by name
else: # target world by slot name
if target_world not in world_name_lookup:
failed(f"Cannot place item to {target_world}'s world as that world does not exist.",
placement['force'])
@ -540,11 +547,10 @@ def distribute_planned(world: MultiWorld):
candidates = list(location for location in world.get_unfilled_locations_for_players(locations,
worlds))
world.random.shuffle(candidates)
world.random.shuffle(items)
count = 0
err = "Unknown error"
err = []
successful_pairs = []
for item_name in items:
item = world.worlds[player].create_item(item_name)
@ -552,6 +558,7 @@ def distribute_planned(world: MultiWorld):
if location in key_drop_data:
warn(
f"Can't place '{item_name}' at '{placement.location}', as key drop shuffle locations are not supported yet.")
continue
if not location.item:
if location.item_rule(item):
if location.can_fill(world.state, item, False):
@ -560,18 +567,19 @@ def distribute_planned(world: MultiWorld):
count = count + 1
break
else:
err = f"Can't place item at {location} due to fill condition not met."
err.append(f"Can't place item at {location} due to fill condition not met.")
else:
err = f"{item_name} not allowed at {location}."
err.append(f"{item_name} not allowed at {location}.")
else:
err = f"Cannot place {item_name} into already filled location {location}."
err.append(f"Cannot place {item_name} into already filled location {location}.")
if count == maxcount:
break
if count < placement['count']['min']:
err = " ".join(err)
m = placement['count']['min']
failed(
f"Plando block failed to place item(s) for {world.player_name[player]}, most recent cause: {err}",
f"Plando block failed to place {m - count} of {m} item(s) for {world.player_name[player]}, error(s): {err}",
placement['force'])
continue
for (item, location) in successful_pairs:
world.push_item(location, item, collect=False)
location.event = True # flag location to be checked during fill

View File

@ -56,9 +56,10 @@ list of specific locations both in their own game or in another player's game.
* Single Placement is when you use a plando block to place a single item at a single location.
* `item` is the item you would like to place and `location` is the location to place it.
* Multi Placement uses a plando block to place multiple items in multiple locations until either list is exhausted.
* `items` defines the items to use and a number letting you place multiple of it.
* `items` defines the items to use and a number letting you place multiple of it. You can use true instead of a number to have it use however many of that item are in your item pool.
* `locations` is a list of possible locations those items can be placed in.
* Using the multi placement method, placements are picked randomly.
* Instead of a number, you can use true
* `count` can be used to set the maximum number of items placed from the block. The default is 1 if using `item` and False if using `items`
* If a number is used it will try to place this number of items.
* If set to false it will try to place as many items from the block as it can.