Exemplo n.º 1
0
        private NodeBase PlaceNewNode(NodeType type, Vector2 mousePos)
        {
            NodeBase newNode = null;

            switch (type)
            {
            case NodeType.Blank:
                newNode = new BlankNode();
                break;

            case NodeType.Lock:
                newNode = new LockNode();
                break;

            case NodeType.RandomKey:
                newNode = new RandomKeyNode();
                break;

            case NodeType.EventKey:
                newNode = new EventKeyNode();
                break;

            default:
                return(null);
            }

            newNode.myPos = (mousePos / ZoomScale) - imageBasePos;

            myMementos.Add(myNodeCollection.AddNode(newNode));

            SaveManager.Dirty = true;

            selectedNode = newNode;

            UpdateNodeSettings();

            panel1.Invalidate();

            return(newNode);
        }
Exemplo n.º 2
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);
        }