/// <summary>
        /// Actually performs room placement in the scratchpad.
        /// </summary>
        /// <param name="roomData"></param>
        /// <param name="mapExit"></param>
        /// <param name="roomExit"></param>
        /// <param name="scope"></param>
        /// <param name="primaryPath"></param>
        private void PlaceRoom(RoomData roomData, Vector2Int mapExit, Vector2Int roomExit, int scope, bool primaryPath)
        {
            int maxDistance = 0;

            roomData.placeCount++;

            for (int x = 0; x < roomData.width; ++x)
            {
                for (int y = 0; y < roomData.height; ++y)
                {
                    int        mapX     = mapExit.x - roomExit.x + x;
                    int        mapY     = mapExit.y - roomExit.y + y;
                    Vector2Int mapPos   = new Vector2Int(mapX, mapY);
                    char       roomTile = roomData.Tile(x, y);

                    // Empty tiles shouldn't be represented in the dictionary
                    if (roomTile == RandomDungeonTileData.EMPTY_TILE)
                    {
                        continue;
                    }

                    // Create a new tile or edit a tile that already exists.
                    // This ensures we don't generate unnecessary garbage.
                    RandomDungeonTileData td = null;
                    if (!mRoomScratch.ContainsKey(mapPos))
                    {
                        td = new RandomDungeonTileData();
                        mRoomScratch.Add(mapPos, td);

                        float distance    = Vector2Int.Distance(roomExit, new Vector2Int(x, y));
                        int   distanceInt = Mathf.CeilToInt(distance);
                        maxDistance = Mathf.Max(maxDistance, distanceInt);

                        td.tileId = mCurrentTileId + distanceInt;

                        td.position = mapPos;
                    }
                    else
                    {
                        td = mRoomScratch[mapPos];
                        td.Clear();
                    }

                    td.tileType = roomTile;
                    td.scope    = scope;
                    td.room     = mCurrentRoomId;

                    if (primaryPath)
                    {
                        td.distanceFromPrimaryPath = 0;
                    }
                    else
                    {
                        // todo bdsowers - this is not perfect
                        // maybe it doesn't need to be perfect, we'll see
                        int mapExitDistance = mRoomScratch[mapExit].distanceFromPrimaryPath;
                        td.distanceFromPrimaryPath = mapExitDistance + 1;
                        maxDistanceFromPrimaryPath = Mathf.Max(maxDistanceFromPrimaryPath, td.distanceFromPrimaryPath);
                    }

                    // Trap
                    // Register it in the map proper as a walkable tile
                    // give a block of trap tiles the same identifier - they form one large trap
                    if (roomTile == RandomDungeonTileData.TRAP_TILE)
                    {
                        int neighborTrap = NeighborTrap(mapX, mapY);
                        int trapId       = neighborTrap;
                        if (trapId == -1)
                        {
                            trapId = mCurrentTrapId;
                            mCurrentTrapId++;
                        }

                        td.tileType = RandomDungeonTileData.WALKABLE_TILE;
                        td.trap     = trapId;
                    }
                    else if (roomTile == RandomDungeonTileData.POSSIBLE_CHEST_TILE)
                    {
                        td.tileType = RandomDungeonTileData.WALKABLE_TILE;
                        td.chest    = 1;
                    }
                    else if (roomTile == RandomDungeonTileData.GUARANTEED_CHEST_TILE)
                    {
                        td.tileType = RandomDungeonTileData.WALKABLE_TILE;
                        td.chest    = 2;
                    }

                    // Add the new exit to the list of available exits
                    if (roomTile == RandomDungeonTileData.EXIT_TILE)
                    {
                        mAvailableExits.Add(mapPos);
                    }
                }
            }

            ++mCurrentRoomId;
            mCurrentTileId += maxDistance + 1;
        }
        /// <summary>
        /// Repeatedly tries to place a room, choosing a random room from the room set and a random exit to place it off of.
        /// This will retry some maximum number of times and quit if it never finds a valid choice.
        /// </summary>
        /// <param name="roomCategory"></param>
        /// <param name="primaryPath"></param>
        /// <param name="scope"></param>
        /// <param name="roomPlacementOptions"></param>
        /// <param name="roomDataOverride"></param>
        /// <returns>The room data for the room that was placed, or null if no room could be placed.</returns>
        private RoomData TryPlaceRoom(string roomCategory, bool primaryPath, int scope, RoomPlacementOptions roomPlacementOptions = RoomPlacementOptions.None, RoomData roomDataOverride = null)
        {
            int maxAttempts = 200;

            while (maxAttempts > 0)
            {
                Vector2Int mapExit    = ChooseMapExitForNextRoom();
                RoomData   randomRoom = roomDataOverride != null ? roomDataOverride : mRoomSet.RandomRoomOfCategory(roomCategory, mPreviousRoomPlaced, mRNG);

                if (randomRoom == null)
                {
                    --maxAttempts;
                    continue;
                }

                Vector2Int roomExit = randomRoom.RandomExit(mRNG);

                if (scope == -1)
                {
                    scope = mRoomScratch[mapExit].scope;
                }

                if (!RoomHasInvalidCollision(randomRoom, mapExit, roomExit, scope, roomPlacementOptions))
                {
                    // If we're constructing a primary path, clear out any exits currently
                    // in our list. Placing a room will add new available exits to build off of.
                    // This also guards against the primary path looping in on itself.
                    if (primaryPath)
                    {
                        mAvailableExits.Clear();

                        mPrimaryPathPositions.Add(new Vector2Int(mapExit.x, mapExit.y));
                    }

                    mAvailableExits.Remove(mapExit);
                    PlaceRoom(randomRoom, mapExit, roomExit, scope, primaryPath);

                    // The probability of picking a room lowers each time it's picked
                    randomRoom.probability /= 2;

                    mPreviousRoomPlaced  = randomRoom;
                    mPreviousMapExitUsed = mapExit;

                    return(randomRoom);
                }

                --maxAttempts;
            }

            return(null);
        }