Пример #1
0
 /// <summary>Try get a meta property.</summary>
 /// <param name="name">Key of the meta data.</param>
 /// <param name="value">Retrieved value.</param>
 /// <returns><c>true</c>, if the meta key could be found.</returns>
 public bool TryGetProperty(string name, out object value)
 {
     if (metaInfo.TryGetValue(name, out value))
     {
         return(true);
     }
     else if (StaticKeys != null && StaticKeys.TryGetValue(name, out value))
     {
         return(true);
     }
     else
     {
         return(false);
     }
 }         // func TryGetProperty
Пример #2
0
        }         // func Merge

        /// <summary>Does the key exists in the meta collection.</summary>
        /// <param name="key">Meta key.</param>
        /// <returns></returns>
        public bool ContainsKey(string key)
        => metaInfo.ContainsKey(key) || (StaticKeys?.ContainsKey(key) ?? false);
Пример #3
0
        private bool KeyAllowedInLocation(Guid key, FillOptions options, Dictionary <string, Guid> itemMap, ItemPool pool, RandomKeyNode location)
        {
            if (itemBlockedLocationRules.ContainsKey(key) && itemBlockedLocationRules[key].Contains(location.myRandomKeyIdentifier))
            {
                // Blocked from being placed here
                return(false);
            }

            if ((StaticKeys.IsMajorItem(key) && options.majorSwap == FillOptions.ItemSwap.Unchanged) ||
                (StaticKeys.IsMinorItem(key) && options.minorSwap == FillOptions.ItemSwap.Unchanged))
            {
                if (key != location.myOriginalKeyId)
                {
                    return(false);
                }
            }

            if (options.majorSwap == FillOptions.ItemSwap.LocalPool && StaticKeys.IsMajorItem(key) && !StaticKeys.IsMajorItem(location.myOriginalKeyId))
            {
                return(false);
            }

            if (options.minorSwap == FillOptions.ItemSwap.LocalPool && StaticKeys.IsMinorItem(key) && !StaticKeys.IsMinorItem(location.myOriginalKeyId))
            {
                return(false);
            }

            if (options.majorSwap == FillOptions.ItemSwap.LocalPool && !StaticKeys.IsMajorItem(key) && StaticKeys.IsMajorItem(location.myOriginalKeyId))
            {
                var allRandomKeyNodes       = keyNodes.Where(node => node is RandomKeyNode randomNode).Select(node => node as RandomKeyNode).OrderBy(node => node.id);
                var openMajorRandomKeyNodes = allRandomKeyNodes.Count(node => !itemMap.ContainsKey(node.myRandomKeyIdentifier) && StaticKeys.IsMajorItem(node.myOriginalKeyId));

                var poolCount = pool.AvailableItems().Count(item => StaticKeys.IsMajorItem(item));
                if (openMajorRandomKeyNodes <= poolCount)
                {
                    return(false);
                }
            }

            if (options.minorSwap == FillOptions.ItemSwap.LocalPool && !StaticKeys.IsMinorItem(key) && StaticKeys.IsMinorItem(location.myOriginalKeyId))
            {
                var allRandomKeyNodes       = keyNodes.Where(node => node is RandomKeyNode randomNode).Select(node => node as RandomKeyNode).OrderBy(node => node.id);
                var openMinorRandomKeyNodes = allRandomKeyNodes.Count(node => !itemMap.ContainsKey(node.myRandomKeyIdentifier) && StaticKeys.IsMinorItem(node.myOriginalKeyId));

                var poolCount = pool.AvailableItems().Count(item => StaticKeys.IsMinorItem(item));
                if (openMinorRandomKeyNodes <= poolCount)
                {
                    return(false);
                }
            }

            var hasRequirementForLocation = false;
            var requirementFulfilled      = true;

            if (itemRequiredLocationRules.ContainsKey(key))
            {
                requirementFulfilled = itemMap.Any(loc => itemRequiredLocationRules[key].Contains(loc.Key) && loc.Value == key);

                hasRequirementForLocation = itemRequiredLocationRules[key].Contains(location.myRandomKeyIdentifier);
            }

            if (!requirementFulfilled && pool.CountKey(key) == 1 && !hasRequirementForLocation)
            {
                // Has requirement to be placed somewhere else and placing it here would not leave any item to place in that location
                return(false);
            }

            if (restrictedLocations.Contains(location.myRandomKeyIdentifier) && (requirementFulfilled || !hasRequirementForLocation))
            {
                // Location is restricted and this key is not one of the items with a requirement to be here
                return(false);
            }

            return(true);
        }
