├── web ├── img │ ├── bed1.png │ ├── bed2.png │ ├── dirt.png │ ├── door.png │ ├── orb.png │ ├── oven.png │ ├── tree.png │ ├── wall.png │ ├── well.png │ ├── barrel.png │ ├── cTable1.png │ ├── cTable2.png │ ├── cTable3.png │ ├── cTable4.png │ ├── chest.png │ ├── clock.png │ ├── column.png │ ├── floor.png │ ├── floor2.png │ ├── grass1.png │ ├── grass2.png │ ├── grill.png │ ├── lChair.png │ ├── lTable.png │ ├── lTorch.png │ ├── locker.png │ ├── mirror.png │ ├── nChair.png │ ├── piano.png │ ├── plant.png │ ├── rChair.png │ ├── rTable.png │ ├── rTorch.png │ ├── sChair.png │ ├── sPiano.png │ ├── shelf.png │ ├── shelf2.png │ ├── throne.png │ ├── water.png │ ├── fakeWall.png │ ├── fireplace.png │ ├── firewall.png │ ├── fountain.png │ ├── jarTable.png │ ├── library1.png │ ├── library2.png │ ├── crossWindow.png │ ├── magicCarpet.png │ ├── smallTable.png │ └── wineBarrel.png ├── index.html └── generator.js ├── src ├── ui │ ├── WebAdapter.js │ └── CanvasRenderer.js ├── Random.js ├── TerrainGenerator.js ├── Arrays.js ├── MapGenerator.js ├── Cells.js ├── CastleStructureGenerator.js ├── RoomBuilder.js └── RoomsGenerator.js └── README.MD /web/img/bed1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/bed1.png -------------------------------------------------------------------------------- /web/img/bed2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/bed2.png -------------------------------------------------------------------------------- /web/img/dirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/dirt.png -------------------------------------------------------------------------------- /web/img/door.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/door.png -------------------------------------------------------------------------------- /web/img/orb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/orb.png -------------------------------------------------------------------------------- /web/img/oven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/oven.png -------------------------------------------------------------------------------- /web/img/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/tree.png -------------------------------------------------------------------------------- /web/img/wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/wall.png -------------------------------------------------------------------------------- /web/img/well.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/well.png -------------------------------------------------------------------------------- /web/img/barrel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/barrel.png -------------------------------------------------------------------------------- /web/img/cTable1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/cTable1.png -------------------------------------------------------------------------------- /web/img/cTable2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/cTable2.png -------------------------------------------------------------------------------- /web/img/cTable3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/cTable3.png -------------------------------------------------------------------------------- /web/img/cTable4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/cTable4.png -------------------------------------------------------------------------------- /web/img/chest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/chest.png -------------------------------------------------------------------------------- /web/img/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/clock.png -------------------------------------------------------------------------------- /web/img/column.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/column.png -------------------------------------------------------------------------------- /web/img/floor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/floor.png -------------------------------------------------------------------------------- /web/img/floor2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/floor2.png -------------------------------------------------------------------------------- /web/img/grass1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/grass1.png -------------------------------------------------------------------------------- /web/img/grass2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/grass2.png -------------------------------------------------------------------------------- /web/img/grill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/grill.png -------------------------------------------------------------------------------- /web/img/lChair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/lChair.png -------------------------------------------------------------------------------- /web/img/lTable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/lTable.png -------------------------------------------------------------------------------- /web/img/lTorch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/lTorch.png -------------------------------------------------------------------------------- /web/img/locker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/locker.png -------------------------------------------------------------------------------- /web/img/mirror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/mirror.png -------------------------------------------------------------------------------- /web/img/nChair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/nChair.png -------------------------------------------------------------------------------- /web/img/piano.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/piano.png -------------------------------------------------------------------------------- /web/img/plant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/plant.png -------------------------------------------------------------------------------- /web/img/rChair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/rChair.png -------------------------------------------------------------------------------- /web/img/rTable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/rTable.png -------------------------------------------------------------------------------- /web/img/rTorch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/rTorch.png -------------------------------------------------------------------------------- /web/img/sChair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/sChair.png -------------------------------------------------------------------------------- /web/img/sPiano.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/sPiano.png -------------------------------------------------------------------------------- /web/img/shelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/shelf.png -------------------------------------------------------------------------------- /web/img/shelf2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/shelf2.png -------------------------------------------------------------------------------- /web/img/throne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/throne.png -------------------------------------------------------------------------------- /web/img/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/water.png -------------------------------------------------------------------------------- /web/img/fakeWall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/fakeWall.png -------------------------------------------------------------------------------- /web/img/fireplace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/fireplace.png -------------------------------------------------------------------------------- /web/img/firewall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/firewall.png -------------------------------------------------------------------------------- /web/img/fountain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/fountain.png -------------------------------------------------------------------------------- /web/img/jarTable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/jarTable.png -------------------------------------------------------------------------------- /web/img/library1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/library1.png -------------------------------------------------------------------------------- /web/img/library2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/library2.png -------------------------------------------------------------------------------- /web/img/crossWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/crossWindow.png -------------------------------------------------------------------------------- /web/img/magicCarpet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/magicCarpet.png -------------------------------------------------------------------------------- /web/img/smallTable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/smallTable.png -------------------------------------------------------------------------------- /web/img/wineBarrel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashman/ultimaCastleGen/HEAD/web/img/wineBarrel.png -------------------------------------------------------------------------------- /src/ui/WebAdapter.js: -------------------------------------------------------------------------------- 1 | window.MapGenerator = require('../MapGenerator'); 2 | window.CanvasRenderer = require('./CanvasRenderer'); -------------------------------------------------------------------------------- /src/Random.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rand: function(low, hi){ 3 | return Math.floor(Math.random() * (hi - low + 1))+low; 4 | }, 5 | randomElementOf: function(array){ 6 | return array[Math.floor(Math.random()*array.length)]; 7 | }, 8 | chance: function(chance){ 9 | return this.rand(0,100) <= chance; 10 | } 11 | } -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | Generates single-floor layouts of castles inspired by Ultima V. 2 | 3 | - Play online: https://slash.itch.io/ultima-castle-generator 4 | - Read more about this generator in my blog: https://blog.slashie.net/category/ultima-castle-map-generator/ 5 | - The non-itch version of the generator outputs intermediate steps of the generator: https://slashie.net/ultimacastlegen/ 6 | 7 | # Building 8 | 9 | browserify -e .\src\ui\WebAdapter.js -o .\web\generator.js -------------------------------------------------------------------------------- /src/TerrainGenerator.js: -------------------------------------------------------------------------------- 1 | var Random = require('./Random'); 2 | var Cells = require('./Cells'); 3 | 4 | function TerrainGenerator(){ 5 | 6 | }; 7 | 8 | TerrainGenerator.prototype = { 9 | generateTerrain: function(generationParams){ 10 | var map = []; 11 | for (var x = 0; x < generationParams.width; x++){ 12 | map[x] = []; 13 | for (var y = 0; y < generationParams.height; y++){ 14 | if (Random.chance(80)) 15 | map[x][y] = Cells.GRASS_1; 16 | else if (Random.chance(80)) 17 | map[x][y] = Cells.GRASS_2; 18 | else 19 | map[x][y] = Cells.TREE; 20 | } 21 | } 22 | return map; 23 | } 24 | } 25 | 26 | module.exports = TerrainGenerator; -------------------------------------------------------------------------------- /src/Arrays.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | shuffle: function(array) { 3 | var counter = array.length; 4 | 5 | // While there are elements in the array 6 | while (counter > 0) { 7 | // Pick a random index 8 | var index = Math.floor(Math.random() * counter); 9 | 10 | // Decrease counter by 1 11 | counter--; 12 | 13 | // And swap the last element with it 14 | var temp = array[counter]; 15 | array[counter] = array[index]; 16 | array[index] = temp; 17 | } 18 | 19 | return array; 20 | }, 21 | removeObject: function(array, object){ 22 | for(var i = 0; i < array.length; i++) { 23 | if(array[i] === object) { 24 | array.splice(i, 1); 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/MapGenerator.js: -------------------------------------------------------------------------------- 1 | var CastleStructureGenerator = require('./CastleStructureGenerator'); 2 | var RoomsGenerator = require('./RoomsGenerator'); 3 | var RoomBuilder = require('./RoomBuilder'); 4 | var TerrainGenerator = require('./TerrainGenerator'); 5 | 6 | function MapGenerator(){ 7 | 8 | }; 9 | 10 | MapGenerator.prototype = { 11 | generateMap: function(generationParams){ 12 | this.generationParams = generationParams; 13 | this.castleStructureGenerator = new CastleStructureGenerator(); 14 | this.roomsGenerator = new RoomsGenerator(); 15 | this.terrainGenerator = new TerrainGenerator(); 16 | this.roomBuilder = new RoomBuilder(); 17 | 18 | var castle = {}; 19 | while (true){ 20 | castle.structure = this.castleStructureGenerator.generateMap(generationParams); 21 | castle.rooms = this.roomsGenerator.generateMap(castle.structure, generationParams); 22 | if (castle.rooms != false) 23 | break; 24 | } 25 | 26 | castle.map = this.terrainGenerator.generateTerrain(generationParams); 27 | this.roomBuilder.buildRooms(castle.map, castle.rooms); 28 | return castle; 29 | } 30 | } 31 | 32 | module.exports = MapGenerator; -------------------------------------------------------------------------------- /src/Cells.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | WALL: 'wall', 3 | FLOOR: 'floor', 4 | FLOOR_2: 'floor2', 5 | GRASS_1: 'grass1', 6 | GRASS_2: 'grass2', 7 | TREE: 'tree', 8 | DOOR: 'door', 9 | CROSS_WINDOW: 'crossWindow', 10 | FOUNTAIN: 'fountain', 11 | WATER: 'water', 12 | WELL: 'well', 13 | DIRT: 'dirt', 14 | FIREPLACE: 'fireplace', 15 | COLUMN: 'column', 16 | R_TORCH: 'rTorch', 17 | L_TORCH: 'lTorch', 18 | THRONE: 'throne', 19 | ORB: 'orb', 20 | SMALL_TABLE: 'smallTable', 21 | MAGIC_CARPET: 'magicCarpet', 22 | BED_1: 'bed1', 23 | BED_2: 'bed2', 24 | LOCKER: 'locker', 25 | JAR_TABLE: 'jarTable', 26 | BARREL: 'barrel', 27 | PLANT: 'plant', 28 | MIRROR: 'mirror', 29 | SHELF: 'shelf', 30 | PIANO: 'piano', 31 | R_CHAIR: 'rChair', 32 | L_CHAIR: 'lChair', 33 | S_CHAIR: 'sChair', 34 | N_CHAIR: 'nChair', 35 | S_PIANO: 'sPiano', 36 | CLOCK: 'clock', 37 | SHELF_2: 'shelf2', 38 | R_TABLE: 'rTable', 39 | L_TABLE: 'lTable', 40 | C_TABLE_1: 'cTable1', 41 | C_TABLE_2: 'cTable2', 42 | C_TABLE_3: 'cTable3', 43 | C_TABLE_4: 'cTable4', 44 | WALL_FIREPLACE: 'firewall', 45 | GRILL: 'grill', 46 | OVEN: 'oven', 47 | WINE_BARREL: 'wineBarrel', 48 | LIBRARY_1: 'library1', 49 | LIBRARY_2: 'library2', 50 | CHEST: 'chest', 51 | FAKE_WALL: 'fakeWall' 52 | }; -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ultima Castle Generator by Slashie 4 | 5 | 20 | 21 |

Ultima Castle Generator

22 |

by Slashie

23 |

Read more about this generator in my blog

24 |

25 |

26 | 27 | 28 |

29 |

30 | 31 |

