public override bool Next() { var caps = this.Logic.Caps.WithoutKey(); var closure = LinkedNodeSet.Closure(this.Node, caps, caps, true); foreach (var edge in closure.UnlinkedEdges()) { if (!this.Logic.Map.HoleFree(this.Node.Room, edge.Static.HoleTarget)) { continue; } if (this.Logic.Random.Next(5) != 0) { continue; } var possibilities = this.Logic.AvailableNewEdges(caps, caps, e => e.FromNode.ParentRoom.Collectables.Count != 0); foreach (var newEdge in possibilities) { var receipt = ConnectAndMapReceipt.Do(this.Logic, edge, newEdge); if (receipt == null) { continue; } var closure2 = LinkedNodeSet.Closure(receipt.EntryNode, caps, caps, true); var seen = new HashSet <UnlinkedCollectable>(); var options = new List <Tuple <UnlinkedCollectable, bool> >(); foreach (var spot in closure2.UnlinkedCollectables()) { seen.Add(spot); options.Add(Tuple.Create(spot, false)); } closure2.Extend(caps, null, true); foreach (var spot in closure2.UnlinkedCollectables()) { if (seen.Contains(spot)) { continue; } options.Add(Tuple.Create(spot, true)); } if (options.Count == 0) { receipt.Undo(); continue; } var pickedSpotTup = options[this.Logic.Random.Next(options.Count)]; var pickedSpot = pickedSpotTup.Item1; var berry = pickedSpot.Static.MustFly ? LinkedNode.LinkedCollectable.WingedStrawberry : this.Logic.Settings.Algorithm == LogicType.Endless ? LinkedNode.LinkedCollectable.LifeBerry : LinkedNode.LinkedCollectable.Strawberry; pickedSpot.Node.Collectables[pickedSpot.Static] = Tuple.Create(berry, pickedSpotTup.Item2); break; } } return(true); }
public override bool Next() { if (this.Tries >= 5) { Logger.Log("randomizer", $"Failure: took too many tries to place key from {Node.Room.Static.Name}:{Node.Static.Name}"); return(false); } // each attempt we should back further away from the idea that we might add a new room bool extendingMap = this.Logic.Random.Next(5) > this.Tries + 1; this.Tries++; var caps = this.Logic.Caps.WithoutKey(); var closure = LinkedNodeSet.Closure(this.Node, caps, caps, this.InternalOnly); closure.Shuffle(this.Logic.Random); if (!extendingMap) { // just try to place a key foreach (var spot in closure.UnlinkedCollectables()) { if (spot.Static.MustFly) { continue; } this.AddReceipt(PlaceCollectableReceipt.Do(spot.Node, spot.Static, LinkedNode.LinkedCollectable.Key)); this.OriginalNode.Room.UsedKeyholes.Add(this.KeyholeID); return(true); } } // see if we can find somewhere to extend the map foreach (var outEdge in closure.UnlinkedEdges()) { if (!this.Logic.Map.HoleFree(outEdge.Node.Room, outEdge.Static.HoleTarget)) { continue; } foreach (var toEdge in this.Logic.AvailableNewEdges(caps, caps, (StaticEdge e) => !e.FromNode.ParentRoom.End)) { var mapped = ConnectAndMapReceipt.Do(this.Logic, outEdge, toEdge); if (mapped == null) { continue; } this.AddReceipt(mapped); this.AddNextTask(new TaskPathwayPlaceKey(this.Logic, mapped.EntryNode, this.KeyholeID, this.OriginalNode, true, this.Tries)); return(true); } } // try again return(this.Next()); }
private ConnectAndMapReceipt WorkingPossibility(UnlinkedEdge fromEdge) { var capsNoKey = this.Logic.Caps.WithoutKey(); foreach (var toEdge in this.Logic.AvailableNewEdges(capsNoKey, capsNoKey, (StaticEdge e) => !e.FromNode.ParentRoom.End)) { var result = ConnectAndMapReceipt.Do(this.Logic, fromEdge, toEdge); if (result != null) { return(result); } } return(null); }
private ConnectAndMapReceipt WorkingPossibility() { var possibilities = this.Logic.AvailableNewEdges(this.Logic.Caps.WithoutKey(), this.Logic.Caps.WithoutKey(), (StaticEdge edge) => !edge.FromNode.ParentRoom.End && !this.TriedEdges.Contains(edge)); foreach (var edge in possibilities) { var result = ConnectAndMapReceipt.Do(this.Logic, this.Edge, edge); if (result != null) { return(result); } } return(null); }
private ConnectAndMapReceipt WorkingPossibility() { var caps = this.Logic.Caps.WithoutKey(); // don't try to enter a door locked from the other side var possibilities = this.Logic.AvailableNewEdges(caps, null, (StaticEdge e) => !this.TriedRooms.Contains(e.FromNode.ParentRoom) && this.IsEnd == e.FromNode.ParentRoom.End); foreach (var edge in possibilities) { var result = ConnectAndMapReceipt.Do(this.Logic, this.Edge, edge); if (result != null) { return(result); } } return(null); }
private ConnectAndMapReceipt WorkingPossibility() { var caps = this.Logic.Caps.WithoutKey(); // don't try to enter a door locked from the other side var possibilities = this.Logic.AvailableNewEdges(caps, null, e => RoomFilter(e.FromNode.ParentRoom)); if (possibilities.Count == 0 && this.IsEnd) { throw new GenerationError("No ending rooms available"); } foreach (var edge in possibilities) { var result = ConnectAndMapReceipt.Do(this.Logic, this.Edge, edge); if (result != null) { var defaultBerry = this.Logic.Settings.Algorithm == LogicType.Endless ? LinkedNode.LinkedCollectable.LifeBerry : LinkedNode.LinkedCollectable.Strawberry; var closure = LinkedNodeSet.Closure(result.EntryNode, caps, caps, true); var seen = new HashSet <UnlinkedCollectable>(); foreach (var spot in closure.UnlinkedCollectables()) { seen.Add(spot); if (!spot.Static.MustFly && this.Logic.Random.Next(5) == 0) { spot.Node.Collectables[spot.Static] = Tuple.Create(defaultBerry, false); } } closure.Extend(caps, null, true); foreach (var spot in closure.UnlinkedCollectables()) { if (!seen.Contains(spot) && !spot.Static.MustFly && this.Logic.Random.Next(10) == 0) { spot.Node.Collectables[spot.Static] = Tuple.Create(defaultBerry, true); } } return(result); } } return(null); }
private ConnectAndMapReceipt WorkingPossibility() { var caps = this.Logic.Caps.WithoutKey(); // don't try to enter a door locked from the other side var possibilities = this.Logic.AvailableNewEdges(caps, null, e => RoomFilter(e.FromNode.ParentRoom)); if (possibilities.Count == 0 && this.IsEnd) { throw new GenerationError("No ending rooms available"); } foreach (var edge in possibilities) { var result = ConnectAndMapReceipt.Do(this.Logic, this.Edge, edge); if (result != null) { return(result); } } return(null); }
private void GenerateLabyrinth() { this.Caps = this.Caps.WithoutKey(); this.StartingGemCount = this.Settings.Length == MapLength.Short ? 3 : 0; void retry() { throw new RetryException(); } foreach (var room in RandoLogic.AllRooms) { if (room.Name == "Celeste/6-Reflection/A/b-00") { var lroom = new LabyrinthStartRoom(room); this.Map.AddRoom(lroom); this.RemainingRooms.Remove(room); this.PossibleContinuations.AddRange(LinkedNodeSet.Closure(lroom.Nodes["main"], this.Caps, this.Caps, true).UnlinkedEdges()); break; } } while (this.PossibleContinuations.Count != 0) { //Logger.Log("DEBUG", $"status: rooms={this.Map.Count} queue={this.PossibleContinuations.Count}"); int idx = this.Random.Next(this.PossibleContinuations.Count); var startEdge = this.PossibleContinuations[idx]; this.PossibleContinuations.RemoveAt(idx); foreach (var toEdge in this.AvailableNewEdges(this.Caps, this.Caps, (edge) => edge.FromNode.ParentRoom.ReqEnd is Impossible && edge.FromNode.ParentRoom.Name != "Celeste/7-Summit/A/g-00b" && edge.FromNode.ParentRoom.Nodes.Values.All(node => node.FlagSetters.Count == 0))) { var result = ConnectAndMapReceipt.Do(this, startEdge, toEdge); if (result != null) { var closure = LinkedNodeSet.Closure(result.EntryNode, this.Caps, this.Caps, true); var ue = closure.UnlinkedEdges(); var uc = new List <Tuple <UnlinkedCollectable, bool> >(); var alreadySeen = new HashSet <UnlinkedCollectable>(); foreach (var c in closure.UnlinkedCollectables()) { alreadySeen.Add(c); uc.Add(Tuple.Create(c, false)); } closure.Extend(this.Caps, null, true); foreach (var c in closure.UnlinkedCollectables()) { if (alreadySeen.Contains(c)) { continue; } uc.Add(Tuple.Create(c, true)); } if (ue.Count == 0 && uc.Count == 0) { result.Undo(); continue; } else if (ue.Count == 0) { this.PriorityCollectables.AddRange(uc); } else { this.PossibleContinuations.AddRange(ue); this.PossibleCollectables.AddRange(uc); } break; } } if (this.Map.Count >= LabyrinthMaximums[(int)this.Settings.Length]) { break; } } while (this.PossibleContinuations.Count != 0) { //Logger.Log("DEBUG", $"Pruning - {this.PossibleContinuations.Count} remaining"); var startEdge = this.PossibleContinuations.Last(); this.PossibleContinuations.RemoveAt(this.PossibleContinuations.Count - 1); var closure = LinkedNodeSet.Closure(startEdge.Node, this.Caps, this.Caps, true); var uc = new List <Tuple <UnlinkedCollectable, bool> >(); var alreadySeen = new HashSet <UnlinkedCollectable>(); foreach (var c in closure.UnlinkedCollectables()) { alreadySeen.Add(c); uc.Add(Tuple.Create(c, false)); } closure.Extend(this.Caps, null, true); foreach (var c in closure.UnlinkedCollectables()) { if (alreadySeen.Contains(c)) { continue; } uc.Add(Tuple.Create(c, true)); } if (uc.Count == 0) { if (startEdge.Node.Room.Static.Name == "Celeste/6-Reflection/A/b-00") { // DON'T REMOVE THE STARTING ROOM OMG continue; } var edgeCount = 0; foreach (var node in startEdge.Node.Room.Nodes.Values) { edgeCount += node.Edges.Count; } if (edgeCount <= 1) { var nodeNames = new List <string>(startEdge.Node.Room.Nodes.Keys); nodeNames.Sort(); foreach (var nodeName in nodeNames) { var node = startEdge.Node.Room.Nodes[nodeName]; foreach (var edge in node.Edges) { var otherNode = edge.OtherNode(node); var otherEdge = edge.OtherEdge(node); otherNode.Edges.Remove(edge); this.PossibleContinuations.Add(new UnlinkedEdge(otherNode, otherEdge)); } } this.Map.RemoveRoom(startEdge.Node.Room); } } else { this.PriorityCollectables.AddRange(uc); foreach (var c in uc) { this.PossibleCollectables.Remove(c); } } } if (this.Map.Count < LabyrinthMinimums[(int)this.Settings.Length]) { //Logger.Log("DEBUG", "retrying - too short"); retry(); } if (this.PossibleCollectables.Count + this.PriorityCollectables.Count < (6 - this.StartingGemCount)) { //Logger.Log("DEBUG", "retrying - not enough spots"); retry(); } for (var gem = LinkedCollectable.Gem1 + this.StartingGemCount; gem <= LinkedCollectable.Gem6; gem++) { //Logger.Log("DEBUG", $"Placing {gem}"); var collection = this.PriorityCollectables.Count != 0 ? this.PriorityCollectables : this.PossibleCollectables; if (collection.Count == 0) // just in case { retry(); } var idx = this.Random.Next(collection.Count); var spot = collection[idx].Item1; var autoBubble = collection[idx].Item2; collection.RemoveAt(idx); if (spot.Static.MustFly) { //Logger.Log("DEBUG", "...winged berry"); if (this.Settings.Strawberries != StrawberryDensity.None) { spot.Node.Collectables[spot.Static] = Tuple.Create(LinkedCollectable.WingedStrawberry, autoBubble); } gem--; } else { spot.Node.Collectables[spot.Static] = Tuple.Create(gem, autoBubble); //Logger.Log("DEBUG", $"Adding gem to {spot}"); if (collection == this.PriorityCollectables) { for (int i = 0; i < collection.Count; i++) { if (collection[i].Item1.Node.Room == spot.Node.Room) { collection.RemoveAt(i); i--; } } } } } var defaultBerry = this.Settings.HasLives ? LinkedCollectable.LifeBerry : LinkedCollectable.Strawberry; while (this.Settings.Strawberries != StrawberryDensity.None && this.PriorityCollectables.Count != 0) { //Logger.Log("DEBUG", $"Pruning priority collectables - {this.PriorityCollectables.Count} remaining"); var spot = this.PriorityCollectables.Last().Item1; var autoBubble = this.PriorityCollectables.Last().Item2; this.PriorityCollectables.RemoveAt(this.PriorityCollectables.Count - 1); spot.Node.Collectables[spot.Static] = Tuple.Create(spot.Static.MustFly ? LinkedCollectable.WingedStrawberry : defaultBerry, autoBubble); } int targetCount = 0; switch (this.Settings.Strawberries) { case StrawberryDensity.High: targetCount = 0; break; case StrawberryDensity.Low: targetCount = this.PossibleCollectables.Count / 3 * 2; break; case StrawberryDensity.None: targetCount = this.PossibleCollectables.Count; break; } this.PossibleCollectables.Shuffle(this.Random); while (this.PossibleCollectables.Count > targetCount) { //Logger.Log("DEBUG", $"Pruning collectables - {this.PossibleContinuations.Count - targetCount} remaining"); var spot = this.PossibleCollectables.Last().Item1; var autoBubble = this.PossibleCollectables.Last().Item2; this.PossibleCollectables.RemoveAt(this.PossibleCollectables.Count - 1); spot.Node.Collectables[spot.Static] = Tuple.Create(spot.Static.MustFly ? LinkedCollectable.WingedStrawberry : defaultBerry, autoBubble); } }
private void GenerateLabyrinth() { this.Caps = this.Caps.WithoutKey(); this.StartingGemCount = this.Settings.Length == MapLength.Short ? 3 : 0; void retry() { this.PossibleCollectables.Clear(); this.PriorityCollectables.Clear(); this.PossibleContinuations.Clear(); this.ResetRooms(); this.Map.Clear(); } tryagain: foreach (var room in this.RemainingRooms) { if (room.Name == "Celeste/6-Reflection/A/b-00") { var lroom = new LabyrinthStartRoom(room); this.Map.AddRoom(lroom); this.RemainingRooms.Remove(room); this.PossibleContinuations.AddRange(LinkedNodeSet.Closure(lroom.Nodes["main"], this.Caps, this.Caps, true).UnlinkedEdges()); break; } } while (this.PossibleContinuations.Count != 0) { //Logger.Log("DEBUG", $"status: rooms={this.Map.Count} queue={this.PossibleContinuations.Count}"); int idx = this.Random.Next(this.PossibleContinuations.Count); var startEdge = this.PossibleContinuations[idx]; this.PossibleContinuations.RemoveAt(idx); foreach (var toEdge in this.AvailableNewEdges(this.Caps, this.Caps, (edge) => !edge.FromNode.ParentRoom.End)) { var result = ConnectAndMapReceipt.Do(this, startEdge, toEdge); if (result != null) { var closure = LinkedNodeSet.Closure(result.EntryNode, this.Caps, this.Caps, true); var ue = closure.UnlinkedEdges(); var uc = closure.UnlinkedCollectables(); if (ue.Count == 0 && uc.Count == 0) { result.Undo(); continue; } else if (ue.Count == 0) { this.PriorityCollectables.AddRange(uc); } else { this.PossibleContinuations.AddRange(ue); this.PossibleCollectables.AddRange(uc); } break; } } if (this.Map.Count >= LabyrinthMaximums[(int)this.Settings.Length]) { break; } } if (this.Map.Count < LabyrinthMinimums[(int)this.Settings.Length]) { //Logger.Log("DEBUG", "retrying - too short"); retry(); goto tryagain; } if (this.PossibleCollectables.Count + this.PriorityCollectables.Count < (6 - this.StartingGemCount)) { //Logger.Log("DEBUG", "retrying - not enough spots"); retry(); goto tryagain; } for (var gem = LinkedNode.LinkedCollectable.Gem1 + this.StartingGemCount; gem <= LinkedNode.LinkedCollectable.Gem6; gem++) { var collection = this.PriorityCollectables.Count != 0 ? this.PriorityCollectables : this.PossibleCollectables; if (collection.Count == 0) // just in case { retry(); goto tryagain; } var idx = this.Random.Next(collection.Count); var spot = collection[idx]; collection.RemoveAt(idx); if (spot.Static.MustFly) { spot.Node.Collectables[spot.Static] = LinkedNode.LinkedCollectable.WingedStrawberry; gem--; } else { spot.Node.Collectables[spot.Static] = gem; } } while (this.PriorityCollectables.Count != 0) { var spot = this.PriorityCollectables[this.PriorityCollectables.Count - 1]; this.PriorityCollectables.RemoveAt(this.PriorityCollectables.Count - 1); spot.Node.Collectables[spot.Static] = spot.Static.MustFly ? LinkedNode.LinkedCollectable.WingedStrawberry : LinkedNode.LinkedCollectable.Strawberry; } var targetCount = this.PossibleCollectables.Count / 3 * 2; this.PossibleCollectables.Shuffle(this.Random); while (this.PossibleCollectables.Count > targetCount) { var spot = this.PossibleCollectables[this.PossibleCollectables.Count - 1]; this.PossibleCollectables.RemoveAt(this.PossibleCollectables.Count - 1); spot.Node.Collectables[spot.Static] = spot.Static.MustFly ? LinkedNode.LinkedCollectable.WingedStrawberry : LinkedNode.LinkedCollectable.Strawberry; } }
public override bool Next() { if (this.Tries >= 5) { Logger.Log("randomizer", $"Failure: took too many tries to satisfy {this.Req} from {Node.Room.Static.Name}:{Node.Static.Name}"); return(false); } // each attempt we should back further away from the idea that we might add a new room int roll = this.Logic.Random.Next(5); bool extendingMap = roll > this.Tries; this.Tries++; var caps = this.Logic.Caps.WithoutKey().WithFlags(this.State); int maxSteps = 99999; if (!InternalOnly) { maxSteps = this.Logic.Random.Next(1, 20); } var closure = LinkedNodeSet.Closure(this.Node, caps, caps, this.InternalOnly, maxSteps); closure.Shuffle(this.Logic.Random); if (this.Req is FlagRequirement fr) { var curval = this.State.TryGetValue(fr.Flag, out var x) ? x : FlagState.Unset; if (fr.Set && (curval == FlagState.Both || curval == FlagState.Set || curval == FlagState.UnsetToSet)) { return(true); } if (!fr.Set && (curval == FlagState.Both || curval == FlagState.Unset || curval == FlagState.SetToUnset)) { return(true); } } if (!extendingMap) { switch (this.Req) { case KeyRequirement keyReq: { // just try to place a key foreach (var spot in closure.UnlinkedCollectables()) { if (spot.Static.MustFly) { continue; } if (spot.Node.Room == this.OriginalNode.Room) { // don't be boring! continue; } this.AddReceipt(PlaceCollectableReceipt.Do(spot.Node, spot.Static, LinkedCollectable.Key, false, keyReq.KeyholeID, this.OriginalNode.Room)); return(true); } // try again for things that we need to bubble back from var newClosure = new LinkedNodeSet(closure); newClosure.Extend(caps, null, true); foreach (var spot in newClosure.UnlinkedCollectables()) { if (spot.Static.MustFly) { continue; } if (spot.Node.Room == this.OriginalNode.Room) { // don't be boring! continue; } this.AddReceipt(PlaceCollectableReceipt.Do(spot.Node, spot.Static, LinkedCollectable.Key, true, keyReq.KeyholeID, this.OriginalNode.Room)); return(true); } // third try: place a room which has a reachable spot var appropriateNodes = this.Logic.RemainingRooms .Where(x => x.ReqEnd is Impossible) .SelectMany(r => r.Nodes.Values) .Where(n => n.Collectables.Count(c => !c.MustFly) != 0) .ToList(); appropriateNodes.Shuffle(this.Logic.Random); foreach (var n in appropriateNodes) { // hack: make a linkednode for each staticnode so that the closure methods can work on it... var edges = LinkedNodeSet .Closure(new LinkedRoom(n.ParentRoom, Vector2.Zero).Nodes[n.Name], caps, caps, true) .Shuffle(this.Logic.Random) .UnlinkedEdges() .Select(e => e.Static); foreach (var edge in edges) { foreach (var startEdge in closure.UnlinkedEdges()) { var receipt = ConnectAndMapReceipt.Do(this.Logic, startEdge, edge, true); if (receipt != null) { this.AddReceipt(receipt); var cols = n.Collectables.Where(c => !c.MustFly).ToList(); cols.Shuffle(this.Logic.Random); this.AddReceipt(PlaceCollectableReceipt.Do(receipt.NewRoom.Nodes[n.Name], cols[this.Logic.Random.Next(cols.Count)], LinkedCollectable.Key, false, keyReq.KeyholeID, this.OriginalNode.Room)); return(true); } } } } break; } case FlagRequirement flagReq: { var appropriateNodes = this.Logic.RemainingRooms .SelectMany(r => r.Nodes.Values) .Where(n => n.FlagSetters.Any(s => s.Item1 == flagReq.Flag && s.Item2 == flagReq.Set)) .ToList(); appropriateNodes.Shuffle(this.Logic.Random); foreach (var n in appropriateNodes) { // hack: make a linkednode for each staticnode so that the closure methods can work on it... var edges = LinkedNodeSet .Closure(new LinkedRoom(n.ParentRoom, Vector2.Zero).Nodes[n.Name], caps, caps, true) .Shuffle(this.Logic.Random) .UnlinkedEdges() .Select(e => e.Static); foreach (var edge in edges) { foreach (var startEdge in closure.UnlinkedEdges()) { var receipt = ConnectAndMapReceipt.Do(this.Logic, startEdge, edge, true); if (receipt != null) { this.AddReceipt(receipt); // TODO: check for reverse traversability back to orig node return(true); } } } } break; } default: { throw new Exception($"Don't know how to satisfy {this.Req}. What?"); } } } // see if we can find somewhere to extend the map foreach (var outEdge in closure.UnlinkedEdges()) { if (!this.Logic.Map.HoleFree(outEdge.Node.Room, outEdge.Static.HoleTarget)) { continue; } foreach (var toEdge in this.Logic.AvailableNewEdges(caps, caps, e => e.FromNode.ParentRoom.ReqEnd is Impossible)) { var mapped = ConnectAndMapReceipt.Do(this.Logic, outEdge, toEdge, isBacktrack: true); if (mapped == null) { continue; } this.AddReceipt(mapped); this.AddNextTask(new TaskPathwaySatisfyRequirement(this.Logic, mapped.EntryNode, this.Req, this.State, this.OriginalNode, true, this.Tries)); return(true); } } // if we failed to both extend the map or place the capability at the same time, we're f****d! if (!extendingMap) { return(false); } // try again return(this.Next()); }
public override bool Next() { if (this.Tries >= 5) { Logger.Log("randomizer", $"Failure: took too many tries to place key from {Node.Room.Static.Name}:{Node.Static.Name}"); return(false); } // each attempt we should back further away from the idea that we might add a new room int roll = this.Logic.Random.Next(5); bool extendingMap = roll > this.Tries; this.Tries++; var caps = this.Logic.Caps.WithoutKey(); int maxSteps = 99999; if (!InternalOnly) { maxSteps = this.Logic.Random.Next(6, 20); } var closure = LinkedNodeSet.Closure(this.Node, caps, caps, this.InternalOnly, maxSteps); closure.Shuffle(this.Logic.Random); if (!extendingMap) { // just try to place a key foreach (var spot in closure.UnlinkedCollectables()) { if (spot.Static.MustFly) { continue; } if (spot.Node.Room == this.OriginalNode.Room) { // don't be boring! continue; } this.AddReceipt(PlaceCollectableReceipt.Do(spot.Node, spot.Static, LinkedNode.LinkedCollectable.Key, false)); this.OriginalNode.Room.UsedKeyholes.Add(this.KeyholeID); return(true); } // try again for things that we need to bubble back from var newClosure = new LinkedNodeSet(closure); newClosure.Extend(caps, null, true); foreach (var spot in newClosure.UnlinkedCollectables()) { if (spot.Static.MustFly) { continue; } if (spot.Node.Room == this.OriginalNode.Room) { // don't be boring! continue; } this.AddReceipt(PlaceCollectableReceipt.Do(spot.Node, spot.Static, LinkedNode.LinkedCollectable.Key, true)); this.OriginalNode.Room.UsedKeyholes.Add(this.KeyholeID); return(true); } } // see if we can find somewhere to extend the map foreach (var outEdge in closure.UnlinkedEdges()) { if (!this.Logic.Map.HoleFree(outEdge.Node.Room, outEdge.Static.HoleTarget)) { continue; } foreach (var toEdge in this.Logic.AvailableNewEdges(caps, caps, e => e.FromNode.ParentRoom.ReqEnd is Impossible)) { var mapped = ConnectAndMapReceipt.Do(this.Logic, outEdge, toEdge, isBacktrack: true); if (mapped == null) { continue; } this.AddReceipt(mapped); this.AddNextTask(new TaskPathwayPlaceKey(this.Logic, mapped.EntryNode, this.KeyholeID, this.OriginalNode, true, this.Tries)); return(true); } } // try again return(this.Next()); }