/// <summary> /// Decides on the bounds for each hall. Also writes to the adjacent rooms' SideReqs and tile permissions /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="vertical"></param> /// <param name="rand">todo: describe rand parameter on ChooseHallBounds</param> public void ChooseHallBounds(IRandom rand, int x, int y, bool vertical) { GridRoomPlan startRoom = this.GetRoomPlan(new Loc(x, y)); GridRoomPlan endRoom = this.GetRoomPlan(new Loc(x + (vertical ? 0 : 1), y + (vertical ? 1 : 0))); GridHallGroup hallGroup = vertical ? this.VHalls[x][y] : this.HHalls[x][y]; if (hallGroup.MainHall != null) { // also sets the sidereqs int tier = vertical ? x : y; Dir4 dir = vertical ? Dir4.Down : Dir4.Right; IntRange startRange = this.GetHallTouchRange(startRoom.RoomGen, dir, tier); IntRange endRange = this.GetHallTouchRange(endRoom.RoomGen, dir.Reverse(), tier); IntRange combinedRange = new IntRange(Math.Min(startRange.Min, endRange.Min), Math.Max(startRange.Max, endRange.Max)); Loc start = startRoom.RoomGen.Draw.End; Loc end = endRoom.RoomGen.Draw.Start; Rect startCell = this.GetCellBounds(startRoom.Bounds); Rect endCell = this.GetCellBounds(endRoom.Bounds); Rect bounds = vertical ? new Rect(combinedRange.Min, start.Y, combinedRange.Length, end.Y - start.Y) : new Rect(start.X, combinedRange.Min, end.X - start.X, combinedRange.Length); // a side constitutes intruding bound when the rectangle moves forward enough to go to the other side // and the other side touched is outside of side B's bound (including borders) // startRange intrudes if startRange goes outside end's tier (borders included) bool startIntrude = !endCell.GetSide(dir.ToAxis()).Contains(startRange); // and end is greater than the edge (borders excluded) bool endTouch = bounds.GetScalar(dir) == endCell.GetScalar(dir.Reverse()); bool endIntrude = !startCell.GetSide(dir.ToAxis()).Contains(endRange); // and end is greater than the edge (borders excluded) bool startTouch = bounds.GetScalar(dir.Reverse()) == startCell.GetScalar(dir); // neither side intrudes bound: use the computed rectangle if ((!startIntrude && !endIntrude) || (endTouch && startTouch) || (!(startIntrude && endIntrude) && ((startIntrude && endTouch) || (endIntrude && startTouch)))) { hallGroup.MainHall.RoomGen.PrepareSize(rand, bounds.Size); hallGroup.MainHall.RoomGen.SetLoc(bounds.Start); } else { int divPoint = startCell.GetScalar(dir) + 1; IntRange startDivRange = startRange; IntRange endDivRange = endRange; if (startIntrude && !endIntrude) { // side A intrudes bound, side B does not: divide A and B; doesn't matter who gets border // side A touches border, side B does not: divide A and B; A gets border // // side A does not, side B touches border: A gets border; don't need B - this cannot happen // side A touches border, side B touches border: A gets border; don't need B - this cannot happen // // in short, divide with start getting the border // startDivRange needs to contain endRange startDivRange = combinedRange; } else if (!startIntrude && endIntrude) { // side A does not, side B intrudes bound: divide A and B; doesn't matter who gets border // side A does not, side B touches border: divide A and B; B gets border // // side A touches border, side B does not: B gets border; don't need A - this cannot happen // side A touches border, side B touches border: B gets border; don't need B - this cannot happen // // in short, divide with end getting the border // endDivRange needs to contain startRange divPoint = startCell.GetScalar(dir); endDivRange = combinedRange; } else { // side A intrudes bound, side B intrudes bound: divide A and B; doesn't matter who gets border if (startTouch) { // side A touches border, side B does not: divide A and B; A gets border } if (endTouch) { // side A does not, side B touches border: divide A and B; B gets border divPoint = startCell.GetScalar(dir); } // side A touches border, side B touches border: A gets border; don't need B - this cannot happen // both sides need to cover the intersection of their cells IntRange interCellSide = IntRange.Intersect(startCell.GetSide(dir.ToAxis()), endCell.GetSide(dir.ToAxis())); startDivRange = IntRange.IncludeRange(startDivRange, interCellSide); endDivRange = IntRange.IncludeRange(endDivRange, interCellSide); } Rect startBox = vertical ? new Rect(startDivRange.Min, start.Y, startDivRange.Length, divPoint - start.Y) : new Rect(start.X, startDivRange.Min, divPoint - start.X, startDivRange.Length); Rect endBox = vertical ? new Rect(endDivRange.Min, divPoint, endDivRange.Length, end.Y - divPoint) : new Rect(divPoint, endDivRange.Min, end.X - divPoint, endDivRange.Length); GridHallPlan originalHall = hallGroup.MainHall; hallGroup.HallParts.Add(new GridHallPlan((IPermissiveRoomGen)originalHall.RoomGen.Copy(), originalHall.Components)); hallGroup.HallParts[0].RoomGen.PrepareSize(rand, startBox.Size); hallGroup.HallParts[0].RoomGen.SetLoc(startBox.Start); hallGroup.HallParts[1].RoomGen.PrepareSize(rand, endBox.Size); hallGroup.HallParts[1].RoomGen.SetLoc(endBox.Start); } } }
/// <summary> /// Gets the minimum range along the side of a room that includes all of its fulfillable borders. /// Special cases arise if the room is multi-cell. /// </summary> /// <param name="room"></param> /// <param name="dir">Direction from room to hall.</param> /// <param name="tier"></param> /// <returns></returns> public virtual IntRange GetHallTouchRange(IRoomGen room, Dir4 dir, int tier) { bool vertical = dir.ToAxis() == Axis4.Vert; // The hall will touch the whole fulfillable side of each room under normal circumstances. // Things get tricky for a target room that occupies more than one cell. // First, try to cover only the part of the target room that's in the cell. int tierStart = vertical ? tier * (this.WidthPerCell + this.CellWall) : tier * (this.HeightPerCell + this.CellWall); int tierLength = vertical ? this.WidthPerCell : this.HeightPerCell; IntRange tierRange = new IntRange(tierStart, tierStart + tierLength); IntRange roomRange = room.Draw.GetSide(dir.ToAxis()); // factor possibletiles into this calculation int borderStart = tierStart - roomRange.Min; if (borderStart < 0) { tierRange.Min -= borderStart; borderStart = 0; } for (int ii = borderStart; ii < roomRange.Length; ii++) { if (room.GetFulfillableBorder(dir, ii)) { break; } else { tierRange.Min += 1; } } int borderEnd = tierStart + tierLength - roomRange.Min; if (borderEnd > roomRange.Length) { tierRange.Max += roomRange.Length - borderEnd; borderEnd = roomRange.Length; } for (int ii = borderEnd - 1; ii >= 0; ii--) { if (room.GetFulfillableBorder(dir, ii)) { break; } else { tierRange.Max -= 1; } } if (tierRange.Max > tierRange.Min) { return(tierRange); } // If that's not possible, then it means that the room must have fulfillable tiles outside of the current bound. // Try to extend the hall until it covers one fulfillable tile of the target room. // Easy method: note that the current tierRange range is covering the zone between the tier and the edge of the room (inverted) // There will be either a workable range at the start or a workable range at the end, never neither. IntRange startRange = new IntRange(tierRange.Max - 1, tierStart + 1); IntRange endRange = new IntRange(tierStart + tierLength - 1, tierRange.Min + 1); bool chooseStart = true; bool chooseEnd = true; // if tierRanges reached the absolute limits of the roomRange, then there is no fulfillable tile on that side if (startRange.Min < roomRange.Min) { chooseStart = false; } else if (endRange.Length > startRange.Length) { chooseEnd = false; } if (endRange.Max > roomRange.Max) { chooseEnd = false; } else if (startRange.Length > endRange.Length) { chooseStart = false; } if (!chooseStart && !chooseEnd) { throw new ArgumentException("PrepareFulfillableBorders did not open at least one open tile for each direction!"); } if (chooseStart) { return(startRange); } return(endRange); }
public override void DrawOnMap(T map) { // check if there are any sides that have intersections such that straight lines are possible var possibleStarts = new Dictionary <Dir4, List <HashSet <int> > >(); foreach (Dir4 dir in DirExt.VALID_DIR4) { int scalarStart = this.Draw.Start.GetScalar(dir.ToAxis().Orth()); // modify the sidereqs: shorten them to accomodate the brush size // if the sidereq cannot be shortened further than a width of 1, just use 1 // the result will have to a degenerate hall, but the important thing is that it will still be functional. int center = this.Brush.Center.GetScalar(dir.ToAxis().Orth()); int width = this.Brush.Size.GetScalar(dir.ToAxis().Orth()); List <IntRange> origReqs = this.RoomSideReqs[dir]; List <IntRange> moddedReqs = new List <IntRange>(); foreach (IntRange range in origReqs) { IntRange newRange = range; newRange.Min = Math.Min(newRange.Min + center, newRange.Max - 1); newRange.Max = Math.Max(newRange.Max + center + 1 - width, newRange.Min + 1); moddedReqs.Add(newRange); } possibleStarts[dir] = this.ChoosePossibleStartRanges(map.Rand, scalarStart, this.BorderToFulfill[dir], moddedReqs); } if ((possibleStarts[Dir4.Down].Count == 0) != (possibleStarts[Dir4.Up].Count == 0) && (possibleStarts[Dir4.Left].Count == 0) != (possibleStarts[Dir4.Right].Count == 0)) { // right angle situation // HallTurnBias holds no sway here // Get the two directions List <Dir4> dirs = new List <Dir4>(); List <int[]> dirStarts = new List <int[]>(); foreach (Dir4 dir in DirExt.VALID_DIR4) { // choose vertical starts if vertical, horiz starts if otherwise if (possibleStarts[dir].Count > 0) { int[] starts = new int[possibleStarts[dir].Count]; // choose their start points at random for (int jj = 0; jj < starts.Length; jj++) { starts[jj] = MathUtils.ChooseFromHash(possibleStarts[dir][jj], map.Rand); } dirs.Add(dir); dirStarts.Add(starts); } } // make the one side extend up to the point where the closest halls would meet if they went straight // make the other side extend up to the point where the farthest halls would meet if they went straight // flip a coin to see which gets which bool extendFar = map.Rand.Next(2) == 0; int minMax1 = GetHallMinMax(dirStarts[1], dirs[0].Reverse(), extendFar); this.DrawCombinedHall(map, dirs[0], minMax1, dirStarts[0]); int minMax2 = GetHallMinMax(dirStarts[0], dirs[1].Reverse(), !extendFar); this.DrawCombinedHall(map, dirs[1], minMax2, dirStarts[1]); // TODO: a better way to resolve right-angle multi-halls // if there are NO opposite sides of sideReqs at all, we have a right angle situation // always connect the closest lines // for any additional lines, randomly select one on a side, and choose, up to the current connection point for the other dimension, where to add this line // which would then increase the connection point for this dimension. // then repeat the process } else { bool up = possibleStarts[Dir4.Up].Count > 0; bool down = possibleStarts[Dir4.Down].Count > 0; bool left = possibleStarts[Dir4.Left].Count > 0; bool right = possibleStarts[Dir4.Right].Count > 0; bool horiz = left && right; bool vert = down && up; if (!horiz && !vert) { // if not a right angle situation, and no opposites here, then there's either 0 or 1 direction that the halls are coming from bool hasHall = false; // iterate through to find the one hall direction, and combine all of the halls (if applicable) foreach (Dir4 dir in DirExt.VALID_DIR4) { // choose vertical starts if vertical, horiz starts if otherwise if (possibleStarts[dir].Count > 0) { IntRange side = this.Draw.GetSide(dir.ToAxis().Orth()); int forwardEnd = map.Rand.Next(side.Min + 1, side.Max - 1); // choose the starts int[] starts = new int[possibleStarts[dir].Count]; for (int jj = 0; jj < possibleStarts[dir].Count; jj++) { hasHall = true; starts[jj] = MathUtils.ChooseFromHash(possibleStarts[dir][jj], map.Rand); } this.DrawCombinedHall(map, dir, forwardEnd, starts); } } // if there is no one hall, throw an error. this room is MEANT to tie together adjacent rooms if (!hasHall) { throw new ArgumentException("No rooms to connect."); } } else { // 2-way (with opposites) to 4-way intersection bool horizTurn = map.Rand.Next(100) < this.HallTurnBias; bool vertTurn = map.Rand.Next(100) < this.HallTurnBias; // force a turn if the sides cannot be connected by a straight line // force a straight line if the sides both land on a single aligned tile HashSet <int> horizCross = GetIntersectedTiles(possibleStarts[Dir4.Left], possibleStarts[Dir4.Right]); if (horizCross.Count == 0) { horizTurn = true; } else if (possibleStarts[Dir4.Left].Count == 1 && possibleStarts[Dir4.Right].Count == 1 && possibleStarts[Dir4.Left][0].Count == 1 && possibleStarts[Dir4.Right][0].Count == 1 && possibleStarts[Dir4.Left][0].SetEquals(possibleStarts[Dir4.Right][0])) { horizTurn = false; } HashSet <int> vertCross = GetIntersectedTiles(possibleStarts[Dir4.Down], possibleStarts[Dir4.Up]); if (vertCross.Count == 0) { vertTurn = true; } else if (possibleStarts[Dir4.Down].Count == 1 && possibleStarts[Dir4.Up].Count == 1 && possibleStarts[Dir4.Down][0].Count == 1 && possibleStarts[Dir4.Up][0].Count == 1 && possibleStarts[Dir4.Down][0].SetEquals(possibleStarts[Dir4.Up][0])) { vertTurn = false; } // in the case where one hall is crossed and another hall isn't, draw the crossed hall first if (horiz && !vert) { this.DrawPrimaryHall(map, horizCross, possibleStarts, false, horizTurn); this.DrawSecondaryHall(map, vertCross, possibleStarts, true, vertTurn); } else if (!horiz && vert) { this.DrawPrimaryHall(map, vertCross, possibleStarts, true, vertTurn); this.DrawSecondaryHall(map, horizCross, possibleStarts, false, horizTurn); } else { // in the case where one hall is straight and another is angled, draw the straight one first // if both are angled, you can draw any first (horiz by default) // if both are straight, you can draw any first (horiz by default) // if horiz is straight and vert is angled, draw horiz first // if vert is straight and horiz is angled, draw vert first if (!vertTurn && horizTurn) { this.DrawPrimaryHall(map, vertCross, possibleStarts, true, vertTurn); this.DrawSecondaryHall(map, horizCross, possibleStarts, false, horizTurn); } else { this.DrawPrimaryHall(map, horizCross, possibleStarts, false, horizTurn); this.DrawSecondaryHall(map, vertCross, possibleStarts, true, vertTurn); } } } } this.SetRoomBorders(map); }
public static void AddLegalPlacements(SpawnList <Loc> possiblePlacements, FloorPlan floorPlan, RoomHallIndex indexFrom, IRoomGen roomFrom, IRoomGen room, Dir4 expandTo) { bool vertical = expandTo.ToAxis() == Axis4.Vert; // this scaling factor equalizes the chances of long sides vs short sides int reverseSideMult = vertical ? roomFrom.Draw.Width * room.Draw.Width : roomFrom.Draw.Height * room.Draw.Height; IntRange side = roomFrom.Draw.GetSide(expandTo.ToAxis()); // subtract the room's original size, not the inflated trialrect size side.Min -= (vertical ? room.Draw.Size.X : room.Draw.Size.Y) - 1; Rect tryRect = room.Draw; // expand in every direction // this will create a one-tile buffer to check for collisions tryRect.Inflate(1, 1); int currentScalar = side.Min; while (currentScalar < side.Max) { // compute the location Loc trialLoc = roomFrom.GetEdgeRectLoc(expandTo, room.Draw.Size, currentScalar); tryRect.Start = trialLoc + new Loc(-1, -1); // check for collisions (not counting the rectangle from) List <RoomHallIndex> collisions = floorPlan.CheckCollision(tryRect); // find the first tile in which no collisions will be found int maxCollideScalar = currentScalar; bool collided = false; foreach (RoomHallIndex collision in collisions) { if (collision != indexFrom) { IRoomGen collideRoom = floorPlan.GetRoomHall(collision).RoomGen; // this is the point at which the new room will barely touch the collided room // the +1 at the end will move it into the safe zone maxCollideScalar = Math.Max(maxCollideScalar, vertical ? collideRoom.Draw.Right : collideRoom.Draw.Bottom); collided = true; } } // if no collisions were hit, do final checks and add the room if (!collided) { Loc locTo = roomFrom.GetEdgeRectLoc(expandTo, room.Draw.Size, currentScalar); // must be within the borders of the floor! if (floorPlan.DrawRect.Contains(new Rect(locTo, room.Draw.Size))) { // check the border match and if add to possible placements int chanceTo = FloorPlan.GetBorderMatch(roomFrom, room, locTo, expandTo); if (chanceTo > 0) { possiblePlacements.Add(locTo, chanceTo * reverseSideMult); } } } currentScalar = maxCollideScalar + 1; } }
void ISpawnRangeDict.SetSpawnRange(object key, IntRange range) { this.SetSpawnRange((TK)key, range); }
public SpawnRange(TV item, int rate, IntRange range) { this.Spawn = item; this.Rate = rate; this.Range = range; }
void ISpawnRangeDict.Add(object key, object spawn, IntRange range, int rate) { this.Add((TK)key, (TV)spawn, range, rate); }
public void SetSpawnRange(TK key, IntRange range) { this.spawns[key] = new SpawnRange(this.spawns[key].Spawn, this.spawns[key].Rate, range); }
void ISpawnRangeList.Insert(int index, object spawn, IntRange range, int rate) { this.Insert(index, (T)spawn, range, rate); }
void ISpawnRangeList.Add(object spawn, IntRange range, int rate) { this.Add((T)spawn, range, rate); }
public void SetSpawnRange(int index, IntRange range) { this.spawns[index] = new SpawnRange(this.spawns[index].Spawn, this.spawns[index].Rate, range); }