Skip to content

Commit

Permalink
Item Plando updates (ArchipelagoMW#226)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
Alchav authored Jan 22, 2022
1 parent c7e87bc commit 219bcb3
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 28 deletions.
62 changes: 35 additions & 27 deletions Fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -447,21 +448,26 @@ def failed(warning: str, force):
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):
items = [items]
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 = []
Expand All @@ -471,7 +477,6 @@ def failed(warning: str, force):
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'])
Expand All @@ -489,17 +494,19 @@ def failed(warning: str, force):
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']
Expand All @@ -509,13 +516,13 @@ def failed(warning: str, force):
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:
Expand All @@ -524,14 +531,14 @@ def failed(warning: str, force):
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'])
Expand All @@ -540,18 +547,18 @@ def failed(warning: str, force):

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)
for location in reversed(candidates):
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):
Expand All @@ -560,18 +567,19 @@ def failed(warning: str, force):
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
Expand All @@ -583,7 +591,7 @@ def failed(warning: str, force):
except ValueError:
warn(
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:
raise Exception(
Expand Down
3 changes: 2 additions & 1 deletion WebHostLib/static/assets/tutorial/archipelago/plando_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 219bcb3

Please sign in to comment.