Пример #4
0
        private Guid FindRelevantKey(Inventory inventory, FillSearcher aSearcher, FillOptions options,
                                     IEnumerable <RandomKeyNode> availableLocations, IEnumerable <NodeBase> unRetracableKeys,
                                     IEnumerable <Guid> restrictedItems, IEnumerable <Guid> prioritizedItems,
                                     ItemPool pool, Dictionary <string, Guid> itemMap,
                                     Random random, LogLayer log)
        {
            var relevantKeyLog = log.AddChild("Relevant Key");

            var relevantKeys = new Dictionary <Guid, List <NodeBase> >();

            foreach (var item in pool.AvailableItems().Distinct().Where(key => !restrictedItems.Contains(key)))
            {
                var testInventory = inventory.Expand(KeyManager.GetKey(item));

                var tempSearcher = new FillSearcher(aSearcher);

                var currentKeyLog = relevantKeyLog.AddChild(KeyManager.GetKeyName(item));

                var  reachableKeys = new List <NodeBase>();
                bool searchAgain;
                do
                {
                    // Find all nodes that can be reached with current inventory
                    reachableKeys.RemoveAll(node => testInventory.myNodes.Contains(node));
                    reachableKeys.AddRange(tempSearcher.ContinueSearch(testInventory, node => (node is KeyNode) && !testInventory.myNodes.Contains(node)));

                    var combinedReachableKeys = unRetracableKeys.Where(key => !testInventory.myNodes.Contains(key)).Union(reachableKeys);

                    searchAgain = false;

                    var retracableKeys = combinedReachableKeys.AsParallel().Where(node => node == endNode || NodeTraverser.PathExists(node, startNode, node is EventKeyNode ? testInventory.Expand(node) : testInventory)).ToList();

                    if (retracableKeys.Any())
                    {
                        // If any events can be reached, add to inventory and update search before continuing
                        var retracableEvents = retracableKeys.Where(node => node is EventKeyNode).ToList();
                        if (retracableEvents.Any())
                        {
                            currentKeyLog.AddChild("Retracable events", retracableEvents.Select(node => node.Name()));
                            testInventory.myNodes.AddRange(retracableEvents);

                            // Prioritize item that will let you beat the game
                            if (retracableEvents.Any(node => node == endNode))
                            {
                                relevantKeyLog.Message += $" : {KeyManager.GetKeyName(item)}";

                                return(item);
                            }

                            searchAgain = true;

                            continue;
                        }

                        var randomizedLocations = retracableKeys.Where(key => key is RandomKeyNode randomNode).Select(key => key as RandomKeyNode).OrderBy(x => x.id).ToList();

                        // Pick up any items already filled in on the map and update search before placing any items
                        var preFilledLocations = randomizedLocations.Where(loc => itemMap.ContainsKey(loc.myRandomKeyIdentifier));

                        if (preFilledLocations.Any())
                        {
                            currentKeyLog.AddChild("Prefilled locations", preFilledLocations.Select(node => $"{node.Name()} - {node.GetKeyName()}"));
                            testInventory.myNodes.AddRange(preFilledLocations);

                            searchAgain = true;

                            continue;
                        }

                        currentKeyLog.AddChild($"Locations", retracableKeys.Select(key => key.Name()));

                        relevantKeys.Add(item, retracableKeys);
                    }
                } while (searchAgain);
            }

            // Filter out any keys that by rules cannot be placed in any available location
            var filteredKeys = relevantKeys.Where(key => availableLocations.Any(location => KeyAllowedInLocation(key.Key, options, itemMap, pool, location))).ToDictionary(key => key.Key, key => key.Value);

            relevantKeyLog.AddChild("Filtered Keys", filteredKeys.Select(key => $"{KeyManager.GetKeyName(key.Key)}"));

            if (!filteredKeys.Any())
            {
                return(Guid.Empty);
            }

            // Correlate prioritized and relevant keys
            var prioritizedRelevantKeys = filteredKeys.Where(key => prioritizedItems.Contains(key.Key));

            if (prioritizedRelevantKeys.Any())
            {
                filteredKeys = prioritizedRelevantKeys.ToDictionary(key => key.Key, key => key.Value);
            }

            relevantKeyLog.AddChild("Prioritized Relevant Keys", filteredKeys.Select(key => $"{KeyManager.GetKeyName(key.Key)}"));

            // Prioritize items that open up major location for local swaps
            if (options.majorSwap == FillOptions.ItemSwap.LocalPool || options.minorSwap == FillOptions.ItemSwap.LocalPool)
            {
                var keysWithMajorLocation = filteredKeys.Where(key => key.Value.Any(location => StaticKeys.IsMajorLocation(location)));
                if (keysWithMajorLocation.Any())
                {
                    filteredKeys = keysWithMajorLocation.ToDictionary(key => key.Key, key => key.Value);
                }
            }

            // Avoid sprawl calculation bias if value is not set or there is only one item to choose from
            if (options.SprawlFactor == 0 || filteredKeys.Count < 2)
            {
                var item = pool.PeekAmong(filteredKeys.Keys, random);

                relevantKeyLog.Message += $" : {KeyManager.GetKeyName(item)}";
                relevantKeyLog.AddChild($"Item pulled from pool: {KeyManager.GetKeyName(item)}");

                return(item);
            }

            relevantKeyLog.AddChild($"Sprawl factor: {options.SprawlFactor}");

            // Translate sprawl factor to a decimal
            var sprawl = ((double)options.SprawlFactor) / 10;

            // Weight is numberOfLocationsUnlocked ^ sprawl
            var keysGroupedByWeight = filteredKeys.ToLookup(key => key.Value.Count, key => key.Key).Select(group => new KeyValuePair <double, List <Guid> >(Math.Pow(group.Key, sprawl), group.ToList())).OrderBy(pair => pair.Key);

            relevantKeyLog.AddChild("Keys with weights", keysGroupedByWeight.SelectMany(group => group.Value.Select(key => $"{KeyManager.GetKeyName(key)} - {group.Key}")));

            // Sum of all weights
            var weightSum = keysGroupedByWeight.Select(pair => pair.Key).Sum();

            relevantKeyLog.AddChild($"Sum of Weights: {weightSum}");

            // Pick random number between 0 and weightSum
            var weightRandom = random.NextDouble() * weightSum;

            relevantKeyLog.AddChild($"Weight Random: {weightRandom}");

            // Remove weight from random value until it goes below zero then pick that key
            foreach (var key in keysGroupedByWeight)
            {
                weightRandom -= key.Key;
                if (weightRandom < 0)
                {
                    relevantKeyLog.AddChild($"Chosen weight: {key.Key}");

                    var item = pool.PeekAmong(key.Value, random);

                    relevantKeyLog.Message += $" : {KeyManager.GetKeyName(item)}";
                    relevantKeyLog.AddChild($"Selected Key: {KeyManager.GetKeyName(item)}");

                    return(item);
                }
            }

            relevantKeyLog.AddChild($"No Key Selected");
            return(Guid.Empty);
        }
