Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[Thinking About] New Style Of Random Dungeon Generator With Forced Perspective and "Mini-Map."

September 27 (10 years ago)

Edited September 28 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Just sent over a new token pack I made for publication on the market.

The plan is to develop a new style of random dungeon generator with forced perspective and "mini-map."

(Its not out yet... but look for it soon.)



Its based on the idea that map areas are divided in 19x19 unit grid, with bird's eye view perspective to each area.

Quick walk-through on manual setup of the pack.... should help with thinking through how an API should be coded.

1) Add a new map page.
2) Set the page side to a multiple of 14 (84 for example: 14 x 5 = 84)

3) Make sure grid is enabled and the grid size is 14 units.


Your map should look like the above.

If you do not have Dynamic Lighting you can skip this step.
4) Now go to the Dynamic Lighting Layer. Outline the first column with a rectangle. Repeat for every other column.

Your map should look like the above.

5) Now we do the rows. Outline the first row with a rectangle. Repeat for every other row

Your map should look like the above.

Note: That process can be repeated on the GM layer. Its not necessary, however if you are using fog of war only it can be helpful.

6) Now you can drop in the major feature of your dungeon. Go to the map layer and drop in the Dungeon Depth tiles as you see fit. Flip and spin as needed. Be sure to be using only the “14x14” room and hallway tiles. They should size properly since the page grip is 14 units.

This a very simple and small dungeon.

Note: The “black” off-map area will look like Dynamic Lighting or Fog of War to the players. This reduces the work you have to do for map setup.

7) Now you can add in other “14x14” features like stairs (up or down.)
While still on the map layer, drop in the Dungeon Depth features you want. Flip and spin as needed. They should size properly since the page grid is 14 units.

Now my dungeon has stairs down into this level and stairs even further down.

8) Now change the grid units to 12.
.
This will break the map alignment, however we will be changing it again. This just lets Roll20 do all the working of properly sizing the tiles for us. Now we can bring in “inner” walls.

I brought in a corner wall section and tee wall section I plan to use.

9) Now change the grid units to 1.

And move the “inner” walls into place.They fit centered on the room.

The map is almost complete!

10) Going back the Dynamic Lighting layer (if you have the feature.) Added the few lines need.


11) Turn on Dynamic Lighting and/or Fog of War. Set the background to black. You can make your grid transparent, the map is already perfectly aligned to the to 1x1 units.


12) Go to the token layers and you are finished!


13) Just add players! All done!


As you can see… my brave dwarven cleric’s perspective is forced to whatever room he is in!




~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Now... for the API.

Because this works in a grid....
1) The dynamic lines are largely known.
2) We can use a simple simple recursive algorithm to generate dungeon floor plans.
3) Additional details can be added in easily.

And.... adding a mini-map is simple.
1) Keep track of what areas the players have entered.
2) Reserve an area of the map that is ~10% of size of the total map. Put a visible to all light source there.
3) Since we know what is in each 14x14 grid... its easy to reproduce the same images in the reserved space in a 2x2 grid.



4) Masked unexplored areas with a black 2x2 image above.
5) Remove the mask as the players progress.
6) You could even add in player tracking.

Anyhow that is the idea.

Worth while?
Or any thoughts?


September 28 (10 years ago)
An issue with this style of mapping is measuring while moving... there are extra squares in between rooms that wouldn't normally be there with traditional top down mapping.
September 28 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
That is an issue I had not considered.... but Its also play style a bit too. Maybe? I guess... I know I don't deal with distance unless its close/important.

I really got my head fixed on the grid and black off map coloring to make the dynamic lighting super simple to lay out. And realized that would make a min-map really easy to fake out.

And then I needed tiles... and once I was in "grid" mode of thinking... well, I did forced perspective per grid.

Still the tile pack doesn't matter... as long as it's "grid." I think that is value here if anything because of the min-map.

Something no-geomorphic BUT easy to code and light.
September 29 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter


Setting a map size is easy, based on the above.

Next would be required starting and ending locations.



This could be random or set by placing maker tokens.

The green box would be required to have stairs into the room (down into and/or up out of.)

The red box would be requited to have a side door or at least on set of stairs in the opposite direction to the green.

You could have corner starts or ends too.



Next would be finding the critical path.

What I have right now is the following shape types:


Full Room


Hall


Corner


Quarter

So with those... have to have pathing code to do something like this.



Would need to make sure this path has doors (added later) to complete the path.

After that, you just need to step through the map until all tiles are accounted for.

First pass start in the upper left square A1.... (like in Excel or "battleship")



This square is "hang" without adjacent information... so skip...



