v0.6.0: moved all level building logic into builder classes

This commit is contained in:
Evan Debenham 2017-03-11 23:11:54 -05:00
parent ec6ca86474
commit e9a7384779
10 changed files with 635 additions and 542 deletions

View File

@ -245,77 +245,82 @@ public class Wandmaker extends NPC {
}
}
public static boolean spawn( PrisonLevel level, Room room, Collection<Room> rooms ) {
private static boolean questRoomSpawned;
public static void spawnWandmaker( PrisonLevel level, Room room, Collection<Room> rooms ) {
if (questRoomSpawned) {
questRoomSpawned = false;
Wandmaker npc = new Wandmaker();
do {
npc.pos = level.pointToCell(room.random());
//Wandmaker must never spawn in the center.
//If he does, and the room is 3x3, there is no room for the stairs.
} while (npc.pos == level.pointToCell(room.center()));
level.mobs.add( npc );
spawned = true;
given = false;
wand1 = (Wand) Generator.random(Generator.Category.WAND);
wand1.cursed = false;
wand1.identify();
wand1.upgrade();
do {
wand2 = (Wand) Generator.random(Generator.Category.WAND);
} while (wand2.getClass().equals(wand1.getClass()));
wand2.cursed = false;
wand2.identify();
wand2.upgrade();
}
}
public static boolean spawnRoom( Collection<Room> rooms) {
questRoomSpawned = false;
if (!spawned && (type != 0 || (Dungeon.depth > 6 && Random.Int( 10 - Dungeon.depth ) == 0))) {
// decide between 1,2, or 3 for quest type.
// but if the no herbalism challenge is enabled, only pick 1 or 2, no rotberry.
if (type == 0) type = Random.Int(Dungeon.isChallenged(Challenges.NO_HERBALISM) ? 2 : 3)+1;
//note that we set the type but can fail here. This ensures that if a level needs to be re-generated
//we don't re-roll the quest, it will try to assign itself to that new level with the same type.
if (setRoom( rooms )){
Wandmaker npc = new Wandmaker();
do {
npc.pos = level.pointToCell(room.random());
//Wandmaker must never spawn in the center.
//If he does, and the room is 3x3, there is no room for the stairs.
} while (npc.pos == level.pointToCell(room.center()));
level.mobs.add( npc );
Room questRoom = null;
for (Room r : rooms){
if (r.type == Room.Type.STANDARD && r.width() > 5 && r.height() > 5){
if (type == 2 || r.connected.size() == 1){
questRoom = r;
break;
}
}
}
spawned = true;
given = false;
wand1 = (Wand) Generator.random(Generator.Category.WAND);
wand1.cursed = false;
wand1.identify();
wand1.upgrade();
do {
wand2 = (Wand) Generator.random(Generator.Category.WAND);
} while (wand2.getClass().equals(wand1.getClass()));
wand2.cursed = false;
wand2.identify();
wand2.upgrade();
return true;
} else {
if (questRoom == null){
return false;
}
switch (type){
case 1: default:
questRoom.type = Room.Type.MASS_GRAVE;
break;
case 2:
questRoom.type = Room.Type.RITUAL_SITE;
break;
case 3:
questRoom.type = Room.Type.ROT_GARDEN;
break;
}
questRoomSpawned = true;
return true;
} else {
return true;
}
}
private static boolean setRoom( Collection<Room> rooms) {
Room questRoom = null;
for (Room r : rooms){
if (r.type == Room.Type.STANDARD && r.width() > 5 && r.height() > 5){
if (type == 2 || r.connected.size() == 1){
questRoom = r;
break;
}
}
}
if (questRoom == null){
return false;
}
switch (type){
case 1: default:
questRoom.type = Room.Type.MASS_GRAVE;
break;
case 2:
questRoom.type = Room.Type.RITUAL_SITE;
break;
case 3:
questRoom.type = Room.Type.ROT_GARDEN;
break;
}
return true;
}
public static void complete() {
wand1 = null;
wand2 = null;

View File

@ -23,7 +23,6 @@ package com.shatteredpixel.shatteredpixeldungeon.levels;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Blacksmith;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room.Type;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.ConfusionTrap;
@ -98,16 +97,6 @@ public class CavesLevel extends RegularLevel {
1 };
}
@Override
protected boolean assignRoomType() {
if (!super.assignRoomType()) return false;
if (!Blacksmith.Quest.spawn( rooms ) && Dungeon.depth == 14)
return false;
return true;
}
@Override
protected void decorate() {

View File

@ -24,8 +24,6 @@ package com.shatteredpixel.shatteredpixeldungeon.levels;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Imp;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room.Type;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.BlazingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.CursingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.DisarmingTrap;
@ -94,19 +92,6 @@ public class CityLevel extends RegularLevel {
1, 1 };
}
@Override
protected boolean assignRoomType() {
if (!super.assignRoomType()) return false;
for (Room r : rooms) {
if (r.type == Type.TUNNEL) {
r.type = Type.PASSAGE;
}
}
return true;
}
@Override
protected void decorate() {

View File

@ -27,15 +27,12 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Imp;
import com.shatteredpixel.shatteredpixeldungeon.items.Heap;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room.Type;
import com.shatteredpixel.shatteredpixeldungeon.levels.builders.Builder;
import com.shatteredpixel.shatteredpixeldungeon.levels.builders.LegacyBuilder;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.watabou.noosa.Group;
import com.watabou.utils.Graph;
import com.watabou.utils.Random;
import java.util.List;
public class LastShopLevel extends RegularLevel {
{
@ -54,83 +51,10 @@ public class LastShopLevel extends RegularLevel {
}
@Override
protected boolean build() {
protected Builder builder() {
feeling = Feeling.CHASM;
viewDistance = 4;
initRooms();
int distance;
int retry = 0;
int minDistance = (int)Math.sqrt( rooms.size() );
do {
int innerRetry = 0;
do {
if (innerRetry++ > 10) {
return false;
}
roomEntrance = Random.element( rooms );
} while (roomEntrance.width() < 4 || roomEntrance.height() < 4);
innerRetry = 0;
do {
if (innerRetry++ > 10) {
return false;
}
roomExit = Random.element( rooms );
} while (roomExit == roomEntrance || roomExit.width() < 6 || roomExit.height() < 6 || roomExit.top == 0);
Graph.buildDistanceMap( rooms, roomExit );
distance = Graph.buildPath( rooms, roomEntrance, roomExit ).size();
if (retry++ > 10) {
return false;
}
} while (distance < minDistance);
roomEntrance.type = Type.ENTRANCE;
roomExit.type = Type.EXIT;
Graph.buildDistanceMap( rooms, roomExit );
List<Room> path = Graph.buildPath( rooms, roomEntrance, roomExit );
Graph.setPrice( path, roomEntrance.distance );
Graph.buildDistanceMap( rooms, roomExit );
path = Graph.buildPath( rooms, roomEntrance, roomExit );
Room room = roomEntrance;
for (Room next : path) {
room.connect( next );
room = next;
}
Room roomShop = null;
int shopSquare = 0;
for (Room r : rooms) {
if (r.type == Type.NULL && r.connected.size() > 0) {
r.type = Type.PASSAGE;
if (r.square() > shopSquare) {
roomShop = r;
shopSquare = r.square();
}
}
}
if (roomShop == null || shopSquare < 54) {
return false;
} else {
roomShop.type = Imp.Quest.isCompleted() ? Room.Type.SHOP : Room.Type.STANDARD;
}
paint();
paintWater();
paintGrass();
return true;
return new LegacyBuilder(LegacyBuilder.Type.LAST_SHOP,
width, height, minRoomSize, maxRoomSize);
}
@Override
@ -226,6 +150,10 @@ public class LastShopLevel extends RegularLevel {
return Patch.generate( width, height, 0.10f, 3, true );
}
protected int nTraps() {
return 0;
}
@Override
public Group addVisuals( ) {
super.addVisuals();

View File

@ -156,7 +156,7 @@ public abstract class Level implements Bundlable {
public int color2 = 0x88CC44;
//FIXME this is sloppy. Should be able to keep track of this without static variables
protected static boolean pitRoomNeeded = false;
public static boolean pitRoomNeeded = false;
public static boolean weakFloorCreated = false;
private static final String VERSION = "version";

View File

@ -26,8 +26,6 @@ import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Wandmaker;
import com.shatteredpixel.shatteredpixeldungeon.effects.Halo;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.FlameParticle;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room.Type;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.AlarmTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.ChillingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.ConfusionTrap;
@ -89,22 +87,11 @@ public class PrisonLevel extends RegularLevel {
1, 1, 1, 1 };
}
@Override
protected boolean assignRoomType() {
if (!super.assignRoomType()) return false;
for (Room r : rooms) {
if (r.type == Type.TUNNEL) {
r.type = Type.PASSAGE;
}
}
return Wandmaker.Quest.spawn( this, roomEntrance, rooms );
}
@Override
protected void decorate() {
Wandmaker.Quest.spawnWandmaker( this, roomEntrance, rooms );
for (int i=width() + 1; i < length() - width() - 1; i++) {
if (map[i] == Terrain.EMPTY) {

View File

@ -22,7 +22,6 @@
package com.shatteredpixel.shatteredpixeldungeon.levels;
import com.shatteredpixel.shatteredpixeldungeon.Bones;
import com.shatteredpixel.shatteredpixeldungeon.Challenges;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Bestiary;
@ -33,16 +32,16 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.potions.Potion;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfWealth;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.Scroll;
import com.shatteredpixel.shatteredpixeldungeon.levels.builders.Builder;
import com.shatteredpixel.shatteredpixeldungeon.levels.builders.LegacyBuilder;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room.Type;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.ShopRoom;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.ChillingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.ExplosiveTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.FireTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.Trap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.WornTrap;
import com.watabou.utils.Bundle;
import com.watabou.utils.Graph;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
import com.watabou.utils.Rect;
@ -50,120 +49,36 @@ import com.watabou.utils.Rect;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
public abstract class RegularLevel extends Level {
protected ArrayList<Room> rooms;
protected Builder builder;
protected Room roomEntrance;
protected Room roomExit;
protected ArrayList<Room.Type> specials;
public int secretDoors;
@Override
protected boolean build() {
if (!initRooms()) {
builder = builder();
rooms = builder.build(null);
if (rooms == null){
return false;
}
int distance;
int retry = 0;
int minDistance = (int)Math.sqrt( rooms.size() );
do {
do {
roomEntrance = Random.element( rooms );
} while (roomEntrance.width() < 4 || roomEntrance.height() < 4);
roomEntrance = ((LegacyBuilder)builder).roomEntrance;
roomExit = ((LegacyBuilder)builder).roomExit;
do {
roomExit = Random.element( rooms );
} while (roomExit == roomEntrance || roomExit.width() < 4 || roomExit.height() < 4);
Graph.buildDistanceMap( rooms, roomExit );
distance = roomEntrance.distance();
if (retry++ > 10) {
return false;
}
} while (distance < minDistance);
roomEntrance.type = Type.ENTRANCE;
roomExit.type = Type.EXIT;
ArrayList<Room> connected = new ArrayList<>();
connected.add( roomEntrance );
Graph.buildDistanceMap( rooms, roomExit );
List<Room> path = Graph.buildPath( rooms, roomEntrance, roomExit );
Room room = roomEntrance;
for (Room next : path) {
room.connect( next );
room = next;
connected.add( room );
}
Graph.setPrice( path, roomEntrance.distance );
Graph.buildDistanceMap( rooms, roomExit );
path = Graph.buildPath( rooms, roomEntrance, roomExit );
room = roomEntrance;
for (Room next : path) {
room.connect( next );
room = next;
connected.add( room );
}
int nConnected = (int)(rooms.size() * Random.Float( 0.5f, 0.7f ));
while (connected.size() < nConnected) {
Room cr = Random.element( connected );
Room or = Random.element( cr.neigbours );
if (!connected.contains( or )) {
cr.connect( or );
connected.add( or );
}
}
if (Dungeon.shopOnLevel()) {
Room shop = null;
for (Room r : roomEntrance.connected.keySet()) {
if (r.connected.size() == 1 && ((r.width()-1)*(r.height()-1) >= ShopRoom.spaceNeeded())) {
shop = r;
break;
}
}
if (shop == null) {
return false;
} else {
shop.type = Room.Type.SHOP;
}
}
specials = new ArrayList<Room.Type>( Room.SPECIALS );
if (Dungeon.bossLevel( Dungeon.depth + 1 )) {
specials.remove( Room.Type.WEAK_FLOOR );
}
if (Dungeon.isChallenged( Challenges.NO_ARMOR )){
//no sense in giving an armor reward room on a run with no armor.
specials.remove( Room.Type.CRYPT );
}
if (Dungeon.isChallenged( Challenges.NO_HERBALISM )){
//sorry warden, no lucky sungrass or blandfruit seeds for you!
specials.remove( Room.Type.GARDEN );
}
if (!assignRoomType())
if (!paint()){
return false;
}
paint();
paintWater();
paintGrass();
@ -172,6 +87,11 @@ public abstract class RegularLevel extends Level {
return true;
}
protected Builder builder(){
return new LegacyBuilder(LegacyBuilder.Type.REGULAR,
width, height, minRoomSize, maxRoomSize);
}
protected void placeSign(){
while (true) {
int pos = pointToCell(roomEntrance.random());
@ -182,122 +102,6 @@ public abstract class RegularLevel extends Level {
}
}
protected boolean initRooms() {
rooms = new ArrayList<>();
split( new Rect( 0, 0, width() - 1, height() - 1 ) );
if (rooms.size() < 8) {
return false;
}
Room[] ra = rooms.toArray( new Room[0] );
for (int i=0; i < ra.length-1; i++) {
for (int j=i+1; j < ra.length; j++) {
ra[i].addNeigbour( ra[j] );
}
}
return true;
}
protected boolean assignRoomType() {
int specialRooms = 0;
boolean pitMade = false;
for (Room r : rooms) {
if (r.type == Type.NULL &&
r.connected.size() == 1) {
if (specials.size() > 0 &&
r.width() > 3 && r.height() > 3 &&
Random.Int( specialRooms * specialRooms + 2 ) == 0) {
if (pitRoomNeeded && !pitMade) {
r.type = Type.PIT;
pitMade = true;
specials.remove( Type.ARMORY );
specials.remove( Type.CRYPT );
specials.remove( Type.LABORATORY );
specials.remove( Type.LIBRARY );
specials.remove( Type.STATUE );
specials.remove( Type.TREASURY );
specials.remove( Type.VAULT );
specials.remove( Type.WEAK_FLOOR );
} else if (Dungeon.depth % 5 == 2 && specials.contains( Type.LABORATORY )) {
r.type = Type.LABORATORY;
} else if (Dungeon.depth >= Dungeon.transmutation && specials.contains( Type.MAGIC_WELL )) {
r.type = Type.MAGIC_WELL;
} else {
int n = specials.size();
r.type = specials.get( Math.min( Random.Int( n ), Random.Int( n ) ) );
if (r.type == Type.WEAK_FLOOR) {
weakFloorCreated = true;
}
}
Room.useType( r.type );
specials.remove( r.type );
specialRooms++;
} else if (Random.Int( 2 ) == 0){
ArrayList<Room> neigbours = new ArrayList<>();
for (Room n : r.neigbours) {
if (!r.connected.containsKey( n ) &&
!Room.SPECIALS.contains( n.type ) &&
n.type != Type.PIT) {
neigbours.add( n );
}
}
if (neigbours.size() > 1) {
r.connect( Random.element( neigbours ) );
}
}
}
}
if (pitRoomNeeded && !pitMade) return false;
int count = 0;
for (Room r : rooms) {
if (r.type == Type.NULL) {
int connections = r.connected.size();
if (connections == 0) {
} else if (Random.Int( connections * connections ) == 0) {
r.type = Type.STANDARD;
count++;
} else {
r.type = Type.TUNNEL;
}
}
}
while (count < 6) {
Room r = randomRoom( Type.TUNNEL, 20 );
if (r != null) {
r.type = Type.STANDARD;
count++;
} else {
return false;
}
}
return true;
}
protected void paintWater() {
boolean[] lake = water();
for (int i=0; i < length(); i++) {
@ -394,45 +198,7 @@ public abstract class RegularLevel extends Level {
protected int minRoomSize = 7;
protected int maxRoomSize = 9;
protected void split( Rect rect ) {
int w = rect.width();
int h = rect.height();
if (w > maxRoomSize && h < minRoomSize) {
int vw = Random.Int( rect.left + 3, rect.right - 3 );
split( new Rect( rect.left, rect.top, vw, rect.bottom ) );
split( new Rect( vw, rect.top, rect.right, rect.bottom ) );
} else
if (h > maxRoomSize && w < minRoomSize) {
int vh = Random.Int( rect.top + 3, rect.bottom - 3 );
split( new Rect( rect.left, rect.top, rect.right, vh ) );
split( new Rect( rect.left, vh, rect.right, rect.bottom ) );
} else
if ((Random.Float() <= (minRoomSize * minRoomSize / rect.square()) && w <= maxRoomSize && h <= maxRoomSize) || w < minRoomSize || h < minRoomSize) {
rooms.add( (Room)new Room().set( rect ) );
} else {
if (Random.Float() < (float)(w - 2) / (w + h - 4)) {
int vw = Random.Int( rect.left + 3, rect.right - 3 );
split( new Rect( rect.left, rect.top, vw, rect.bottom ) );
split( new Rect( vw, rect.top, rect.right, rect.bottom ) );
} else {
int vh = Random.Int( rect.top + 3, rect.bottom - 3 );
split( new Rect( rect.left, rect.top, rect.right, vh ) );
split( new Rect( rect.left, vh, rect.right, rect.bottom ) );
}
}
}
protected void paint() {
protected boolean paint() {
for (Room r : rooms) {
if (r.type != Type.NULL) {
@ -448,6 +214,8 @@ public abstract class RegularLevel extends Level {
for (Room r : rooms) {
paintDoors( r );
}
return true;
}
private void placeDoors( Room r ) {

View File

@ -29,19 +29,17 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Bestiary;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.items.Heap;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.levels.builders.Builder;
import com.shatteredpixel.shatteredpixeldungeon.levels.builders.LegacyBuilder;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room.Type;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.watabou.noosa.Group;
import com.watabou.utils.Bundle;
import com.watabou.utils.Graph;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
import java.util.ArrayList;
import java.util.List;
public class SewerBossLevel extends RegularLevel {
{
@ -64,102 +62,8 @@ public class SewerBossLevel extends RegularLevel {
@Override
protected boolean build() {
initRooms();
int distance;
//if we ever need to try 20 or more times to find a room, better to give up and try again.
int retry = 0;
//start with finding an entrance room (will also contain exit)
//the room must be at least 4x4 and be nearer the top of the map(so that it is less likely something connects to the top)
do {
if (retry++ > 20) {
return false;
}
roomEntrance = Random.element( rooms );
} while (roomEntrance.width() != 8 || roomEntrance.height() < 5 || roomEntrance.top == 0 || roomEntrance.top >= 8);
roomEntrance.type = Type.ENTRANCE;
roomExit = roomEntrance;
//now find the rest of the rooms for this boss mini-maze
Room curRoom = null;
Room lastRoom = roomEntrance;
//we make 4 rooms, last iteration is tieing the final room to the start
for(int i = 0; i <= 4; i++){
retry = 0;
//find a suitable room the first four times
//suitable room should be empty, have a distance of 2 from the current room, and not touch the entrance.
if (i < 4) {
do {
if (retry++ > 20) {
return false;
}
curRoom = Random.element(rooms);
Graph.buildDistanceMap(rooms, curRoom);
distance = lastRoom.distance();
} while (curRoom.type != Type.NULL || distance != 3 || curRoom.neigbours.contains(roomEntrance));
curRoom.type = Type.STANDARD;
//otherwise, we're on the last iteration.
} else {
//set the current room to the entrance, so we can build a connection to it.
curRoom = roomEntrance;
}
//now build a connection between the current room and the last one.
Graph.buildDistanceMap( rooms, curRoom );
List<Room> path = Graph.buildPath( rooms, lastRoom, curRoom );
Graph.setPrice( path, lastRoom.distance );
path = Graph.buildPath( rooms, lastRoom, curRoom );
Room room = lastRoom;
for (Room next : path) {
room.connect( next );
room = next;
}
if (i == 4) {
//we must find a room for his royal highness!
//look at rooms adjacent to the final found room (likely to be furthest from start)
ArrayList<Room> candidates = new ArrayList<Room>();
for (Room r : lastRoom.neigbours) {
if (r.type == Type.NULL && r.connected.size() == 0 && !r.neigbours.contains(roomEntrance)) {
candidates.add(r);
}
}
//if we have candidates, pick a room and put the king there
if (candidates.size() > 0) {
Room kingsRoom = Random.element(candidates);
kingsRoom.connect(lastRoom);
kingsRoom.type = Room.Type.RAT_KING;
//unacceptable! make a new level...
} else {
return false;
}
}
lastRoom = curRoom;
}
//the connection structure ensures that (most of the time) there is a nice loop for the player to kite the
//boss around. What's nice is that there is enough chaos such that the loop is rarely straightforward
//and boring.
//fills our connection rooms in with tunnel
for (Room r : rooms) {
if (r.type == Type.NULL && r.connected.size() > 0) {
r.type = Type.TUNNEL;
}
}
paint();
if (!super.build())
return false;
//sticks the exit in the room entrance.
exit = roomEntrance.top * width() + (roomEntrance.left + roomEntrance.right) / 2;
@ -175,13 +79,14 @@ public class SewerBossLevel extends RegularLevel {
if (count > 3)
return false;
paintWater();
paintGrass();
return true;
}
protected Builder builder(){
return new LegacyBuilder(LegacyBuilder.Type.SEWER_BOSS,
width, height, minRoomSize, maxRoomSize);
}
protected boolean[] water() {
return Patch.generate( width, height, 0.5f, 5, true );
}
@ -190,6 +95,10 @@ public class SewerBossLevel extends RegularLevel {
return Patch.generate( width, height, 0.20f, 4, true );
}
protected int nTraps() {
return 0;
}
@Override
protected void decorate() {
int start = roomExit.top * width() + roomExit.left + 1;

View File

@ -0,0 +1,15 @@
package com.shatteredpixel.shatteredpixeldungeon.levels.builders;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
import java.util.ArrayList;
public abstract class Builder {
//If builders require additional parameters, they should request them in their constructor
//builders take a list of rooms and returns them as a connected map
//returns null on failure
public abstract ArrayList<Room> build(ArrayList<Room> rooms);
}

View File

@ -0,0 +1,507 @@
package com.shatteredpixel.shatteredpixeldungeon.levels.builders;
import com.shatteredpixel.shatteredpixeldungeon.Challenges;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Blacksmith;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Imp;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Wandmaker;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.ShopRoom;
import com.watabou.utils.Graph;
import com.watabou.utils.Random;
import com.watabou.utils.Rect;
import java.util.ArrayList;
import java.util.List;
//This builder exactly mimics pre-0.6.0 levelgen, including all of its limitations
//Currently implemented during this transition period, it will likely not survive to 0.6.0 release
public class LegacyBuilder extends Builder {
public enum Type{
REGULAR,
SEWER_BOSS,
LAST_SHOP
}
private Type type;
private int width, height;
private int minRoomSize, maxRoomSize;
public LegacyBuilder(Type t, int w, int h, int min, int max){
type = t;
width = w;
height = h;
minRoomSize = min;
maxRoomSize = max;
}
private ArrayList<Room> rooms;
public Room roomEntrance;
public Room roomExit;
protected ArrayList<Room.Type> specials;
@Override
//The list of rooms passed to this method is ignored
public ArrayList<Room> build(ArrayList<Room> ignoredRooms) {
if (!initRooms()){
return null;
}
switch(type){
case REGULAR: default:
return buildRegularLevel();
case SEWER_BOSS:
return buildSewerBossLevel();
case LAST_SHOP:
return buildsLastShopLevel();
}
}
private ArrayList<Room> buildRegularLevel(){
int distance;
int retry = 0;
int minDistance = (int)Math.sqrt( rooms.size() );
do {
do {
roomEntrance = Random.element( rooms );
} while (roomEntrance.width() < 4 || roomEntrance.height() < 4);
do {
roomExit = Random.element( rooms );
} while (roomExit == roomEntrance || roomExit.width() < 4 || roomExit.height() < 4);
Graph.buildDistanceMap( rooms, roomExit );
distance = roomEntrance.distance();
if (retry++ > 10) {
return null;
}
} while (distance < minDistance);
roomEntrance.type = Room.Type.ENTRANCE;
roomExit.type = Room.Type.EXIT;
ArrayList<Room> connected = new ArrayList<>();
connected.add( roomEntrance );
Graph.buildDistanceMap( rooms, roomExit );
List<Room> path = Graph.buildPath( rooms, roomEntrance, roomExit );
Room room = roomEntrance;
for (Room next : path) {
room.connect( next );
room = next;
connected.add( room );
}
Graph.setPrice( path, roomEntrance.distance );
Graph.buildDistanceMap( rooms, roomExit );
path = Graph.buildPath( rooms, roomEntrance, roomExit );
room = roomEntrance;
for (Room next : path) {
room.connect( next );
room = next;
connected.add( room );
}
int nConnected = (int)(rooms.size() * Random.Float( 0.5f, 0.7f ));
while (connected.size() < nConnected) {
Room cr = Random.element( connected );
Room or = Random.element( cr.neigbours );
if (!connected.contains( or )) {
cr.connect( or );
connected.add( or );
}
}
if (Dungeon.shopOnLevel()) {
Room shop = null;
for (Room r : roomEntrance.connected.keySet()) {
if (r.connected.size() == 1 && ((r.width()-1)*(r.height()-1) >= ShopRoom.spaceNeeded())) {
shop = r;
break;
}
}
if (shop == null) {
return null;
} else {
shop.type = Room.Type.SHOP;
}
}
specials = new ArrayList<Room.Type>( Room.SPECIALS );
if (Dungeon.bossLevel( Dungeon.depth + 1 )) {
specials.remove( Room.Type.WEAK_FLOOR );
}
if (Dungeon.isChallenged( Challenges.NO_ARMOR )){
//no sense in giving an armor reward room on a run with no armor.
specials.remove( Room.Type.CRYPT );
}
if (Dungeon.isChallenged( Challenges.NO_HERBALISM )){
//sorry warden, no lucky sungrass or blandfruit seeds for you!
specials.remove( Room.Type.GARDEN );
}
if (!assignRoomType())
return null;
//Quest generation logic
if (Dungeon.depth >= 6 && Dungeon.depth <= 9){
if (!Wandmaker.Quest.spawnRoom( rooms ) && Dungeon.depth == 9)
return null;
} else if (Dungeon.depth >= 11 && Dungeon.depth <= 14){
if (!Blacksmith.Quest.spawn( rooms ) && Dungeon.depth == 14)
return null;
}
return rooms;
}
private ArrayList<Room> buildSewerBossLevel(){
int distance;
//if we ever need to try 20 or more times to find a room, better to give up and try again.
int retry = 0;
//start with finding an entrance room (will also contain exit)
//the room must be at least 4x4 and be nearer the top of the map(so that it is less likely something connects to the top)
do {
if (retry++ > 20) {
return null;
}
roomEntrance = Random.element( rooms );
} while (roomEntrance.width() != 8 || roomEntrance.height() < 5 || roomEntrance.top == 0 || roomEntrance.top >= 8);
roomEntrance.type = Room.Type.ENTRANCE;
roomExit = roomEntrance;
//now find the rest of the rooms for this boss mini-maze
Room curRoom = null;
Room lastRoom = roomEntrance;
//we make 4 rooms, last iteration is tieing the final room to the start
for(int i = 0; i <= 4; i++){
retry = 0;
//find a suitable room the first four times
//suitable room should be empty, have a distance of 2 from the current room, and not touch the entrance.
if (i < 4) {
do {
if (retry++ > 20) {
return null;
}
curRoom = Random.element(rooms);
Graph.buildDistanceMap(rooms, curRoom);
distance = lastRoom.distance();
} while (curRoom.type != Room.Type.NULL || distance != 3 || curRoom.neigbours.contains(roomEntrance));
curRoom.type = Room.Type.STANDARD;
//otherwise, we're on the last iteration.
} else {
//set the current room to the entrance, so we can build a connection to it.
curRoom = roomEntrance;
}
//now build a connection between the current room and the last one.
Graph.buildDistanceMap( rooms, curRoom );
List<Room> path = Graph.buildPath( rooms, lastRoom, curRoom );
Graph.setPrice( path, lastRoom.distance );
path = Graph.buildPath( rooms, lastRoom, curRoom );
Room room = lastRoom;
for (Room next : path) {
room.connect( next );
room = next;
}
if (i == 4) {
//we must find a room for his royal highness!
//look at rooms adjacent to the final found room (likely to be furthest from start)
ArrayList<Room> candidates = new ArrayList<Room>();
for (Room r : lastRoom.neigbours) {
if (r.type == Room.Type.NULL && r.connected.size() == 0 && !r.neigbours.contains(roomEntrance)) {
candidates.add(r);
}
}
//if we have candidates, pick a room and put the king there
if (candidates.size() > 0) {
Room kingsRoom = Random.element(candidates);
kingsRoom.connect(lastRoom);
kingsRoom.type = Room.Type.RAT_KING;
//unacceptable! make a new level...
} else {
return null;
}
}
lastRoom = curRoom;
}
//the connection structure ensures that (most of the time) there is a nice loop for the player to kite the
//boss around. What's nice is that there is enough chaos such that the loop is rarely straightforward
//and boring.
//fills our connection rooms in with tunnel
for (Room r : rooms) {
if (r.type == Room.Type.NULL && r.connected.size() > 0) {
r.type = Room.Type.TUNNEL;
}
}
return rooms;
}
private ArrayList<Room> buildsLastShopLevel(){
int distance;
int retry = 0;
int minDistance = (int)Math.sqrt( rooms.size() );
do {
int innerRetry = 0;
do {
if (innerRetry++ > 10) {
return null;
}
roomEntrance = Random.element( rooms );
} while (roomEntrance.width() < 4 || roomEntrance.height() < 4);
innerRetry = 0;
do {
if (innerRetry++ > 10) {
return null;
}
roomExit = Random.element( rooms );
} while (roomExit == roomEntrance || roomExit.width() < 6 || roomExit.height() < 6 || roomExit.top == 0);
Graph.buildDistanceMap( rooms, roomExit );
distance = Graph.buildPath( rooms, roomEntrance, roomExit ).size();
if (retry++ > 10) {
return null;
}
} while (distance < minDistance);
roomEntrance.type = Room.Type.ENTRANCE;
roomExit.type = Room.Type.EXIT;
Graph.buildDistanceMap( rooms, roomExit );
List<Room> path = Graph.buildPath( rooms, roomEntrance, roomExit );
Graph.setPrice( path, roomEntrance.distance );
Graph.buildDistanceMap( rooms, roomExit );
path = Graph.buildPath( rooms, roomEntrance, roomExit );
Room room = roomEntrance;
for (Room next : path) {
room.connect( next );
room = next;
}
Room roomShop = null;
int shopSquare = 0;
for (Room r : rooms) {
if (r.type == Room.Type.NULL && r.connected.size() > 0) {
r.type = Room.Type.PASSAGE;
if (r.square() > shopSquare) {
roomShop = r;
shopSquare = r.square();
}
}
}
if (roomShop == null || shopSquare < 54) {
return null;
} else {
roomShop.type = Imp.Quest.isCompleted() ? Room.Type.SHOP : Room.Type.STANDARD;
}
return rooms;
}
private boolean initRooms(){
rooms = new ArrayList<>();
split( new Rect( 0, 0, width-1, height-1));
if (rooms.size() < 8){
return false;
}
Room[] ra = rooms.toArray( new Room[0] );
for (int i=0; i < ra.length-1; i++) {
for (int j=i+1; j < ra.length; j++) {
ra[i].addNeigbour( ra[j] );
}
}
return true;
}
private void split( Rect rect ) {
int w = rect.width();
int h = rect.height();
if (w > maxRoomSize && h < minRoomSize) {
int vw = Random.Int( rect.left + 3, rect.right - 3 );
split( new Rect( rect.left, rect.top, vw, rect.bottom ) );
split( new Rect( vw, rect.top, rect.right, rect.bottom ) );
} else
if (h > maxRoomSize && w < minRoomSize) {
int vh = Random.Int( rect.top + 3, rect.bottom - 3 );
split( new Rect( rect.left, rect.top, rect.right, vh ) );
split( new Rect( rect.left, vh, rect.right, rect.bottom ) );
} else
if ((Random.Float() <= (minRoomSize * minRoomSize / rect.square()) && w <= maxRoomSize && h <= maxRoomSize) || w < minRoomSize || h < minRoomSize) {
rooms.add( new Room(rect) );
} else {
if (Random.Float() < (float)(w - 2) / (w + h - 4)) {
int vw = Random.Int( rect.left + 3, rect.right - 3 );
split( new Rect( rect.left, rect.top, vw, rect.bottom ) );
split( new Rect( vw, rect.top, rect.right, rect.bottom ) );
} else {
int vh = Random.Int( rect.top + 3, rect.bottom - 3 );
split( new Rect( rect.left, rect.top, rect.right, vh ) );
split( new Rect( rect.left, vh, rect.right, rect.bottom ) );
}
}
}
protected boolean assignRoomType() {
int specialRooms = 0;
boolean pitMade = false;
for (Room r : rooms) {
if (r.type == Room.Type.NULL &&
r.connected.size() == 1) {
if (specials.size() > 0 &&
r.width() > 3 && r.height() > 3 &&
Random.Int( specialRooms * specialRooms + 2 ) == 0) {
if (Level.pitRoomNeeded && !pitMade) {
r.type = Room.Type.PIT;
pitMade = true;
specials.remove( Room.Type.ARMORY );
specials.remove( Room.Type.CRYPT );
specials.remove( Room.Type.LABORATORY );
specials.remove( Room.Type.LIBRARY );
specials.remove( Room.Type.STATUE );
specials.remove( Room.Type.TREASURY );
specials.remove( Room.Type.VAULT );
specials.remove( Room.Type.WEAK_FLOOR );
} else if (Dungeon.depth % 5 == 2 && specials.contains( Room.Type.LABORATORY )) {
r.type = Room.Type.LABORATORY;
} else if (Dungeon.depth >= Dungeon.transmutation && specials.contains( Room.Type.MAGIC_WELL )) {
r.type = Room.Type.MAGIC_WELL;
} else {
int n = specials.size();
r.type = specials.get( Math.min( Random.Int( n ), Random.Int( n ) ) );
if (r.type == Room.Type.WEAK_FLOOR) {
Level.weakFloorCreated = true;
}
}
Room.useType( r.type );
specials.remove( r.type );
specialRooms++;
} else if (Random.Int( 2 ) == 0){
ArrayList<Room> neigbours = new ArrayList<>();
for (Room n : r.neigbours) {
if (!r.connected.containsKey( n ) &&
!Room.SPECIALS.contains( n.type ) &&
n.type != Room.Type.PIT) {
neigbours.add( n );
}
}
if (neigbours.size() > 1) {
r.connect( Random.element( neigbours ) );
}
}
}
}
if (Level.pitRoomNeeded && !pitMade) return false;
Room.Type tunnelType = Room.Type.TUNNEL;
if ((Dungeon.depth > 5 && Dungeon.depth <= 10) ||
(Dungeon.depth > 15 && Dungeon.depth <= 20)){
tunnelType = Room.Type.PASSAGE;
}
int count = 0;
for (Room r : rooms) {
if (r.type == Room.Type.NULL) {
int connections = r.connected.size();
if (connections == 0) {
} else if (Random.Int( connections * connections ) == 0) {
r.type = Room.Type.STANDARD;
count++;
} else {
r.type = tunnelType;
}
}
}
while (count < 6) {
Room r = randomRoom( tunnelType, 20 );
if (r != null) {
r.type = Room.Type.STANDARD;
count++;
} else {
return false;
}
}
return true;
}
private Room randomRoom( Room.Type type, int tries ) {
for (int i=0; i < tries; i++) {
Room room = Random.element( rooms );
if (room.type == type) {
return room;
}
}
return null;
}
}