/// <summary> /// Finds appropriate place to use as unlock place for given locked place. /// </summary> /// <param name="lockedPlace"></param> /// <returns>Index of unlock place in Places list</returns> public int GetUnlock(PuzzlePlace lockedPlace) { var lockedPlaceIndex = lockedPlace.PlaceIndex; // Get index of room behind lockedPlace door. lockedPlaceIndex = this.Places[lockedPlaceIndex].Room.Neighbor[lockedPlace.DoorDirection].RoomIndex; if (lockedPlaceIndex == 0 || lockedPlaceIndex == -1) // should be last room in section. { lockedPlaceIndex = this.Places.Count; } List <int> possiblePlacesOnPath = new List <int>(); List <int> possiblePlacesNotOnPath = new List <int>(); var deadEnd = -1; var deadEndDepth = 0; RoomTrait room; // places before lockedPlaceIndex are always behind it. for (var i = 0; i < lockedPlaceIndex; ++i) { room = this.Places[i].Room; if (!this.Places[i].IsUsed && !room.isLocked && room.RoomType != RoomType.Start) { // Check is this place have locked doors. // Locked places tend to not reserve themselves, so they could share place with some puzzle, like another locked place. // But they couldn't be shared with unlock places, because they have to control all doors. var haveLockedDoors = false; for (var j = 0; j < 4; ++j) { if (room.DoorType[j] == (int)DungeonBlockType.DoorWithLock && room.Links[j] == LinkType.To) { haveLockedDoors = true; break; } } if (!haveLockedDoors) { var emptyNeighborCount = 0; for (var dir = 0; dir < 4; ++dir) { if (room.IsLinked(dir) && !room.Neighbor[dir].isReserved) { emptyNeighborCount++; } } if (emptyNeighborCount < 2) { if (deadEndDepth < this.Places[i].Depth) { deadEnd = i; } } if (room.isOnPath) { possiblePlacesOnPath.Add(i); } else { possiblePlacesNotOnPath.Add(i); } } } } // Chance to not use deepest corner for unlock place. if (_rng.GetUInt32(100) < 40) { deadEnd = -1; } if (possiblePlacesOnPath.Count == 0 && possiblePlacesNotOnPath.Count == 0) { // Convert locked place room back to alley if there are no more locked doors. room = this.Places[lockedPlace.PlaceIndex].Room; room.SetDoorType(lockedPlace.DoorDirection, (int)DungeonBlockType.Alley); room.SetPuzzleDoor(null, lockedPlace.DoorDirection); var isLockedRoom = room.DoorType.Any(x => (x == (int)DungeonBlockType.DoorWithLock || x == (int)DungeonBlockType.BossDoor)); if (!isLockedRoom) { room.isLocked = false; room.RoomType = RoomType.Alley; } // Return locked place door to list on available doors. var lockedDoorCandidate = new LockedDoorCandidateNode(room, lockedPlace.DoorDirection, room.RoomIndex); _lockedDoorCandidates.AddFirst(lockedDoorCandidate); throw new PuzzleException("Out of unlock places."); } var possiblePlaces = (possiblePlacesNotOnPath.Count > 0 ? possiblePlacesNotOnPath : possiblePlacesOnPath); var placeIndex = deadEnd != -1 ? deadEnd : possiblePlaces[(int)_rng.GetUInt32((uint)possiblePlaces.Count)]; // Walk down from current place to our path and add new possible doors to this._lockedDoorCandidates room = this.Places[placeIndex].Room; while (room.RoomType != RoomType.Start) { if (room.isOnPath) { break; } var dir = room.GetIncomingDirection(); room.isOnPath = true; room = room.Neighbor[dir]; // skip reserved doors //if (room.ReservedDoor[Direction.GetOppositeDirection(dir)]) continue; // skip reserved places if (room.isReserved) { continue; } var lockedDoorCandidate = new LockedDoorCandidateNode(room, Direction.GetOppositeDirection(dir), room.RoomIndex); _lockedDoorCandidates.AddFirst(lockedDoorCandidate); } return(placeIndex); }