As you move through... A1, B1, C1, D1, E1.... A2, B2, C2... etc.) they all have enough information to make a random, choosing not to place a tile is part of the choose (not skipping, but randomly choosing not to place a tile.

Returning to A1, there is now adjacent tile misinformation so something is picked.



Critical doors and stairs and such are placed.



After that is just addling door and any inner walls (if any) for the other rooms and tiles.


Great thing about this... is you could make several level deep dungeon (more than one page... automatically and the stairs would match, map to map to map.)



September 29 (10 years ago)
The Aaron
Roll20 Production Team
API Scripter
I think this would be really cool, even though I don't know that it would show up in any of my games.

However, I could totally see creating a light weight board-game-esque project out of this, ala Mice & Mystics.

+1 =D
October 01 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter


Well.... that is a start..... but a long way to the end.

At map size selection I make an x/y array to keep track of the tiles.

DungeonDepthMapArray[x][y]

All the "cells" have the proper "left" "top" coordinates... but "none" for tile name and "" for the URL

{"name":"none","tileURL":"","xValue":1470,"yValue":4410}

Once filled I update it.

{"name":"Start","tileURL":"https://s3.amazonaws.com/files.d20.io/images/5781339/IoqT_z2YCp7i95bZC@N8UA/thumb.jpg?1412113478","xValue":1470,"yValue":5390}

Will likely have to add "spin", "vFlip" and "hFlip" to this.
October 01 (10 years ago)

Edited October 01 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter


Finding my path.... next review each path tile in order and change to properly rotated tiles.

I need the complete path to do this in an array.... so I can "look" one step ahead.

["Right","Right","Right","Right","Up","Up","Up"]

So from "Start" I move right... going right. ( ["Right","Right","Right","Right","Up","Up","Up"] )

...then I move right ( ["Right","Right","Right","Right","Up","Up","Up"] )

...then I move right ( ["Right","Right","Right","Right","Up","Up","Up"] )

...then I move right ( ["Right","Right","Right","Right","Up","Up","Up"] ).... I would be in the corner "place holder" in the bottom right.

...then I move up ( ["Right","Right","Right","Right","Up","Up","Up"] )

...then I move up ( ["Right","Right","Right","Right","Up","Up","Up"] )... right below the "End"...

Just need random but valid tile swaps as I step along this path.
October 01 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter


Such a pain... have to look forward and backward to make sure "flip" doesn't break the path.
October 02 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Ok.... this was getting away from me.... But I think I have way to simplify this.


Will on work in the corners of the path in some cases.

That is helpful...

You have to "look" forward to spot the "end" and "look" back to avoid:



I was comparing the last tiles spin and flip to the next tile to be placed spin and flip.

That got CRAZY.

But... I just care about the side of the last tile.... which can be though of as being on of these three.



Now the new tile can be randomly spun and flipped... then turned until we have at least 1/2 wall alignment.

October 02 (10 years ago)

Edited October 02 (10 years ago)
I might suggest doing something along the lines of a breadth-first search from the start to the end:

Determine connecting faces (maybe even a bit each for "clockwise of middle" and "counterclockwise of middle" doors, so an L-shaped tile in "L" orientation would have a "counterclockwise of middle" connection upwards and a "clockwise of middle" connection right) of each tile.

Initialize your grid, considering empty squares fully-connected (excluding those connections on the edges of the map). For each square, mark each of its connections as a path to the end.

From here on out, you'll be placing tiles. Each time you place a tile, update the square's connection info and visit each neighboring square. Remove any of the neighbor's connections which don't match the placed tile, and unmark the matching connections as possible paths to the end. If this leaves the neighbor without any connections marked as paths to the end, visit each of the neighbor's neighbors and unmark their matching connections as possible paths to the end; recurse as necessary (note, you only need to recurse if some connection's mark was removed; spaces which were already unconnected need no further work).

