From bd86a07115a6dc0a817820206c25547617e2dc83 Mon Sep 17 00:00:00 2001 From: Fabian Dill <fabian.dill@web.de> Date: Mon, 4 Jan 2021 15:14:20 +0100 Subject: [PATCH] make random world targeting smarter, in only considering possible unfilled locations --- BaseClasses.py | 8 ++++++- Fill.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++++- Main.py | 39 ++------------------------------- 3 files changed, 66 insertions(+), 39 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 9cc25bcb..4729e4fd 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -5,7 +5,7 @@ from enum import Enum, unique import logging import json from collections import OrderedDict, Counter, deque -from typing import Union, Optional, List, Set, Dict, NamedTuple +from typing import Union, Optional, List, Set, Dict, NamedTuple, Iterable import secrets import random @@ -393,6 +393,12 @@ class World(object): return [location for location in self.get_locations() if (player is None or location.player == player) and location.item is None and location.can_reach(state)] + def get_unfilled_locations_for_players(self, location_name: str, players: Iterable[int]): + for player in players: + location = self.get_location(location_name, player) + if location.item is None: + yield location + def unlocks_new_location(self, item) -> bool: temp_state = self.state.copy() temp_state.collect(item, True) diff --git a/Fill.py b/Fill.py index a4ef61d9..0ad4332f 100644 --- a/Fill.py +++ b/Fill.py @@ -1,12 +1,14 @@ import logging import typing -from BaseClasses import CollectionState +from BaseClasses import CollectionState, PlandoItem +from Items import ItemFactory class FillError(RuntimeError): pass + def fill_restrictive(world, base_state: CollectionState, locations, itempool, single_player_placement=False): def sweep_from_pool(): new_state = base_state.copy() @@ -339,3 +341,57 @@ def balance_multiworld_progression(world): break elif not sphere_locations: raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') + + +def distribute_planned(world): + world_name_lookup = {world.player_names[player_id][0]: player_id for player_id in world.player_ids} + + for player in world.player_ids: + placement: PlandoItem + for placement in world.plando_items[player]: + item = ItemFactory(placement.item, player) + target_world: int = placement.world + if target_world is False or world.players == 1: + target_world = player # in own world + elif target_world is True: # in any other world + unfilled = list(location for location in world.get_unfilled_locations_for_players( + placement.location, + set(world.player_ids) - {player}) if location.item_rule(item) + ) + if not unfilled: + raise FillError(f"Could not find a world with an unfilled location {placement.location}") + + target_world = world.random.choice(unfilled).player + + elif target_world is None: # any random world + unfilled = list(location for location in world.get_unfilled_locations_for_players( + placement.location, + set(world.player_ids)) if location.item_rule(item) + ) + if not unfilled: + raise FillError(f"Could not find a world with an unfilled location {placement.location}") + + target_world = world.random.choice(unfilled).player + + elif type(target_world) == int: # target world by player id + pass + else: # find world by name + target_world = world_name_lookup[target_world] + + location = world.get_location(placement.location, target_world) + if location.item: + raise Exception(f"Cannot place item into already filled location {location}.") + + if placement.from_pool: + try: + world.itempool.remove(item) + except ValueError: + logging.warning(f"Could not remove {item} from pool as it's already missing from it.") + + if location.can_fill(world.state, item, False): + world.push_item(location, item, collect=False) + location.event = True # flag location to be checked during fill + location.locked = True + logging.debug(f"Plando placed {item} at {location}") + else: + raise Exception(f"Can't place {item} at {location} due to fill condition not met.") diff --git a/Main.py b/Main.py index 007178a5..ffa6670c 100644 --- a/Main.py +++ b/Main.py @@ -17,7 +17,7 @@ from EntranceShuffle import link_entrances, link_inverted_entrances, plando_conn 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 +from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned from ItemPool import generate_itempool, difficulties, fill_prizes from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple import Patch @@ -179,42 +179,7 @@ def main(args, seed=None): logger.info("Running Item Plando") - world_name_lookup = {world.player_names[player_id][0]: player_id for player_id in world.player_ids} - - for player in world.player_ids: - placement: PlandoItem - for placement in world.plando_items[player]: - target_world: int = placement.world - if target_world is False or world.players == 1: - target_world = player # in own world - elif target_world is True: # in any other world - target_world = player - while target_world == player: - target_world = world.random.randint(1, world.players + 1) - elif target_world is None: # any random world - target_world = world.random.randint(1, world.players + 1) - elif type(target_world) == int: # target world by player id - pass - else: # find world by name - target_world = world_name_lookup[target_world] - - location = world.get_location(placement.location, target_world) - if location.item: - raise Exception(f"Cannot place item into already filled location {location}.") - item = ItemFactory(placement.item, player) - if placement.from_pool: - try: - world.itempool.remove(item) - except ValueError: - logger.warning(f"Could not remove {item} from pool as it's already missing from it.") - - if location.can_fill(world.state, item, False): - world.push_item(location, item, collect=False) - location.event = True # flag location to be checked during fill - location.locked = True - logger.debug(f"Plando placed {item} at {location}") - else: - raise Exception(f"Can't place {item} at {location} due to fill condition not met.") + distribute_planned(world) logger.info('Placing Dungeon Items.')