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:
parent
c7e87bc16a
commit
219bcb3521
62
Fill.py
62
Fill.py
|
@ -409,15 +409,16 @@ def swap_location_item(location_1: Location, location_2: Location, check_locked=
|
||||||
location_1.item.location = location_1
|
location_1.item.location = location_1
|
||||||
location_2.item.location = location_2
|
location_2.item.location = location_2
|
||||||
location_1.event, location_2.event = location_2.event, location_1.event
|
location_1.event, location_2.event = location_2.event, location_1.event
|
||||||
|
|
||||||
def distribute_planned(world: MultiWorld):
|
def distribute_planned(world: MultiWorld):
|
||||||
def warn(warning: str, force):
|
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}')
|
logging.warning(f'{warning}')
|
||||||
else:
|
else:
|
||||||
logging.debug(f'{warning}')
|
logging.debug(f'{warning}')
|
||||||
|
|
||||||
def failed(warning: str, force):
|
def failed(warning: str, force):
|
||||||
if force in ['true', 'fail', 'failure']:
|
if force in [True, 'fail', 'failure']:
|
||||||
raise Exception(warning)
|
raise Exception(warning)
|
||||||
else:
|
else:
|
||||||
warn(warning, force)
|
warn(warning, force)
|
||||||
|
@ -447,10 +448,13 @@ def distribute_planned(world: MultiWorld):
|
||||||
if 'count' not in block:
|
if 'count' not in block:
|
||||||
block['count'] = 1
|
block['count'] = 1
|
||||||
else:
|
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):
|
if isinstance(items, dict):
|
||||||
item_list = []
|
item_list = []
|
||||||
for key, value in items.items():
|
for key, value in items.items():
|
||||||
|
if value is True:
|
||||||
|
value = world.itempool.count(world.worlds[player].create_item(key))
|
||||||
item_list += [key] * value
|
item_list += [key] * value
|
||||||
items = item_list
|
items = item_list
|
||||||
if isinstance(items, str):
|
if isinstance(items, str):
|
||||||
|
@ -458,10 +462,12 @@ def distribute_planned(world: MultiWorld):
|
||||||
block['items'] = items
|
block['items'] = items
|
||||||
|
|
||||||
locations = []
|
locations = []
|
||||||
if 'locations' in block:
|
if 'location' in block:
|
||||||
locations.extend(block['locations'])
|
locations = block['location'] # just allow 'location' to keep old yamls compatible
|
||||||
elif 'location' in block:
|
elif 'locations' in block:
|
||||||
locations.extend(block['location'])
|
locations = block['locations']
|
||||||
|
if isinstance(locations, str):
|
||||||
|
locations = [locations]
|
||||||
|
|
||||||
if isinstance(locations, dict):
|
if isinstance(locations, dict):
|
||||||
location_list = []
|
location_list = []
|
||||||
|
@ -471,7 +477,6 @@ def distribute_planned(world: MultiWorld):
|
||||||
if isinstance(locations, str):
|
if isinstance(locations, str):
|
||||||
locations = [locations]
|
locations = [locations]
|
||||||
block['locations'] = locations
|
block['locations'] = locations
|
||||||
c = block['count']
|
|
||||||
|
|
||||||
if not block['count']:
|
if not block['count']:
|
||||||
block['count'] = (min(len(block['items']), len(block['locations'])) if len(block['locations'])
|
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'])
|
block['count'] = len(block['items'])
|
||||||
if block['count']['max'] > len(block['locations']) > 0:
|
if block['count']['max'] > len(block['locations']) > 0:
|
||||||
count = block['count']
|
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'] = len(block['locations'])
|
||||||
block['count']['target'] = world.random.randint(block['count']['min'], block['count']['max'])
|
block['count']['target'] = world.random.randint(block['count']['min'], block['count']['max'])
|
||||||
c = block['count']
|
|
||||||
|
|
||||||
if block['count']['target'] > 0:
|
if block['count']['target'] > 0:
|
||||||
plando_blocks.append(block)
|
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)
|
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:
|
for placement in plando_blocks:
|
||||||
player = placement['player']
|
player = placement['player']
|
||||||
|
@ -509,13 +516,13 @@ def distribute_planned(world: MultiWorld):
|
||||||
items = placement['items']
|
items = placement['items']
|
||||||
maxcount = placement['count']['target']
|
maxcount = placement['count']['target']
|
||||||
from_pool = placement['from_pool']
|
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}
|
worlds = {player}
|
||||||
elif target_world is True:
|
elif target_world is True: # target any worlds besides own
|
||||||
worlds = set(world.player_ids) - {player}
|
worlds = set(world.player_ids) - {player}
|
||||||
elif target_world is None:
|
elif target_world is None: # target all worlds
|
||||||
worlds = set(world.player_ids)
|
worlds = set(world.player_ids)
|
||||||
elif type(target_world) == list:
|
elif type(target_world) == list: # list of target worlds
|
||||||
worlds = []
|
worlds = []
|
||||||
for listed_world in target_world:
|
for listed_world in target_world:
|
||||||
if listed_world not in world_name_lookup:
|
if listed_world not in world_name_lookup:
|
||||||
|
@ -524,14 +531,14 @@ def distribute_planned(world: MultiWorld):
|
||||||
continue
|
continue
|
||||||
worlds.append(world_name_lookup[listed_world])
|
worlds.append(world_name_lookup[listed_world])
|
||||||
worlds = set(worlds)
|
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):
|
if target_world not in range(1, world.players + 1):
|
||||||
failed(
|
failed(
|
||||||
f"Cannot place item in world {target_world} as it is not in range of (1, {world.players})",
|
f"Cannot place item in world {target_world} as it is not in range of (1, {world.players})",
|
||||||
placement['forced'])
|
placement['force'])
|
||||||
continue
|
continue
|
||||||
worlds = {target_world}
|
worlds = {target_world}
|
||||||
else: # find world by name
|
else: # target world by slot name
|
||||||
if target_world not in world_name_lookup:
|
if target_world not in world_name_lookup:
|
||||||
failed(f"Cannot place item to {target_world}'s world as that world does not exist.",
|
failed(f"Cannot place item to {target_world}'s world as that world does not exist.",
|
||||||
placement['force'])
|
placement['force'])
|
||||||
|
@ -540,11 +547,10 @@ def distribute_planned(world: MultiWorld):
|
||||||
|
|
||||||
candidates = list(location for location in world.get_unfilled_locations_for_players(locations,
|
candidates = list(location for location in world.get_unfilled_locations_for_players(locations,
|
||||||
worlds))
|
worlds))
|
||||||
|
|
||||||
world.random.shuffle(candidates)
|
world.random.shuffle(candidates)
|
||||||
world.random.shuffle(items)
|
world.random.shuffle(items)
|
||||||
count = 0
|
count = 0
|
||||||
err = "Unknown error"
|
err = []
|
||||||
successful_pairs = []
|
successful_pairs = []
|
||||||
for item_name in items:
|
for item_name in items:
|
||||||
item = world.worlds[player].create_item(item_name)
|
item = world.worlds[player].create_item(item_name)
|
||||||
|
@ -552,6 +558,7 @@ def distribute_planned(world: MultiWorld):
|
||||||
if location in key_drop_data:
|
if location in key_drop_data:
|
||||||
warn(
|
warn(
|
||||||
f"Can't place '{item_name}' at '{placement.location}', as key drop shuffle locations are not supported yet.")
|
f"Can't place '{item_name}' at '{placement.location}', as key drop shuffle locations are not supported yet.")
|
||||||
|
continue
|
||||||
if not location.item:
|
if not location.item:
|
||||||
if location.item_rule(item):
|
if location.item_rule(item):
|
||||||
if location.can_fill(world.state, item, False):
|
if location.can_fill(world.state, item, False):
|
||||||
|
@ -560,18 +567,19 @@ def distribute_planned(world: MultiWorld):
|
||||||
count = count + 1
|
count = count + 1
|
||||||
break
|
break
|
||||||
else:
|
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:
|
else:
|
||||||
err = f"{item_name} not allowed at {location}."
|
err.append(f"{item_name} not allowed at {location}.")
|
||||||
else:
|
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:
|
if count == maxcount:
|
||||||
break
|
break
|
||||||
if count < placement['count']['min']:
|
if count < placement['count']['min']:
|
||||||
|
err = " ".join(err)
|
||||||
|
m = placement['count']['min']
|
||||||
failed(
|
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'])
|
placement['force'])
|
||||||
continue
|
|
||||||
for (item, location) in successful_pairs:
|
for (item, location) in successful_pairs:
|
||||||
world.push_item(location, item, collect=False)
|
world.push_item(location, item, collect=False)
|
||||||
location.event = True # flag location to be checked during fill
|
location.event = True # flag location to be checked during fill
|
||||||
|
@ -583,7 +591,7 @@ def distribute_planned(world: MultiWorld):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
warn(
|
warn(
|
||||||
f"Could not remove {item} from pool for {world.player_name[player]} as it's already missing from it.",
|
f"Could not remove {item} from pool for {world.player_name[player]} as it's already missing from it.",
|
||||||
placement['force'])
|
placement['force'])
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
|
|
|
@ -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.
|
* 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.
|
* `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.
|
* 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.
|
* `locations` is a list of possible locations those items can be placed in.
|
||||||
* Using the multi placement method, placements are picked randomly.
|
* 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`
|
* `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 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.
|
* If set to false it will try to place as many items from the block as it can.
|
||||||
|
|
Loading…
Reference in New Issue