Place your start and end tiles, updating the connection info of the relevant grid spaces (at this point you know that there's a possible path from start to end, because every other space is a fully-connected empty space).

Place random tiles adjacent to each connection. Restrict your random options to those tiles with matching connections, and only those tiles which maintain at least one potential path from start to end. Update the connection info for the relevant spaces.
Repeat the previous step until no connections remain.

Possibly something like this (untested, so probably not quite right):
CONNECTION_UL = (1<<0); //numbered around the circle so that geometric rotation can be done by bitwise rotation
CONNECTION_UR = (1<<1);
CONNECTION_RU = (1<<2);
CONNECTION_RD = (1<<3);
CONNECTION_DR = (1<<4);
CONNECTION_DL = (1<<5);
CONNECTION_LD = (1<<6);
CONNECTION_LU = (1<<7);

MASK_U = CONNECTION_UL | CONNECTION_UR;
MASK_R = CONNECTION_RU | CONNECTION_RD;
MASK_D = CONNECTION_DR | CONNECTION_DL;
MASK_L = CONNECTION_LD | CONNECTION_LU;

ALL_DIRECTIONS = [MASK_U, MASK_R, MASK_D, MASK_L];

TILE_BIG_ROOM = 0xff; //all connection bits, setting by number instead of symbolic constant for brevity
TILE_SMALL_ROOM = CONNECTION_UR | CONNECTION RU; //small room in top right corner
TILE_HALL = CONNECTION_LU | CONNECTION_RU; //hall across top

ALL_TILES = [TILE_BIG_ROOM, TILE_SMALL_ROOM, TILE_HALL];

function rotateClockwise(tile){
    return (tile >> 6) | ((tile << 2) & 0xff);
}

function rotateCounterClockwise(tile){
    return (tile >> 2) | ((tile << 6) & 0xff);
}

function flip(tile){
    return (tile >> 4) | ((tile << 4) & 0xff);
}

function directionCount(c){
    return (c & MASK_U ? 1 : 0) + (c & MASK_R ? 1 : 0)  + (c & MASK_D ? 1 : 0) + (c & MASK_L ? 1 : 0);
}

var grid = [];
var squaresToFill = [];
var startPoint = [];
var endPoint = [];

function initGrid(w, h){
    for (var i = 0; i < w; i++){
        grid[i] = [];
        for (var j = 0; j < h; j++){
            connections = 0;
            if (i > 0){ connections |= CONNECTION_LU | CONNECTION_LD; }
            if (i < w - 1){ connections |= CONNECTION_RU | CONNECTION_RD; }
            if (j > 0){ connections |= CONNECTION_UL | CONNECTION_UR; }
            if (j < h - 1){ connections |=CONNECTION_DL | CONNECTION_DR; }
            grid[i][j] = {'connections': connections, 'paths': connections, 'filled': 0};
        }
    }
}

function addSquareToFill(x, y, requiredConnections){
    if ((x < 0) || (x >= grid.length) || (y < 0) || (y >= grid[x].length)){ return; } //square outside of grid
    if (grid[x][y].filled){ return; } //square already filled
    for (var i = 0; i < squaresToFill.length; i++){
        if ((squaresToFill[i][0] == x) && (squaresToFill[i][1] == y)){
            squaresToFill[i][2] |= requiredConnections;
            return;
        }
    }
    squaresToFill.push([x, y, requiredConnections]);
}

function updateUnplaced(x, y, connections, mask){
    if ((x < 0) || (x >= grid.length) || (y < 0) || (y >= grid[x].length)){ return; } //square outside of grid
    if (grid[x][y].filled){ return; } //tile already placed, no more work to do here
    if (!(grid[x][y].paths & mask)){ return; } //square doesn't depend on connections from this direction
    var invMask = ~mask & 0xff;
    grid[x][y].connections &= (connections | invMask);
    grid[x][y].paths &= invMask;
    if (directionCount(grid[x][y].paths) > 1){ return; } //square still connected in multiple other directions
    var flippedConnections = flip(grid[x][y].connections);
    updateUnplaced(x - 1, y, flippedConnections, MASK_R);
    updateUnplaced(x + 1, y, flippedConnections, MASK_L);
    updateUnplaced(x, y - 1, flippedConnections, MASK_D);
    updateUnplaced(x, y + 1, flippedConnections, MASK_U);
}

function placeTile(tile, x, y){
    grid[x][y].filled = 1;
    grid[x][y].connections &= tile;
    grid[x][y].paths = 0;
    var flippedConnections = flip(grid[x][y].connections);
    if (grid[x][y].connections & MASK_L){ addSquareToFill(x - 1, y, flippedConnections & MASK_R); }
    if (grid[x][y].connections & MASK_R){ addSquareToFill(x + 1, y, flippedConnections & MASK_L); }
    if (grid[x][y].connections & MASK_U){ addSquareToFill(x, y - 1, flippedConnections & MASK_D); }
    if (grid[x][y].connections & MASK_D){ addSquareToFill(x, y + 1, flippedConnections & MASK_U); }
    updateUnplaced(x - 1, y, flippedConnections, MASK_R);
    updateUnplaced(x + 1, y, flippedConnections, MASK_L);
    updateUnplaced(x, y - 1, flippedConnections, MASK_D);
    updateUnplaced(x, y + 1, flippedConnections, MASK_U);
}

function placeEndpoints(){
    startPoint = [randomInteger(grid.length) - 1, randomInteger(grid[0].length) - 1];
    endPoint = [randomInteger(grid.length) - 1, randomInteger(grid[0].length) - 1];
    placeTile(TILE_BIG_ROOM, startPoint[0], startPoint[1]);
    placeTile(TILE_BIG_ROOM, endPoint[0], endPoint[1]);
}

function candidateValid(candidate, requiredConnections, outboundPaths){
    var overlap = candidate & requiredConnections
    for (var mask in ALL_DIRECTIONS){
        if ((requiredConnections & mask) && !(overlap & mask)){
            return 0; //candidate doesn't make a required connection
        }
    } //candidate makes all required connections
    if (!outboundPaths){ return 1; } //no outbound paths are necessary, so candidate is valid
    return candidate & outboundPaths; //valid iff candidate matches at least one outboundPath
}

function fillSquare(x, y, requiredConnections){
    var outboundPaths = 0;
    if ((x > 0) && (!grid[x-1][y].filled) && (grid[x-1][y].paths)){ outboundPaths |= MASK_L; }
    if ((x < grid.length - 1) && (!grid[x+1][y].filled) && (grid[x+1][y].paths)){ outboundPaths |= MASK_R; }
    if ((y > 0) && (!grid[x][y-1].filled) && (grid[x][y-1].paths)){ outboundPaths |= MASK_U; }
    if ((y > grid[x].length - 1) && (!grid[x][y+1].filled) && (grid[x][y+1].paths)){ outboundPaths |= MASK_D; }
    for (var i = 0; i < squaresToFill.length; i++){
        if (grid[squaresToFill[i][0]][squaresToFill[i][1]].paths){
            outboundPaths = 0;
            break;
        }
    }
    var candidates = [];
    for (var i = 0; i < ALL_TILES.length; i++){
        var candidate = ALL_TILES[i];
        for (var j = 0; j < 4; j++){
            if (candidateValid(candidate, requiredConnections, outboundPaths)){
                candidates.push(candidate);
            }
            candidate = rotateClockwise(candidate);
        }
    }
    var tileIndex = randomInteger(candidates.length) - 1;
    placeTile(candidates[tileIndex], x, y);
}

function fillTiles(){
    while (squaresToFill.length > 0){
        square = squaresToFill.shift();
        fillSquare(square[0], square[1], square[2]);
    }
}

/////
//
// main entry point: generateMap(width, height)
//
/////
function generateMap(w, h){
    grid = []; //re-initialize globals in case this gets called multiple times
    squaresToFill = [];
    startPoint = [];
    endPoint = [];
    initGrid(w, h);
    placeEndpoints();
    fillTiles();
}

Grr markdown. There went all my formatting...
October 02 (10 years ago)
Really looking forward to the tileset and generator. I loved your other script and am using it a lot.
Just hoping I won't have to set up a 100+ rollable table for it =p
October 02 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
manveti Thanks! That is the sort of gold nugget I was hoping for!


xNidhogg No...this is the cool thing about this one.... the pack has a lot of tiles for people that want to set up a specific map. But... I think this API will need under ten (since doors and other featrures will be overlaid.)


October 02 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter


Is really.... (bold strike through is upper left)

1|1
0|1

"H" flip makes it...

1|1
0|1

"H" flip and "V" flip make it...

0|1
1|1

With the flipping done....

Its just a shift register for spin...0 spin would be.

0111

90 degree spin from that would be...

1011

Top side would be 10
Right side would be 01
Bottom would be 11
Left side would be 11
October 02 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter


Need to add the doors on the path still... but for now working to fill in the map by stepping through and looking for tile placement.
October 03 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter


Still tweaking... pushed out the start and end to avoid large maps with small dungeons.

And have increase the randomization of the critical path.

Just have to work on "L" shapes.. they sometimes don't play well with others.

October 03 (10 years ago)

Edited October 03 (10 years ago)
The Aaron
Roll20 Production Team
API Scripter
I'd probably encode with each tile a number that represents the 8 possible exit locations (or more, if you're counting 3 per side instead of 2 per side), then do some bitwise operations do check validity for adjacent neighbors.