Пример #5
0
        public Dictionary <string, Guid> FillLocations(SaveData someData, FillOptions options, ItemPool pool, Inventory startingInventory, Random random, Dictionary <string, Guid> originalItemMap)
        {
            Log = new LogLayer("Item Placement");

            var inventory = new Inventory(startingInventory);

            var nodeCollection = new NodeCollection();

            nodeCollection.InitializeNodes(someData);

            keyNodes = nodeCollection.myNodes.Where(node => node is KeyNode).ToList();
            var eventNodes = keyNodes.Where(node => node is EventKeyNode).Select(node => node as EventKeyNode);

            startNode   = eventNodes.FirstOrDefault(node => node.myKeyId == StaticKeys.GameStart);
            endNode     = eventNodes.FirstOrDefault(node => node.myKeyId == StaticKeys.GameFinish);
            charlieNode = eventNodes.FirstOrDefault(node => node.myKeyId == StaticKeys.CharlieDefeated);

            // Generate Item map
            var itemMap = new Dictionary <string, Guid>(originalItemMap);

            if (options.majorSwap == FillOptions.ItemSwap.Unchanged || options.minorSwap == FillOptions.ItemSwap.Unchanged)
            {
                var allRandomKeyNodes  = keyNodes.Where(node => node is RandomKeyNode randomNode).Select(node => node as RandomKeyNode).OrderBy(node => node.id);
                var openRandomKeyNodes = allRandomKeyNodes.Where(node => !itemMap.ContainsKey(node.myRandomKeyIdentifier));

                foreach (var keyNode in openRandomKeyNodes)
                {
                    var item = keyNode.GetOriginalKey();
                    if (item == null)
                    {
                        continue;
                    }

                    if ((StaticKeys.IsMajorItem(item.Id) && options.majorSwap == FillOptions.ItemSwap.Unchanged) ||
                        (StaticKeys.IsMinorItem(item.Id) && options.minorSwap == FillOptions.ItemSwap.Unchanged))
                    {
                        var pulled = pool.Pull(item.Id);
                        if (pulled)
                        {
                            itemMap.Add(keyNode.myRandomKeyIdentifier, item.Id);
                        }
                    }
                }
            }

            KeyManager.SetRandomKeyMap(itemMap);

            // Set up location rules
            itemRequiredLocationRules = ItemRuleUtility.GetRequiredLocationRules(options.itemRules);
            itemBlockedLocationRules  = ItemRuleUtility.GetBlockedLocationRules(options.itemRules);

            restrictedLocations = new HashSet <string>();

            if (itemRequiredLocationRules.Any())
            {
                Log.AddChild("RequiredLocationRules", itemRequiredLocationRules.Select(group => new LogLayer(KeyManager.GetKeyName(group.Key), group.Value)));
            }

            if (itemBlockedLocationRules.Any())
            {
                Log.AddChild("BlockedLocationRules", itemBlockedLocationRules.Select(group => new LogLayer(KeyManager.GetKeyName(group.Key), group.Value)));
            }

            UpdateLocationRestrictions(inventory, itemMap, pool, random, Log);

            // Initialize search terms
            searcher = new FillSearcher();

            var reachableKeys   = new List <NodeBase>();
            var retracableKeys  = new List <NodeBase>();
            var restrictedItems = new List <Guid>();

            var searchDepth = 0;
            var stepCount   = 1;

            if (options.gameCompletion == FillOptions.GameCompletion.NoLogic)
            {
                Log.AddChild("Game Completion has No Logic - Skipping standard steps");
            }
            else
            {
                var logSteps = Log.AddChild("Standard Steps");
                while (true)
                {
                    var logCurrentStep = logSteps.AddChild($"Step {stepCount++}, depth {searchDepth}");
                    logCurrentStep.AddChild(inventory.GetKeyLog());

                    // Beatable conditional
                    if (options.gameCompletion == FillOptions.GameCompletion.Beatable && inventory.myNodes.Contains(endNode))
                    {
                        logCurrentStep.AddChild($"EndNode reached");
                        break;
                    }

                    // Do not place any power bombs until obtaining powered suit
                    restrictedItems.Clear();
                    if (options.noEarlyPbs && !inventory.ContainsKey(StaticKeys.CharlieDefeated))
                    {
                        restrictedItems.Add(StaticKeys.PowerBombs);
                    }

                    var itemDepthRestrictions = options.itemRules.Where(rest => rest is ItemRuleRestrictedBeforeDepth depthRest && depthRest.SearchDepth > searchDepth);
                    if (itemDepthRestrictions.Any())
                    {
                        restrictedItems.AddRange(itemDepthRestrictions.Select(rest => rest.ItemId));
                    }

                    if (restrictedItems.Any())
                    {
                        logCurrentStep.AddChild("Restricted Items", restrictedItems.Select(key => KeyManager.GetKeyName(key)));
                    }

                    // Find all nodes that can be reached with current inventory
                    reachableKeys.RemoveAll(node => inventory.myNodes.Contains(node));
                    reachableKeys.AddRange(searcher.ContinueSearch(startNode, inventory, node => (node is KeyNode) && !inventory.myNodes.Contains(node)));

                    logCurrentStep.AddChild("Reachable keys", reachableKeys.Select(node => node.Name()));

                    // Find all reachable keys that are also possible to get back from
                    // (End node is always considered retracable, since being able to reach it at all is just akin to "being in go mode")
                    retracableKeys.RemoveAll(node => inventory.myNodes.Contains(node));
                    var notRetracable = reachableKeys.Except(retracableKeys);
                    retracableKeys.AddRange(notRetracable.AsParallel().Where(node => node == endNode || NodeTraverser.PathExists(node, startNode, node is EventKeyNode ? inventory.Expand(node) : inventory)).ToList());

                    logCurrentStep.AddChild("Retracable keys", retracableKeys.Select(node => node.Name()));

                    if (!retracableKeys.Any())
                    {
                        break;
                    }

                    // If any events can be reached, add to inventory and update search before continuing
                    var retracableEvents = retracableKeys.Where(node => node is EventKeyNode).ToList();
                    if (retracableEvents.Any())
                    {
                        logCurrentStep.AddChild("Retracable events", retracableEvents.Select(node => node.Name()));
                        inventory.myNodes.AddRange(retracableEvents);
                        continue;
                    }

                    var randomizedLocations = retracableKeys.Where(key => key is RandomKeyNode randomNode).Select(key => key as RandomKeyNode).OrderBy(x => x.id).ToList();

                    // Pick up any items already filled in on the map and update search before placing any items
                    var preFilledLocations = randomizedLocations.Where(loc => itemMap.ContainsKey(loc.myRandomKeyIdentifier));

                    if (preFilledLocations.Any())
                    {
                        logCurrentStep.AddChild("Prefilled locations", preFilledLocations.Select(node => $"{node.Name()} - {node.GetKeyName()}"));
                        inventory.myNodes.AddRange(preFilledLocations);
                        continue;
                    }

                    // Get items that are prioritized according to item rules
                    var prioritizedItems = options.itemRules.Where(rest => rest is ItemRulePrioritizedAfterDepth depthRest && depthRest.SearchDepth <= searchDepth)
                                           .Select(rest => rest.ItemId)
                                           .Where(item => !restrictedItems.Contains(item) && !inventory.ContainsKey(item))
                                           .ToList();

                    logCurrentStep.AddChild("Prioritized Items", prioritizedItems.Select(key => KeyManager.GetKeyName(key)));

                    var selectedRelevantKey = FindRelevantKey(inventory, searcher, options, randomizedLocations, reachableKeys.Except(retracableKeys), restrictedItems, prioritizedItems, pool, itemMap, random, logCurrentStep);

                    // Special case to handle how chozodia area is built
                    // Specifically can't get out of it without power bombs, which creates awkward dynamics regarding the placements of said power bombs)
                    // Special case triggers on reaching charlie when no early pbs is enabled
                    if (reachableKeys.Any(key => key is EventKeyNode eventKey && eventKey.myKeyId == StaticKeys.CharlieDefeated) && options.noEarlyPbs &&
                        (NodeTraverser.PathExists(charlieNode, endNode, inventory.Expand(charlieNode)) || selectedRelevantKey == Guid.Empty))
                    {
                        FillRandomly(restrictedItems, inventory, options, itemMap, pool, random, logCurrentStep.AddChild("Charlie random fill"));

                        if (!inventory.ContainsKey(StaticKeys.CharlieDefeated))
                        {
                            // Unless Charlie was for some reason reached during fill, start new search with Charlie as new start node
                            startNode = charlieNode;
                            inventory.myNodes.Add(startNode);
                            searcher = new FillSearcher();
                        }

                        continue;
                    }

                    if (selectedRelevantKey == Guid.Empty)
                    {
                        break;
                    }

                    // Filter out available locations where selected key cannot be placed
                    var filteredLocations = randomizedLocations.Where(location => KeyAllowedInLocation(selectedRelevantKey, options, itemMap, pool, location)).OrderBy(x => x.id).ToList();

                    logCurrentStep.AddChild("Filtered Locations", filteredLocations.Select(node => node.Name()));

                    if (!filteredLocations.Any())
                    {
                        break;
                    }

                    pool.Pull(selectedRelevantKey);

                    // Pick out one random accessible location, place the selected key there and add that item to inventory
                    var selectedLocation = filteredLocations.ElementAt(random.Next(filteredLocations.Count));
                    randomizedLocations.Remove(selectedLocation);
                    itemMap.Add(selectedLocation.myRandomKeyIdentifier, selectedRelevantKey);
                    inventory.myNodes.Add(selectedLocation);

                    logCurrentStep.AddChild($"Selected Location: {selectedLocation.Name()}");

                    prioritizedItems.Remove(selectedRelevantKey);

                    // Only increase searchDepth if an actual item is placed (debatable if this is the correct approach)
                    searchDepth++;

                    UpdateLocationRestrictions(inventory, itemMap, pool, random, logCurrentStep);

                    // Fill remaining accessible locations with random items
                    if (options.majorSwap != FillOptions.ItemSwap.LocalPool && options.minorSwap != FillOptions.ItemSwap.LocalPool)
                    {
                        var randomizedLocationLog = logCurrentStep.AddChild("Randomized Locations");
                        foreach (var node in randomizedLocations)
                        {
                            var locationLog = randomizedLocationLog.AddChild(node.Name());

                            // This is possible through Required Location Rules
                            if (itemMap.ContainsKey(node.myRandomKeyIdentifier))
                            {
                                locationLog.AddChild($"Already filled with: {node.GetKeyName()}");
                                inventory.myNodes.Add(node);
                                continue;
                            }

                            var filteredPrioritizedItems = prioritizedItems.Where(key => KeyAllowedInLocation(key, options, itemMap, pool, node));

                            if (filteredPrioritizedItems.Any())
                            {
                                locationLog.AddChild("Filtered Prioritized Items", filteredPrioritizedItems.Select(key => KeyManager.GetKeyName(key)));

                                var randomKey = pool.PullAmong(filteredPrioritizedItems, random);
                                prioritizedItems.Remove(randomKey);
                                itemMap.Add(node.myRandomKeyIdentifier, randomKey);
                                inventory.myNodes.Add(node);

                                locationLog.Message += $" : {KeyManager.GetKeyName(randomKey)}";
                                locationLog.AddChild($"Prioritized Key placed: {KeyManager.GetKeyName(randomKey)}");
                            }
                            else
                            {
                                // Add all items that cannot be in this location to restrictedItems
                                var locationRestrictedItems = restrictedItems.Union(pool.AvailableItems().Where(key => !KeyAllowedInLocation(key, options, itemMap, pool, node)));

                                locationLog.AddChild("Location Restricted Items", locationRestrictedItems.Select(key => KeyManager.GetKeyName(key)));

                                var selectedKey = pool.PullExcept(locationRestrictedItems, random);

                                if (selectedKey != Guid.Empty)
                                {
                                    itemMap.Add(node.myRandomKeyIdentifier, selectedKey);
                                    inventory.myNodes.Add(node);

                                    locationLog.Message += $" : {KeyManager.GetKeyName(selectedKey)}";
                                    locationLog.AddChild($"Selected Key placed: {KeyManager.GetKeyName(selectedKey)}");
                                }
                            }

                            UpdateLocationRestrictions(inventory, itemMap, pool, random, locationLog);
                        }
                    }

                    // Go back to start of loop to continue search with updated inventory
                }
            }

            var postFillLog = Log.AddChild("Post fill");

            // POST-FILL:
            // Reachable if seed is beatable or if it ran out of possible relevant keys
            // Fill in remaining locations as well as possible without breaking any restrictions

            // Do not place any power bombs until obtaining powered suit
            restrictedItems.Clear();
            if (options.noEarlyPbs && !inventory.ContainsKey(StaticKeys.CharlieDefeated))
            {
                restrictedItems.Add(StaticKeys.PowerBombs);
            }

            postFillLog.AddChild("Restricted Items", restrictedItems.Select(key => KeyManager.GetKeyName(key)));

            if (options.gameCompletion == FillOptions.GameCompletion.AllItems)
            {
                var nonEmptyLog = postFillLog.AddChild("Add non-empty items");

                // Prioritize filling in non-empty items
                var restrictedAndEmpty = new List <Guid>(restrictedItems);
                restrictedAndEmpty.Add(StaticKeys.Nothing);

                FillRandomly(restrictedAndEmpty, inventory, options, itemMap, pool, random, nonEmptyLog);
            }

            var reachableLog = postFillLog.AddChild("Fill remaining reachable locations");

            // Fill remaining reachable nodes randomly
            FillRandomly(restrictedItems, inventory, options, itemMap, pool, random, reachableLog);

            restrictedItems.Clear();

            // Fill all remaining (unreachable) locations with any remaining items
            var remainingNodes = keyNodes.Where(node => node is RandomKeyNode randomNode && !itemMap.ContainsKey(randomNode.myRandomKeyIdentifier)).Select(key => key as RandomKeyNode).OrderBy(x => x.id);

            if (remainingNodes.Any())
            {
                var respectableLog = postFillLog.AddChild("Fill unreachable locations");

                FillRandomly(remainingNodes, restrictedItems, inventory, options, itemMap, pool, random, respectableLog);
            }

            // Fill final locations with blanks
            var finalEmptyLocations = keyNodes.Where(node => node is RandomKeyNode randomNode && !itemMap.ContainsKey(randomNode.myRandomKeyIdentifier)).Select(key => key as RandomKeyNode).OrderBy(x => x.id);

            if (finalEmptyLocations.Any())
            {
                var finalFill = postFillLog.AddChild("Final fill");
                foreach (var node in finalEmptyLocations)
                {
                    if (itemMap.ContainsKey(node.myRandomKeyIdentifier))
                    {
                        continue;
                    }

                    var locationLog = finalFill.AddChild(node.myRandomKeyIdentifier);

                    var item = StaticKeys.Nothing;

                    locationLog.Message += $" : {KeyManager.GetKeyName(item)}";
                    locationLog.AddChild($"Selected item: {KeyManager.GetKeyName(item)}");

                    itemMap.Add(node.myRandomKeyIdentifier, item);

                    UpdateLocationRestrictions(inventory, itemMap, pool, random, locationLog);
                }
            }

            return(itemMap);
        }