32 | 33 | 36 | -------------------------------------------------------------------------------- /src/ui/CanvasRenderer.js: -------------------------------------------------------------------------------- 1 | var Cells = require('../Cells'); 2 | 3 | 4 | 5 | function CanvasRenderer(config){ 6 | this.config = config; 7 | this.tiles = {}; 8 | for (key in Cells){ 9 | var val = Cells[key]; 10 | this.tiles[val] = new Image(); 11 | totalImages++; 12 | this.tiles[val].onload = increaseLoadedCount; 13 | this.tiles[val].src = 'img/'+val+'.png'; 14 | } 15 | setTimeout(checkImagesLoaded, 500); 16 | } 17 | 18 | var totalImages = 0; 19 | var loadedImages = 0; 20 | 21 | function increaseLoadedCount(){ 22 | loadedImages++; 23 | } 24 | 25 | function checkImagesLoaded(){ 26 | if (loadedImages == totalImages){ 27 | imagesLoaded(); 28 | } else { 29 | setTimeout(checkImagesLoaded, 500); 30 | } 31 | } 32 | 33 | CanvasRenderer.prototype = { 34 | drawSketch: function(rooms, canvas, overlay){ 35 | var canvas = document.getElementById(canvas); 36 | var context = canvas.getContext('2d'); 37 | context.font="16px Avatar"; 38 | if (!overlay) 39 | context.clearRect(0, 0, canvas.width, canvas.height); 40 | var zoom = 16; 41 | for (var i = 0; i < rooms.length; i++){ 42 | var area = rooms[i]; 43 | context.beginPath(); 44 | context.rect(area.x * zoom, area.y * zoom, area.width * zoom, area.height * zoom); 45 | if (!overlay){ 46 | if (area.type === 'rooms'){ 47 | context.fillStyle = 'blue'; 48 | context.globalAlpha = 0.5; 49 | } 50 | else{ 51 | context.fillStyle = 'yellow'; 52 | context.globalAlpha = 1; 53 | } 54 | 55 | context.fill(); 56 | } 57 | context.lineWidth = 2; 58 | context.strokeStyle = 'black'; 59 | context.stroke(); 60 | if (area.bridges) for (var j = 0; j < area.bridges.length; j++){ 61 | var bridge = area.bridges[j]; 62 | context.beginPath(); 63 | context.rect((bridge.x) * zoom, (bridge.y) * zoom, zoom, zoom); 64 | context.lineWidth = 2; 65 | context.strokeStyle = 'red'; 66 | context.stroke(); 67 | } 68 | } 69 | context.globalAlpha = 1; 70 | for (var i = 0; i < rooms.length; i++){ 71 | var area = rooms[i]; 72 | context.fillStyle = 'black'; 73 | context.fillText(area.name,(area.x)* zoom + 5,(area.y + area.height/2)* zoom + 10); 74 | } 75 | }, 76 | drawLevel: function(level, canvas){ 77 | 78 | }, 79 | drawLevelWithIcons: function(cells, canvas){ 80 | var canvas = document.getElementById(canvas); 81 | var context = canvas.getContext('2d'); 82 | context.clearRect(0, 0, canvas.width, canvas.height); 83 | var zoom = 8; 84 | for (var x = 0; x < this.config.LEVEL_WIDTH; x++){ 85 | for (var y = 0; y < this.config.LEVEL_HEIGHT; y++){ 86 | var cell = cells[x][y]; 87 | if (cell) 88 | context.drawImage(this.tiles[cell], x * 14, y * 16); 89 | } 90 | } 91 | } 92 | } 93 | 94 | module.exports = CanvasRenderer; -------------------------------------------------------------------------------- /src/CastleStructureGenerator.js: -------------------------------------------------------------------------------- 1 | var Random = require('./Random'); 2 | 3 | function CastleStructureGenerator(){}; 4 | 5 | CastleStructureGenerator.prototype = { 6 | generateMap: function(generationParams){ 7 | this.generationParams = generationParams; 8 | var castle = {}; 9 | this.castle = castle; 10 | castle.general = this.selectGeneral(); 11 | castle.surroundings = this.selectSurroundings(); 12 | castle.towers = this.selectTowers(); 13 | castle.central = this.selectCentral(); 14 | castle.entrances = this.selectEntrances(); 15 | castle.rooms = this.selectRooms(castle); 16 | return castle; 17 | }, 18 | selectGeneral: function(){ 19 | return { 20 | size: Random.chance(80) ? 'big' : 'small', 21 | superSymmetric: Random.chance(20) 22 | } 23 | }, 24 | selectSurroundings: function(){ 25 | return { 26 | hasMoat: Random.chance(50) 27 | } 28 | }, 29 | selectEntrances: function(){ 30 | var entranceStructure = {}; 31 | if (Random.chance(50)){ 32 | entranceStructure.northExit = this.selectEntrance(false); 33 | } 34 | entranceStructure.southExit = this.selectEntrance(true); 35 | return entranceStructure; 36 | }, 37 | selectEntrance: function(mainEntrance){ 38 | var entranceStructure = {}; 39 | entranceStructure.hasFloor = Random.chance(50); 40 | entranceStructure.hasCrossWindows = Random.chance(50); 41 | entranceStructure.lighting = Random.randomElementOf(['none', 'torches', 'firepits']); 42 | entranceStructure.hasBanners = mainEntrance && Random.chance(60); 43 | entranceStructure.isMain = mainEntrance; 44 | entranceStructure.width = this.castle.central.width - Random.rand(3, 6) * 2; 45 | if (entranceStructure.width < 3) 46 | entranceStructure.width = 3; 47 | entranceStructure.openingWidth = Random.rand(1,entranceStructure.width-2); 48 | if (entranceStructure.openingWidth % 2 == 0) 49 | entranceStructure.openingWidth--; 50 | entranceStructure.closingWidth = Random.rand(1,entranceStructure.width-2); 51 | if (entranceStructure.closingWidth % 2 == 0) 52 | entranceStructure.closingWidth--; 53 | return entranceStructure; 54 | }, 55 | selectTowers: function(){ 56 | var towerStructure = {}; 57 | towerStructure.size = 5 + Random.rand(0,1) * 2; 58 | towerStructure.crossWindows = Random.chance(50); 59 | towerStructure.circle = Random.chance(50); 60 | 61 | towerStructure.verticalConnections = Random.chance(50); 62 | towerStructure.horizontalConnections = Random.randomElementOf(['both', 'top', 'bottom']); 63 | towerStructure.connectionCorridors = {}; 64 | if (Random.chance(50)){ 65 | towerStructure.connectionCorridors.type = 'corridor'; 66 | } else { 67 | towerStructure.connectionCorridors.type = 'halls'; 68 | towerStructure.connectionCorridors.hallDecoration = { 69 | torches: Random.chance(50), 70 | plants: Random.chance(50), 71 | columns: Random.chance(50), 72 | fountains: Random.chance(50) 73 | } 74 | towerStructure.connectionCorridors.hallWidth = Random.rand(3,5); 75 | } 76 | return towerStructure; 77 | }, 78 | selectCentral: function(){ 79 | var centralStructure = {}; 80 | if (Random.chance(50)){ 81 | centralStructure.type = 'courtyard'; 82 | switch (Random.rand(0,2)){ 83 | case 0: 84 | if (Random.chance(60)) 85 | centralStructure.centralFeature = 'fountain'; 86 | if (Random.chance(50)){ 87 | centralStructure.additionalFountains = true; 88 | centralStructure.fountainSymmetry = Random.randomElementOf(['x', 'y', 'full']); 89 | } 90 | centralStructure.hasSmallLake = Random.chance(50); 91 | break; 92 | case 1: 93 | centralStructure.centralFeature = 'well'; 94 | break; 95 | case 2: 96 | if (Random.chance(40)) 97 | centralStructure.centralFeature = 'fountain'; 98 | centralStructure.hasSmallLake = true; 99 | break; 100 | } 101 | centralStructure.connectionWithRooms = { 102 | type: Random.randomElementOf(['radial', 'around']), 103 | terrain: Random.randomElementOf(['floor', 'dirt']) 104 | }; 105 | } else { 106 | centralStructure.type = 'mainHall'; 107 | centralStructure.hasSpecialFloor = Random.chance(50); 108 | if (Random.chance(50)){ 109 | centralStructure.centralFeature = 'fountain'; 110 | } 111 | centralStructure.centralFireplace = Random.chance(50); 112 | centralStructure.cornerFireplaces = Random.chance(50); 113 | } 114 | centralStructure.width = 9 + Random.rand(0,3)*2; 115 | var maxWidth = 15; 116 | if (this.castle.towers.verticalConnections) 117 | maxWidth -= 8; 118 | if (this.castle.towers.connectionCorridors.hallWidth > 3) 119 | maxWidth -= 2; 120 | var maxHeight = 15; 121 | if (this.castle.towers.horizontalConnections === 'both') 122 | maxHeight -= 4; 123 | if (centralStructure.width > maxWidth) 124 | centralStructure.width = maxWidth; 125 | if (Random.chance(50)){ 126 | centralStructure.height = 9 + Random.rand(0,3)*2; 127 | if (centralStructure.height > maxHeight) 128 | centralStructure.height = maxHeight; 129 | } else { 130 | centralStructure.height = centralStructure.width; 131 | } 132 | centralStructure.shape = Random.randomElementOf(['square', 'circle', 'cross']); 133 | return centralStructure; 134 | }, 135 | selectRooms: function(castle){ 136 | var numberOfRooms = 0; 137 | if (castle.general.size === 'small'){ 138 | // Only four rooms, one below each "Tower" 139 | numberOfRooms = 4; 140 | } else { 141 | // Number of rooms depends on available space 142 | /** 143 | * Base is 8 rooms 144 | * -1 If North Exit 145 | * +4 If no vertical connections between towers 146 | * +2 If no top connection between towers 147 | * +2 If no bottom connection between towers 148 | * -4 If central feature is too big 149 | */ 150 | numberOfRooms = 8; 151 | if (castle.entrances.northExit) 152 | numberOfRooms--; 153 | if (castle.towers.verticalConnections) 154 | numberOfRooms+= 4; 155 | if (castle.towers.horizontalConnections != 'both') 156 | numberOfRooms+= 2; 157 | if (castle.central.width > 10) 158 | numberOfRooms-= 4; 159 | if (numberOfRooms < 0) 160 | numberOfRooms = 0; 161 | } 162 | var rooms = []; 163 | this.currentRooms = {}; 164 | for (var i = 0; i < numberOfRooms; i++){ 165 | var room = this.selectRoom(rooms); 166 | this.fillRoom(room); 167 | rooms.push(room); 168 | } 169 | return rooms; 170 | }, 171 | fillRoom: function(room){ 172 | switch (room.type){ 173 | case 'livingQuarters': 174 | room.freeSpace = Random.rand(0, 50); 175 | break; 176 | case 'guestRoom': 177 | room.beds = Random.rand(1,2); 178 | room.mirror = Random.chance(50); 179 | room.piano = Random.chance(30); 180 | room.fireplace = Random.chance(50); 181 | break; 182 | case 'storage': 183 | room.filled = Random.rand(60,90); 184 | room.barrels = Random.rand(0,room.filled); 185 | room.boxes = room.filled - room.barrels; 186 | break; 187 | case 'diningRoom': 188 | room.luxury = Random.rand(1,3); 189 | room.fireplace = Random.chance(50); 190 | break; 191 | case 'kitchen': 192 | room.filled = Random.rand(0,20); 193 | room.barrels = Random.rand(0,room.filled); 194 | room.boxes = room.filled - room.barrels; 195 | room.hasOven = Random.chance(50); 196 | room.isNextTo = 'diningRoom'; 197 | break; 198 | case 'throneRoom': 199 | room.hasCarpet = Random.chance(70); 200 | room.hasMagicCarpet = Random.chance(20); 201 | room.linedWithColumns = Random.chance(30); 202 | room.linedWithTorches = Random.chance(70); 203 | room.hasSecondaryThrone = Random.chance(50); 204 | room.hasMagicOrb = Random.chance(50); 205 | room.placeNorth = true; 206 | room.southRoom = 'throneHall'; 207 | room.linkeable = false; 208 | room.isBig = true; 209 | room.level = 2; 210 | break; 211 | case 'lordQuarters': 212 | room.piano = Random.chance(50); 213 | room.clock = Random.chance(50); 214 | room.bookshelf = Random.chance(70); 215 | room.fireplace = Random.chance(80); 216 | room.placeNorth = true; 217 | room.isBig = true; 218 | break; 219 | case 'hall': 220 | room.torches = Random.chance(50); 221 | room.plants = Random.chance(50); 222 | room.columns = Random.chance(50); 223 | room.fountains = Random.chance(50); 224 | break; 225 | } 226 | }, 227 | selectRoom: function(rooms){ 228 | /** 229 | * Throne Room - Required if the castle is Royal 230 | * Castle Lord Room - Required 231 | * Staff Living Quarters - At least one 232 | * Dining rooms - At least one 233 | * Kitchen - At least one 234 | * Forge - Optional, only one 235 | * Prison Cells - Optional 236 | * Dungeon - Optional 237 | * Hall - Optional 238 | * Library - Optional 239 | */ 240 | var room = {}; 241 | if (this.generationParams.royal && !this.currentRooms.throneRoom){ 242 | room.type = 'throneRoom'; 243 | } else if (!this.currentRooms.lordQuarters){ 244 | room.type = 'lordQuarters'; 245 | } else if (!this.currentRooms.diningRoom){ 246 | room.type = 'diningRoom'; 247 | } else if (!this.currentRooms.kitchen){ 248 | room.type = 'kitchen'; 249 | } else if (!this.currentRooms.livingQuarters){ 250 | room.type = 'livingQuarters'; 251 | } else { 252 | var possibleRooms = ['livingQuarters', 'diningRoom', 'kitchen', /*'prison', 'dungeon',*/ 'hall', 'guestRoom', 'library']; 253 | /*if (!this.currentRooms.forge){ 254 | possibleRooms.push('forge'); 255 | }*/ 256 | room.type = Random.randomElementOf(possibleRooms); 257 | } 258 | this.currentRooms[room.type] = true; 259 | return room; 260 | } 261 | } 262 | 263 | module.exports = CastleStructureGenerator; -------------------------------------------------------------------------------- /src/RoomBuilder.js: -------------------------------------------------------------------------------- 1 | var Random = require('./Random'); 2 | var Arrays = require('./Arrays'); 3 | var Cells = require('./Cells'); 4 | 5 | function RoomBuilder(){}; 6 | 7 | RoomBuilder.prototype = { 8 | buildRooms: function(map, rooms){ 9 | this.map = map; 10 | rooms = rooms.sort(function(a,b){var aLevel = a.level ? a.level : 0; var bLevel = b.level ? b.level : 0; return aLevel - bLevel;}); 11 | for (var i = 0; i < rooms.length; i++){ 12 | var buildFunction = this["build_"+rooms[i].type]; 13 | if (buildFunction){ 14 | buildFunction.call(this, rooms[i]); 15 | } else 16 | this.buildRoom(rooms[i]); 17 | } 18 | for (var i = 0; i < rooms.length; i++){ 19 | this.placeDoors(rooms[i]); 20 | } 21 | for (var x = 0; x < 32; x++){ 22 | for (var y = 0; y < 32; y++){ 23 | if (this.map[x][y] === Cells.CHEST){ 24 | // Place secret door nearby 25 | if (x > 16 && Random.chance(90) && this.map[x+1][y] === Cells.WALL){ 26 | this.map[x+1][y] = Cells.FAKE_WALL; 27 | } 28 | if (x < 16 && Random.chance(90) && this.map[x-1][y] === Cells.WALL){ 29 | this.map[x-1][y] = Cells.FAKE_WALL; 30 | } 31 | } 32 | } 33 | } 34 | 35 | }, 36 | buildRoom: function(room){ 37 | for (var x = room.x; x < room.x + room.width; x++){ 38 | for (var y = room.y; y < room.y + room.height; y++){ 39 | if (x == room.x || x == room.x + room.width - 1 || y == room.y || y == room.y + room.height - 1){ 40 | this.map[x][y] = Cells.WALL; 41 | } else { 42 | this.map[x][y] = Cells.FLOOR; 43 | } 44 | } 45 | } 46 | }, 47 | build_rooms: function(room){ // Foundations 48 | for (var x = room.x; x < room.x + room.width; x++){ 49 | for (var y = room.y; y < room.y + room.height; y++){ 50 | if (x == room.x || x == room.x + room.width - 1 || y == room.y || y == room.y + room.height - 1){ 51 | this.map[x][y] = Cells.WALL; 52 | } else { 53 | this.map[x][y] = Random.chance(80) ? Cells.FLOOR : Cells.CHEST; 54 | } 55 | } 56 | } 57 | }, 58 | build_tower: function(room){ 59 | if (room.features.walls.north == 'exit') 60 | room.northDoors = [{position: room.x + Math.floor(room.width/2), cell: Cells.DOOR}]; 61 | if (room.features.walls.south == 'exit') 62 | room.southDoors = [{position: room.x + Math.floor(room.width/2), cell: Cells.DOOR}]; 63 | if (room.features.walls.east == 'exit') 64 | room.eastDoors = [{position: room.y + Math.floor(room.height/2), cell: Cells.DOOR}]; 65 | if (room.features.walls.west == 'exit') 66 | room.westDoors = [{position: room.y + Math.floor(room.height/2), cell: Cells.DOOR}]; 67 | 68 | for (var x = room.x; x < room.x + room.width; x++){ 69 | for (var y = room.y; y < room.y + room.height; y++){ 70 | var wall = false; 71 | if (x == room.x){ 72 | wall = room.features.walls.west; 73 | } else if (x == room.x + room.width - 1){ 74 | wall = room.features.walls.east; 75 | } else if (y == room.y){ 76 | wall = room.features.walls.north; 77 | } else if (y == room.y + room.height - 1){ 78 | wall = room.features.walls.south; 79 | } 80 | if (wall){ 81 | if (wall === 'solid'){ 82 | this.map[x][y] = Cells.WALL; 83 | } else if (wall === 'crossWindows'){ 84 | if (x === room.x + Math.floor(room.width/2) || 85 | y === room.y + Math.floor(room.height/2) ) 86 | this.map[x][y] = Cells.CROSS_WINDOW; 87 | else 88 | this.map[x][y] = Cells.WALL; 89 | } else if (wall === 'exit'){ 90 | this.map[x][y] = Cells.WALL; 91 | } else if (wall === 'open'){ 92 | this.map[x][y] = Cells.FLOOR; 93 | } 94 | } else { 95 | this.map[x][y] = Cells.FLOOR; 96 | } 97 | } 98 | } 99 | }, 100 | build_mainHall: function(room){ 101 | for (var x = room.x; x < room.x + room.width; x++){ 102 | for (var y = room.y; y < room.y + room.height; y++){ 103 | if (x == room.x || x == room.x + room.width - 1 || y == room.y || y == room.y + room.height - 1){ 104 | this.map[x][y] = Cells.WALL; 105 | } else { 106 | if (room.features.hasSpecialFloor) 107 | this.map[x][y] = Cells.FLOOR_2; 108 | else 109 | this.map[x][y] = Cells.FLOOR; 110 | } 111 | } 112 | } 113 | var midx = room.x+Math.floor(room.width/2); 114 | var midy = room.y+Math.floor(room.height/2); 115 | if (room.features.centralFireplace){ 116 | if (room.features.centralFeature){ 117 | if (room.width > 6 && room.height > 6){ 118 | this.map[midx - 1][midy - 1] = Cells.FIREPLACE; 119 | this.map[midx + 1][midy - 1] = Cells.FIREPLACE; 120 | this.map[midx - 1][midy + 1] = Cells.FIREPLACE; 121 | this.map[midx + 1][midy + 1] = Cells.FIREPLACE; 122 | } 123 | } else { 124 | this.map[midx][midy] = Cells.FIREPLACE; 125 | } 126 | } 127 | if (room.features.cornerFireplaces && (room.height > 5 || room.width > 5)){ 128 | if (!room.features.centralFireplace || (room.height > 7 || room.width > 7)){ 129 | this.map[room.x+1][room.y+1] = Cells.FIREPLACE; 130 | this.map[room.x+1][room.y+room.height-2] = Cells.FIREPLACE; 131 | this.map[room.x+room.width-2][room.y+1] = Cells.FIREPLACE; 132 | this.map[room.x+room.width-2][room.y+room.height-2] = Cells.FIREPLACE; 133 | } 134 | } 135 | if (room.features.centralFeature === 'fountain'){ 136 | this.map[midx][midy] = Cells.FOUNTAIN; 137 | } 138 | //TODO: Implement room.features.shape === 'circle' 139 | //TODO: Implement room.features.shape === 'cross' 140 | }, 141 | build_courtyard: function(room){ 142 | for (var x = room.x; x < room.x + room.width; x++){ 143 | for (var y = room.y; y < room.y + room.height; y++){ 144 | if (x == room.x || x == room.x + room.width - 1 || y == room.y || y == room.y + room.height - 1){ 145 | this.map[x][y] = Cells.WALL; 146 | } else { 147 | if (Random.chance(80)) 148 | this.map[x][y] = Cells.GRASS_1; 149 | else if (Random.chance(80)) 150 | this.map[x][y] = Cells.GRASS_2; 151 | else 152 | this.map[x][y] = Cells.TREE; 153 | } 154 | } 155 | } 156 | var midx = room.x+Math.floor(room.width/2); 157 | var midy = room.y+Math.floor(room.height/2); 158 | var connectionTerrain = room.features.connectionWithRooms.terrain; 159 | if (room.features.connectionWithRooms.type === 'radial'){ 160 | if (room.width > 6) for (var x = room.x + 1; x < room.x + room.width - 1; x++){ 161 | this.map[x][midy-1] = connectionTerrain; 162 | this.map[x][midy] = connectionTerrain; 163 | this.map[x][midy+1] = connectionTerrain; 164 | } 165 | if (room.height > 6) for (var y = room.y + 1; y < room.y + room.height - 1; y++){ 166 | this.map[midx-1][y] = connectionTerrain; 167 | this.map[midx][y] = connectionTerrain; 168 | this.map[midx+1][y] = connectionTerrain; 169 | } 170 | if (room.width > 7 && room.width > 7) { 171 | this.map[midx - 2][midy - 2] = connectionTerrain; 172 | this.map[midx - 2][midy + 2] = connectionTerrain; 173 | this.map[midx + 2][midy - 2] = connectionTerrain; 174 | this.map[midx + 2][midy + 2] = connectionTerrain; 175 | } 176 | } else if (room.features.connectionWithRooms.type === 'around'){ 177 | for (var x = room.x + 1; x < room.x + room.width - 1; x++){ 178 | this.map[x][room.y+1] = connectionTerrain; 179 | this.map[x][room.y+room.height - 2] = connectionTerrain; 180 | } 181 | for (var y = room.y + 1; y < room.y + room.height - 1; y++){ 182 | this.map[room.x+1][y] = connectionTerrain; 183 | this.map[room.x+room.width - 2][y] = connectionTerrain; 184 | } 185 | if (room.width > 7 && room.height > 7){ 186 | this.map[room.x+2][room.y+2] = connectionTerrain; 187 | this.map[room.x+2][room.y+room.height-3] = connectionTerrain; 188 | this.map[room.x+room.width-3][room.y+2] = connectionTerrain; 189 | this.map[room.x+room.width-3][room.y+room.height-3] = connectionTerrain; 190 | } 191 | } 192 | if (room.features.hasSmallLake && room.height > 6 && room.width > 6){ 193 | this.map[midx-1][midy] = Cells.WATER; 194 | this.map[midx][midy+1] = Cells.WATER; 195 | this.map[midx][midy] = Cells.WATER; 196 | this.map[midx][midy-1] = Cells.WATER; 197 | this.map[midx+1][midy] = Cells.WATER; 198 | 199 | if (Random.chance(60) && room.height > 7 && room.width > 7){ 200 | this.map[midx-1][midy+1] = Cells.WATER; 201 | this.map[midx-1][midy-1] = Cells.WATER; 202 | this.map[midx+1][midy+1] = Cells.WATER; 203 | this.map[midx+1][midy-1] = Cells.WATER; 204 | } 205 | } 206 | if (room.features.centralFeature === 'fountain'){ 207 | this.map[midx][midy] = Cells.FOUNTAIN; 208 | } else if (room.features.centralFeature === 'well'){ 209 | this.map[midx][midy] = Cells.WELL; 210 | } 211 | if (room.features.additionalFountains){ 212 | if (room.features.fountainSymmetry === 'x' && room.height > 9){ 213 | this.map[midx][midy + 2] = Cells.FOUNTAIN; 214 | this.map[midx][midy - 2] = Cells.FOUNTAIN; 215 | } else if (room.features.fountainSymmetry === 'y' && room.width > 9){ 216 | this.map[midx - 2][midy] = Cells.FOUNTAIN; 217 | this.map[midx + 2][midy] = Cells.FOUNTAIN; 218 | } else if (room.features.fountainSymmetry === 'full' && room.width > 9 && room.height > 9){ 219 | this.map[midx - 2][midy - 2] = Cells.FOUNTAIN; 220 | this.map[midx + 2][midy - 2] = Cells.FOUNTAIN; 221 | this.map[midx - 2][midy + 2] = Cells.FOUNTAIN; 222 | this.map[midx + 2][midy + 2] = Cells.FOUNTAIN; 223 | } 224 | } 225 | }, 226 | build_entranceHall: function(room){ 227 | var halfOpening = Math.floor((room.width - room.features.openingWidth) / 2); 228 | var halfClosing = Math.floor((room.width - room.features.closingWidth) / 2); 229 | for (var x = room.x; x < room.x + room.width; x++){ 230 | for (var y = room.y; y < room.y + room.height; y++){ 231 | if (x == room.x || x == room.x + room.width - 1){ 232 | this.map[x][y] = Cells.WALL; 233 | } else if ((room.features.isMain && y == room.y) || (!room.features.isMain && y == room.y + room.height - 1) ){ 234 | if (x >= room.x+halfOpening && x <= room.x + room.width - halfOpening-1){ 235 | this.map[x][y] = Cells.FLOOR; 236 | } else { 237 | this.map[x][y] = Cells.WALL; 238 | } 239 | } else if ((!room.features.isMain && y == room.y) || (room.features.isMain && y == room.y + room.height - 1) ){ 240 | if (x >= room.x+halfClosing && x <= room.x + room.width - halfClosing-1){ 241 | this.map[x][y] = Cells.FLOOR; 242 | } else { 243 | this.map[x][y] = Cells.WALL; 244 | } 245 | } else { 246 | this.map[x][y] = Cells.FLOOR; 247 | } 248 | } 249 | } 250 | }, 251 | build_entrance: function(room){ 252 | for (var x = room.x; x < room.x + room.width; x++){ 253 | for (var y = room.y; y < room.y + room.height; y++){ 254 | this.map[x][y] = Cells.FLOOR; 255 | } 256 | } 257 | }, 258 | build_throneRoom: function(room){ 259 | // Pad room 260 | var modifiedWidth = false; 261 | if (room.width % 2 == 0){ 262 | room.width--; 263 | for (var y = room.y; y < room.y + room.height; y++){ 264 | this.map[room.x+room.width-1][y] = Cells.WALL; 265 | } 266 | modifiedWidth = true; 267 | } 268 | this.buildRoom(room); 269 | var midx = room.x+Math.floor(room.width/2); 270 | // Place throne 271 | this.map[midx][room.y+1] = Cells.THRONE; 272 | if (room.features.hasSecondaryThrone) 273 | this.map[midx-1][room.y+1] = Cells.SMALL_TABLE; 274 | if (room.features.hasMagicOrb) 275 | this.map[midx+1][room.y+1] = Cells.ORB; 276 | if (room.features.hasMagicCarpet) 277 | this.map[midx][room.y+2] = Cells.MAGIC_CARPET; 278 | 279 | var columnsPosition = Random.rand(2, Math.floor(room.width/2) - 2); 280 | var columnsSpacing = Random.rand(2,3); 281 | var torchesSpacing = Random.rand(2,3); 282 | var placeColumns = room.features.linedWithColumns && room.width >= 9; 283 | var placeTorches = room.features.linedWithTorches && room.width >= 9; 284 | var placeSmallCarpet = true; 285 | var placeBigCarpet = room.width >= 7; 286 | var carpetStart = room.features.hasMagicCarpet ? room.y + 3 : room.y + 2; 287 | 288 | for (var y = room.y+1; y < room.y + room.height - 1; y++){ 289 | // Place carpet 290 | if (room.features.hasCarpet) if (y >= carpetStart){ 291 | if (placeSmallCarpet) 292 | this.map[midx][y] = Cells.FLOOR_2; 293 | if (placeBigCarpet){ 294 | this.map[midx-1][y] = Cells.FLOOR_2; 295 | this.map[midx+1][y] = Cells.FLOOR_2; 296 | } 297 | } 298 | // Place columns 299 | if (placeColumns) if (y % columnsSpacing == 0){ 300 | this.map[room.x+columnsPosition][y] = Cells.COLUMN; 301 | this.map[room.x+room.width-columnsPosition-1][y] = Cells.COLUMN; 302 | } 303 | // Place torches 304 | if (placeTorches) if ((y+1) % torchesSpacing == 0){ 305 | this.map[room.x+1][y] = Cells.L_TORCH; 306 | this.map[room.x+room.width-2][y] = Cells.R_TORCH; 307 | } 308 | } 309 | // Place door 310 | room.southDoors = [{position: midx, cell: Cells.DOOR}]; 311 | if (modifiedWidth){ 312 | room.width++; 313 | } 314 | }, 315 | build_livingQuarters: function(room){ 316 | this.buildLivingQuarters(room, 'simple'); 317 | }, 318 | build_guestRoom: function(room){ 319 | this.buildLivingQuarters(room, 'guestRoom'); 320 | }, 321 | build_lordQuarters: function(room){ 322 | this.buildLivingQuarters(room, 'lord'); 323 | }, 324 | buildLivingQuarters: function(room, quartersType){ 325 | this.buildRoom(room); 326 | var leftSize = room.width >= 6 ? 3 : 2; 327 | var rightSize = room.width >= 8 ? room.width > 8 ? 3 : 2 : 0; 328 | var topSize = room.height >= 6 ? 3 : 2; 329 | var bottomSize = room.height >= 8 ? room.height > 8 ? 3 : 2 : 0; 330 | 331 | if (Random.chance(50)){ 332 | // Flip sides 333 | var temp = rightSize; 334 | rightSize = leftSize; 335 | leftSize = temp; 336 | } 337 | room.placedElements = {}; 338 | // Place furniture 339 | for (var y = room.y+1; y < room.y + room.height - 1; y++){ 340 | if (leftSize){ 341 | var mainBlock = this.selectLivingQuartersBlock(room, y, quartersType); 342 | if (mainBlock) 343 | this.placeBlock(mainBlock, room.x+1, y, leftSize, false, quartersType); 344 | } 345 | if (rightSize){ 346 | var mainBlock = this.selectLivingQuartersBlock(room, y, quartersType); 347 | if (mainBlock) 348 | this.placeBlock(mainBlock, room.x+room.width-rightSize-1, y, rightSize, true, quartersType); 349 | } 350 | } 351 | for (var x = room.x+3; x < room.x + room.width - 3; x++){ 352 | if (topSize){ 353 | var mainBlock = this.selectLivingQuartersVBlock(room, quartersType); 354 | if (mainBlock) 355 | this.placeVBlock(mainBlock, x, room.y+1, topSize, false, quartersType); 356 | } 357 | if (bottomSize){ 358 | var mainBlock = this.selectLivingQuartersVBlock(room, quartersType); 359 | if (mainBlock) 360 | this.placeVBlock(mainBlock, x, room.y+room.height-bottomSize-1, bottomSize, true, quartersType); 361 | } 362 | } 363 | 364 | if (quartersType === 'lord' || quartersType === 'guestRoom'){ 365 | // Place bed(s) for royal and guest quarters 366 | var numberOfBeds = quartersType === 'guestRoom' ? 2 : 1; 367 | for (var i = 0; i < numberOfBeds; i++){ 368 | var y = Random.rand(room.y+1, room.y + room.height - 2 - (quartersType === 'lord' ? 1 : 0)); 369 | if (leftSize){ 370 | this.placeBlock('bed', room.x+1, y, leftSize, false, quartersType); 371 | } else if (rightSize){ 372 | this.placeBlock('bed', room.x+room.width-rightSize-1, y, rightSize, true, quartersType); 373 | } 374 | } 375 | this.addWallFireplace(room); 376 | } 377 | this.addTorchesToRoom(room); 378 | 379 | }, 380 | selectLivingQuartersBlock: function(room, y, quartersType){ 381 | // Beds? 382 | if (quartersType === 'simple' && ((y%2 == 0 && Random.chance(70)) || (y%2 != 0 && Random.chance(30)))){ 383 | return 'bed'; 384 | } 385 | // Nothing? 386 | if (Random.chance(20)){ 387 | return false; 388 | } 389 | // Table with chair 390 | if (!room.placedElements["tableAndChair"] && (quartersType === 'lord' || quartersType === 'guestRoom') && Random.chance(40)){ 391 | room.placedElements["tableAndChair"] = true; 392 | return 'tableAndChair' 393 | } 394 | // Kitchen Table 395 | if (!room.placedElements["diningTable"] && quartersType === 'kitchen' && y > room.y+1 && y < room.y+room.height - 2 && Random.chance(40)){ 396 | room.placedElements["diningTable"] = true; 397 | return 'diningTable' 398 | } 399 | var additionalElements = false; 400 | switch (quartersType){ 401 | case 'simple': 402 | additionalElements = [Cells.BARREL, Cells.LOCKER, Cells.PLANT, Cells.SMALL_TABLE]; 403 | break; 404 | case 'guestRoom': 405 | additionalElements = [Cells.BARREL, Cells.SHELF, Cells.PLANT, Cells.JAR_TABLE]; 406 | break; 407 | case 'lord': 408 | additionalElements = [Cells.SHELF, Cells.PLANT, Cells.JAR_TABLE]; 409 | break; 410 | case 'kitchen': 411 | additionalElements = [Cells.WINE_BARREL, Cells.OVEN, Cells.BARREL, Cells.GRILL, 'tableAndChair']; 412 | break; 413 | case 'library': 414 | additionalElements = ['bookshelf', 'tableAndChair', Cells.PLANT, Cells.SHELF_2]; 415 | break; 416 | case 'hall': 417 | additionalElements = [Cells.PLANT]; 418 | if (!room.placedElements[Cells.FOUNTAIN]) 419 | additionalElements.push(Cells.FOUNTAIN); 420 | break; 421 | 422 | } 423 | var element = Random.randomElementOf(additionalElements); 424 | room.placedElements[element] = true; 425 | return element; 426 | }, 427 | selectLivingQuartersVBlock: function(room, quartersType){ 428 | if (!room.placedElements["tableAndPiano"] && quartersType === 'lord' && Random.chance(40)){ 429 | room.placedElements["tableAndPiano"] = true; 430 | return 'tableAndPiano' 431 | } 432 | if (Random.chance(60)){ 433 | return false; 434 | } 435 | var additionalElements = false; 436 | switch (quartersType){ 437 | case 'simple': 438 | additionalElements = [Cells.BARREL, Cells.LOCKER, Cells.PLANT, Cells.SMALL_TABLE]; 439 | break; 440 | case 'guestRoom': 441 | additionalElements = [Cells.BARREL, Cells.SHELF, Cells.SHELF_2, Cells.PLANT, Cells.JAR_TABLE]; 442 | if (!room.placedElements[Cells.MIRROR]) 443 | additionalElements.push(Cells.MIRROR); 444 | break; 445 | case 'lord': 446 | additionalElements = [Cells.SHELF, Cells.SHELF_2, Cells.PLANT, Cells.JAR_TABLE]; 447 | if (!room.placedElements[Cells.MIRROR]) 448 | additionalElements.push(Cells.MIRROR); 449 | if (!room.placedElements[Cells.CLOCK]) 450 | additionalElements.push(Cells.CLOCK); 451 | break; 452 | case 'kitchen': 453 | additionalElements = [Cells.WINE_BARREL, Cells.OVEN, Cells.BARREL, Cells.GRILL]; 454 | break; 455 | case 'library': 456 | additionalElements = [Cells.PLANT, Cells.SHELF_2]; 457 | if (!room.placedElements[Cells.CLOCK]) 458 | additionalElements.push(Cells.CLOCK); 459 | break; 460 | case 'hall': 461 | additionalElements = [Cells.PLANT]; 462 | if (!room.placedElements[Cells.FOUNTAIN]) 463 | additionalElements.push(Cells.FOUNTAIN); 464 | break; 465 | 466 | } 467 | var element = Random.randomElementOf(additionalElements); 468 | room.placedElements[element] = true; 469 | return element; 470 | }, 471 | placeVBlock: function(type, x,y,size, flip, quartersType){ 472 | switch (type){ 473 | case 'tableAndChair': 474 | if (!flip){ 475 | this.map[x][y] = Cells.SMALL_TABLE; 476 | this.map[x][y+1] = Cells.N_CHAIR; 477 | } else { 478 | this.map[x][y+size-1] = Cells.SMALL_TABLE; 479 | this.map[x][y+size-2] = Cells.S_CHAIR; 480 | } 481 | break; 482 | case 'tableAndPiano': 483 | if (!flip){ 484 | this.map[x][y] = Cells.S_PIANO; 485 | this.map[x][y+1] = Cells.N_CHAIR; 486 | } else { 487 | this.map[x][y+size-1] = Cells.PIANO; 488 | this.map[x][y+size-2] = Cells.S_CHAIR; 489 | } 490 | break; 491 | default: 492 | var y = flip ? y+size-1 : y; 493 | this.map[x][y] = type; 494 | break; 495 | } 496 | }, 497 | placeBlock: function(type, x,y,size, flip, quartersType){ 498 | switch (type){ 499 | case 'bed': 500 | if (!flip){ 501 | this.map[x][y] = Cells.BED_1; 502 | this.map[x+1][y] = Cells.BED_2; 503 | if (size > 2 && quartersType === 'simple'){ 504 | this.map[x+2][y] = Random.chance(70) ? Cells.LOCKER : Cells.SMALL_TABLE; 505 | } 506 | } else { 507 | if (size > 2){ 508 | this.map[x+1][y] = Cells.BED_1; 509 | this.map[x+2][y] = Cells.BED_2; 510 | if (quartersType === 'simple') 511 | this.map[x][y] = Random.chance(70) ? Cells.LOCKER : Cells.SMALL_TABLE; 512 | } else { 513 | this.map[x][y] = Cells.BED_1; 514 | this.map[x+1][y] = Cells.BED_2; 515 | } 516 | } 517 | break; 518 | case 'tableAndChair': 519 | if (!flip){ 520 | this.map[x][y] = Cells.SMALL_TABLE; 521 | this.map[x+1][y] = Cells.L_CHAIR; 522 | } else { 523 | this.map[x+size-1][y] = Cells.SMALL_TABLE; 524 | this.map[x+size-2][y] = Cells.R_CHAIR; 525 | } 526 | break; 527 | case 'tableAndPiano': 528 | var x = flip ? x+size-1 : x; 529 | if (this.map[x][y-1] !== Cells.BED_2 && this.map[x][y-1] !== Cells.BED_2){ 530 | this.map[x][y-1] = Cells.S_CHAIR; 531 | this.map[x][y] = Cells.PIANO; 532 | } 533 | break; 534 | case 'diningTable': 535 | if (size == 1){ 536 | this.map[x][y] = Cells.SMALL_TABLE; 537 | } else { 538 | this.map[x][y] = Cells.L_TABLE; 539 | this.map[x+size-1][y] = Cells.R_TABLE; 540 | for (var i = 0; i < size-2; i++){ 541 | switch (Random.rand(0,3)){ 542 | case 0: this.map[x+1+i][y] = Cells.C_TABLE_1; break; 543 | case 1: this.map[x+1+i][y] = Cells.C_TABLE_2; break; 544 | case 2: this.map[x+1+i][y] = Cells.C_TABLE_3; break; 545 | case 3: this.map[x+1+i][y] = Cells.C_TABLE_4; break; 546 | } 547 | } 548 | } 549 | break; 550 | case 'bookshelf': 551 | if (flip){ 552 | this.map[x+size-2][y] = Cells.LIBRARY_1; 553 | this.map[x+size-1][y] = Cells.LIBRARY_2; 554 | } else { 555 | this.map[x][y] = Cells.LIBRARY_1; 556 | this.map[x+1][y] = Cells.LIBRARY_2; 557 | } 558 | break; 559 | case 'upChairs': 560 | if (size === 1){ 561 | this.map[x][y] = Cells.N_CHAIR; 562 | } else for (var i = x; i < x+size-1; i++){ 563 | if (Random.chance(80)) this.map[i][y] = Cells.N_CHAIR; 564 | } 565 | break; 566 | case 'downChairs': 567 | if (size === 1){ 568 | this.map[x][y] = Cells.S_CHAIR; 569 | } else for (var i = x+1; i < x+size; i++){ 570 | if (Random.chance(80)) this.map[i][y] = Cells.S_CHAIR; 571 | } 572 | break; 573 | default: 574 | var x = flip ? x+size-1 : x; 575 | this.map[x][y] = type; 576 | break; 577 | } 578 | }, 579 | placeDoors: function(room){ 580 | if (room.northDoors) for (var i = 0; i < room.northDoors.length; i++){ 581 | var door = room.northDoors[i]; 582 | if (door.cell === Cells.DOOR){ 583 | this.tryClear(door.position,room.y-1) 584 | this.tryClear(door.position,room.y+1) 585 | } 586 | if (this.map[door.position][room.y] === Cells.WALL) 587 | this.map[door.position][room.y] = door.cell; 588 | } 589 | if (room.southDoors) for (var i = 0; i < room.southDoors.length; i++){ 590 | var door = room.southDoors[i]; 591 | if (door.cell === Cells.DOOR){ 592 | this.tryClear(door.position,room.y+room.height); 593 | this.tryClear(door.position,room.y+room.height-2); 594 | } 595 | if (this.map[door.position][room.y+room.height-1] === Cells.WALL) 596 | this.map[door.position][room.y+room.height-1] = door.cell; 597 | } 598 | if (room.westDoors) for (var i = 0; i < room.westDoors.length; i++){ 599 | var door = room.westDoors[i]; 600 | if (door.cell === Cells.DOOR){ 601 | this.tryClear(room.x-1,door.position); 602 | this.tryClear(room.x+1,door.position); 603 | } 604 | if (this.map[room.x][door.position] === Cells.WALL) 605 | this.map[room.x][door.position] = door.cell; 606 | } 607 | 608 | if (room.eastDoors) for (var i = 0; i < room.eastDoors.length; i++){ 609 | var door = room.eastDoors[i]; 610 | if (door.cell === Cells.DOOR){ 611 | this.tryClear(room.x+room.width,door.position); 612 | this.tryClear(room.x+room.width-2,door.position); 613 | } 614 | if (this.map[room.x+room.width-1][door.position] === Cells.WALL) 615 | this.map[room.x+room.width-1][door.position] = door.cell; 616 | } 617 | }, 618 | tryClear: function(x,y){ 619 | if (!this.isFloor(x,y)){ 620 | if (this.map[x][y] === Cells.BED_2) 621 | this.map[x-1][y] = Cells.FLOOR; 622 | if (this.map[x][y] === Cells.BED_1) 623 | this.map[x+1][y] = Cells.FLOOR; 624 | if (this.map[x][y] === Cells.LIBRARY_2) 625 | this.map[x-1][y] = Cells.FLOOR; 626 | if (this.map[x][y] === Cells.LIBRARY_1) 627 | this.map[x+1][y] = Cells.FLOOR; 628 | if (this.map[x][y] === Cells.R_TABLE){ 629 | if (this.map[x-1][y] === Cells.L_TABLE){ 630 | this.map[x-1][y] = Cells.SMALL_TABLE; 631 | } else { 632 | this.map[x-1][y] = Cells.R_TABLE; 633 | } 634 | } else if (this.map[x][y] === Cells.L_TABLE){ 635 | if (this.map[x+1][y] === Cells.R_TABLE){ 636 | this.map[x+1][y] = Cells.SMALL_TABLE; 637 | } else { 638 | this.map[x+1][y] = Cells.L_TABLE; 639 | } 640 | } 641 | this.map[x][y] = Cells.FLOOR; 642 | } 643 | }, 644 | isFloor: function(x,y){ 645 | return this.map[x][y] === Cells.FLOOR || 646 | this.map[x][y] === Cells.FLOOR_2 || 647 | this.map[x][y] === Cells.DIRT || 648 | this.map[x][y] === Cells.GRASS_1 || 649 | this.map[x][y] === Cells.GRASS_2; 650 | }, 651 | isMultiTile: function(x,y){ 652 | return this.map[x][y] === Cells.L_TABLE || 653 | this.map[x][y] === Cells.R_TABLE || 654 | this.map[x][y] === Cells.C_TABLE_1 || 655 | this.map[x][y] === Cells.C_TABLE_2 || 656 | this.map[x][y] === Cells.C_TABLE_3 || 657 | this.map[x][y] === Cells.C_TABLE_4 || 658 | this.map[x][y] === Cells.LIBRARY_1 || 659 | this.map[x][y] === Cells.LIBRARY_2 || 660 | this.map[x][y] === Cells.BED_1 || 661 | this.map[x][y] === Cells.BED_2; 662 | }, 663 | build_diningRoom: function(room){ 664 | var tableLength = room.width - 3; 665 | this.buildRoom(room); 666 | var leftSize = Random.chance(50); 667 | room.placedElements = {}; 668 | var location = Random.rand(room.x+1, room.x+room.width-tableLength-1); 669 | for (var y = room.y+1; y < room.y + room.height - 1; y++){ 670 | var phase = (y-room.y)%3; 671 | if (phase == 1){ 672 | if ((room.y+room.height)-y<4) 673 | break; 674 | location = Random.rand(room.x+1, room.x+room.width-tableLength-1); 675 | } 676 | // Place tables and chairs 677 | switch(phase){ 678 | case 0: 679 | this.placeBlock('upChairs', location, y, tableLength, false, 'diningRoom'); 680 | break; 681 | case 1: 682 | this.placeBlock('downChairs', location, y, tableLength, false, 'diningRoom'); 683 | break; 684 | case 2: 685 | this.placeBlock('diningTable', location, y, tableLength, false, 'diningRoom'); 686 | break; 687 | } 688 | } 689 | this.addWallFireplace(room); 690 | this.addTorchesToRoom(room); 691 | }, 692 | addTorchesToRoom: function(room){ 693 | for (var y = room.y+1; y < room.y + room.height - 1; y++){ 694 | if (Random.chance(30) && !this.isMultiTile(room.x+1, y)){ 695 | this.map[room.x+1][y] = Cells.L_TORCH; 696 | y++; 697 | } 698 | } 699 | for (var y = room.y+1; y < room.y + room.height - 1; y++){ 700 | if (Random.chance(30) && !this.isMultiTile(room.x+room.width-2, y)){ 701 | this.map[room.x+room.width-2][y] = Cells.R_TORCH; 702 | y++; 703 | } 704 | } 705 | }, 706 | addWallFireplace: function(room){ 707 | var wall = room.northDoors; 708 | if (!wall) { 709 | wall = []; 710 | room.northDoors = wall; 711 | } 712 | wall.push({cell: Cells.WALL_FIREPLACE, position: Random.rand(room.x+1, room.x+room.width - 2)}); 713 | }, 714 | build_kitchen: function(room){ 715 | this.buildLivingQuarters(room, 'kitchen'); 716 | }, 717 | build_library: function(room){ 718 | this.buildLivingQuarters(room, 'library'); 719 | }, 720 | build_throneHall: function(room){ 721 | this.build_hall(room); 722 | }, 723 | build_halls: function(room){ // Wall halls} 724 | this.buildRoom(room); 725 | if (room.width > 3) 726 | this.addTorchesToRoom(room); 727 | }, 728 | build_hall: function(room){ 729 | this.buildLivingQuarters(room, 'hall'); 730 | } 731 | } 732 | 733 | module.exports = RoomBuilder; -------------------------------------------------------------------------------- /src/RoomsGenerator.js: -------------------------------------------------------------------------------- 1 | var Random = require('./Random'); 2 | var Arrays = require('./Arrays'); 3 | var Cells = require('./Cells'); 4 | 5 | function RoomsGenerator(){}; 6 | 7 | RoomsGenerator.prototype = { 8 | generateMap: function(structure, generationParams){ 9 | this.structure = structure; 10 | this.generationParams = generationParams; 11 | this.rooms = []; 12 | this.anchorPoints = {}; 13 | this.placeFoundations(); 14 | this.placeTowers(); 15 | this.placeCentralFeature(); 16 | this.placeEntrances(); 17 | var retries = 0; 18 | while(true){ 19 | var emptyRooms = this.placeRooms(); 20 | if (emptyRooms == false){ 21 | return false; 22 | } 23 | var assigned = this.assignRooms(emptyRooms); 24 | if (!assigned){ 25 | if (retries++ < 50){ 26 | continue; 27 | } else { 28 | return false; 29 | } 30 | } 31 | break; 32 | } 33 | this.linkRooms(); 34 | return this.rooms; 35 | }, 36 | placeFoundations: function(){ 37 | var def = this.structure.towers; 38 | var connectionWidth = def.connectionCorridors.type === 'corridor' ? 3 : def.connectionCorridors.hallWidth; 39 | var x = 3; 40 | if (def.verticalConnections){ 41 | x = 1 + Math.floor(def.size / 2) + Math.floor(connectionWidth/2) - 1; 42 | } 43 | var y = 3; 44 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'top'){ 45 | y = 1 + Math.floor(def.size / 2) + Math.floor(connectionWidth/2); 46 | } 47 | var yEnd = this.generationParams.height - 5; 48 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'bottom'){ 49 | yEnd = this.generationParams.height - 3 - Math.floor(def.size / 2) - Math.floor(connectionWidth/2); 50 | } 51 | this.addRoom( 52 | x, 53 | y, 54 | 'rooms', 55 | '', 56 | this.generationParams.width - 2 * x - 1, 57 | yEnd - y + 1, 58 | {}, 59 | -2 60 | ); 61 | }, 62 | 63 | placeTowers: function(){ 64 | var def = this.structure.towers; 65 | //NW 66 | this.addRoom(1, 1,'tower', 'Northwest Tower', def.size, def.size, { 67 | shape: def.circle ? 'circle' : 'square', 68 | walls: { 69 | north: def.crossWindows ? 'crossWindows' : 'solid', 70 | west: def.crossWindows ? 'crossWindows' : 'solid', 71 | south: def.verticalConnections ? 'exit' : 'solid', 72 | east: def.horizontalConnections === 'both' || def.horizontalConnections === 'top' ? 'exit' : 'solid' 73 | } 74 | }, -1); 75 | //NE 76 | this.addRoom(this.generationParams.width - def.size - 2, 1,'tower', 'Northeast Tower', def.size, def.size, { 77 | shape: def.circle ? 'circle' : 'square', 78 | walls: { 79 | north: def.crossWindows ? 'crossWindows' : 'solid', 80 | east: def.crossWindows ? 'crossWindows' : 'solid', 81 | south: def.verticalConnections ? 'exit' : 'solid', 82 | west: def.horizontalConnections === 'both' || def.horizontalConnections === 'top' ? 'exit' : 'solid' 83 | } 84 | }, -1); 85 | //SW 86 | this.addRoom(1, this.generationParams.height - def.size - 2,'tower', 'Southwest Tower', def.size, def.size, { 87 | shape: def.circle ? 'circle' : 'square', 88 | walls: { 89 | south: def.crossWindows ? 'crossWindows' : 'solid', 90 | west: def.crossWindows ? 'crossWindows' : 'solid', 91 | north: def.verticalConnections ? 'exit' : 'solid', 92 | east: def.horizontalConnections === 'both' || def.horizontalConnections === 'bottom' ? 'exit' : 'solid' 93 | } 94 | }, -1); 95 | //SE 96 | this.addRoom(this.generationParams.width - def.size - 2, this.generationParams.height - def.size - 2,'tower', 'Southeast Tower', def.size, def.size, { 97 | shape: def.circle ? 'circle' : 'square', 98 | walls: { 99 | south: def.crossWindows ? 'crossWindows' : 'solid', 100 | east: def.crossWindows ? 'crossWindows' : 'solid', 101 | north: def.verticalConnections ? 'exit' : 'solid', 102 | west: def.horizontalConnections === 'both' || def.horizontalConnections === 'bottom' ? 'exit' : 'solid' 103 | } 104 | }, -1); 105 | var connectionWidth = def.connectionCorridors.type === 'corridor' ? 3 : def.connectionCorridors.hallWidth; 106 | if (def.verticalConnections){ 107 | // West corridor 108 | this.addRoom( 109 | 1 + Math.floor(def.size / 2) - Math.floor(connectionWidth/2), 110 | 1 + def.size - 1, 111 | def.connectionCorridors.type, 'West '+def.connectionCorridors.type, 112 | connectionWidth, 113 | this.generationParams.height - 2 * def.size - 1, 114 | def.hallDecoration, 115 | 0, 116 | true 117 | ); 118 | // East corridor 119 | this.addRoom( 120 | this.generationParams.width - 3 - Math.floor(def.size / 2) - Math.floor(connectionWidth/2) + ((def.size - connectionWidth)%2 != 0 ? 1 : 0), 121 | 1 + def.size - 1, 122 | def.connectionCorridors.type, 'East '+def.connectionCorridors.type, 123 | connectionWidth, 124 | this.generationParams.height - 2 * def.size - 1, 125 | def.hallDecoration, 126 | 0, 127 | true 128 | ); 129 | } 130 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'top'){ 131 | // North corridor 132 | this.anchorPoints.northBound = 1 + Math.floor(def.size / 2) - Math.floor(connectionWidth/2); 133 | this.addRoom( 134 | 1 + def.size - 1, 135 | this.anchorPoints.northBound, 136 | def.connectionCorridors.type, 'North '+def.connectionCorridors.type, 137 | this.generationParams.width - 2 * def.size - 1, 138 | connectionWidth, 139 | def.hallDecoration, 140 | 0, 141 | true 142 | ); 143 | } else { 144 | this.anchorPoints.northBound = 3; 145 | } 146 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'bottom'){ 147 | this.anchorPoints.southBound = this.generationParams.height - 3 - Math.floor(def.size / 2) - Math.floor(connectionWidth/2)+ ((def.size - connectionWidth)%2 != 0 ? 1 : 0) + connectionWidth; 148 | // South corridor 149 | this.addRoom( 150 | 1 + def.size - 1, 151 | this.anchorPoints.southBound - connectionWidth, 152 | def.connectionCorridors.type, 'South '+def.connectionCorridors.type, 153 | this.generationParams.width - 2 * def.size - 1, 154 | connectionWidth, 155 | def.hallDecoration, 156 | 0, 157 | true 158 | ); 159 | } else { 160 | this.anchorPoints.southBound = this.generationParams.height - 4; 161 | } 162 | }, 163 | placeCentralFeature: function(){ 164 | var def = this.structure.central; 165 | this.centerRoom = this.addRoom( 166 | Math.floor(this.generationParams.width / 2) - Math.floor(def.width /2) - 1, 167 | Math.floor(this.generationParams.height / 2) - Math.floor(def.height /2) - 1, 168 | def.type, 169 | def.type === 'courtyard' ? 'Courtyard' : 'Main Hall', 170 | def.width, 171 | def.height, 172 | def 173 | ); 174 | }, 175 | placeEntrances: function(){ 176 | var entranceLength = Math.floor(this.generationParams.height / 2) - Math.floor(this.structure.central.height /2) - 1; 177 | 178 | // North entrance 179 | if (this.structure.entrances.northExit){ 180 | var def = this.structure.entrances.northExit; 181 | // Northern Exit 182 | this.addRoom( 183 | Math.floor(this.generationParams.width / 2) - Math.floor(def.width /2) - 1, 184 | 0, 185 | 'entrance', 186 | 'Entrance', 187 | def.width, 188 | this.anchorPoints.northBound, 189 | def, 190 | 1, 191 | false 192 | ); 193 | 194 | // Northern entrance hall 195 | this.addRoom( 196 | Math.floor(this.generationParams.width / 2) - Math.floor(def.width /2) - 1, 197 | this.anchorPoints.northBound, 198 | 'entranceHall', 199 | 'Entrance Hall', 200 | def.width, 201 | entranceLength - this.anchorPoints.northBound + 1, 202 | def, 203 | 1, 204 | false 205 | ); 206 | } 207 | // South Entrance 208 | if (this.structure.entrances.southExit){ 209 | var def = this.structure.entrances.southExit; 210 | this.addRoom( 211 | Math.floor(this.generationParams.width / 2) - Math.floor(def.width /2) - 1, 212 | entranceLength + this.structure.central.height - 1, 213 | 'entranceHall', 214 | 'Entrance Hall', 215 | def.width, 216 | this.anchorPoints.southBound - (entranceLength + this.structure.central.height - 1), 217 | def, 218 | 1, 219 | false 220 | ); 221 | this.addRoom( 222 | Math.floor(this.generationParams.width / 2) - Math.floor(def.width /2) - 1, 223 | this.anchorPoints.southBound, 224 | 'entrance', 225 | 'Entrance', 226 | def.width, 227 | this.generationParams.height - this.anchorPoints.southBound, 228 | def, 229 | 1, 230 | false 231 | ); 232 | } 233 | 234 | }, 235 | placeRooms: function(){ 236 | var def = this.structure.towers; 237 | var connectionWidth = def.connectionCorridors.type === 'corridor' ? 3 : def.connectionCorridors.hallWidth; 238 | var x = 3; 239 | var adjustment = (def.size - connectionWidth)%2 == 0 ? 1 : 0; 240 | adjustment = 0; 241 | if (def.verticalConnections){ 242 | x = 1 + Math.floor(def.size / 2) + Math.floor(connectionWidth/2) + adjustment - 1; 243 | } 244 | var y = 3; 245 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'top'){ 246 | y = 1 + Math.floor(def.size / 2) + Math.floor(connectionWidth/2) + adjustment; 247 | } 248 | var yEnd = this.generationParams.height - 5; 249 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'bottom'){ 250 | yEnd = this.generationParams.height - 3 - Math.floor(def.size / 2) - Math.floor(connectionWidth/2) - adjustment; 251 | } 252 | var area = { 253 | x: x, 254 | y: y, 255 | w: Math.floor(this.generationParams.width / 2) - x, 256 | h: yEnd - y + 1 257 | }; 258 | // Brute force! Let's try a lot of times to fit the rooms in the space we have! 259 | var roomsToPlace = Math.ceil(this.structure.rooms.length / 2); 260 | this.roomsArea = area; 261 | var minHeight = 5; 262 | var maxHeight = 7; 263 | var minWidth = 5; 264 | var maxWidth = 7; 265 | var addedRooms = []; 266 | var placedRooms = 0; 267 | var reduceRoomsToPlace = 100; 268 | // Place room connecting with courtyard 269 | addedRooms.push({ 270 | x: this.centerRoom.x - 4, 271 | y: this.centerRoom.y + Math.floor(this.centerRoom.height/2) - 2, 272 | w: 5, 273 | h: 5 274 | }); 275 | out: for (var i = 0; i < 500; i++){ 276 | for (var j = 0; j < 500; j++){ 277 | var room = { 278 | x: Random.rand(area.x, area.x + area.w - 2 - minWidth), 279 | y: Random.rand(area.y, area.y + area.h - 2 - minHeight), 280 | w: Random.rand(minWidth, maxWidth), 281 | h: Random.rand(minHeight, maxHeight), 282 | }; 283 | if (this.validRoom(room, addedRooms)){ 284 | addedRooms.push(room); 285 | placedRooms++; 286 | if (placedRooms == roomsToPlace) 287 | break out; 288 | } 289 | } 290 | if (reduceRoomsToPlace == 0){ 291 | if (roomsToPlace > 4) 292 | roomsToPlace--; 293 | reduceRoomsToPlace = 100; 294 | } else { 295 | reduceRoomsToPlace --; 296 | } 297 | addedRooms = []; 298 | } 299 | if (placedRooms != roomsToPlace) 300 | return false; // Couldn't place enough rooms, sucks! 301 | 302 | // Is the castle super symmetric? if not, we mirror the rooms before making them grow, and extend the play area 303 | if (!this.structure.general.superSymmetric){ 304 | var originalRoomsCount = addedRooms.length; 305 | // Mirror the added rooms over the y axis 306 | for (var i = 0; i < originalRoomsCount; i++){ 307 | var room = addedRooms[i]; 308 | var dist = this.roomsArea.x + this.roomsArea.w - (room.x + room.w); 309 | addedRooms.push({ 310 | x: this.roomsArea.x + this.roomsArea.w + dist - 1, 311 | y: room.y, 312 | w: room.w, 313 | h: room.h 314 | }); 315 | } 316 | area.w = this.generationParams.width - 2 * x - 1; 317 | } 318 | 319 | while(true){ 320 | //Try to make each room bigger in a direction, until it's not possible for any 321 | var grew = false; 322 | for (var i = 0; i < addedRooms.length; i++){ 323 | var room = addedRooms[i]; 324 | //Select a random direction first, if it can't grow in that direction, check in order 325 | var randomDirection = Random.rand(0,3); 326 | if (this.roomCanGrow(room, addedRooms, randomDirection)){ 327 | this.growRoom(room, randomDirection); 328 | grew = true; 329 | } else { 330 | for (var j = 0; j <= 3; j++){ 331 | if (this.roomCanGrow(room, addedRooms, j)){ 332 | this.growRoom(room, j); 333 | grew = true; 334 | break; 335 | } 336 | } 337 | } 338 | } 339 | if (!grew){ 340 | break; 341 | } 342 | } 343 | 344 | // If the castle is super symmetric, mirror them now (after growing) 345 | if (this.structure.general.superSymmetric){ 346 | var originalRoomsCount = addedRooms.length; 347 | // Mirror the added rooms over the y axis 348 | for (var i = 0; i < originalRoomsCount; i++){ 349 | var room = addedRooms[i]; 350 | var dist = this.roomsArea.x + this.roomsArea.w - (room.x + room.w); 351 | addedRooms.push({ 352 | x: this.roomsArea.x + this.roomsArea.w + dist - 1, 353 | y: room.y, 354 | w: room.w, 355 | h: room.h 356 | }); 357 | } 358 | } 359 | 360 | // Now let's fill the spaces and make these rooms grow again 361 | area.w = this.generationParams.width - 2 * x - 1; 362 | while(true){ 363 | var noHoles = true; 364 | hole: for (var x = area.x; x <= area.x + area.w - 2; x++){ 365 | for (var y = area.y; y <= area.y + area.h - 2; y++){ 366 | // Is there a hole here? is it big enough? 367 | if (this.holeAt(addedRooms, x,y)){ 368 | noHoles = false; 369 | // Lets plot a room and make it grow 370 | var fillRoom = { 371 | x: x, 372 | y: y, 373 | w: 5, 374 | h: 5 375 | }; 376 | console.log("Placing a fill room", fillRoom); 377 | addedRooms.push(fillRoom); 378 | while(true){ 379 | var grew = false; 380 | //Select a random direction first, if it can't grow in that direction, check in order 381 | var randomDirection = Random.rand(0,3); 382 | if (this.roomCanGrow(fillRoom, addedRooms, randomDirection)){ 383 | this.growRoom(fillRoom, randomDirection); 384 | grew = true; 385 | } else { 386 | for (var j = 0; j <= 3; j++){ 387 | if (this.roomCanGrow(fillRoom, addedRooms, j)){ 388 | this.growRoom(fillRoom, j); 389 | grew = true; 390 | break; 391 | } 392 | } 393 | } 394 | if (!grew){ 395 | break; 396 | } 397 | } 398 | break hole; 399 | } 400 | } 401 | } 402 | if (noHoles) 403 | break; 404 | } 405 | return addedRooms; 406 | }, 407 | holeAt: function(rooms, x,y){ 408 | for (var dx = 0; dx < 5; dx++){ 409 | for (var dy = 0; dy < 5; dy++){ 410 | if (!this.emptySpace(x+dx, y+dy, rooms)){ 411 | return false; 412 | } 413 | } 414 | } 415 | return true; 416 | }, 417 | assignRooms: function(rooms, force){ 418 | /** We now have a lot of empty rooms, give a function to each based on the super structure 419 | * We will try several times 420 | * Following room attributes are relevant: 421 | * placeNorth: boolean - Room must be placed as north as possible 422 | * isNextTo: string - Room must be placed as close as possible to a room with the given code 423 | * southRoom: string - If there is a room in the southern exit of this room, it has to be one of the given code. Only for "placeNorth" rooms 424 | * isBig: boolean - Pick one of the rooms with greatest area 425 | */ 426 | 427 | // Do we have enough big rooms in the north to satisfy requirements, can we whine? 428 | if (!force){ 429 | var bigHeightThreshold = 8; 430 | var bigNorthRequiredRooms = 0; 431 | var northRequiredRooms = 0; 432 | var bigRequiredRooms = 0; 433 | var bigNorthAvailableRooms = 0; 434 | var northAvailableRooms = 0; 435 | var bigAvailableRooms = 0; 436 | 437 | // Gather requirements 438 | for (var i = 0; i < this.structure.rooms.length; i++){ 439 | var room = this.structure.rooms[i]; 440 | if (room.placeNorth){ 441 | if (room.isBig){ 442 | bigNorthRequiredRooms++; 443 | } else { 444 | northRequiredRooms++; 445 | } 446 | } else if (room.isBig){ 447 | bigRequiredRooms++; 448 | } 449 | } 450 | 451 | // Sum available rooms 452 | for (var i = 0; i < rooms.length; i++){ 453 | var room = rooms[i]; 454 | if (room.y == this.roomsArea.y){ 455 | if (room.h > bigHeightThreshold) 456 | bigNorthAvailableRooms ++; 457 | else 458 | northAvailableRooms ++; 459 | } else if (room.h > bigHeightThreshold) 460 | bigAvailableRooms ++; 461 | } 462 | 463 | // Check if we comply 464 | if (bigNorthAvailableRooms < bigNorthRequiredRooms || 465 | northAvailableRooms < northRequiredRooms || 466 | bigAvailableRooms < bigRequiredRooms) 467 | return false; // Give me better rooms! 468 | } 469 | 470 | // Split the rooms by category 471 | var northAvailableRooms = []; 472 | var northBigAvailableRooms = []; 473 | var bigAvailableRooms = []; 474 | var otherAvailableRooms = []; 475 | for (var i = 0; i < rooms.length; i++){ 476 | var room = rooms[i]; 477 | if (room.y == this.roomsArea.y){ 478 | if (room.h > bigHeightThreshold){ 479 | northBigAvailableRooms.push(room); 480 | } else { 481 | northAvailableRooms.push(room); 482 | } 483 | } else { 484 | if (room.h > bigHeightThreshold){ 485 | bigAvailableRooms.push(room); 486 | } else { 487 | otherAvailableRooms.push(room); 488 | } 489 | } 490 | } 491 | 492 | // Shuffle the rooms 493 | var roomsToAdd = Arrays.shuffle(this.structure.rooms); 494 | // but place rooms with southRoom first 495 | roomsToAdd = roomsToAdd.sort(function(a, b){ 496 | return (a.southRoom && b.southRoom) ? 0 : a.southRoom ? -1 : 1; 497 | }); 498 | for (var i = 0; i < roomsToAdd.length; i++){ 499 | var requiredRoom = roomsToAdd[i]; 500 | var room = false; 501 | if (requiredRoom.placeNorth){ 502 | if (requiredRoom.isBig || northAvailableRooms.length == 0){ 503 | room = Random.randomElementOf(northBigAvailableRooms); 504 | } else { 505 | room = Random.randomElementOf(northAvailableRooms); 506 | } 507 | } else if (requiredRoom.isBig){ 508 | room = Random.randomElementOf(bigAvailableRooms); 509 | } else { 510 | room = Random.randomElementOf(otherAvailableRooms); 511 | } 512 | if (!room){ 513 | // Couldn't pick from preferred? pick from any available!! 514 | if (otherAvailableRooms.length) room = Random.randomElementOf(otherAvailableRooms); 515 | if (northBigAvailableRooms.length) room = Random.randomElementOf(northBigAvailableRooms); 516 | if (northAvailableRooms.length) room = Random.randomElementOf(northAvailableRooms); 517 | if (bigAvailableRooms.length) room = Random.randomElementOf(bigAvailableRooms); 518 | } 519 | // Remove used space 520 | Arrays.removeObject(northBigAvailableRooms, room); 521 | Arrays.removeObject(northAvailableRooms, room); 522 | Arrays.removeObject(bigAvailableRooms, room); 523 | Arrays.removeObject(otherAvailableRooms, room); 524 | if (room){ 525 | var linkeable = requiredRoom.linkeable != undefined ? requiredRoom.linkeable : true; 526 | this.addRoom(room.x, room.y, requiredRoom.type, requiredRoom.type, room.w, room.h, requiredRoom, requiredRoom.level, linkeable); 527 | // Place south rooms 528 | if (requiredRoom.southRoom){ 529 | var southRoom = this.getRoomAt(rooms, room.x + Math.floor(room.w/2), room.y + room.h+2); 530 | if (!southRoom){ 531 | // There's probably a hall, or the central feature which is fine. 532 | } else { 533 | this.addRoom(southRoom.x, southRoom.y, requiredRoom.southRoom, requiredRoom.southRoom, southRoom.w, southRoom.h, requiredRoom, requiredRoom.level, true); 534 | Arrays.removeObject(bigAvailableRooms, southRoom); // Available space used 535 | Arrays.removeObject(otherAvailableRooms, southRoom); // Available space used 536 | } 537 | } 538 | } else { 539 | return false; // Give me better rooms! 540 | } 541 | } 542 | 543 | // Fill unused rooms with living quarters 544 | var remainingRooms = northBigAvailableRooms.concat(northAvailableRooms).concat(bigAvailableRooms).concat(otherAvailableRooms); 545 | for (var i = 0; i < remainingRooms.length; i++){ 546 | var availableRoom = remainingRooms[i]; 547 | this.addRoom(availableRoom.x, availableRoom.y, 'livingQuarters', 'livingQuarters*', availableRoom.w, availableRoom.h, availableRoom, 0, true); 548 | } 549 | return true; 550 | }, 551 | roomCanGrow: function(room, tempRooms, direction){ 552 | var testRoom = { 553 | x: room.x, 554 | y: room.y, 555 | w: room.w, 556 | h: room.h 557 | } 558 | switch(direction){ 559 | case 0: 560 | testRoom.x --; 561 | testRoom.w ++; 562 | break; 563 | case 1: 564 | testRoom.w ++; 565 | break; 566 | case 2: 567 | testRoom.y --; 568 | testRoom.h ++; 569 | break; 570 | case 3: 571 | testRoom.h ++; 572 | break; 573 | } 574 | return this.validRoom(testRoom, tempRooms, room); 575 | }, 576 | growRoom: function(room, direction){ 577 | switch(direction){ 578 | case 0: 579 | room.x --; 580 | room.w ++; 581 | break; 582 | case 1: 583 | room.w ++; 584 | break; 585 | case 2: 586 | room.y --; 587 | room.h ++; 588 | break; 589 | case 3: 590 | room.h ++; 591 | break; 592 | } 593 | }, 594 | getRoomAt: function(rooms,x,y){ 595 | for (var i = 0; i < rooms.length; i++){ 596 | var room = rooms[i]; 597 | if (x >= room.x && x < room.x + room.w && y >= room.y && y < room.y + room.h) 598 | return room; 599 | } 600 | return false; 601 | }, 602 | getRealRoomAt: function(x,y,exclude){ 603 | for (var i = 0; i < this.rooms.length; i++){ 604 | var room = this.rooms[i]; 605 | if (room.type === 'rooms') 606 | continue; 607 | if (room == exclude) 608 | continue; 609 | if (x >= room.x && x < room.x + room.width && y >= room.y && y < room.y + room.height) 610 | return room; 611 | } 612 | return false; 613 | }, 614 | validRoom: function(room, tempRooms, skipRoom){ 615 | // Must be inside the rooms area 616 | if (room.x >= this.roomsArea.x && room.x + room.w <= this.roomsArea.x + this.roomsArea.w && 617 | room.y >= this.roomsArea.y && room.y + room.h <= this.roomsArea.y + this.roomsArea.h) { 618 | // Ok... 619 | } else { 620 | return false; 621 | } 622 | 623 | // Must not collide with other rooms (except the towers) 624 | for (var i = 0; i < this.rooms.length; i++){ 625 | var existingRoom = this.rooms[i]; 626 | if (existingRoom.type === 'tower' || existingRoom.type === 'rooms'){ 627 | continue; 628 | } 629 | if (existingRoom.x < room.x + room.w - 1 && existingRoom.x + existingRoom.width - 1 > room.x && 630 | existingRoom.y < room.y + room.h - 1 && existingRoom.y + existingRoom.height - 1 > room.y) { 631 | return false; 632 | } 633 | } 634 | // Must not collide with other temp rooms 635 | for (var i = 0; i < tempRooms.length; i++){ 636 | var existingRoom = tempRooms[i]; 637 | if (skipRoom && skipRoom == existingRoom){ 638 | continue; 639 | } 640 | if (existingRoom.x < room.x + room.w - 1 && existingRoom.x + existingRoom.w - 1 > room.x && 641 | existingRoom.y < room.y + room.h - 1 && existingRoom.y + existingRoom.h - 1 > room.y) { 642 | return false; 643 | } 644 | } 645 | return true; 646 | }, 647 | emptySpace: function(x, y, tempRooms){ 648 | // Must be inside the rooms area 649 | if (x >= this.roomsArea.x && x <= this.roomsArea.x + this.roomsArea.w && 650 | y >= this.roomsArea.y && y <= this.roomsArea.y + this.roomsArea.h) { 651 | // Ok... 652 | } else { 653 | return false; 654 | } 655 | 656 | // Must not collide with other rooms (except the towers) 657 | for (var i = 0; i < this.rooms.length; i++){ 658 | var existingRoom = this.rooms[i]; 659 | if (existingRoom.type === 'tower' || existingRoom.type === 'rooms'){ 660 | continue; 661 | } 662 | if (existingRoom.x < x && existingRoom.x + existingRoom.width - 1 > x && 663 | existingRoom.y < y && existingRoom.y + existingRoom.height - 1 > y) { 664 | return false; 665 | } 666 | } 667 | // Must not collide with other temp rooms 668 | for (var i = 0; i < tempRooms.length; i++){ 669 | var existingRoom = tempRooms[i]; 670 | if (existingRoom.x < x && existingRoom.x + existingRoom.w - 1 > x && 671 | existingRoom.y < y && existingRoom.y + existingRoom.h - 1 > y) { 672 | return false; 673 | } 674 | } 675 | return true; 676 | }, 677 | addRoom: function(x, y, type, name, width, height, features, level, linkeable){ 678 | var room = { 679 | x: x, 680 | y: y, 681 | type: type, 682 | name: name, 683 | width: width, 684 | height: height, 685 | features: features, 686 | level: level, 687 | linkeable: linkeable 688 | } 689 | this.rooms.push(room); 690 | return room; 691 | }, 692 | linkRooms: function(){ 693 | // Starting from the courtyard or main hall, go into each direction connection rooms if possible 694 | // Go westeros 695 | var westRoom = this.getRealRoomAt(this.centerRoom.x - 1, this.centerRoom.y + Math.floor(this.centerRoom.height/2), this.centerRoom); 696 | if (westRoom){ 697 | this.linkRoom(westRoom); 698 | if (!this.centerRoom.westDoors){ 699 | this.centerRoom.westDoors = []; 700 | } 701 | this.centerRoom.westDoors.push({position: this.centerRoom.y + Math.floor(this.centerRoom.height/2), cell: Cells.DOOR}); 702 | } 703 | 704 | // Go wessos 705 | var eastRoom = this.getRealRoomAt(this.centerRoom.x + this.centerRoom.width, this.centerRoom.y + Math.floor(this.centerRoom.height/2), this.centerRoom); 706 | if (eastRoom){ 707 | this.linkRoom(eastRoom); 708 | if (!this.centerRoom.eastDoors) 709 | this.centerRoom.eastDoors = []; 710 | this.centerRoom.eastDoors.push({position: this.centerRoom.y + Math.floor(this.centerRoom.height/2), cell: Cells.DOOR}); 711 | } 712 | 713 | 714 | // At the end, for unconnected rooms, connect to nearby 715 | }, 716 | linkRoom: function(room){ 717 | //First, get all nearby linkeable rooms, and take note of the segments 718 | room.linkeable = false; 719 | var northRooms = this.getLinkeableRooms(room, 'north'); 720 | var southRooms = this.getLinkeableRooms(room, 'south'); 721 | var westRooms = this.getLinkeableRooms(room, 'west'); 722 | var eastRooms = this.getLinkeableRooms(room, 'east'); 723 | 724 | //Then link using all the segments 725 | for (var i = 0; i < northRooms.length; i++){ 726 | var segment = northRooms[i]; 727 | var x = Random.rand(segment.start, segment.end); 728 | if (!room.northDoors) 729 | room.northDoors = []; 730 | room.northDoors.push({position: x, cell: Cells.DOOR}); 731 | this.linkRoom(segment.room); 732 | } 733 | 734 | 735 | for (var i = 0; i < southRooms.length; i++){ 736 | var segment = southRooms[i]; 737 | var x = Random.rand(segment.start, segment.end); 738 | if (!room.southDoors) 739 | room.southDoors = []; 740 | room.southDoors.push({position: x, cell: Cells.DOOR}); 741 | this.linkRoom(segment.room); 742 | } 743 | 744 | for (var i = 0; i < westRooms.length; i++){ 745 | var segment = westRooms[i]; 746 | var x = Random.rand(segment.start, segment.end); 747 | if (!room.westDoors) 748 | room.westDoors = []; 749 | room.westDoors.push({position: x, cell: Cells.DOOR}); 750 | this.linkRoom(segment.room); 751 | } 752 | 753 | for (var i = 0; i < eastRooms.length; i++){ 754 | var segment = eastRooms[i]; 755 | var x = Random.rand(segment.start, segment.end); 756 | if (!room.eastDoors) 757 | room.eastDoors = []; 758 | room.eastDoors.push({position: x, cell: Cells.DOOR}); 759 | this.linkRoom(segment.room); 760 | } 761 | }, 762 | getLinkeableRooms: function(room, direction){ 763 | var currentSegment = false; 764 | var segments = []; 765 | switch (direction){ 766 | case 'north': case 'south': 767 | start = room.x + 1; 768 | end = room.x + room.width - 2; 769 | break; 770 | case 'west': case 'east': 771 | start = room.y + 1; 772 | end = room.y + room.height - 2; 773 | break; 774 | } 775 | for (var x = start; x <= end; x++){ 776 | var nearRoom = false; 777 | switch (direction){ 778 | case 'north': 779 | nearRoom = this.getRealRoomAt(x, room.y - 1, room); 780 | break; 781 | case 'south': 782 | nearRoom = this.getRealRoomAt(x, room.y + room.height+1, room); 783 | break; 784 | case 'west': 785 | nearRoom = this.getRealRoomAt(room.x - 1, x, room); 786 | break; 787 | case 'east': 788 | nearRoom = this.getRealRoomAt(room.x + room.width +1, x, room); 789 | break; 790 | } 791 | 792 | if (nearRoom){ 793 | if (!currentSegment){ 794 | if (!nearRoom.linkeable) 795 | continue; 796 | currentSegment = { 797 | start: x + 1, 798 | room: nearRoom 799 | } 800 | segments.push(currentSegment); 801 | } 802 | if (nearRoom == currentSegment.room){ 803 | // The segment continues 804 | } else { 805 | currentSegment.end = x - 2; 806 | currentSegment.room.linkeable = false; 807 | if (nearRoom.linkeable){ 808 | // New segment 809 | currentSegment = { 810 | start: x + 1, 811 | room: nearRoom 812 | } 813 | segments.push(currentSegment); 814 | } else { 815 | currentSegment = false; 816 | } 817 | } 818 | } else if (currentSegment){ 819 | // No room, segment ends 820 | currentSegment.end = x - 2; 821 | currentSegment.room.linkeable = false; 822 | currentSegment = false; 823 | } 824 | } 825 | if (currentSegment && !currentSegment.end){ 826 | currentSegment.end = end - 1; 827 | } 828 | // room.segments = segments; 829 | return segments; 830 | } 831 | } 832 | 833 | module.exports = RoomsGenerator; -------------------------------------------------------------------------------- /web/generator.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { 8 | // Pick a random index 9 | var index = Math.floor(Math.random() * counter); 10 | 11 | // Decrease counter by 1 12 | counter--; 13 | 14 | // And swap the last element with it 15 | var temp = array[counter]; 16 | array[counter] = array[index]; 17 | array[index] = temp; 18 | } 19 | 20 | return array; 21 | }, 22 | removeObject: function(array, object){ 23 | for(var i = 0; i < array.length; i++) { 24 | if(array[i] === object) { 25 | array.splice(i, 1); 26 | } 27 | } 28 | } 29 | } 30 | },{}],"/home/administrator/git/ultimacastlegen/src/CastleStructureGenerator.js":[function(require,module,exports){ 31 | var Random = require('./Random'); 32 | 33 | function CastleStructureGenerator(){}; 34 | 35 | CastleStructureGenerator.prototype = { 36 | generateMap: function(generationParams){ 37 | this.generationParams = generationParams; 38 | var castle = {}; 39 | this.castle = castle; 40 | castle.general = this.selectGeneral(); 41 | castle.surroundings = this.selectSurroundings(); 42 | castle.towers = this.selectTowers(); 43 | castle.central = this.selectCentral(); 44 | castle.entrances = this.selectEntrances(); 45 | castle.rooms = this.selectRooms(castle); 46 | return castle; 47 | }, 48 | selectGeneral: function(){ 49 | return { 50 | size: Random.chance(80) ? 'big' : 'small', 51 | superSymmetric: Random.chance(20) 52 | } 53 | }, 54 | selectSurroundings: function(){ 55 | return { 56 | hasMoat: Random.chance(50) 57 | } 58 | }, 59 | selectEntrances: function(){ 60 | var entranceStructure = {}; 61 | if (Random.chance(50)){ 62 | entranceStructure.northExit = this.selectEntrance(false); 63 | } 64 | entranceStructure.southExit = this.selectEntrance(true); 65 | return entranceStructure; 66 | }, 67 | selectEntrance: function(mainEntrance){ 68 | var entranceStructure = {}; 69 | entranceStructure.hasFloor = Random.chance(50); 70 | entranceStructure.hasCrossWindows = Random.chance(50); 71 | entranceStructure.lighting = Random.randomElementOf(['none', 'torches', 'firepits']); 72 | entranceStructure.hasBanners = mainEntrance && Random.chance(60); 73 | entranceStructure.isMain = mainEntrance; 74 | entranceStructure.width = this.castle.central.width - Random.rand(3, 6) * 2; 75 | if (entranceStructure.width < 3) 76 | entranceStructure.width = 3; 77 | entranceStructure.openingWidth = Random.rand(1,entranceStructure.width-2); 78 | if (entranceStructure.openingWidth % 2 == 0) 79 | entranceStructure.openingWidth--; 80 | entranceStructure.closingWidth = Random.rand(1,entranceStructure.width-2); 81 | if (entranceStructure.closingWidth % 2 == 0) 82 | entranceStructure.closingWidth--; 83 | return entranceStructure; 84 | }, 85 | selectTowers: function(){ 86 | var towerStructure = {}; 87 | towerStructure.size = 5 + Random.rand(0,1) * 2; 88 | towerStructure.crossWindows = Random.chance(50); 89 | towerStructure.circle = Random.chance(50); 90 | 91 | towerStructure.verticalConnections = Random.chance(50); 92 | towerStructure.horizontalConnections = Random.randomElementOf(['both', 'top', 'bottom']); 93 | towerStructure.connectionCorridors = {}; 94 | if (Random.chance(50)){ 95 | towerStructure.connectionCorridors.type = 'corridor'; 96 | } else { 97 | towerStructure.connectionCorridors.type = 'halls'; 98 | towerStructure.connectionCorridors.hallDecoration = { 99 | torches: Random.chance(50), 100 | plants: Random.chance(50), 101 | columns: Random.chance(50), 102 | fountains: Random.chance(50) 103 | } 104 | towerStructure.connectionCorridors.hallWidth = Random.rand(3,5); 105 | } 106 | return towerStructure; 107 | }, 108 | selectCentral: function(){ 109 | var centralStructure = {}; 110 | if (Random.chance(50)){ 111 | centralStructure.type = 'courtyard'; 112 | switch (Random.rand(0,2)){ 113 | case 0: 114 | if (Random.chance(60)) 115 | centralStructure.centralFeature = 'fountain'; 116 | if (Random.chance(50)){ 117 | centralStructure.additionalFountains = true; 118 | centralStructure.fountainSymmetry = Random.randomElementOf(['x', 'y', 'full']); 119 | } 120 | centralStructure.hasSmallLake = Random.chance(50); 121 | break; 122 | case 1: 123 | centralStructure.centralFeature = 'well'; 124 | break; 125 | case 2: 126 | if (Random.chance(40)) 127 | centralStructure.centralFeature = 'fountain'; 128 | centralStructure.hasSmallLake = true; 129 | break; 130 | } 131 | centralStructure.connectionWithRooms = { 132 | type: Random.randomElementOf(['radial', 'around']), 133 | terrain: Random.randomElementOf(['floor', 'dirt']) 134 | }; 135 | } else { 136 | centralStructure.type = 'mainHall'; 137 | centralStructure.hasSpecialFloor = Random.chance(50); 138 | if (Random.chance(50)){ 139 | centralStructure.centralFeature = 'fountain'; 140 | } 141 | centralStructure.centralFireplace = Random.chance(50); 142 | centralStructure.cornerFireplaces = Random.chance(50); 143 | } 144 | centralStructure.width = 9 + Random.rand(0,3)*2; 145 | var maxWidth = 15; 146 | if (this.castle.towers.verticalConnections) 147 | maxWidth -= 8; 148 | if (this.castle.towers.connectionCorridors.hallWidth > 3) 149 | maxWidth -= 2; 150 | var maxHeight = 15; 151 | if (this.castle.towers.horizontalConnections === 'both') 152 | maxHeight -= 4; 153 | if (centralStructure.width > maxWidth) 154 | centralStructure.width = maxWidth; 155 | if (Random.chance(50)){ 156 | centralStructure.height = 9 + Random.rand(0,3)*2; 157 | if (centralStructure.height > maxHeight) 158 | centralStructure.height = maxHeight; 159 | } else { 160 | centralStructure.height = centralStructure.width; 161 | } 162 | centralStructure.shape = Random.randomElementOf(['square', 'circle', 'cross']); 163 | return centralStructure; 164 | }, 165 | selectRooms: function(castle){ 166 | var numberOfRooms = 0; 167 | if (castle.general.size === 'small'){ 168 | // Only four rooms, one below each "Tower" 169 | numberOfRooms = 4; 170 | } else { 171 | // Number of rooms depends on available space 172 | /** 173 | * Base is 8 rooms 174 | * -1 If North Exit 175 | * +4 If no vertical connections between towers 176 | * +2 If no top connection between towers 177 | * +2 If no bottom connection between towers 178 | * -4 If central feature is too big 179 | */ 180 | numberOfRooms = 8; 181 | if (castle.entrances.northExit) 182 | numberOfRooms--; 183 | if (castle.towers.verticalConnections) 184 | numberOfRooms+= 4; 185 | if (castle.towers.horizontalConnections != 'both') 186 | numberOfRooms+= 2; 187 | if (castle.central.width > 10) 188 | numberOfRooms-= 4; 189 | if (numberOfRooms < 0) 190 | numberOfRooms = 0; 191 | } 192 | var rooms = []; 193 | this.currentRooms = {}; 194 | for (var i = 0; i < numberOfRooms; i++){ 195 | var room = this.selectRoom(rooms); 196 | this.fillRoom(room); 197 | rooms.push(room); 198 | } 199 | return rooms; 200 | }, 201 | fillRoom: function(room){ 202 | switch (room.type){ 203 | case 'livingQuarters': 204 | room.freeSpace = Random.rand(0, 50); 205 | break; 206 | case 'guestRoom': 207 | room.beds = Random.rand(1,2); 208 | room.mirror = Random.chance(50); 209 | room.piano = Random.chance(30); 210 | room.fireplace = Random.chance(50); 211 | break; 212 | case 'storage': 213 | room.filled = Random.rand(60,90); 214 | room.barrels = Random.rand(0,room.filled); 215 | room.boxes = room.filled - room.barrels; 216 | break; 217 | case 'diningRoom': 218 | room.luxury = Random.rand(1,3); 219 | room.fireplace = Random.chance(50); 220 | break; 221 | case 'kitchen': 222 | room.filled = Random.rand(0,20); 223 | room.barrels = Random.rand(0,room.filled); 224 | room.boxes = room.filled - room.barrels; 225 | room.hasOven = Random.chance(50); 226 | room.isNextTo = 'diningRoom'; 227 | break; 228 | case 'throneRoom': 229 | room.hasCarpet = Random.chance(70); 230 | room.hasMagicCarpet = Random.chance(20); 231 | room.linedWithColumns = Random.chance(30); 232 | room.linedWithTorches = Random.chance(70); 233 | room.hasSecondaryThrone = Random.chance(50); 234 | room.hasMagicOrb = Random.chance(50); 235 | room.placeNorth = true; 236 | room.southRoom = 'throneHall'; 237 | room.linkeable = false; 238 | room.isBig = true; 239 | room.level = 2; 240 | break; 241 | case 'lordQuarters': 242 | room.piano = Random.chance(50); 243 | room.clock = Random.chance(50); 244 | room.bookshelf = Random.chance(70); 245 | room.fireplace = Random.chance(80); 246 | room.placeNorth = true; 247 | room.isBig = true; 248 | break; 249 | case 'hall': 250 | room.torches = Random.chance(50); 251 | room.plants = Random.chance(50); 252 | room.columns = Random.chance(50); 253 | room.fountains = Random.chance(50); 254 | break; 255 | } 256 | }, 257 | selectRoom: function(rooms){ 258 | /** 259 | * Throne Room - Required if the castle is Royal 260 | * Castle Lord Room - Required 261 | * Staff Living Quarters - At least one 262 | * Dining rooms - At least one 263 | * Kitchen - At least one 264 | * Forge - Optional, only one 265 | * Prison Cells - Optional 266 | * Dungeon - Optional 267 | * Hall - Optional 268 | * Library - Optional 269 | */ 270 | var room = {}; 271 | if (this.generationParams.royal && !this.currentRooms.throneRoom){ 272 | room.type = 'throneRoom'; 273 | } else if (!this.currentRooms.lordQuarters){ 274 | room.type = 'lordQuarters'; 275 | } else if (!this.currentRooms.diningRoom){ 276 | room.type = 'diningRoom'; 277 | } else if (!this.currentRooms.kitchen){ 278 | room.type = 'kitchen'; 279 | } else if (!this.currentRooms.livingQuarters){ 280 | room.type = 'livingQuarters'; 281 | } else { 282 | var possibleRooms = ['livingQuarters', 'diningRoom', 'kitchen', /*'prison', 'dungeon',*/ 'hall', 'guestRoom', 'library']; 283 | /*if (!this.currentRooms.forge){ 284 | possibleRooms.push('forge'); 285 | }*/ 286 | room.type = Random.randomElementOf(possibleRooms); 287 | } 288 | this.currentRooms[room.type] = true; 289 | return room; 290 | } 291 | } 292 | 293 | module.exports = CastleStructureGenerator; 294 | },{"./Random":"/home/administrator/git/ultimacastlegen/src/Random.js"}],"/home/administrator/git/ultimacastlegen/src/Cells.js":[function(require,module,exports){ 295 | module.exports = { 296 | WALL: 'wall', 297 | FLOOR: 'floor', 298 | FLOOR_2: 'floor2', 299 | GRASS_1: 'grass1', 300 | GRASS_2: 'grass2', 301 | TREE: 'tree', 302 | DOOR: 'door', 303 | CROSS_WINDOW: 'crossWindow', 304 | FOUNTAIN: 'fountain', 305 | WATER: 'water', 306 | WELL: 'well', 307 | DIRT: 'dirt', 308 | FIREPLACE: 'fireplace', 309 | COLUMN: 'column', 310 | R_TORCH: 'rTorch', 311 | L_TORCH: 'lTorch', 312 | THRONE: 'throne', 313 | ORB: 'orb', 314 | SMALL_TABLE: 'smallTable', 315 | MAGIC_CARPET: 'magicCarpet', 316 | BED_1: 'bed1', 317 | BED_2: 'bed2', 318 | LOCKER: 'locker', 319 | JAR_TABLE: 'jarTable', 320 | BARREL: 'barrel', 321 | PLANT: 'plant', 322 | MIRROR: 'mirror', 323 | SHELF: 'shelf', 324 | PIANO: 'piano', 325 | R_CHAIR: 'rChair', 326 | L_CHAIR: 'lChair', 327 | S_CHAIR: 'sChair', 328 | N_CHAIR: 'nChair', 329 | S_PIANO: 'sPiano', 330 | CLOCK: 'clock', 331 | SHELF_2: 'shelf2', 332 | R_TABLE: 'rTable', 333 | L_TABLE: 'lTable', 334 | C_TABLE_1: 'cTable1', 335 | C_TABLE_2: 'cTable2', 336 | C_TABLE_3: 'cTable3', 337 | C_TABLE_4: 'cTable4', 338 | WALL_FIREPLACE: 'firewall', 339 | GRILL: 'grill', 340 | OVEN: 'oven', 341 | WINE_BARREL: 'wineBarrel', 342 | LIBRARY_1: 'library1', 343 | LIBRARY_2: 'library2', 344 | CHEST: 'chest', 345 | FAKE_WALL: 'fakeWall' 346 | }; 347 | },{}],"/home/administrator/git/ultimacastlegen/src/MapGenerator.js":[function(require,module,exports){ 348 | var CastleStructureGenerator = require('./CastleStructureGenerator'); 349 | var RoomsGenerator = require('./RoomsGenerator'); 350 | var RoomBuilder = require('./RoomBuilder'); 351 | var TerrainGenerator = require('./TerrainGenerator'); 352 | 353 | function MapGenerator(){ 354 | 355 | }; 356 | 357 | MapGenerator.prototype = { 358 | generateMap: function(generationParams){ 359 | this.generationParams = generationParams; 360 | this.castleStructureGenerator = new CastleStructureGenerator(); 361 | this.roomsGenerator = new RoomsGenerator(); 362 | this.terrainGenerator = new TerrainGenerator(); 363 | this.roomBuilder = new RoomBuilder(); 364 | 365 | var castle = {}; 366 | while (true){ 367 | castle.structure = this.castleStructureGenerator.generateMap(generationParams); 368 | castle.rooms = this.roomsGenerator.generateMap(castle.structure, generationParams); 369 | if (castle.rooms != false) 370 | break; 371 | } 372 | 373 | castle.map = this.terrainGenerator.generateTerrain(generationParams); 374 | this.roomBuilder.buildRooms(castle.map, castle.rooms); 375 | return castle; 376 | } 377 | } 378 | 379 | module.exports = MapGenerator; 380 | },{"./CastleStructureGenerator":"/home/administrator/git/ultimacastlegen/src/CastleStructureGenerator.js","./RoomBuilder":"/home/administrator/git/ultimacastlegen/src/RoomBuilder.js","./RoomsGenerator":"/home/administrator/git/ultimacastlegen/src/RoomsGenerator.js","./TerrainGenerator":"/home/administrator/git/ultimacastlegen/src/TerrainGenerator.js"}],"/home/administrator/git/ultimacastlegen/src/Random.js":[function(require,module,exports){ 381 | module.exports = { 382 | rand: function(low, hi){ 383 | return Math.floor(Math.random() * (hi - low + 1))+low; 384 | }, 385 | randomElementOf: function(array){ 386 | return array[Math.floor(Math.random()*array.length)]; 387 | }, 388 | chance: function(chance){ 389 | return this.rand(0,100) <= chance; 390 | } 391 | } 392 | },{}],"/home/administrator/git/ultimacastlegen/src/RoomBuilder.js":[function(require,module,exports){ 393 | var Random = require('./Random'); 394 | var Arrays = require('./Arrays'); 395 | var Cells = require('./Cells'); 396 | 397 | function RoomBuilder(){}; 398 | 399 | RoomBuilder.prototype = { 400 | buildRooms: function(map, rooms){ 401 | this.map = map; 402 | rooms = rooms.sort(function(a,b){var aLevel = a.level ? a.level : 0; var bLevel = b.level ? b.level : 0; return aLevel - bLevel;}); 403 | for (var i = 0; i < rooms.length; i++){ 404 | var buildFunction = this["build_"+rooms[i].type]; 405 | if (buildFunction){ 406 | buildFunction.call(this, rooms[i]); 407 | } else 408 | this.buildRoom(rooms[i]); 409 | } 410 | for (var i = 0; i < rooms.length; i++){ 411 | this.placeDoors(rooms[i]); 412 | } 413 | for (var x = 0; x < 32; x++){ 414 | for (var y = 0; y < 32; y++){ 415 | if (this.map[x][y] === Cells.CHEST){ 416 | // Place secret door nearby 417 | if (x > 16 && Random.chance(90) && this.map[x+1][y] === Cells.WALL){ 418 | this.map[x+1][y] = Cells.FAKE_WALL; 419 | } 420 | if (x < 16 && Random.chance(90) && this.map[x-1][y] === Cells.WALL){ 421 | this.map[x-1][y] = Cells.FAKE_WALL; 422 | } 423 | } 424 | } 425 | } 426 | 427 | }, 428 | buildRoom: function(room){ 429 | for (var x = room.x; x < room.x + room.width; x++){ 430 | for (var y = room.y; y < room.y + room.height; y++){ 431 | if (x == room.x || x == room.x + room.width - 1 || y == room.y || y == room.y + room.height - 1){ 432 | this.map[x][y] = Cells.WALL; 433 | } else { 434 | this.map[x][y] = Cells.FLOOR; 435 | } 436 | } 437 | } 438 | }, 439 | build_rooms: function(room){ // Foundations 440 | for (var x = room.x; x < room.x + room.width; x++){ 441 | for (var y = room.y; y < room.y + room.height; y++){ 442 | if (x == room.x || x == room.x + room.width - 1 || y == room.y || y == room.y + room.height - 1){ 443 | this.map[x][y] = Cells.WALL; 444 | } else { 445 | this.map[x][y] = Random.chance(80) ? Cells.FLOOR : Cells.CHEST; 446 | } 447 | } 448 | } 449 | }, 450 | build_tower: function(room){ 451 | if (room.features.walls.north == 'exit') 452 | room.northDoors = [{position: room.x + Math.floor(room.width/2), cell: Cells.DOOR}]; 453 | if (room.features.walls.south == 'exit') 454 | room.southDoors = [{position: room.x + Math.floor(room.width/2), cell: Cells.DOOR}]; 455 | if (room.features.walls.east == 'exit') 456 | room.eastDoors = [{position: room.y + Math.floor(room.height/2), cell: Cells.DOOR}]; 457 | if (room.features.walls.west == 'exit') 458 | room.westDoors = [{position: room.y + Math.floor(room.height/2), cell: Cells.DOOR}]; 459 | 460 | for (var x = room.x; x < room.x + room.width; x++){ 461 | for (var y = room.y; y < room.y + room.height; y++){ 462 | var wall = false; 463 | if (x == room.x){ 464 | wall = room.features.walls.west; 465 | } else if (x == room.x + room.width - 1){ 466 | wall = room.features.walls.east; 467 | } else if (y == room.y){ 468 | wall = room.features.walls.north; 469 | } else if (y == room.y + room.height - 1){ 470 | wall = room.features.walls.south; 471 | } 472 | if (wall){ 473 | if (wall === 'solid'){ 474 | this.map[x][y] = Cells.WALL; 475 | } else if (wall === 'crossWindows'){ 476 | if (x === room.x + Math.floor(room.width/2) || 477 | y === room.y + Math.floor(room.height/2) ) 478 | this.map[x][y] = Cells.CROSS_WINDOW; 479 | else 480 | this.map[x][y] = Cells.WALL; 481 | } else if (wall === 'exit'){ 482 | this.map[x][y] = Cells.WALL; 483 | } else if (wall === 'open'){ 484 | this.map[x][y] = Cells.FLOOR; 485 | } 486 | } else { 487 | this.map[x][y] = Cells.FLOOR; 488 | } 489 | } 490 | } 491 | }, 492 | build_mainHall: function(room){ 493 | for (var x = room.x; x < room.x + room.width; x++){ 494 | for (var y = room.y; y < room.y + room.height; y++){ 495 | if (x == room.x || x == room.x + room.width - 1 || y == room.y || y == room.y + room.height - 1){ 496 | this.map[x][y] = Cells.WALL; 497 | } else { 498 | if (room.features.hasSpecialFloor) 499 | this.map[x][y] = Cells.FLOOR_2; 500 | else 501 | this.map[x][y] = Cells.FLOOR; 502 | } 503 | } 504 | } 505 | var midx = room.x+Math.floor(room.width/2); 506 | var midy = room.y+Math.floor(room.height/2); 507 | if (room.features.centralFireplace){ 508 | if (room.features.centralFeature){ 509 | if (room.width > 6 && room.height > 6){ 510 | this.map[midx - 1][midy - 1] = Cells.FIREPLACE; 511 | this.map[midx + 1][midy - 1] = Cells.FIREPLACE; 512 | this.map[midx - 1][midy + 1] = Cells.FIREPLACE; 513 | this.map[midx + 1][midy + 1] = Cells.FIREPLACE; 514 | } 515 | } else { 516 | this.map[midx][midy] = Cells.FIREPLACE; 517 | } 518 | } 519 | if (room.features.cornerFireplaces && (room.height > 5 || room.width > 5)){ 520 | if (!room.features.centralFireplace || (room.height > 7 || room.width > 7)){ 521 | this.map[room.x+1][room.y+1] = Cells.FIREPLACE; 522 | this.map[room.x+1][room.y+room.height-2] = Cells.FIREPLACE; 523 | this.map[room.x+room.width-2][room.y+1] = Cells.FIREPLACE; 524 | this.map[room.x+room.width-2][room.y+room.height-2] = Cells.FIREPLACE; 525 | } 526 | } 527 | if (room.features.centralFeature === 'fountain'){ 528 | this.map[midx][midy] = Cells.FOUNTAIN; 529 | } 530 | //TODO: Implement room.features.shape === 'circle' 531 | //TODO: Implement room.features.shape === 'cross' 532 | }, 533 | build_courtyard: function(room){ 534 | for (var x = room.x; x < room.x + room.width; x++){ 535 | for (var y = room.y; y < room.y + room.height; y++){ 536 | if (x == room.x || x == room.x + room.width - 1 || y == room.y || y == room.y + room.height - 1){ 537 | this.map[x][y] = Cells.WALL; 538 | } else { 539 | if (Random.chance(80)) 540 | this.map[x][y] = Cells.GRASS_1; 541 | else if (Random.chance(80)) 542 | this.map[x][y] = Cells.GRASS_2; 543 | else 544 | this.map[x][y] = Cells.TREE; 545 | } 546 | } 547 | } 548 | var midx = room.x+Math.floor(room.width/2); 549 | var midy = room.y+Math.floor(room.height/2); 550 | var connectionTerrain = room.features.connectionWithRooms.terrain; 551 | if (room.features.connectionWithRooms.type === 'radial'){ 552 | if (room.width > 6) for (var x = room.x + 1; x < room.x + room.width - 1; x++){ 553 | this.map[x][midy-1] = connectionTerrain; 554 | this.map[x][midy] = connectionTerrain; 555 | this.map[x][midy+1] = connectionTerrain; 556 | } 557 | if (room.height > 6) for (var y = room.y + 1; y < room.y + room.height - 1; y++){ 558 | this.map[midx-1][y] = connectionTerrain; 559 | this.map[midx][y] = connectionTerrain; 560 | this.map[midx+1][y] = connectionTerrain; 561 | } 562 | if (room.width > 7 && room.width > 7) { 563 | this.map[midx - 2][midy - 2] = connectionTerrain; 564 | this.map[midx - 2][midy + 2] = connectionTerrain; 565 | this.map[midx + 2][midy - 2] = connectionTerrain; 566 | this.map[midx + 2][midy + 2] = connectionTerrain; 567 | } 568 | } else if (room.features.connectionWithRooms.type === 'around'){ 569 | for (var x = room.x + 1; x < room.x + room.width - 1; x++){ 570 | this.map[x][room.y+1] = connectionTerrain; 571 | this.map[x][room.y+room.height - 2] = connectionTerrain; 572 | } 573 | for (var y = room.y + 1; y < room.y + room.height - 1; y++){ 574 | this.map[room.x+1][y] = connectionTerrain; 575 | this.map[room.x+room.width - 2][y] = connectionTerrain; 576 | } 577 | if (room.width > 7 && room.height > 7){ 578 | this.map[room.x+2][room.y+2] = connectionTerrain; 579 | this.map[room.x+2][room.y+room.height-3] = connectionTerrain; 580 | this.map[room.x+room.width-3][room.y+2] = connectionTerrain; 581 | this.map[room.x+room.width-3][room.y+room.height-3] = connectionTerrain; 582 | } 583 | } 584 | if (room.features.hasSmallLake && room.height > 6 && room.width > 6){ 585 | this.map[midx-1][midy] = Cells.WATER; 586 | this.map[midx][midy+1] = Cells.WATER; 587 | this.map[midx][midy] = Cells.WATER; 588 | this.map[midx][midy-1] = Cells.WATER; 589 | this.map[midx+1][midy] = Cells.WATER; 590 | 591 | if (Random.chance(60) && room.height > 7 && room.width > 7){ 592 | this.map[midx-1][midy+1] = Cells.WATER; 593 | this.map[midx-1][midy-1] = Cells.WATER; 594 | this.map[midx+1][midy+1] = Cells.WATER; 595 | this.map[midx+1][midy-1] = Cells.WATER; 596 | } 597 | } 598 | if (room.features.centralFeature === 'fountain'){ 599 | this.map[midx][midy] = Cells.FOUNTAIN; 600 | } else if (room.features.centralFeature === 'well'){ 601 | this.map[midx][midy] = Cells.WELL; 602 | } 603 | if (room.features.additionalFountains){ 604 | if (room.features.fountainSymmetry === 'x' && room.height > 9){ 605 | this.map[midx][midy + 2] = Cells.FOUNTAIN; 606 | this.map[midx][midy - 2] = Cells.FOUNTAIN; 607 | } else if (room.features.fountainSymmetry === 'y' && room.width > 9){ 608 | this.map[midx - 2][midy] = Cells.FOUNTAIN; 609 | this.map[midx + 2][midy] = Cells.FOUNTAIN; 610 | } else if (room.features.fountainSymmetry === 'full' && room.width > 9 && room.height > 9){ 611 | this.map[midx - 2][midy - 2] = Cells.FOUNTAIN; 612 | this.map[midx + 2][midy - 2] = Cells.FOUNTAIN; 613 | this.map[midx - 2][midy + 2] = Cells.FOUNTAIN; 614 | this.map[midx + 2][midy + 2] = Cells.FOUNTAIN; 615 | } 616 | } 617 | }, 618 | build_entranceHall: function(room){ 619 | var halfOpening = Math.floor((room.width - room.features.openingWidth) / 2); 620 | var halfClosing = Math.floor((room.width - room.features.closingWidth) / 2); 621 | for (var x = room.x; x < room.x + room.width; x++){ 622 | for (var y = room.y; y < room.y + room.height; y++){ 623 | if (x == room.x || x == room.x + room.width - 1){ 624 | this.map[x][y] = Cells.WALL; 625 | } else if ((room.features.isMain && y == room.y) || (!room.features.isMain && y == room.y + room.height - 1) ){ 626 | if (x >= room.x+halfOpening && x <= room.x + room.width - halfOpening-1){ 627 | this.map[x][y] = Cells.FLOOR; 628 | } else { 629 | this.map[x][y] = Cells.WALL; 630 | } 631 | } else if ((!room.features.isMain && y == room.y) || (room.features.isMain && y == room.y + room.height - 1) ){ 632 | if (x >= room.x+halfClosing && x <= room.x + room.width - halfClosing-1){ 633 | this.map[x][y] = Cells.FLOOR; 634 | } else { 635 | this.map[x][y] = Cells.WALL; 636 | } 637 | } else { 638 | this.map[x][y] = Cells.FLOOR; 639 | } 640 | } 641 | } 642 | }, 643 | build_entrance: function(room){ 644 | for (var x = room.x; x < room.x + room.width; x++){ 645 | for (var y = room.y; y < room.y + room.height; y++){ 646 | this.map[x][y] = Cells.FLOOR; 647 | } 648 | } 649 | }, 650 | build_throneRoom: function(room){ 651 | // Pad room 652 | var modifiedWidth = false; 653 | if (room.width % 2 == 0){ 654 | room.width--; 655 | for (var y = room.y; y < room.y + room.height; y++){ 656 | this.map[room.x+room.width-1][y] = Cells.WALL; 657 | } 658 | modifiedWidth = true; 659 | } 660 | this.buildRoom(room); 661 | var midx = room.x+Math.floor(room.width/2); 662 | // Place throne 663 | this.map[midx][room.y+1] = Cells.THRONE; 664 | if (room.features.hasSecondaryThrone) 665 | this.map[midx-1][room.y+1] = Cells.SMALL_TABLE; 666 | if (room.features.hasMagicOrb) 667 | this.map[midx+1][room.y+1] = Cells.ORB; 668 | if (room.features.hasMagicCarpet) 669 | this.map[midx][room.y+2] = Cells.MAGIC_CARPET; 670 | 671 | var columnsPosition = Random.rand(2, Math.floor(room.width/2) - 2); 672 | var columnsSpacing = Random.rand(2,3); 673 | var torchesSpacing = Random.rand(2,3); 674 | var placeColumns = room.features.linedWithColumns && room.width >= 9; 675 | var placeTorches = room.features.linedWithTorches && room.width >= 9; 676 | var placeSmallCarpet = true; 677 | var placeBigCarpet = room.width >= 7; 678 | var carpetStart = room.features.hasMagicCarpet ? room.y + 3 : room.y + 2; 679 | 680 | for (var y = room.y+1; y < room.y + room.height - 1; y++){ 681 | // Place carpet 682 | if (room.features.hasCarpet) if (y >= carpetStart){ 683 | if (placeSmallCarpet) 684 | this.map[midx][y] = Cells.FLOOR_2; 685 | if (placeBigCarpet){ 686 | this.map[midx-1][y] = Cells.FLOOR_2; 687 | this.map[midx+1][y] = Cells.FLOOR_2; 688 | } 689 | } 690 | // Place columns 691 | if (placeColumns) if (y % columnsSpacing == 0){ 692 | this.map[room.x+columnsPosition][y] = Cells.COLUMN; 693 | this.map[room.x+room.width-columnsPosition-1][y] = Cells.COLUMN; 694 | } 695 | // Place torches 696 | if (placeTorches) if ((y+1) % torchesSpacing == 0){ 697 | this.map[room.x+1][y] = Cells.L_TORCH; 698 | this.map[room.x+room.width-2][y] = Cells.R_TORCH; 699 | } 700 | } 701 | // Place door 702 | room.southDoors = [{position: midx, cell: Cells.DOOR}]; 703 | if (modifiedWidth){ 704 | room.width++; 705 | } 706 | }, 707 | build_livingQuarters: function(room){ 708 | this.buildLivingQuarters(room, 'simple'); 709 | }, 710 | build_guestRoom: function(room){ 711 | this.buildLivingQuarters(room, 'guestRoom'); 712 | }, 713 | build_lordQuarters: function(room){ 714 | this.buildLivingQuarters(room, 'lord'); 715 | }, 716 | buildLivingQuarters: function(room, quartersType){ 717 | this.buildRoom(room); 718 | var leftSize = room.width >= 6 ? 3 : 2; 719 | var rightSize = room.width >= 8 ? room.width > 8 ? 3 : 2 : 0; 720 | var topSize = room.height >= 6 ? 3 : 2; 721 | var bottomSize = room.height >= 8 ? room.height > 8 ? 3 : 2 : 0; 722 | 723 | if (Random.chance(50)){ 724 | // Flip sides 725 | var temp = rightSize; 726 | rightSize = leftSize; 727 | leftSize = temp; 728 | } 729 | room.placedElements = {}; 730 | // Place furniture 731 | for (var y = room.y+1; y < room.y + room.height - 1; y++){ 732 | if (leftSize){ 733 | var mainBlock = this.selectLivingQuartersBlock(room, y, quartersType); 734 | if (mainBlock) 735 | this.placeBlock(mainBlock, room.x+1, y, leftSize, false, quartersType); 736 | } 737 | if (rightSize){ 738 | var mainBlock = this.selectLivingQuartersBlock(room, y, quartersType); 739 | if (mainBlock) 740 | this.placeBlock(mainBlock, room.x+room.width-rightSize-1, y, rightSize, true, quartersType); 741 | } 742 | } 743 | for (var x = room.x+3; x < room.x + room.width - 3; x++){ 744 | if (topSize){ 745 | var mainBlock = this.selectLivingQuartersVBlock(room, quartersType); 746 | if (mainBlock) 747 | this.placeVBlock(mainBlock, x, room.y+1, topSize, false, quartersType); 748 | } 749 | if (bottomSize){ 750 | var mainBlock = this.selectLivingQuartersVBlock(room, quartersType); 751 | if (mainBlock) 752 | this.placeVBlock(mainBlock, x, room.y+room.height-bottomSize-1, bottomSize, true, quartersType); 753 | } 754 | } 755 | 756 | if (quartersType === 'lord' || quartersType === 'guestRoom'){ 757 | // Place bed(s) for royal and guest quarters 758 | var numberOfBeds = quartersType === 'guestRoom' ? 2 : 1; 759 | for (var i = 0; i < numberOfBeds; i++){ 760 | var y = Random.rand(room.y+1, room.y + room.height - 2 - (quartersType === 'lord' ? 1 : 0)); 761 | if (leftSize){ 762 | this.placeBlock('bed', room.x+1, y, leftSize, false, quartersType); 763 | } else if (rightSize){ 764 | this.placeBlock('bed', room.x+room.width-rightSize-1, y, rightSize, true, quartersType); 765 | } 766 | } 767 | this.addWallFireplace(room); 768 | } 769 | this.addTorchesToRoom(room); 770 | 771 | }, 772 | selectLivingQuartersBlock: function(room, y, quartersType){ 773 | // Beds? 774 | if (quartersType === 'simple' && ((y%2 == 0 && Random.chance(70)) || (y%2 != 0 && Random.chance(30)))){ 775 | return 'bed'; 776 | } 777 | // Nothing? 778 | if (Random.chance(20)){ 779 | return false; 780 | } 781 | // Table with chair 782 | if (!room.placedElements["tableAndChair"] && (quartersType === 'lord' || quartersType === 'guestRoom') && Random.chance(40)){ 783 | room.placedElements["tableAndChair"] = true; 784 | return 'tableAndChair' 785 | } 786 | // Kitchen Table 787 | if (!room.placedElements["diningTable"] && quartersType === 'kitchen' && y > room.y+1 && y < room.y+room.height - 2 && Random.chance(40)){ 788 | room.placedElements["diningTable"] = true; 789 | return 'diningTable' 790 | } 791 | var additionalElements = false; 792 | switch (quartersType){ 793 | case 'simple': 794 | additionalElements = [Cells.BARREL, Cells.LOCKER, Cells.PLANT, Cells.SMALL_TABLE]; 795 | break; 796 | case 'guestRoom': 797 | additionalElements = [Cells.BARREL, Cells.SHELF, Cells.PLANT, Cells.JAR_TABLE]; 798 | break; 799 | case 'lord': 800 | additionalElements = [Cells.SHELF, Cells.PLANT, Cells.JAR_TABLE]; 801 | break; 802 | case 'kitchen': 803 | additionalElements = [Cells.WINE_BARREL, Cells.OVEN, Cells.BARREL, Cells.GRILL, 'tableAndChair']; 804 | break; 805 | case 'library': 806 | additionalElements = ['bookshelf', 'tableAndChair', Cells.PLANT, Cells.SHELF_2]; 807 | break; 808 | case 'hall': 809 | additionalElements = [Cells.PLANT]; 810 | if (!room.placedElements[Cells.FOUNTAIN]) 811 | additionalElements.push(Cells.FOUNTAIN); 812 | break; 813 | 814 | } 815 | var element = Random.randomElementOf(additionalElements); 816 | room.placedElements[element] = true; 817 | return element; 818 | }, 819 | selectLivingQuartersVBlock: function(room, quartersType){ 820 | if (!room.placedElements["tableAndPiano"] && quartersType === 'lord' && Random.chance(40)){ 821 | room.placedElements["tableAndPiano"] = true; 822 | return 'tableAndPiano' 823 | } 824 | if (Random.chance(60)){ 825 | return false; 826 | } 827 | var additionalElements = false; 828 | switch (quartersType){ 829 | case 'simple': 830 | additionalElements = [Cells.BARREL, Cells.LOCKER, Cells.PLANT, Cells.SMALL_TABLE]; 831 | break; 832 | case 'guestRoom': 833 | additionalElements = [Cells.BARREL, Cells.SHELF, Cells.SHELF_2, Cells.PLANT, Cells.JAR_TABLE]; 834 | if (!room.placedElements[Cells.MIRROR]) 835 | additionalElements.push(Cells.MIRROR); 836 | break; 837 | case 'lord': 838 | additionalElements = [Cells.SHELF, Cells.SHELF_2, Cells.PLANT, Cells.JAR_TABLE]; 839 | if (!room.placedElements[Cells.MIRROR]) 840 | additionalElements.push(Cells.MIRROR); 841 | if (!room.placedElements[Cells.CLOCK]) 842 | additionalElements.push(Cells.CLOCK); 843 | break; 844 | case 'kitchen': 845 | additionalElements = [Cells.WINE_BARREL, Cells.OVEN, Cells.BARREL, Cells.GRILL]; 846 | break; 847 | case 'library': 848 | additionalElements = [Cells.PLANT, Cells.SHELF_2]; 849 | if (!room.placedElements[Cells.CLOCK]) 850 | additionalElements.push(Cells.CLOCK); 851 | break; 852 | case 'hall': 853 | additionalElements = [Cells.PLANT]; 854 | if (!room.placedElements[Cells.FOUNTAIN]) 855 | additionalElements.push(Cells.FOUNTAIN); 856 | break; 857 | 858 | } 859 | var element = Random.randomElementOf(additionalElements); 860 | room.placedElements[element] = true; 861 | return element; 862 | }, 863 | placeVBlock: function(type, x,y,size, flip, quartersType){ 864 | switch (type){ 865 | case 'tableAndChair': 866 | if (!flip){ 867 | this.map[x][y] = Cells.SMALL_TABLE; 868 | this.map[x][y+1] = Cells.N_CHAIR; 869 | } else { 870 | this.map[x][y+size-1] = Cells.SMALL_TABLE; 871 | this.map[x][y+size-2] = Cells.S_CHAIR; 872 | } 873 | break; 874 | case 'tableAndPiano': 875 | if (!flip){ 876 | this.map[x][y] = Cells.S_PIANO; 877 | this.map[x][y+1] = Cells.N_CHAIR; 878 | } else { 879 | this.map[x][y+size-1] = Cells.PIANO; 880 | this.map[x][y+size-2] = Cells.S_CHAIR; 881 | } 882 | break; 883 | default: 884 | var y = flip ? y+size-1 : y; 885 | this.map[x][y] = type; 886 | break; 887 | } 888 | }, 889 | placeBlock: function(type, x,y,size, flip, quartersType){ 890 | switch (type){ 891 | case 'bed': 892 | if (!flip){ 893 | this.map[x][y] = Cells.BED_1; 894 | this.map[x+1][y] = Cells.BED_2; 895 | if (size > 2 && quartersType === 'simple'){ 896 | this.map[x+2][y] = Random.chance(70) ? Cells.LOCKER : Cells.SMALL_TABLE; 897 | } 898 | } else { 899 | if (size > 2){ 900 | this.map[x+1][y] = Cells.BED_1; 901 | this.map[x+2][y] = Cells.BED_2; 902 | if (quartersType === 'simple') 903 | this.map[x][y] = Random.chance(70) ? Cells.LOCKER : Cells.SMALL_TABLE; 904 | } else { 905 | this.map[x][y] = Cells.BED_1; 906 | this.map[x+1][y] = Cells.BED_2; 907 | } 908 | } 909 | break; 910 | case 'tableAndChair': 911 | if (!flip){ 912 | this.map[x][y] = Cells.SMALL_TABLE; 913 | this.map[x+1][y] = Cells.L_CHAIR; 914 | } else { 915 | this.map[x+size-1][y] = Cells.SMALL_TABLE; 916 | this.map[x+size-2][y] = Cells.R_CHAIR; 917 | } 918 | break; 919 | case 'tableAndPiano': 920 | var x = flip ? x+size-1 : x; 921 | if (this.map[x][y-1] !== Cells.BED_2 && this.map[x][y-1] !== Cells.BED_2){ 922 | this.map[x][y-1] = Cells.S_CHAIR; 923 | this.map[x][y] = Cells.PIANO; 924 | } 925 | break; 926 | case 'diningTable': 927 | if (size == 1){ 928 | this.map[x][y] = Cells.SMALL_TABLE; 929 | } else { 930 | this.map[x][y] = Cells.L_TABLE; 931 | this.map[x+size-1][y] = Cells.R_TABLE; 932 | for (var i = 0; i < size-2; i++){ 933 | switch (Random.rand(0,3)){ 934 | case 0: this.map[x+1+i][y] = Cells.C_TABLE_1; break; 935 | case 1: this.map[x+1+i][y] = Cells.C_TABLE_2; break; 936 | case 2: this.map[x+1+i][y] = Cells.C_TABLE_3; break; 937 | case 3: this.map[x+1+i][y] = Cells.C_TABLE_4; break; 938 | } 939 | } 940 | } 941 | break; 942 | case 'bookshelf': 943 | if (flip){ 944 | this.map[x+size-2][y] = Cells.LIBRARY_1; 945 | this.map[x+size-1][y] = Cells.LIBRARY_2; 946 | } else { 947 | this.map[x][y] = Cells.LIBRARY_1; 948 | this.map[x+1][y] = Cells.LIBRARY_2; 949 | } 950 | break; 951 | case 'upChairs': 952 | if (size === 1){ 953 | this.map[x][y] = Cells.N_CHAIR; 954 | } else for (var i = x; i < x+size-1; i++){ 955 | if (Random.chance(80)) this.map[i][y] = Cells.N_CHAIR; 956 | } 957 | break; 958 | case 'downChairs': 959 | if (size === 1){ 960 | this.map[x][y] = Cells.S_CHAIR; 961 | } else for (var i = x+1; i < x+size; i++){ 962 | if (Random.chance(80)) this.map[i][y] = Cells.S_CHAIR; 963 | } 964 | break; 965 | default: 966 | var x = flip ? x+size-1 : x; 967 | this.map[x][y] = type; 968 | break; 969 | } 970 | }, 971 | placeDoors: function(room){ 972 | if (room.northDoors) for (var i = 0; i < room.northDoors.length; i++){ 973 | var door = room.northDoors[i]; 974 | if (door.cell === Cells.DOOR){ 975 | this.tryClear(door.position,room.y-1) 976 | this.tryClear(door.position,room.y+1) 977 | } 978 | if (this.map[door.position][room.y] === Cells.WALL) 979 | this.map[door.position][room.y] = door.cell; 980 | } 981 | if (room.southDoors) for (var i = 0; i < room.southDoors.length; i++){ 982 | var door = room.southDoors[i]; 983 | if (door.cell === Cells.DOOR){ 984 | this.tryClear(door.position,room.y+room.height); 985 | this.tryClear(door.position,room.y+room.height-2); 986 | } 987 | if (this.map[door.position][room.y+room.height-1] === Cells.WALL) 988 | this.map[door.position][room.y+room.height-1] = door.cell; 989 | } 990 | if (room.westDoors) for (var i = 0; i < room.westDoors.length; i++){ 991 | var door = room.westDoors[i]; 992 | if (door.cell === Cells.DOOR){ 993 | this.tryClear(room.x-1,door.position); 994 | this.tryClear(room.x+1,door.position); 995 | } 996 | if (this.map[room.x][door.position] === Cells.WALL) 997 | this.map[room.x][door.position] = door.cell; 998 | } 999 | 1000 | if (room.eastDoors) for (var i = 0; i < room.eastDoors.length; i++){ 1001 | var door = room.eastDoors[i]; 1002 | if (door.cell === Cells.DOOR){ 1003 | this.tryClear(room.x+room.width,door.position); 1004 | this.tryClear(room.x+room.width-2,door.position); 1005 | } 1006 | if (this.map[room.x+room.width-1][door.position] === Cells.WALL) 1007 | this.map[room.x+room.width-1][door.position] = door.cell; 1008 | } 1009 | }, 1010 | tryClear: function(x,y){ 1011 | if (!this.isFloor(x,y)){ 1012 | if (this.map[x][y] === Cells.BED_2) 1013 | this.map[x-1][y] = Cells.FLOOR; 1014 | if (this.map[x][y] === Cells.BED_1) 1015 | this.map[x+1][y] = Cells.FLOOR; 1016 | if (this.map[x][y] === Cells.LIBRARY_2) 1017 | this.map[x-1][y] = Cells.FLOOR; 1018 | if (this.map[x][y] === Cells.LIBRARY_1) 1019 | this.map[x+1][y] = Cells.FLOOR; 1020 | if (this.map[x][y] === Cells.R_TABLE){ 1021 | if (this.map[x-1][y] === Cells.L_TABLE){ 1022 | this.map[x-1][y] = Cells.SMALL_TABLE; 1023 | } else { 1024 | this.map[x-1][y] = Cells.R_TABLE; 1025 | } 1026 | } else if (this.map[x][y] === Cells.L_TABLE){ 1027 | if (this.map[x+1][y] === Cells.R_TABLE){ 1028 | this.map[x+1][y] = Cells.SMALL_TABLE; 1029 | } else { 1030 | this.map[x+1][y] = Cells.L_TABLE; 1031 | } 1032 | } 1033 | this.map[x][y] = Cells.FLOOR; 1034 | } 1035 | }, 1036 | isFloor: function(x,y){ 1037 | return this.map[x][y] === Cells.FLOOR || 1038 | this.map[x][y] === Cells.FLOOR_2 || 1039 | this.map[x][y] === Cells.DIRT || 1040 | this.map[x][y] === Cells.GRASS_1 || 1041 | this.map[x][y] === Cells.GRASS_2; 1042 | }, 1043 | isMultiTile: function(x,y){ 1044 | return this.map[x][y] === Cells.L_TABLE || 1045 | this.map[x][y] === Cells.R_TABLE || 1046 | this.map[x][y] === Cells.C_TABLE_1 || 1047 | this.map[x][y] === Cells.C_TABLE_2 || 1048 | this.map[x][y] === Cells.C_TABLE_3 || 1049 | this.map[x][y] === Cells.C_TABLE_4 || 1050 | this.map[x][y] === Cells.LIBRARY_1 || 1051 | this.map[x][y] === Cells.LIBRARY_2 || 1052 | this.map[x][y] === Cells.BED_1 || 1053 | this.map[x][y] === Cells.BED_2; 1054 | }, 1055 | build_diningRoom: function(room){ 1056 | var tableLength = room.width - 3; 1057 | this.buildRoom(room); 1058 | var leftSize = Random.chance(50); 1059 | room.placedElements = {}; 1060 | var location = Random.rand(room.x+1, room.x+room.width-tableLength-1); 1061 | for (var y = room.y+1; y < room.y + room.height - 1; y++){ 1062 | var phase = (y-room.y)%3; 1063 | if (phase == 1){ 1064 | if ((room.y+room.height)-y<4) 1065 | break; 1066 | location = Random.rand(room.x+1, room.x+room.width-tableLength-1); 1067 | } 1068 | // Place tables and chairs 1069 | switch(phase){ 1070 | case 0: 1071 | this.placeBlock('upChairs', location, y, tableLength, false, 'diningRoom'); 1072 | break; 1073 | case 1: 1074 | this.placeBlock('downChairs', location, y, tableLength, false, 'diningRoom'); 1075 | break; 1076 | case 2: 1077 | this.placeBlock('diningTable', location, y, tableLength, false, 'diningRoom'); 1078 | break; 1079 | } 1080 | } 1081 | this.addWallFireplace(room); 1082 | this.addTorchesToRoom(room); 1083 | }, 1084 | addTorchesToRoom: function(room){ 1085 | for (var y = room.y+1; y < room.y + room.height - 1; y++){ 1086 | if (Random.chance(30) && !this.isMultiTile(room.x+1, y)){ 1087 | this.map[room.x+1][y] = Cells.L_TORCH; 1088 | y++; 1089 | } 1090 | } 1091 | for (var y = room.y+1; y < room.y + room.height - 1; y++){ 1092 | if (Random.chance(30) && !this.isMultiTile(room.x+room.width-2, y)){ 1093 | this.map[room.x+room.width-2][y] = Cells.R_TORCH; 1094 | y++; 1095 | } 1096 | } 1097 | }, 1098 | addWallFireplace: function(room){ 1099 | var wall = room.northDoors; 1100 | if (!wall) { 1101 | wall = []; 1102 | room.northDoors = wall; 1103 | } 1104 | wall.push({cell: Cells.WALL_FIREPLACE, position: Random.rand(room.x+1, room.x+room.width - 2)}); 1105 | }, 1106 | build_kitchen: function(room){ 1107 | this.buildLivingQuarters(room, 'kitchen'); 1108 | }, 1109 | build_library: function(room){ 1110 | this.buildLivingQuarters(room, 'library'); 1111 | }, 1112 | build_throneHall: function(room){ 1113 | this.build_hall(room); 1114 | }, 1115 | build_halls: function(room){ // Wall halls} 1116 | this.buildRoom(room); 1117 | if (room.width > 3) 1118 | this.addTorchesToRoom(room); 1119 | }, 1120 | build_hall: function(room){ 1121 | this.buildLivingQuarters(room, 'hall'); 1122 | } 1123 | } 1124 | 1125 | module.exports = RoomBuilder; 1126 | },{"./Arrays":"/home/administrator/git/ultimacastlegen/src/Arrays.js","./Cells":"/home/administrator/git/ultimacastlegen/src/Cells.js","./Random":"/home/administrator/git/ultimacastlegen/src/Random.js"}],"/home/administrator/git/ultimacastlegen/src/RoomsGenerator.js":[function(require,module,exports){ 1127 | var Random = require('./Random'); 1128 | var Arrays = require('./Arrays'); 1129 | var Cells = require('./Cells'); 1130 | 1131 | function RoomsGenerator(){}; 1132 | 1133 | RoomsGenerator.prototype = { 1134 | generateMap: function(structure, generationParams){ 1135 | this.structure = structure; 1136 | this.generationParams = generationParams; 1137 | this.rooms = []; 1138 | this.anchorPoints = {}; 1139 | this.placeFoundations(); 1140 | this.placeTowers(); 1141 | this.placeCentralFeature(); 1142 | this.placeEntrances(); 1143 | var retries = 0; 1144 | while(true){ 1145 | var emptyRooms = this.placeRooms(); 1146 | if (emptyRooms == false){ 1147 | return false; 1148 | } 1149 | var assigned = this.assignRooms(emptyRooms); 1150 | if (!assigned){ 1151 | if (retries++ < 50){ 1152 | continue; 1153 | } else { 1154 | return false; 1155 | } 1156 | } 1157 | break; 1158 | } 1159 | this.linkRooms(); 1160 | return this.rooms; 1161 | }, 1162 | placeFoundations: function(){ 1163 | var def = this.structure.towers; 1164 | var connectionWidth = def.connectionCorridors.type === 'corridor' ? 3 : def.connectionCorridors.hallWidth; 1165 | var x = 3; 1166 | if (def.verticalConnections){ 1167 | x = 1 + Math.floor(def.size / 2) + Math.floor(connectionWidth/2) - 1; 1168 | } 1169 | var y = 3; 1170 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'top'){ 1171 | y = 1 + Math.floor(def.size / 2) + Math.floor(connectionWidth/2); 1172 | } 1173 | var yEnd = this.generationParams.height - 5; 1174 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'bottom'){ 1175 | yEnd = this.generationParams.height - 3 - Math.floor(def.size / 2) - Math.floor(connectionWidth/2); 1176 | } 1177 | this.addRoom( 1178 | x, 1179 | y, 1180 | 'rooms', 1181 | '', 1182 | this.generationParams.width - 2 * x - 1, 1183 | yEnd - y + 1, 1184 | {}, 1185 | -2 1186 | ); 1187 | }, 1188 | 1189 | placeTowers: function(){ 1190 | var def = this.structure.towers; 1191 | //NW 1192 | this.addRoom(1, 1,'tower', 'Northwest Tower', def.size, def.size, { 1193 | shape: def.circle ? 'circle' : 'square', 1194 | walls: { 1195 | north: def.crossWindows ? 'crossWindows' : 'solid', 1196 | west: def.crossWindows ? 'crossWindows' : 'solid', 1197 | south: def.verticalConnections ? 'exit' : 'solid', 1198 | east: def.horizontalConnections === 'both' || def.horizontalConnections === 'top' ? 'exit' : 'solid' 1199 | } 1200 | }, -1); 1201 | //NE 1202 | this.addRoom(this.generationParams.width - def.size - 2, 1,'tower', 'Northeast Tower', def.size, def.size, { 1203 | shape: def.circle ? 'circle' : 'square', 1204 | walls: { 1205 | north: def.crossWindows ? 'crossWindows' : 'solid', 1206 | east: def.crossWindows ? 'crossWindows' : 'solid', 1207 | south: def.verticalConnections ? 'exit' : 'solid', 1208 | west: def.horizontalConnections === 'both' || def.horizontalConnections === 'top' ? 'exit' : 'solid' 1209 | } 1210 | }, -1); 1211 | //SW 1212 | this.addRoom(1, this.generationParams.height - def.size - 2,'tower', 'Southwest Tower', def.size, def.size, { 1213 | shape: def.circle ? 'circle' : 'square', 1214 | walls: { 1215 | south: def.crossWindows ? 'crossWindows' : 'solid', 1216 | west: def.crossWindows ? 'crossWindows' : 'solid', 1217 | north: def.verticalConnections ? 'exit' : 'solid', 1218 | east: def.horizontalConnections === 'both' || def.horizontalConnections === 'bottom' ? 'exit' : 'solid' 1219 | } 1220 | }, -1); 1221 | //SE 1222 | this.addRoom(this.generationParams.width - def.size - 2, this.generationParams.height - def.size - 2,'tower', 'Southeast Tower', def.size, def.size, { 1223 | shape: def.circle ? 'circle' : 'square', 1224 | walls: { 1225 | south: def.crossWindows ? 'crossWindows' : 'solid', 1226 | east: def.crossWindows ? 'crossWindows' : 'solid', 1227 | north: def.verticalConnections ? 'exit' : 'solid', 1228 | west: def.horizontalConnections === 'both' || def.horizontalConnections === 'bottom' ? 'exit' : 'solid' 1229 | } 1230 | }, -1); 1231 | var connectionWidth = def.connectionCorridors.type === 'corridor' ? 3 : def.connectionCorridors.hallWidth; 1232 | if (def.verticalConnections){ 1233 | // West corridor 1234 | this.addRoom( 1235 | 1 + Math.floor(def.size / 2) - Math.floor(connectionWidth/2), 1236 | 1 + def.size - 1, 1237 | def.connectionCorridors.type, 'West '+def.connectionCorridors.type, 1238 | connectionWidth, 1239 | this.generationParams.height - 2 * def.size - 1, 1240 | def.hallDecoration, 1241 | 0, 1242 | true 1243 | ); 1244 | // East corridor 1245 | this.addRoom( 1246 | this.generationParams.width - 3 - Math.floor(def.size / 2) - Math.floor(connectionWidth/2) + ((def.size - connectionWidth)%2 != 0 ? 1 : 0), 1247 | 1 + def.size - 1, 1248 | def.connectionCorridors.type, 'East '+def.connectionCorridors.type, 1249 | connectionWidth, 1250 | this.generationParams.height - 2 * def.size - 1, 1251 | def.hallDecoration, 1252 | 0, 1253 | true 1254 | ); 1255 | } 1256 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'top'){ 1257 | // North corridor 1258 | this.anchorPoints.northBound = 1 + Math.floor(def.size / 2) - Math.floor(connectionWidth/2); 1259 | this.addRoom( 1260 | 1 + def.size - 1, 1261 | this.anchorPoints.northBound, 1262 | def.connectionCorridors.type, 'North '+def.connectionCorridors.type, 1263 | this.generationParams.width - 2 * def.size - 1, 1264 | connectionWidth, 1265 | def.hallDecoration, 1266 | 0, 1267 | true 1268 | ); 1269 | } else { 1270 | this.anchorPoints.northBound = 3; 1271 | } 1272 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'bottom'){ 1273 | this.anchorPoints.southBound = this.generationParams.height - 3 - Math.floor(def.size / 2) - Math.floor(connectionWidth/2)+ ((def.size - connectionWidth)%2 != 0 ? 1 : 0) + connectionWidth; 1274 | // South corridor 1275 | this.addRoom( 1276 | 1 + def.size - 1, 1277 | this.anchorPoints.southBound - connectionWidth, 1278 | def.connectionCorridors.type, 'South '+def.connectionCorridors.type, 1279 | this.generationParams.width - 2 * def.size - 1, 1280 | connectionWidth, 1281 | def.hallDecoration, 1282 | 0, 1283 | true 1284 | ); 1285 | } else { 1286 | this.anchorPoints.southBound = this.generationParams.height - 4; 1287 | } 1288 | }, 1289 | placeCentralFeature: function(){ 1290 | var def = this.structure.central; 1291 | this.centerRoom = this.addRoom( 1292 | Math.floor(this.generationParams.width / 2) - Math.floor(def.width /2) - 1, 1293 | Math.floor(this.generationParams.height / 2) - Math.floor(def.height /2) - 1, 1294 | def.type, 1295 | def.type === 'courtyard' ? 'Courtyard' : 'Main Hall', 1296 | def.width, 1297 | def.height, 1298 | def 1299 | ); 1300 | }, 1301 | placeEntrances: function(){ 1302 | var entranceLength = Math.floor(this.generationParams.height / 2) - Math.floor(this.structure.central.height /2) - 1; 1303 | 1304 | // North entrance 1305 | if (this.structure.entrances.northExit){ 1306 | var def = this.structure.entrances.northExit; 1307 | // Northern Exit 1308 | this.addRoom( 1309 | Math.floor(this.generationParams.width / 2) - Math.floor(def.width /2) - 1, 1310 | 0, 1311 | 'entrance', 1312 | 'Entrance', 1313 | def.width, 1314 | this.anchorPoints.northBound, 1315 | def, 1316 | 1, 1317 | false 1318 | ); 1319 | 1320 | // Northern entrance hall 1321 | this.addRoom( 1322 | Math.floor(this.generationParams.width / 2) - Math.floor(def.width /2) - 1, 1323 | this.anchorPoints.northBound, 1324 | 'entranceHall', 1325 | 'Entrance Hall', 1326 | def.width, 1327 | entranceLength - this.anchorPoints.northBound + 1, 1328 | def, 1329 | 1, 1330 | false 1331 | ); 1332 | } 1333 | // South Entrance 1334 | if (this.structure.entrances.southExit){ 1335 | var def = this.structure.entrances.southExit; 1336 | this.addRoom( 1337 | Math.floor(this.generationParams.width / 2) - Math.floor(def.width /2) - 1, 1338 | entranceLength + this.structure.central.height - 1, 1339 | 'entranceHall', 1340 | 'Entrance Hall', 1341 | def.width, 1342 | this.anchorPoints.southBound - (entranceLength + this.structure.central.height - 1), 1343 | def, 1344 | 1, 1345 | false 1346 | ); 1347 | this.addRoom( 1348 | Math.floor(this.generationParams.width / 2) - Math.floor(def.width /2) - 1, 1349 | this.anchorPoints.southBound, 1350 | 'entrance', 1351 | 'Entrance', 1352 | def.width, 1353 | this.generationParams.height - this.anchorPoints.southBound, 1354 | def, 1355 | 1, 1356 | false 1357 | ); 1358 | } 1359 | 1360 | }, 1361 | placeRooms: function(){ 1362 | var def = this.structure.towers; 1363 | var connectionWidth = def.connectionCorridors.type === 'corridor' ? 3 : def.connectionCorridors.hallWidth; 1364 | var x = 3; 1365 | var adjustment = (def.size - connectionWidth)%2 == 0 ? 1 : 0; 1366 | adjustment = 0; 1367 | if (def.verticalConnections){ 1368 | x = 1 + Math.floor(def.size / 2) + Math.floor(connectionWidth/2) + adjustment - 1; 1369 | } 1370 | var y = 3; 1371 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'top'){ 1372 | y = 1 + Math.floor(def.size / 2) + Math.floor(connectionWidth/2) + adjustment; 1373 | } 1374 | var yEnd = this.generationParams.height - 5; 1375 | if (def.horizontalConnections === 'both' || def.horizontalConnections === 'bottom'){ 1376 | yEnd = this.generationParams.height - 3 - Math.floor(def.size / 2) - Math.floor(connectionWidth/2) - adjustment; 1377 | } 1378 | var area = { 1379 | x: x, 1380 | y: y, 1381 | w: Math.floor(this.generationParams.width / 2) - x, 1382 | h: yEnd - y + 1 1383 | }; 1384 | // Brute force! Let's try a lot of times to fit the rooms in the space we have! 1385 | var roomsToPlace = Math.ceil(this.structure.rooms.length / 2); 1386 | this.roomsArea = area; 1387 | var minHeight = 5; 1388 | var maxHeight = 7; 1389 | var minWidth = 5; 1390 | var maxWidth = 7; 1391 | var addedRooms = []; 1392 | var placedRooms = 0; 1393 | var reduceRoomsToPlace = 100; 1394 | // Place room connecting with courtyard 1395 | addedRooms.push({ 1396 | x: this.centerRoom.x - 4, 1397 | y: this.centerRoom.y + Math.floor(this.centerRoom.height/2) - 2, 1398 | w: 5, 1399 | h: 5 1400 | }); 1401 | out: for (var i = 0; i < 500; i++){ 1402 | for (var j = 0; j < 500; j++){ 1403 | var room = { 1404 | x: Random.rand(area.x, area.x + area.w - 2 - minWidth), 1405 | y: Random.rand(area.y, area.y + area.h - 2 - minHeight), 1406 | w: Random.rand(minWidth, maxWidth), 1407 | h: Random.rand(minHeight, maxHeight), 1408 | }; 1409 | if (this.validRoom(room, addedRooms)){ 1410 | addedRooms.push(room); 1411 | placedRooms++; 1412 | if (placedRooms == roomsToPlace) 1413 | break out; 1414 | } 1415 | } 1416 | if (reduceRoomsToPlace == 0){ 1417 | if (roomsToPlace > 4) 1418 | roomsToPlace--; 1419 | reduceRoomsToPlace = 100; 1420 | } else { 1421 | reduceRoomsToPlace --; 1422 | } 1423 | addedRooms = []; 1424 | } 1425 | if (placedRooms != roomsToPlace) 1426 | return false; // Couldn't place enough rooms, sucks! 1427 | 1428 | // Is the castle super symmetric? if not, we mirror the rooms before making them grow, and extend the play area 1429 | if (!this.structure.general.superSymmetric){ 1430 | var originalRoomsCount = addedRooms.length; 1431 | // Mirror the added rooms over the y axis 1432 | for (var i = 0; i < originalRoomsCount; i++){ 1433 | var room = addedRooms[i]; 1434 | var dist = this.roomsArea.x + this.roomsArea.w - (room.x + room.w); 1435 | addedRooms.push({ 1436 | x: this.roomsArea.x + this.roomsArea.w + dist - 1, 1437 | y: room.y, 1438 | w: room.w, 1439 | h: room.h 1440 | }); 1441 | } 1442 | area.w = this.generationParams.width - 2 * x - 1; 1443 | } 1444 | 1445 | while(true){ 1446 | //Try to make each room bigger in a direction, until it's not possible for any 1447 | var grew = false; 1448 | for (var i = 0; i < addedRooms.length; i++){ 1449 | var room = addedRooms[i]; 1450 | //Select a random direction first, if it can't grow in that direction, check in order 1451 | var randomDirection = Random.rand(0,3); 1452 | if (this.roomCanGrow(room, addedRooms, randomDirection)){ 1453 | this.growRoom(room, randomDirection); 1454 | grew = true; 1455 | } else { 1456 | for (var j = 0; j <= 3; j++){ 1457 | if (this.roomCanGrow(room, addedRooms, j)){ 1458 | this.growRoom(room, j); 1459 | grew = true; 1460 | break; 1461 | } 1462 | } 1463 | } 1464 | } 1465 | if (!grew){ 1466 | break; 1467 | } 1468 | } 1469 | 1470 | // If the castle is super symmetric, mirror them now (after growing) 1471 | if (this.structure.general.superSymmetric){ 1472 | var originalRoomsCount = addedRooms.length; 1473 | // Mirror the added rooms over the y axis 1474 | for (var i = 0; i < originalRoomsCount; i++){ 1475 | var room = addedRooms[i]; 1476 | var dist = this.roomsArea.x + this.roomsArea.w - (room.x + room.w); 1477 | addedRooms.push({ 1478 | x: this.roomsArea.x + this.roomsArea.w + dist - 1, 1479 | y: room.y, 1480 | w: room.w, 1481 | h: room.h 1482 | }); 1483 | } 1484 | } 1485 | 1486 | // Now let's fill the spaces and make these rooms grow again 1487 | area.w = this.generationParams.width - 2 * x - 1; 1488 | while(true){ 1489 | var noHoles = true; 1490 | hole: for (var x = area.x; x <= area.x + area.w - 2; x++){ 1491 | for (var y = area.y; y <= area.y + area.h - 2; y++){ 1492 | // Is there a hole here? is it big enough? 1493 | if (this.holeAt(addedRooms, x,y)){ 1494 | noHoles = false; 1495 | // Lets plot a room and make it grow 1496 | var fillRoom = { 1497 | x: x, 1498 | y: y, 1499 | w: 5, 1500 | h: 5 1501 | }; 1502 | console.log("Placing a fill room", fillRoom); 1503 | addedRooms.push(fillRoom); 1504 | while(true){ 1505 | var grew = false; 1506 | //Select a random direction first, if it can't grow in that direction, check in order 1507 | var randomDirection = Random.rand(0,3); 1508 | if (this.roomCanGrow(fillRoom, addedRooms, randomDirection)){ 1509 | this.growRoom(fillRoom, randomDirection); 1510 | grew = true; 1511 | } else { 1512 | for (var j = 0; j <= 3; j++){ 1513 | if (this.roomCanGrow(fillRoom, addedRooms, j)){ 1514 | this.growRoom(fillRoom, j); 1515 | grew = true; 1516 | break; 1517 | } 1518 | } 1519 | } 1520 | if (!grew){ 1521 | break; 1522 | } 1523 | } 1524 | break hole; 1525 | } 1526 | } 1527 | } 1528 | if (noHoles) 1529 | break; 1530 | } 1531 | return addedRooms; 1532 | }, 1533 | holeAt: function(rooms, x,y){ 1534 | for (var dx = 0; dx < 5; dx++){ 1535 | for (var dy = 0; dy < 5; dy++){ 1536 | if (!this.emptySpace(x+dx, y+dy, rooms)){ 1537 | return false; 1538 | } 1539 | } 1540 | } 1541 | return true; 1542 | }, 1543 | assignRooms: function(rooms, force){ 1544 | /** We now have a lot of empty rooms, give a function to each based on the super structure 1545 | * We will try several times 1546 | * Following room attributes are relevant: 1547 | * placeNorth: boolean - Room must be placed as north as possible 1548 | * isNextTo: string - Room must be placed as close as possible to a room with the given code 1549 | * southRoom: string - If there is a room in the southern exit of this room, it has to be one of the given code. Only for "placeNorth" rooms 1550 | * isBig: boolean - Pick one of the rooms with greatest area 1551 | */ 1552 | 1553 | // Do we have enough big rooms in the north to satisfy requirements, can we whine? 1554 | if (!force){ 1555 | var bigHeightThreshold = 8; 1556 | var bigNorthRequiredRooms = 0; 1557 | var northRequiredRooms = 0; 1558 | var bigRequiredRooms = 0; 1559 | var bigNorthAvailableRooms = 0; 1560 | var northAvailableRooms = 0; 1561 | var bigAvailableRooms = 0; 1562 | 1563 | // Gather requirements 1564 | for (var i = 0; i < this.structure.rooms.length; i++){ 1565 | var room = this.structure.rooms[i]; 1566 | if (room.placeNorth){ 1567 | if (room.isBig){ 1568 | bigNorthRequiredRooms++; 1569 | } else { 1570 | northRequiredRooms++; 1571 | } 1572 | } else if (room.isBig){ 1573 | bigRequiredRooms++; 1574 | } 1575 | } 1576 | 1577 | // Sum available rooms 1578 | for (var i = 0; i < rooms.length; i++){ 1579 | var room = rooms[i]; 1580 | if (room.y == this.roomsArea.y){ 1581 | if (room.h > bigHeightThreshold) 1582 | bigNorthAvailableRooms ++; 1583 | else 1584 | northAvailableRooms ++; 1585 | } else if (room.h > bigHeightThreshold) 1586 | bigAvailableRooms ++; 1587 | } 1588 | 1589 | // Check if we comply 1590 | if (bigNorthAvailableRooms < bigNorthRequiredRooms || 1591 | northAvailableRooms < northRequiredRooms || 1592 | bigAvailableRooms < bigRequiredRooms) 1593 | return false; // Give me better rooms! 1594 | } 1595 | 1596 | // Split the rooms by category 1597 | var northAvailableRooms = []; 1598 | var northBigAvailableRooms = []; 1599 | var bigAvailableRooms = []; 1600 | var otherAvailableRooms = []; 1601 | for (var i = 0; i < rooms.length; i++){ 1602 | var room = rooms[i]; 1603 | if (room.y == this.roomsArea.y){ 1604 | if (room.h > bigHeightThreshold){ 1605 | northBigAvailableRooms.push(room); 1606 | } else { 1607 | northAvailableRooms.push(room); 1608 | } 1609 | } else { 1610 | if (room.h > bigHeightThreshold){ 1611 | bigAvailableRooms.push(room); 1612 | } else { 1613 | otherAvailableRooms.push(room); 1614 | } 1615 | } 1616 | } 1617 | 1618 | // Shuffle the rooms 1619 | var roomsToAdd = Arrays.shuffle(this.structure.rooms); 1620 | // but place rooms with southRoom first 1621 | roomsToAdd = roomsToAdd.sort(function(a, b){ 1622 | return (a.southRoom && b.southRoom) ? 0 : a.southRoom ? -1 : 1; 1623 | }); 1624 | for (var i = 0; i < roomsToAdd.length; i++){ 1625 | var requiredRoom = roomsToAdd[i]; 1626 | var room = false; 1627 | if (requiredRoom.placeNorth){ 1628 | if (requiredRoom.isBig || northAvailableRooms.length == 0){ 1629 | room = Random.randomElementOf(northBigAvailableRooms); 1630 | } else { 1631 | room = Random.randomElementOf(northAvailableRooms); 1632 | } 1633 | } else if (requiredRoom.isBig){ 1634 | room = Random.randomElementOf(bigAvailableRooms); 1635 | } else { 1636 | room = Random.randomElementOf(otherAvailableRooms); 1637 | } 1638 | if (!room){ 1639 | // Couldn't pick from preferred? pick from any available!! 1640 | if (otherAvailableRooms.length) room = Random.randomElementOf(otherAvailableRooms); 1641 | if (northBigAvailableRooms.length) room = Random.randomElementOf(northBigAvailableRooms); 1642 | if (northAvailableRooms.length) room = Random.randomElementOf(northAvailableRooms); 1643 | if (bigAvailableRooms.length) room = Random.randomElementOf(bigAvailableRooms); 1644 | } 1645 | // Remove used space 1646 | Arrays.removeObject(northBigAvailableRooms, room); 1647 | Arrays.removeObject(northAvailableRooms, room); 1648 | Arrays.removeObject(bigAvailableRooms, room); 1649 | Arrays.removeObject(otherAvailableRooms, room); 1650 | if (room){ 1651 | var linkeable = requiredRoom.linkeable != undefined ? requiredRoom.linkeable : true; 1652 | this.addRoom(room.x, room.y, requiredRoom.type, requiredRoom.type, room.w, room.h, requiredRoom, requiredRoom.level, linkeable); 1653 | // Place south rooms 1654 | if (requiredRoom.southRoom){ 1655 | var southRoom = this.getRoomAt(rooms, room.x + Math.floor(room.w/2), room.y + room.h+2); 1656 | if (!southRoom){ 1657 | // There's probably a hall, or the central feature which is fine. 1658 | } else { 1659 | this.addRoom(southRoom.x, southRoom.y, requiredRoom.southRoom, requiredRoom.southRoom, southRoom.w, southRoom.h, requiredRoom, requiredRoom.level, true); 1660 | Arrays.removeObject(bigAvailableRooms, southRoom); // Available space used 1661 | Arrays.removeObject(otherAvailableRooms, southRoom); // Available space used 1662 | } 1663 | } 1664 | } else { 1665 | return false; // Give me better rooms! 1666 | } 1667 | } 1668 | 1669 | // Fill unused rooms with living quarters 1670 | var remainingRooms = northBigAvailableRooms.concat(northAvailableRooms).concat(bigAvailableRooms).concat(otherAvailableRooms); 1671 | for (var i = 0; i < remainingRooms.length; i++){ 1672 | var availableRoom = remainingRooms[i]; 1673 | this.addRoom(availableRoom.x, availableRoom.y, 'livingQuarters', 'livingQuarters*', availableRoom.w, availableRoom.h, availableRoom, 0, true); 1674 | } 1675 | return true; 1676 | }, 1677 | roomCanGrow: function(room, tempRooms, direction){ 1678 | var testRoom = { 1679 | x: room.x, 1680 | y: room.y, 1681 | w: room.w, 1682 | h: room.h 1683 | } 1684 | switch(direction){ 1685 | case 0: 1686 | testRoom.x --; 1687 | testRoom.w ++; 1688 | break; 1689 | case 1: 1690 | testRoom.w ++; 1691 | break; 1692 | case 2: 1693 | testRoom.y --; 1694 | testRoom.h ++; 1695 | break; 1696 | case 3: 1697 | testRoom.h ++; 1698 | break; 1699 | } 1700 | return this.validRoom(testRoom, tempRooms, room); 1701 | }, 1702 | growRoom: function(room, direction){ 1703 | switch(direction){ 1704 | case 0: 1705 | room.x --; 1706 | room.w ++; 1707 | break; 1708 | case 1: 1709 | room.w ++; 1710 | break; 1711 | case 2: 1712 | room.y --; 1713 | room.h ++; 1714 | break; 1715 | case 3: 1716 | room.h ++; 1717 | break; 1718 | } 1719 | }, 1720 | getRoomAt: function(rooms,x,y){ 1721 | for (var i = 0; i < rooms.length; i++){ 1722 | var room = rooms[i]; 1723 | if (x >= room.x && x < room.x + room.w && y >= room.y && y < room.y + room.h) 1724 | return room; 1725 | } 1726 | return false; 1727 | }, 1728 | getRealRoomAt: function(x,y,exclude){ 1729 | for (var i = 0; i < this.rooms.length; i++){ 1730 | var room = this.rooms[i]; 1731 | if (room.type === 'rooms') 1732 | continue; 1733 | if (room == exclude) 1734 | continue; 1735 | if (x >= room.x && x < room.x + room.width && y >= room.y && y < room.y + room.height) 1736 | return room; 1737 | } 1738 | return false; 1739 | }, 1740 | validRoom: function(room, tempRooms, skipRoom){ 1741 | // Must be inside the rooms area 1742 | if (room.x >= this.roomsArea.x && room.x + room.w <= this.roomsArea.x + this.roomsArea.w && 1743 | room.y >= this.roomsArea.y && room.y + room.h <= this.roomsArea.y + this.roomsArea.h) { 1744 | // Ok... 1745 | } else { 1746 | return false; 1747 | } 1748 | 1749 | // Must not collide with other rooms (except the towers) 1750 | for (var i = 0; i < this.rooms.length; i++){ 1751 | var existingRoom = this.rooms[i]; 1752 | if (existingRoom.type === 'tower' || existingRoom.type === 'rooms'){ 1753 | continue; 1754 | } 1755 | if (existingRoom.x < room.x + room.w - 1 && existingRoom.x + existingRoom.width - 1 > room.x && 1756 | existingRoom.y < room.y + room.h - 1 && existingRoom.y + existingRoom.height - 1 > room.y) { 1757 | return false; 1758 | } 1759 | } 1760 | // Must not collide with other temp rooms 1761 | for (var i = 0; i < tempRooms.length; i++){ 1762 | var existingRoom = tempRooms[i]; 1763 | if (skipRoom && skipRoom == existingRoom){ 1764 | continue; 1765 | } 1766 | if (existingRoom.x < room.x + room.w - 1 && existingRoom.x + existingRoom.w - 1 > room.x && 1767 | existingRoom.y < room.y + room.h - 1 && existingRoom.y + existingRoom.h - 1 > room.y) { 1768 | return false; 1769 | } 1770 | } 1771 | return true; 1772 | }, 1773 | emptySpace: function(x, y, tempRooms){ 1774 | // Must be inside the rooms area 1775 | if (x >= this.roomsArea.x && x <= this.roomsArea.x + this.roomsArea.w && 1776 | y >= this.roomsArea.y && y <= this.roomsArea.y + this.roomsArea.h) { 1777 | // Ok... 1778 | } else { 1779 | return false; 1780 | } 1781 | 1782 | // Must not collide with other rooms (except the towers) 1783 | for (var i = 0; i < this.rooms.length; i++){ 1784 | var existingRoom = this.rooms[i]; 1785 | if (existingRoom.type === 'tower' || existingRoom.type === 'rooms'){ 1786 | continue; 1787 | } 1788 | if (existingRoom.x < x && existingRoom.x + existingRoom.width - 1 > x && 1789 | existingRoom.y < y && existingRoom.y + existingRoom.height - 1 > y) { 1790 | return false; 1791 | } 1792 | } 1793 | // Must not collide with other temp rooms 1794 | for (var i = 0; i < tempRooms.length; i++){ 1795 | var existingRoom = tempRooms[i]; 1796 | if (existingRoom.x < x && existingRoom.x + existingRoom.w - 1 > x && 1797 | existingRoom.y < y && existingRoom.y + existingRoom.h - 1 > y) { 1798 | return false; 1799 | } 1800 | } 1801 | return true; 1802 | }, 1803 | addRoom: function(x, y, type, name, width, height, features, level, linkeable){ 1804 | var room = { 1805 | x: x, 1806 | y: y, 1807 | type: type, 1808 | name: name, 1809 | width: width, 1810 | height: height, 1811 | features: features, 1812 | level: level, 1813 | linkeable: linkeable 1814 | } 1815 | this.rooms.push(room); 1816 | return room; 1817 | }, 1818 | linkRooms: function(){ 1819 | // Starting from the courtyard or main hall, go into each direction connection rooms if possible 1820 | // Go westeros 1821 | var westRoom = this.getRealRoomAt(this.centerRoom.x - 1, this.centerRoom.y + Math.floor(this.centerRoom.height/2), this.centerRoom); 1822 | if (westRoom){ 1823 | this.linkRoom(westRoom); 1824 | if (!this.centerRoom.westDoors){ 1825 | this.centerRoom.westDoors = []; 1826 | } 1827 | this.centerRoom.westDoors.push({position: this.centerRoom.y + Math.floor(this.centerRoom.height/2), cell: Cells.DOOR}); 1828 | } 1829 | 1830 | // Go wessos 1831 | var eastRoom = this.getRealRoomAt(this.centerRoom.x + this.centerRoom.width, this.centerRoom.y + Math.floor(this.centerRoom.height/2), this.centerRoom); 1832 | if (eastRoom){ 1833 | this.linkRoom(eastRoom); 1834 | if (!this.centerRoom.eastDoors) 1835 | this.centerRoom.eastDoors = []; 1836 | this.centerRoom.eastDoors.push({position: this.centerRoom.y + Math.floor(this.centerRoom.height/2), cell: Cells.DOOR}); 1837 | } 1838 | 1839 | 1840 | // At the end, for unconnected rooms, connect to nearby 1841 | }, 1842 | linkRoom: function(room){ 1843 | //First, get all nearby linkeable rooms, and take note of the segments 1844 | room.linkeable = false; 1845 | var northRooms = this.getLinkeableRooms(room, 'north'); 1846 | var southRooms = this.getLinkeableRooms(room, 'south'); 1847 | var westRooms = this.getLinkeableRooms(room, 'west'); 1848 | var eastRooms = this.getLinkeableRooms(room, 'east'); 1849 | 1850 | //Then link using all the segments 1851 | for (var i = 0; i < northRooms.length; i++){ 1852 | var segment = northRooms[i]; 1853 | var x = Random.rand(segment.start, segment.end); 1854 | if (!room.northDoors) 1855 | room.northDoors = []; 1856 | room.northDoors.push({position: x, cell: Cells.DOOR}); 1857 | this.linkRoom(segment.room); 1858 | } 1859 | 1860 | 1861 | for (var i = 0; i < southRooms.length; i++){ 1862 | var segment = southRooms[i]; 1863 | var x = Random.rand(segment.start, segment.end); 1864 | if (!room.southDoors) 1865 | room.southDoors = []; 1866 | room.southDoors.push({position: x, cell: Cells.DOOR}); 1867 | this.linkRoom(segment.room); 1868 | } 1869 | 1870 | for (var i = 0; i < westRooms.length; i++){ 1871 | var segment = westRooms[i]; 1872 | var x = Random.rand(segment.start, segment.end); 1873 | if (!room.westDoors) 1874 | room.westDoors = []; 1875 | room.westDoors.push({position: x, cell: Cells.DOOR}); 1876 | this.linkRoom(segment.room); 1877 | } 1878 | 1879 | for (var i = 0; i < eastRooms.length; i++){ 1880 | var segment = eastRooms[i]; 1881 | var x = Random.rand(segment.start, segment.end); 1882 | if (!room.eastDoors) 1883 | room.eastDoors = []; 1884 | room.eastDoors.push({position: x, cell: Cells.DOOR}); 1885 | this.linkRoom(segment.room); 1886 | } 1887 | }, 1888 | getLinkeableRooms: function(room, direction){ 1889 | var currentSegment = false; 1890 | var segments = []; 1891 | switch (direction){ 1892 | case 'north': case 'south': 1893 | start = room.x + 1; 1894 | end = room.x + room.width - 2; 1895 | break; 1896 | case 'west': case 'east': 1897 | start = room.y + 1; 1898 | end = room.y + room.height - 2; 1899 | break; 1900 | } 1901 | for (var x = start; x <= end; x++){ 1902 | var nearRoom = false; 1903 | switch (direction){ 1904 | case 'north': 1905 | nearRoom = this.getRealRoomAt(x, room.y - 1, room); 1906 | break; 1907 | case 'south': 1908 | nearRoom = this.getRealRoomAt(x, room.y + room.height+1, room); 1909 | break; 1910 | case 'west': 1911 | nearRoom = this.getRealRoomAt(room.x - 1, x, room); 1912 | break; 1913 | case 'east': 1914 | nearRoom = this.getRealRoomAt(room.x + room.width +1, x, room); 1915 | break; 1916 | } 1917 | 1918 | if (nearRoom){ 1919 | if (!currentSegment){ 1920 | if (!nearRoom.linkeable) 1921 | continue; 1922 | currentSegment = { 1923 | start: x + 1, 1924 | room: nearRoom 1925 | } 1926 | segments.push(currentSegment); 1927 | } 1928 | if (nearRoom == currentSegment.room){ 1929 | // The segment continues 1930 | } else { 1931 | currentSegment.end = x - 2; 1932 | currentSegment.room.linkeable = false; 1933 | if (nearRoom.linkeable){ 1934 | // New segment 1935 | currentSegment = { 1936 | start: x + 1, 1937 | room: nearRoom 1938 | } 1939 | segments.push(currentSegment); 1940 | } else { 1941 | currentSegment = false; 1942 | } 1943 | } 1944 | } else if (currentSegment){ 1945 | // No room, segment ends 1946 | currentSegment.end = x - 2; 1947 | currentSegment.room.linkeable = false; 1948 | currentSegment = false; 1949 | } 1950 | } 1951 | if (currentSegment && !currentSegment.end){ 1952 | currentSegment.end = end - 1; 1953 | } 1954 | // room.segments = segments; 1955 | return segments; 1956 | } 1957 | } 1958 | 1959 | module.exports = RoomsGenerator; 1960 | },{"./Arrays":"/home/administrator/git/ultimacastlegen/src/Arrays.js","./Cells":"/home/administrator/git/ultimacastlegen/src/Cells.js","./Random":"/home/administrator/git/ultimacastlegen/src/Random.js"}],"/home/administrator/git/ultimacastlegen/src/TerrainGenerator.js":[function(require,module,exports){ 1961 | var Random = require('./Random'); 1962 | var Cells = require('./Cells'); 1963 | 1964 | function TerrainGenerator(){ 1965 | 1966 | }; 1967 | 1968 | TerrainGenerator.prototype = { 1969 | generateTerrain: function(generationParams){ 1970 | var map = []; 1971 | for (var x = 0; x < generationParams.width; x++){ 1972 | map[x] = []; 1973 | for (var y = 0; y < generationParams.height; y++){ 1974 | if (Random.chance(80)) 1975 | map[x][y] = Cells.GRASS_1; 1976 | else if (Random.chance(80)) 1977 | map[x][y] = Cells.GRASS_2; 1978 | else 1979 | map[x][y] = Cells.TREE; 1980 | } 1981 | } 1982 | return map; 1983 | } 1984 | } 1985 | 1986 | module.exports = TerrainGenerator; 1987 | },{"./Cells":"/home/administrator/git/ultimacastlegen/src/Cells.js","./Random":"/home/administrator/git/ultimacastlegen/src/Random.js"}],"/home/administrator/git/ultimacastlegen/src/ui/CanvasRenderer.js":[function(require,module,exports){ 1988 | var Cells = require('../Cells'); 1989 | 1990 | 1991 | 1992 | function CanvasRenderer(config){ 1993 | this.config = config; 1994 | this.tiles = {}; 1995 | for (key in Cells){ 1996 | var val = Cells[key]; 1997 | this.tiles[val] = new Image(); 1998 | totalImages++; 1999 | this.tiles[val].onload = increaseLoadedCount; 2000 | this.tiles[val].src = 'img/'+val+'.png'; 2001 | } 2002 | setTimeout(checkImagesLoaded, 500); 2003 | } 2004 | 2005 | var totalImages = 0; 2006 | var loadedImages = 0; 2007 | 2008 | function increaseLoadedCount(){ 2009 | loadedImages++; 2010 | } 2011 | 2012 | function checkImagesLoaded(){ 2013 | if (loadedImages == totalImages){ 2014 | imagesLoaded(); 2015 | } else { 2016 | setTimeout(checkImagesLoaded, 500); 2017 | } 2018 | } 2019 | 2020 | CanvasRenderer.prototype = { 2021 | drawSketch: function(rooms, canvas, overlay){ 2022 | var canvas = document.getElementById(canvas); 2023 | var context = canvas.getContext('2d'); 2024 | context.font="16px Avatar"; 2025 | if (!overlay) 2026 | context.clearRect(0, 0, canvas.width, canvas.height); 2027 | var zoom = 16; 2028 | for (var i = 0; i < rooms.length; i++){ 2029 | var area = rooms[i]; 2030 | context.beginPath(); 2031 | context.rect(area.x * zoom, area.y * zoom, area.width * zoom, area.height * zoom); 2032 | if (!overlay){ 2033 | if (area.type === 'rooms'){ 2034 | context.fillStyle = 'blue'; 2035 | context.globalAlpha = 0.5; 2036 | } 2037 | else{ 2038 | context.fillStyle = 'yellow'; 2039 | context.globalAlpha = 1; 2040 | } 2041 | 2042 | context.fill(); 2043 | } 2044 | context.lineWidth = 2; 2045 | context.strokeStyle = 'black'; 2046 | context.stroke(); 2047 | if (area.bridges) for (var j = 0; j < area.bridges.length; j++){ 2048 | var bridge = area.bridges[j]; 2049 | context.beginPath(); 2050 | context.rect((bridge.x) * zoom, (bridge.y) * zoom, zoom, zoom); 2051 | context.lineWidth = 2; 2052 | context.strokeStyle = 'red'; 2053 | context.stroke(); 2054 | } 2055 | } 2056 | context.globalAlpha = 1; 2057 | for (var i = 0; i < rooms.length; i++){ 2058 | var area = rooms[i]; 2059 | context.fillStyle = 'black'; 2060 | context.fillText(area.name,(area.x)* zoom + 5,(area.y + area.height/2)* zoom + 10); 2061 | } 2062 | }, 2063 | drawLevel: function(level, canvas){ 2064 | 2065 | }, 2066 | drawLevelWithIcons: function(cells, canvas){ 2067 | var canvas = document.getElementById(canvas); 2068 | var context = canvas.getContext('2d'); 2069 | context.clearRect(0, 0, canvas.width, canvas.height); 2070 | var zoom = 8; 2071 | for (var x = 0; x < this.config.LEVEL_WIDTH; x++){ 2072 | for (var y = 0; y < this.config.LEVEL_HEIGHT; y++){ 2073 | var cell = cells[x][y]; 2074 | if (cell) 2075 | context.drawImage(this.tiles[cell], x * 14, y * 16); 2076 | } 2077 | } 2078 | } 2079 | } 2080 | 2081 | module.exports = CanvasRenderer; 2082 | },{"../Cells":"/home/administrator/git/ultimacastlegen/src/Cells.js"}],"/home/administrator/git/ultimacastlegen/src/ui/WebAdapter.js":[function(require,module,exports){ 2083 | window.MapGenerator = require('../MapGenerator'); 2084 | window.CanvasRenderer = require('./CanvasRenderer'); 2085 | },{"../MapGenerator":"/home/administrator/git/ultimacastlegen/src/MapGenerator.js","./CanvasRenderer":"/home/administrator/git/ultimacastlegen/src/ui/CanvasRenderer.js"}]},{},["/home/administrator/git/ultimacastlegen/src/ui/WebAdapter.js"]); 2086 | --------------------------------------------------------------------------------