Edit: you could also use that information when dynamically drawing dynamic lighting and doors, since your ties are extremely regular. =D
Edit 2: Really you could describe your tiles with only 4 bits of information, specifying which of the 4 quadrants the tile takes up. your squares would be 0b1111, or 0xf, your horizontal rooms would be 0b1100 and 0b0011, and your L shaped room with the red circle could be 0b1110. You could then define some masks to represent the 4 blocks, and do some ANDing to see if the two tiles are compatible.
October 03 (10 years ago)
The Aaron
Roll20 Production Team
API Scripter
Are each of your tiles going to have specific doors, or will you add doors where desired with a tile over the top?
October 03 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
You do know I have google some of the things you say (i.e. bitwise operations" :P )

For the generator I was planning to drop in the doors, this will keep the rollable table as small as possible, and set up easy.

And yes... the dynamic lighting and doors should be easy... the map is just an "egg carton"

I just drop in eggs and then need to make sure they are turned properly.

My poorly coded working draft.

https://gist.github.com/BaldarSilveraxe/746534f5d9...
October 03 (10 years ago)
The Aaron
Roll20 Production Team
API Scripter
cool! Looking forward to playing with it this weekend. =D
October 03 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter


I think I get the concept... but i have little to no idea how to effectively code with the concept.

Starting at top left this room is "1110"



The tiles are 14 roll20 map units square.
Or 980 px with a 490 center point.

So the array for the entire map is just this.
buildMapArray = function() {
for (i = 1; i < Math.floor((DungeonDepthMapHeight/14) + 1); i++) {
DungeonDepthMapArray[i] = new Array(Math.floor(DungeonDepthMapWidth/14))
for (j = 1; j < Math.floor((DungeonDepthMapWidth/14) + 1); j++) {
DungeonDepthMapArray[i][j] = {};
DungeonDepthMapArray[i][j] = {
name: "none",
tileURL: "",
xValue: Math.floor(((j - 1) * 980) + 490),
yValue: Math.floor(((i - 1) * 980) + 490),
spin: 0,
flipv: false,
fliph: false,
walls: "9999",
};
};
};
};

This should care for all tile placement.

Getting the critical path works well enough..

As I do the above... I get a path array

placePathTile = function(xCurrent,yCurrent,xWasAt,yWasAt) {
DungeonDepthMapArray[yCurrent][xCurrent].name = "Holder";
DungeonDepthMapArray[yCurrent][xCurrent].tileURL = findObjs({ _rollabletableid: DungeonDepthTableId, _type: "tableitem", name: "Holder"})[0].get("avatar").replace("med.jpg?","thumb.jpg?");
DungeonDepthMapArray[yCurrent][xCurrent].spin = 0;
var stepDirection;
if(xCurrent < xWasAt){stepDirection = "Left";};
if(xCurrent > xWasAt){stepDirection = "Right";};
if(yCurrent < yWasAt){stepDirection = "Up";};
if(yCurrent > yWasAt){stepDirection = "Down";};
pathToTake.push({direction: stepDirection, xPath: xCurrent, yPath: yCurrent})
};

But bitwise placement... along that path... for just three tiles?



Give me fits.

:-/

October 03 (10 years ago)
The logic in the fillSquare and candidateValid functions in what I posted above goes something like this:
determine possible/required connections in each direction:
  rotate each adjacent tile 180 degrees, bitwise AND with the appropriate side mask
  no connection required for empty adjacent squares

for each tile type {
  for each orientation {
    make sure that each side matches up with a connection if necessary
    make sure there's at least one outgoing connection towards the next tile in your path
    if both of the above are satisfied, add this orientation of this tile type to the list of candidates
  }
}

randomly select a tile from the list of candidates
Rotating a tile can be accomplished by bitwise rotation of its binary representation.
A tile matches the necessary connections if (tile & requirements & side_mask) is nonzero.
A tile has an outgoing connection on a side if (tile & side_mask) is nonzero.
Using your representation above, TOP_MASK = 0xc, RIGHT_MASK = 0x6, BOTTOM_MASK = 0x3, and LEFT_MASK = 0x9 (note that you can also get this by starting with one of the masks and getting the others by rotation).
October 03 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter

Working through your code, however I honest don't understand all the syntax.

For example...

(tile >> 6) | ((tile << 2) & 0xff);

I have no clue what that is :P

0xff is hex for a binary value?

I was first introduced to bitwise in this thread :P
October 04 (10 years ago)

Edited October 10 (10 years ago)
tile >> 6 means shift tile 6 bits to the right (it takes the top two bits and puts them into the bottom two bits). tile << 2 shifts tile 2 bits to the left. 0xff is eight 1 bits: it keeps the left shift (which would otherwise put our top two bits into the ninth and tenth bits) restricted to just the bottom eight bits that we care about.

I did some reworking and made a functioning prototype. It works as a standalone web page, and expects the following images to be in its directory (where N ranges from 0 to 3, rotating the room 90 degrees clockwise each step):
bigN.png: copies of the big room
smallN.png: the small room, starting in the top right (so small0 is top right, small1 is bottom right, etc.)
hallN.png: the hallway, starting across the top
cornerN.png: the L-shaped room, starting in the top and right (so corner2 is the one oriented like an L)

Edit: I'm tired of that huge block of irrelevant code taking up screen space; I moved it to pastebin: http://pastebin.com/xipU0k23
October 04 (10 years ago)

Edited October 04 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Many thanks!

And check your PMs.

I need all the help I can get... because my reach is once again exceeding my grasp.


Edit; Got your HTML working... That is really slick!

October 05 (10 years ago)
Here's a prototype that works ingame: https://github.com/manveti/dungen/blob/master/dung...
Type "!dungen [width] [height] [optional: tile size in pixels]" to generate a [width]x[height] map at the top left of the page with the player flag. There isn't any real error-checking, so make sure your map is big enough (and tile size should probably be a multiple of 70).

I'm not sure why, but the placed tiles have a really big clickable area (way beyond the extents of the tokens themselves). Still trying to figure that one out.
October 05 (10 years ago)

Edited October 05 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter

That is awesome!



LOL.... In 1/8th of the lines of code it was taking me to even TRY to do this.


October 05 (10 years ago)
Nice work!

Unfortunately I run into trouble every time I try and run the script.

I get the following error: 'Sandbox closed due to error.'
October 06 (10 years ago)
I'll have to put in some error checking when I get a chance, but my guess would be that it's not letting you use the image URLs from my library. You can test for that either by uploading your own images and substituting in those URLs, or by adding an error message if we fail to create the tile: immediately after the 'var tile = createObj(...)' statement, add 'if(!tile){log("Failed to create tile with URL="+tileUrl+", size="+tileSize+", rotation=" (map[i][j].orientation * 90) + ", page ID=" + Campaign().get("playerpageid"));}'; if you see the message in the log, verify that you can see the image in a browser, that size is a multiple of 70, that rotation is 0, 90, 180, or 270, and that page ID is nonempty.

If that's not it, I could also see problems happening if the start and end are the same (another error check not present in the prototype). This is less likely to happen the larger your map; if it happens a few times in a row using, say, a 5x5 map, either this isn't it or you're REALLY unlucky. This can be tested by adding 'log("Start point: "+startPoint[0]+","+startPoint[1]);' after the 'var startPoint = ...' line and the same thing (but replacing "start" with "end") after the 'var endPoint = ...' line.
October 08 (10 years ago)

Edited October 08 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Would it be best if the tiles had the doors?

The issue I see with that is in greatly increases the numbers of map tile (they are all made but it would be such a pain to load them into a table or hard code the URLs... or if someone wanted to make tile just for this... they would have t make a lot more tiles.)

At any wall touch you would want one door... easier to delete doors than add doors when polishing up the map is also the thinking.
October 09 (10 years ago)
The doors are probably better as overlays (I didn't check, but I assume that door graphic you have is set up so that it can be flipped and/or rotated to work in any of the 8 possible door slots). We already know which potential doors are touching, so we could keep track of those spots (or possibly randomly select one if neighboring tiles touch on both spots) and put doors there at the end.
October 09 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
The door I set over was 2x5 (Roll20 grid units) jpg.

This one is a full tile png
https://drive.google.com/file/d/0B7d47ZZymuXPTTd2b...

October 10 (10 years ago)
I realized another possible source of troubles: my prototype wasn't packaged in any way, so it was pretty likely to collide with other API scripts. I fixed that up and added some error-checking (and retry logic for placing the end, so start and end are guaranteed to be different). I also added the ability to toggle sparse map generation: the original would stop placing tiles once an area was cut off from the exit and couldn't form an independent path there. With sparse generation turned off, we'll continue to place tiles as long as they can connect somewhere, so generated maps will have fewer holes.

I'll look into placing doors over the weekend, and maybe add the ability to configure the images (select a token and run a command to use it as a particular tile) so it won't have to ship with magic paths.
October 10 (10 years ago)

Edited October 10 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
DunGen: !dungen WIDTH HEIGHT [TILE SIZE]
Generate a WIDTHxHEIGHT dungeon from square tiles of the specified size (in pixels)
TILE SIZE defaults to 980; it should generally be a multiple of 70
!dungen sparse [on|off]
Display or set status of sparse map generation
If sparse is on, no tiles will be added to paths which cannot connect to the exit
DunGen: Sparse Map Generation: on
Sparse Map Generation: off
Works great... The sparse option is really nice.

I did run into one error (super trivial.) Line 278 in the showHelp function.. typo.... "sendChat" not "sentChat."

This is really good code, gradually working through it were I can understand the syntax.

Having this simple dynamic line layout and "egg carton" room structure makes it so easy to "spice" up the dungeon.

Some simple "floor" overlays dropped in below.

October 11 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
function basicDynamicLighting(width,height){
for (var i = DunGen.TILE_SIZE; i < width*DunGen.TILE_SIZE; i = i + DunGen.TILE_SIZE){
var pathTop = 0;
var pathLeft = i;
var pathPath = "[[\"M\",0,0],[\"L\",0," + height*DunGen.TILE_SIZE + "]]";
createPath(pathTop,pathLeft,pathPath)
}
for (var i = DunGen.TILE_SIZE; i < height*DunGen.TILE_SIZE; i = i + DunGen.TILE_SIZE){
var pathTop = i;
var pathLeft = 0;
var pathPath = "[[\"M\",0,0],[\"L\"," + width*DunGen.TILE_SIZE + ",0]]";
createPath(pathTop,pathLeft,pathPath)
}
}
function createPath(pathTop,pathLeft,pathPath){
createObj("path",{
fill: "#00ff00",
stroke: "#00ff00",
stroke_width: 5,
width: 0,
height: 0,
top: pathTop,
left: pathLeft,
scaleX:1,
scaleY:1,
layer: "walls",
_path: pathPath,
pageid: Campaign().get("playerpageid")
});
};


Played around with the above... adds the basic Dynamic Lighting paths.
October 12 (10 years ago)
I've got the doors working with the full-size overlay, but it's kind of a pain to do any manual editing with them. I think I'll probably modify it to use the door-only image (and just add a warning that if tile size is not a multiple of 14 then rounding in the math for placing the doors might mean that some of them end up off by a pixel).

I haven't looked into the performance implications, but it might be a good idea to do the lines for dynamic lighting on a per-tile basis so people can open up sight lines through doors (or even "perforate" the sides, splitting each into 5 lines of lengths 3, 2, 4, 2, 3; then people could just delete the 2's for doors).
October 12 (10 years ago)

Edited October 12 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter


After looking at this and playing around with it....

Aren't the doors really just a few simple 10 x 10 tiles?

Each tile + intersection would get an appropriate "DoorS tile"?

This will make selecting them and editing them.... Because you the "Room Tile" and the "Doors Tiles" are accessible to click on!

At those meet points... there can only be the following.


And... if the room tile are in a roll-able table.... and the doors are in a roll-able table... you can leverage the Roll20 "multi-sided" controls.



I looked at the Dynamic Light... I think you are right... That much I should be able to fix on my own :P
October 13 (10 years ago)
Smaller, door-only door overlays are working now, as are dynamic lighting lines. I've got it making yellow lines for walls and green for doors. It looks a little strange because of the perspective (you have to pick a height at which to draw the lines; I chose to do it at floor level, but that makes the lines look wrong when you look at the dynamic lighting layer), but it's pretty easy do delete out the green lines where you want open doors.
October 13 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
Looks good and works great!

The advantage/disadvantage of thick walls....

Might be worth it to have "super thick" Dynamic Lines..... lots of rooms on the tiles.. and from either side gives that "doorway" feel.

But its just a nuance.. Great script!


October 13 (10 years ago)
Oh, that's a good idea. Maybe something like 1/14 of the tile size, with a minimum of maybe two or three pixels just to be sure there's something reasonably visible even when zoomed out a bit (although really, if you're generating a map with tiles less than 14 pixels on a side you probably deserve broken dynamic lighting lines).
October 14 (10 years ago)
Wider lines are in now. They're still based on ground-level offsets, just because that's easiest to get perfect. That effective height can be changed by reducing DOOR_OFFSET and increasing DOOR_WIDTH (by less than what DOOR_OFFSET was reduced by). It looks like DOOR_OFFSET should be reduced by about one per 2% of the way up to ceiling height, and DOOR_WIDTH should be increased by about half that.
October 17 (10 years ago)
So I just went to use this in my Friday night campaign and realized that the width of the dynamic lighting lines doesn't seem to have any effect at all: the light/LoS blocking only takes place along the center of the line. I'll have to replace the lines with rectangles when I get a chance.
October 17 (10 years ago)

Edited October 18 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
I think you are right, DL needs a rectangle, seems to ignore line thickness.

I finally got the pack on the market: https://marketplace.roll20.net/browse/set/384/dung...

(The delay was entirely my fault.)
October 17 (10 years ago)
Updated to use rectangles. I'll replace the start/end tiles with full-room tiles with stairs down/up overlays and remove the image URLs this weekend, and remove the images I used for testing from my library so the URLs currently in the script stop working once I'm not using an auto-generated dungeon in my Friday night campaign. Hopefully I'll get a chance to add some setup commands (list tile URLs, use image URL from selected token for specified tile, etc.) too, but if not I'll at least put some comments in the script explaining how to set up the necessary tile URLs.
October 18 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter
I think its great. And should work with any "box" shaped token set. Really great script. No way I could have coded that. many thanks for all your effort.
October 19 (10 years ago)
The script now allows/requires the user to set tile/overlay images, and I've removed the images whose URLs were in previous versions of the script from my library so it doesn't act as a back door for free tile images. At some point I'll build in a way to set some variables in the script to hold default tile URLs, so people can modify their copy of the script to point at their images and not have to set them for each campaign. It also now uses stairs overlays instead of those special "start" and "end" tiles.
October 22 (10 years ago)

Edited October 22 (10 years ago)
I now it's probably a lot to ask, but can we get a wiki page on how to set everything up? Especially now that your pack was released Stephen (will buy as soon as script is done). Or is the script not at a public-releasable stage currently?
October 23 (10 years ago)

Edited October 23 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter

Hope this helps:

1) Basic tiles needed can be found here: https://drive.google.com/file/d/0B7d47ZZymuXPQjRGc...

2) I started a blank campaign and use the Transmogrifier to placed finished maps in player campaigns.

