/******************************************************************************* * Copyright (c) 2013 Philip Collin. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * Philip Collin - initial API and implementation ******************************************************************************/ package com.lyeeedar.Roguelike3D.Game.Level; import java.awt.Color; import java.util.ArrayList; import java.util.Random; import com.lyeeedar.Roguelike3D.Game.Level.AbstractTile.TileType; import com.lyeeedar.Roguelike3D.Game.Level.DungeonRoom.RoomType; import com.lyeeedar.Roguelike3D.Game.Level.DelaunayTriangulation.*; import com.lyeeedar.Roguelike3D.Game.Level.XML.BiomeReader; import com.lyeeedar.Utils.Bag; /** * Dungeon generation inspired by: http://forums.tigsource.com/index.php?topic=5174.msg799973#msg799973 * @author Philip * */ public class SerkGenerator implements AbstractGenerator{ protected static final int NOISE_PERSISTANCE = 3; protected static final int NOISE_OCTAVES = 5; protected static final int ROOM_PLACE_ATTEMPTS = 150; protected final int ROOM_PLACE_PADDING; protected final int START_MIN; protected final int START_VAR; protected final int END_MIN; protected final int END_VAR; protected final int MAIN_MIN; protected final int MAIN_VAR; protected final int SPECIAL_MIN; protected final int SPECIAL_VAR; protected final int OTHER_MIN; protected final int OTHER_VAR; final Bag<DungeonRoom> rooms = new Bag<DungeonRoom>(); final AbstractTile[][] tiles; final Random ran = new Random(); final int width; final int height; final int up; final int down; public SerkGenerator(AbstractTile[][] tiles, BiomeReader biome, int up, int down) { this.tiles = tiles; this.width = tiles.length; this.height = tiles[0].length; this.up = up; this.down = down; ROOM_PLACE_PADDING = biome.getRoomPadding(); START_MIN = biome.getRoomSizeMin(RoomType.START); START_VAR = biome.getRoomSizeVar(RoomType.START); END_MIN = biome.getRoomSizeMin(RoomType.END); END_VAR = biome.getRoomSizeVar(RoomType.END); MAIN_MIN = biome.getRoomSizeMin(RoomType.MAIN); MAIN_VAR = biome.getRoomSizeVar(RoomType.MAIN)+1; SPECIAL_MIN = biome.getRoomSizeMin(RoomType.SPECIAL); SPECIAL_VAR = biome.getRoomSizeVar(RoomType.SPECIAL)+1; OTHER_MIN = biome.getRoomSizeMin(RoomType.OTHER); OTHER_VAR = biome.getRoomSizeVar(RoomType.OTHER)+1; } @Override public Bag<DungeonRoom> generate(BiomeReader biome) { setInfluence(); int reps = up; for (int i = 0; i < reps; i++) while(!placeRoom(RoomType.START)){} reps = down; for (int i = 0; i < reps; i++) while(!placeRoom(RoomType.END)){} reps = biome.getRoomNumberMin(RoomType.MAIN)+ran.nextInt(biome.getRoomNumberVar(RoomType.MAIN)); for (int i = 0 ; i < reps; i++) { placeRoom(RoomType.MAIN); } reps = biome.getRoomNumberMin(RoomType.SPECIAL)+ran.nextInt(biome.getRoomNumberVar(RoomType.SPECIAL)); for (int i = 0 ; i < reps; i++) { placeRoom(RoomType.SPECIAL); } reps = biome.getRoomNumberMin(RoomType.OTHER)+ran.nextInt(biome.getRoomNumberVar(RoomType.OTHER)); for (int i = 0 ; i < reps; i++) { placeRoom(RoomType.OTHER); } connectRooms(); return rooms; } protected void connectRooms() { ArrayList<Pnt> roomPnts = new ArrayList<Pnt>(); for (DungeonRoom dr : rooms) { Pnt p = new Pnt(dr.x+(dr.width/2), dr.y+(dr.height/2)); roomPnts.add(p); } Triangle initialTriangle = new Triangle( new Pnt(-10000, -10000), new Pnt(10000, -10000), new Pnt(0, 10000)); Triangulation dt = new Triangulation(initialTriangle); for (Pnt p : roomPnts) { dt.delaunayPlace(p); } ArrayList<Pnt[]> paths = new ArrayList<Pnt[]>(); for (Triangle tri : dt) { calculatePaths(paths, tri); } for (Pnt[] p : paths) { AStarPathfind pathFind = new AStarPathfind(tiles, (int)p[0].coord(0), (int)p[0].coord(1), (int)p[1].coord(0), (int)p[1].coord(1)); carveCorridor(pathFind.getPath()); } } protected void carveCorridor(int[][] path) { boolean room = tiles[path[0][0]][path[0][1]].room; if (!room) System.err.println("Error! Room Linking path did not start in a Room!"); AbstractTile t = null; AbstractTile lt = null; for (int[] pos : path) { lt = t; t = tiles[pos[0]][pos[1]]; if (!room && t.room) { lt.tileType = TileType.DOOR; t.tileType = TileType.FLOOR; } else if (room && !t.room) { t.tileType =TileType.DOOR; } else { t.tileType = TileType.FLOOR; } t.influence = 0; room = t.room; } } protected void calculatePaths(ArrayList<Pnt[]> paths, Triangle triangle) { Pnt[] vertices = triangle.toArray(new Pnt[0]); int ignore = 0; double dist = 0; dist = Math.pow(2, vertices[0].coord(0)-vertices[1].coord(0))+Math.pow(2, vertices[0].coord(1)-vertices[1].coord(1)); double temp = Math.pow(2, vertices[0].coord(0)-vertices[2].coord(0))+Math.pow(2, vertices[0].coord(1)-vertices[2].coord(1)); if (dist < temp) { dist = temp; ignore = 1; } temp = Math.pow(2, vertices[1].coord(0)-vertices[2].coord(0))+Math.pow(2, vertices[1].coord(1)-vertices[2].coord(1)); if (dist < temp) { dist = temp; ignore = 2; } if (ignore != 0 && checkIgnored(vertices[0], vertices[1]) && !checkAdded(vertices[0], vertices[1])) { addPath(vertices[0], vertices[1], paths); } else { ignorePnts.add(new Pnt[]{vertices[0], vertices[1]}); } if (ignore != 1 && checkIgnored(vertices[0], vertices[2]) && !checkAdded(vertices[0], vertices[2])) { addPath(vertices[0], vertices[2], paths); } else { ignorePnts.add(new Pnt[]{vertices[0], vertices[2]}); } if (ignore != 2 && checkIgnored(vertices[1], vertices[2]) && !checkAdded(vertices[1], vertices[2])) { addPath(vertices[1], vertices[2], paths); } else { ignorePnts.add(new Pnt[]{vertices[1], vertices[2]}); } } protected void addPath(Pnt p1, Pnt p2, ArrayList<Pnt[]> paths) { if (p1.coord(0) < 0 || p2.coord(0) < 0) { ignorePnts.add(new Pnt[]{p1, p2}); } else if (p1.coord(1) < 0 || p2.coord(1) < 0) { ignorePnts.add(new Pnt[]{p1, p2}); } else if (p1.coord(0) > 1000 || p2.coord(0) > 1000) { ignorePnts.add(new Pnt[]{p1, p2}); } else if (p1.coord(1) > 1000 || p2.coord(1) > 1000) { ignorePnts.add(new Pnt[]{p1, p2}); } else { addedPnts.add(new Pnt[]{p1, p2}); paths.add(new Pnt[]{p1, p2}); } } ArrayList<Pnt[]> ignorePnts = new ArrayList<Pnt[]>(); ArrayList<Pnt[]> addedPnts = new ArrayList<Pnt[]>(); protected boolean checkIgnored(Pnt p1, Pnt p2) { for (Pnt[] p : ignorePnts) { if (p[0].equals(p1) && p[1].equals(p2)) { return false; } else if (p[0].equals(p2) && p[1].equals(p1)) { return false; } } return true; } protected boolean checkAdded(Pnt p1, Pnt p2) { for (Pnt[] p : addedPnts) { if (p[0].equals(p1) && p[1].equals(p2)) { return true; } else if (p[0].equals(p2) && p[1].equals(p1)) { return true; } } return false; } protected void setInfluence() { PerlinNoise noise = new PerlinNoise(); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { tiles[x][y].influence += noise.PerlinNoise_2D(x, y, NOISE_PERSISTANCE, NOISE_OCTAVES)*50; } } } protected boolean placeRoom(RoomType roomType) { int rwidth = 0; int rheight = 0; int px = 0; int py = 0; if (roomType == RoomType.START) { rwidth = START_MIN+ran.nextInt(START_VAR); rheight = START_MIN+ran.nextInt(START_VAR); } else if (roomType == RoomType.END) { rwidth = END_MIN+ran.nextInt(END_VAR); rheight = END_MIN+ran.nextInt(END_VAR); } else if (roomType == RoomType.MAIN) { rwidth = MAIN_MIN+ran.nextInt(MAIN_VAR); rheight = MAIN_MIN+ran.nextInt(MAIN_VAR); } else if (roomType == RoomType.SPECIAL) { rwidth = SPECIAL_MIN+ran.nextInt(SPECIAL_VAR); rheight = SPECIAL_MIN+ran.nextInt(SPECIAL_VAR); } else if (roomType == RoomType.OTHER) { rwidth = OTHER_MIN+ran.nextInt(OTHER_VAR); rheight = OTHER_MIN+ran.nextInt(OTHER_VAR); } for (int i = 0; i < ROOM_PLACE_ATTEMPTS; i++) { px = ROOM_PLACE_PADDING+ran.nextInt(width-rwidth-ROOM_PLACE_PADDING-ROOM_PLACE_PADDING); py = ROOM_PLACE_PADDING+ran.nextInt(height-rheight-ROOM_PLACE_PADDING-ROOM_PLACE_PADDING); if (checkRoom(px, py, rwidth, rheight)) { addRoom(px, py, rwidth, rheight, roomType); return true; } } System.err.println("Failed to place room!"); return false; } protected void addRoom(int px, int py, int width, int height, RoomType roomType) { for (int x = px; x < px+width; x++) { for (int y = py; y < py+height; y++) { tiles[x][y].room = true; tiles[x][y].influence = 0; tiles[x][y].tileType = TileType.FLOOR; } } DungeonRoom room = new DungeonRoom(px, py, width, height, roomType); rooms.add(room); } protected boolean checkRoom(int px, int py, int rwidth, int rheight) { if (px+rwidth+ROOM_PLACE_PADDING > width) { return false; } else if (py+rheight+ROOM_PLACE_PADDING > height) { return false; } else if (px-ROOM_PLACE_PADDING < 0) { return false; } else if (py-ROOM_PLACE_PADDING < 0) { return false; } for (int x = px-ROOM_PLACE_PADDING; x < px+rwidth+ROOM_PLACE_PADDING; x++) { for (int y = py-ROOM_PLACE_PADDING; y < py+rheight+ROOM_PLACE_PADDING; y++) { if (tiles[x][y].room) { return false; } } } return true; } }