diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Wandmaker.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Wandmaker.java index 87df2e354..4c68bc746 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Wandmaker.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Wandmaker.java @@ -245,75 +245,80 @@ public class Wandmaker extends NPC { } } - public static boolean spawn( PrisonLevel level, Room room, Collection rooms ) { - 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; + private static boolean questRoomSpawned; + + public static void spawnWandmaker( PrisonLevel level, Room room, Collection 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 ); - //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 ); + spawned = true; - spawned = true; + given = false; + wand1 = (Wand) Generator.random(Generator.Category.WAND); + wand1.cursed = false; + wand1.identify(); + wand1.upgrade(); - 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 { - return false; - } - } else { - return true; + do { + wand2 = (Wand) Generator.random(Generator.Category.WAND); + } while (wand2.getClass().equals(wand1.getClass())); + wand2.cursed = false; + wand2.identify(); + wand2.upgrade(); + } } - private static boolean setRoom( Collection 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; + public static boolean spawnRoom( Collection 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. + 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; + } + + questRoomSpawned = true; + return true; + } else { + return true; } - - 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() { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/CavesLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/CavesLevel.java index e46244067..80d41563d 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/CavesLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/CavesLevel.java @@ -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() { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/CityLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/CityLevel.java index 56124d9e9..fe626aa38 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/CityLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/CityLevel.java @@ -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() { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/LastShopLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/LastShopLevel.java index c58bc592d..8ae761914 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/LastShopLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/LastShopLevel.java @@ -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 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 @@ -225,6 +149,10 @@ public class LastShopLevel extends RegularLevel { protected boolean[] grass() { return Patch.generate( width, height, 0.10f, 3, true ); } + + protected int nTraps() { + return 0; + } @Override public Group addVisuals( ) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java index ee9c0b49e..56378510c 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java @@ -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"; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/PrisonLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/PrisonLevel.java index ce23e9182..4131725ef 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/PrisonLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/PrisonLevel.java @@ -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; @@ -88,23 +86,12 @@ public class PrisonLevel extends RegularLevel { 2, 2, 2, 2, 2, 2, 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) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java index 972ba0f4c..096f1bb48 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java @@ -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 rooms; + protected Builder builder; + protected Room roomEntrance; protected Room roomExit; - protected ArrayList 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); - - 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; + roomEntrance = ((LegacyBuilder)builder).roomEntrance; + roomExit = ((LegacyBuilder)builder).roomExit; - ArrayList connected = new ArrayList<>(); - connected.add( roomEntrance ); - - Graph.buildDistanceMap( rooms, roomExit ); - List 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.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(); @@ -171,6 +86,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) { @@ -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 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 ) { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/SewerBossLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/SewerBossLevel.java index dd11ec011..4cbf7eb97 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/SewerBossLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/SewerBossLevel.java @@ -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,107 +62,13 @@ 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 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 candidates = new ArrayList(); - 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; map[exit] = Terrain.LOCKED_EXIT; - + //make sure the exit is only visible in the entrance room. int count = 0; for (int i : PathFinder.NEIGHBOURS8){ @@ -174,13 +78,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; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/Builder.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/Builder.java new file mode 100644 index 000000000..2e70e1fa2 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/Builder.java @@ -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 build(ArrayList rooms); + +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/LegacyBuilder.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/LegacyBuilder.java new file mode 100644 index 000000000..39936d22c --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/LegacyBuilder.java @@ -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 rooms; + + public Room roomEntrance; + public Room roomExit; + + protected ArrayList specials; + + @Override + //The list of rooms passed to this method is ignored + public ArrayList build(ArrayList ignoredRooms) { + + if (!initRooms()){ + return null; + } + + switch(type){ + case REGULAR: default: + return buildRegularLevel(); + case SEWER_BOSS: + return buildSewerBossLevel(); + case LAST_SHOP: + return buildsLastShopLevel(); + } + } + + private ArrayList 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 connected = new ArrayList<>(); + connected.add( roomEntrance ); + + Graph.buildDistanceMap( rooms, roomExit ); + List 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.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 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 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 candidates = new ArrayList(); + 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 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 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 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; + } +}