3) Set up 8 macros in the macro bar:



  • createMap = !dungen 7 7
  • BigRoom = !dungen setimage BigRoom
  • Corner = !dungen setimage Corner
  • Door = !dungen setimage Door
  • Hall = !dungen setimage Hall
  • SmallRoom = !dungen setimage SmallRoom
  • StairsDown= !dungen setimage StairsDown
  • StairsUp= !dungen setimage StairsUp

4) The campaign has two maps:



5) The Tiles Map is used to setup the tiles


Map size is 48w x 12h and the Grid size = 12 Units
Place the basic tiles (don't worry about the "stretched" look of the doors.)

6) One by one select each tile and click the appropriate macro.
  • Example Select "BigRoom" and click the BigRoom macro
  • Chat will output something like:


7) The DungeonDepth map should be 98w by 98h for a 7 tile by 7 tile map ("!dungen 7 7)



8) Click the "createMap" macro. You should get a random map with Dynamic Lighting.



(Note in some rare cases the "z" position of overlays is wrong, you may have to push a tile or two to the back.)


The Code can be found here:


manveti said:
Here's a prototype that works ingame: https://github.com/manveti/dungen/blob/master/dung...
Type "!dungen [width] [height] [optional: tile size in pixels]" to generate a [width]x[height] map at the top left of the page with the player flag. There isn't any real error-checking, so make sure your map is big enough (and tile size should probably be a multiple of 70).




October 25 (10 years ago)
Sick stuff, bought the pack to support you. Thanks for the explanation.
October 25 (10 years ago)
Stephen S.
Pro
Marketplace Creator
Sheet Author
API Scripter

Thanks. manveti saved me from myself, His code is really slick!


I am still toying around with an idea for a "mini" map. (Seen below and to the right.)



I am just reading through the map layer looking for "room" tiles and creating smaller version of them to the right.

The idea would be to save the players from zooming and scrolling... but to really do that, they would need to be able call the mini-map to the chat pane or somewhere besides the map... I think.

Best thing would be to get the URLs in an array as the map was made i.e. maptile[x][y] = tile.url

Then another array that keeps track of the explored areas i.e. if(explored[x][y] == false){ //use all black tile } else { //use maptile[x][y] = tile.url };

Then players could call up the miniMap... just not sure where to put it or if its worth it.

October 28 (10 years ago)

Edited October 28 (10 years ago)
You could wire into the map-drawing code, right above or below the first call to createObj. You could even create two new tokens for each room: one for the room in the mini-map, and one a black overlay that gets deleted when the room is explored.

Incidentally, I just made a couple of minor user-friendliness improvements:
  • Added a "tiles" subcommand which gives a name and description for each tile so tile setup isn't so full of guess-and-check magic
  • Integrated with my new Shell module (https://github.com/manveti/roll20/blob/master/shell.js), which (among other things) adds a general "!help" command to give a brief description of each command registered with it. It also has some access-control features which I'll leverage later to make the "!dungen" command GM-only (edit: I just remembered that Shell commands are GM-only by default, so that's already done).
Adding an easy way to paste the image URLs into some constants near the top of the file so you don't have to redo the tile setup each campaign is still in the pipeline.
October 29 (10 years ago)
I added a structure near the top of the file with a constant for each necessary image, which will act as a default URL if the relevant URL isn't already set in the campaign. I also modified the script to generate dynamic light lines even where there aren't actually tiles, so that we don't get the diagonal sight lines across empty tiles like you see in the instruction image a few posts up.