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 ed2dfb5d5..eb2d23566 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java @@ -139,22 +139,6 @@ public abstract class RegularLevel extends Level { protected abstract Painter painter(); - protected float waterFill(){ - return 0; - } - - protected int waterSmoothing(){ - return 0; - } - - protected float grassFill(){ - return 0; - } - - protected int grassSmoothing(){ - return 0; - } - protected int nTraps() { return Random.NormalIntRange( 1, 3+(Dungeon.depth/3) ); } 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 f076e6306..9a567b4dc 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/SewerBossLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/SewerBossLevel.java @@ -23,15 +23,17 @@ package com.shatteredpixel.shatteredpixeldungeon.levels; import com.shatteredpixel.shatteredpixeldungeon.Bones; import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; -import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Goo; 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.LoopBuilder; +import com.shatteredpixel.shatteredpixeldungeon.levels.builders.FigureEightBuilder; +import com.shatteredpixel.shatteredpixeldungeon.levels.painters.Painter; +import com.shatteredpixel.shatteredpixeldungeon.levels.painters.SewerPainter; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.secret.RatKingRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.sewerboss.GooBossRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.sewerboss.SewerBossEntranceRoom; +import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.sewerboss.SewerBossExitRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.StandardRoom; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.watabou.utils.Bundle; @@ -51,14 +53,23 @@ public class SewerBossLevel extends SewerLevel { @Override protected ArrayList initRooms() { ArrayList initRooms = new ArrayList<>(); - initRooms.add ( roomEntrance = roomExit = new SewerBossEntranceRoom()); + + //TODO it would be nice for these rooms to have some custom tile visuals + initRooms.add( roomEntrance = new SewerBossEntranceRoom() ); + initRooms.add( roomExit = new SewerBossExitRoom() ); int standards = standardRooms(); for (int i = 0; i < standards; i++) { - initRooms.add(StandardRoom.createRoom()); + StandardRoom s = StandardRoom.createRoom(); + //force to normal size + s.setSizeCat(0, 0); + initRooms.add(s); } - initRooms.add(GooBossRoom.randomGooRoom()); + //TODO need to improve the visual appearance of goo's nest + GooBossRoom gooRoom = GooBossRoom.randomGooRoom(); + initRooms.add(gooRoom); + ((FigureEightBuilder)builder).setLandmarkRoom(gooRoom); initRooms.add(new RatKingRoom()); return initRooms; } @@ -70,29 +81,18 @@ public class SewerBossLevel extends SewerLevel { } protected Builder builder(){ - return new LoopBuilder() + return new FigureEightBuilder() + .setLoopShape( 2 , Random.Float(0.4f, 0.7f), Random.Float(0f, 0.5f)) .setPathLength(1f, new float[]{1}) - .setTunnelLength(new float[]{0, 3, 1}, new float[]{1}); + .setTunnelLength(new float[]{1, 2}, new float[]{1}); } @Override - protected float waterFill(){ - return 0.50f; - } - - @Override - protected int waterSmoothing(){ - return 5; - } - - @Override - protected float grassFill() { - return 0.20f; - } - - @Override - protected int grassSmoothing() { - return 4; + protected Painter painter() { + return new SewerPainter() + .setWater(0.50f, 5) + .setGrass(0.20f, 4) + .setTraps(nTraps(), trapClasses(), trapChances()); } protected int nTraps() { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/FigureEightBuilder.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/FigureEightBuilder.java new file mode 100644 index 000000000..e7e003509 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/FigureEightBuilder.java @@ -0,0 +1,278 @@ +/* + * Pixel Dungeon + * Copyright (C) 2012-2015 Oleg Dolya + * + * Shattered Pixel Dungeon + * Copyright (C) 2014-2019 Evan Debenham + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +package com.shatteredpixel.shatteredpixeldungeon.levels.builders; + +import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room; +import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.connection.ConnectionRoom; +import com.watabou.utils.PointF; +import com.watabou.utils.Random; + +import java.util.ArrayList; + +public class FigureEightBuilder extends RegularBuilder { + + //These methods allow for the adjusting of the shape of the loop + //by default the loop is a perfect circle, but it can be adjusted + + //increasing the exponent will increase the the curvature, making the loop more oval shaped. + private int curveExponent = 0; + + //This is a percentage (range 0-1) of the intensity of the curve function + // 0 makes for a perfect linear curve (circle) + // 1 means the curve is completely determined by the curve exponent + private float curveIntensity = 1; + + //Adjusts the starting point along the loop. + // a common example, setting to 0.25 will make for a short fat oval instead of a long one. + private float curveOffset = 0; + + public FigureEightBuilder setLoopShape(int exponent, float intensity, float offset){ + this.curveExponent = Math.abs(exponent); + curveIntensity = intensity % 1f; + curveOffset = offset % 0.5f; + return this; + } + + private float targetAngle( float percentAlong ){ + percentAlong += curveOffset; + return 360f * (float)( + curveIntensity * curveEquation(percentAlong) + + (1-curveIntensity)*(percentAlong) + - curveOffset); + } + + private double curveEquation( double x ){ + return Math.pow(4, 2*curveExponent) + *(Math.pow((x % 0.5f )-0.25, 2*curveExponent + 1)) + + 0.25 + 0.5*Math.floor(2*x); + } + + private Room landmarkRoom; + + public FigureEightBuilder setLandmarkRoom(Room room){ + landmarkRoom = room; + return this; + } + + ArrayList firstLoop, secondLoop; + PointF firstLoopCenter, secondLoopCenter; + + @Override + public ArrayList build(ArrayList rooms) { + setupRooms(rooms); + + //TODO might want to make this able to work without an exit. Probably a random room would be landmark and the landmark room would become exit + if (landmarkRoom == null){ + landmarkRoom = Random.element(multiConnections); + } + + if (multiConnections.contains(landmarkRoom)){ + multiConnections.remove(landmarkRoom); + } + + float startAngle = Random.Float(0, 180); + + int roomsOnLoop = (int)(multiConnections.size()*pathLength) + Random.chances(pathLenJitterChances); + roomsOnLoop = Math.min(roomsOnLoop, multiConnections.size()); + + int roomsOnFirstLoop = roomsOnLoop/2; + if (roomsOnLoop % 2 == 1) roomsOnFirstLoop += Random.Int(2); + + firstLoop = new ArrayList<>(); + float[] pathTunnels = pathTunnelChances.clone(); + for (int i = 0; i <= roomsOnFirstLoop; i++){ + if (i == 0) + firstLoop.add(landmarkRoom); + else + firstLoop.add(multiConnections.remove(0)); + + int tunnels = Random.chances(pathTunnels); + if (tunnels == -1){ + pathTunnels = pathTunnelChances.clone(); + tunnels = Random.chances(pathTunnels); + } + pathTunnels[tunnels]--; + + for (int j = 0; j < tunnels; j++){ + firstLoop.add(ConnectionRoom.createRoom()); + } + } + if (entrance != null) firstLoop.add((firstLoop.size()+1)/2, entrance); + + int roomsOnSecondLoop = roomsOnLoop - roomsOnFirstLoop; + + secondLoop = new ArrayList<>(); + for (int i = 0; i <= roomsOnSecondLoop; i++){ + if (i == 0) + secondLoop.add(landmarkRoom); + else + secondLoop.add(multiConnections.remove(0)); + + int tunnels = Random.chances(pathTunnels); + if (tunnels == -1){ + pathTunnels = pathTunnelChances.clone(); + tunnels = Random.chances(pathTunnels); + } + pathTunnels[tunnels]--; + + for (int j = 0; j < tunnels; j++){ + secondLoop.add(ConnectionRoom.createRoom()); + } + } + if (exit != null) secondLoop.add((secondLoop.size()+1)/2, exit); + + landmarkRoom.setSize(); + landmarkRoom.setPos(0, 0); + + Room prev = landmarkRoom; + float targetAngle; + for (int i = 1; i < firstLoop.size(); i++){ + Room r = firstLoop.get(i); + targetAngle = startAngle + targetAngle( i / (float)firstLoop.size()); + if (placeRoom(rooms, prev, r, targetAngle) != -1) { + prev = r; + if (!rooms.contains(prev)) + rooms.add(prev); + } else { + //FIXME this is lazy, there are ways to do this without relying on chance + return null; + } + } + + //FIXME this is still fairly chance reliant + // should just write a general function for stitching two rooms together in builder + while (!prev.connect(landmarkRoom)){ + + ConnectionRoom c = ConnectionRoom.createRoom(); + if (placeRoom(firstLoop, prev, c, angleBetweenRooms(prev, entrance)) == -1){ + return null; + } + firstLoop.add(c); + rooms.add(c); + prev = c; + } + + prev = landmarkRoom; + startAngle += 180f; + for (int i = 1; i < secondLoop.size(); i++){ + Room r = secondLoop.get(i); + targetAngle = startAngle + targetAngle( i / (float)secondLoop.size()); + if (placeRoom(rooms, prev, r, targetAngle) != -1) { + prev = r; + if (!rooms.contains(prev)) + rooms.add(prev); + } else { + //FIXME this is lazy, there are ways to do this without relying on chance + return null; + } + } + + //FIXME this is still fairly chance reliant + // should just write a general function for stitching two rooms together in builder + while (!prev.connect(landmarkRoom)){ + + ConnectionRoom c = ConnectionRoom.createRoom(); + if (placeRoom(secondLoop, prev, c, angleBetweenRooms(prev, entrance)) == -1){ + return null; + } + secondLoop.add(c); + rooms.add(c); + prev = c; + } + + if (shop != null) { + float angle; + int tries = 10; + do { + angle = placeRoom(firstLoop, entrance, shop, Random.Float(360f)); + tries--; + } while (angle == -1 && tries >= 0); + if (angle == -1) return null; + } + + firstLoopCenter = new PointF(); + for (Room r : firstLoop){ + firstLoopCenter.x += (r.left + r.right)/2f; + firstLoopCenter.y += (r.top + r.bottom)/2f; + } + firstLoopCenter.x /= firstLoop.size(); + firstLoopCenter.y /= firstLoop.size(); + + secondLoopCenter = new PointF(); + for (Room r : secondLoop){ + secondLoopCenter.x += (r.left + r.right)/2f; + secondLoopCenter.y += (r.top + r.bottom)/2f; + } + secondLoopCenter.x /= secondLoop.size(); + secondLoopCenter.y /= secondLoop.size(); + + ArrayList branchable = new ArrayList<>(firstLoop); + branchable.addAll(secondLoop); + branchable.remove(landmarkRoom); //remove once so it isn't present twice + + ArrayList roomsToBranch = new ArrayList<>(); + roomsToBranch.addAll(multiConnections); + roomsToBranch.addAll(singleConnections); + weightRooms(branchable); + createBranches(rooms, branchable, roomsToBranch, branchTunnelChances); + + findNeighbours(rooms); + + for (Room r : rooms){ + for (Room n : r.neigbours){ + if (!n.connected.containsKey(r) + && Random.Float() < extraConnectionChance){ + r.connect(n); + } + } + } + + return rooms; + } + + @Override + protected float randomBranchAngle( Room r ) { + PointF center; + if (firstLoop.contains(r)){ + center = firstLoopCenter; + } else { + center = secondLoopCenter; + } + if (center == null) + return super.randomBranchAngle( r ); + else { + //generate four angles randomly and return the one which points closer to the center + float toCenter = angleBetweenPoints( new PointF((r.left + r.right)/2f, (r.top + r.bottom)/2f), center); + if (toCenter < 0) toCenter += 360f; + + float currAngle = Random.Float(360f); + for( int i = 0; i < 4; i ++){ + float newAngle = Random.Float(360f); + if (Math.abs(toCenter - newAngle) < Math.abs(toCenter - currAngle)){ + currAngle = newAngle; + } + } + return currAngle; + } + } + +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/LoopBuilder.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/LoopBuilder.java index b8061b187..82650e560 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/LoopBuilder.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/LoopBuilder.java @@ -86,10 +86,9 @@ public class LoopBuilder extends RegularBuilder { ArrayList loop = new ArrayList<>(); int roomsOnLoop = (int)(multiConnections.size()*pathLength) + Random.chances(pathLenJitterChances); roomsOnLoop = Math.min(roomsOnLoop, multiConnections.size()); - roomsOnLoop++; float[] pathTunnels = pathTunnelChances.clone(); - for (int i = 0; i < roomsOnLoop; i++){ + for (int i = 0; i <= roomsOnLoop; i++){ if (i == 0) loop.add(entrance); else @@ -137,14 +136,6 @@ public class LoopBuilder extends RegularBuilder { prev = c; } - loopCenter = new PointF(); - for (Room r : loop){ - loopCenter.x += (r.left + r.right)/2f; - loopCenter.y += (r.top + r.bottom)/2f; - } - loopCenter.x /= loop.size(); - loopCenter.y /= loop.size(); - if (shop != null) { float angle; int tries = 10; @@ -155,6 +146,14 @@ public class LoopBuilder extends RegularBuilder { if (angle == -1) return null; } + loopCenter = new PointF(); + for (Room r : loop){ + loopCenter.x += (r.left + r.right)/2f; + loopCenter.y += (r.top + r.bottom)/2f; + } + loopCenter.x /= loop.size(); + loopCenter.y /= loop.size(); + ArrayList branchable = new ArrayList<>(loop); ArrayList roomsToBranch = new ArrayList<>(); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/sewerboss/SewerBossEntranceRoom.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/sewerboss/SewerBossEntranceRoom.java index 8d63f256b..63b3e6b79 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/sewerboss/SewerBossEntranceRoom.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/sewerboss/SewerBossEntranceRoom.java @@ -30,35 +30,11 @@ import com.watabou.utils.Point; public class SewerBossEntranceRoom extends EntranceRoom { - @Override - public int minWidth() { - return 9; - } - - @Override - public int maxWidth() { - return 9; - } - @Override public int minHeight() { return 6; } - @Override - public int maxHeight() { - return 10; - } - - //TODO perhaps I just want to deny all top-side connections - @Override - public boolean canConnect(Point p) { - //refuses connections on the center 3 tiles on the top side, and the top tile along left/right - return super.canConnect(p) - && !(p.y == top && p.x >= (left + (width()/2 - 1)) && p.x <= (left + (width()/2 + 1))) - && p.y != top+1; - } - public void paint(Level level ) { Painter.fill( level, this, Terrain.WALL ); @@ -67,9 +43,6 @@ public class SewerBossEntranceRoom extends EntranceRoom { Painter.fill( level, left+1, top+1, width()-2, 1, Terrain.WALL_DECO); Painter.fill( level, left+1, top+2, width()-2, 1, Terrain.WATER); - Painter.set( level, left+width()/2, top+1, Terrain.LOCKED_EXIT); - level.exit = level.pointToCell(new Point(left+width()/2, top+1)); - do { level.entrance = level.pointToCell(random(3)); } while (level.findMob(level.entrance) != null); @@ -78,8 +51,8 @@ public class SewerBossEntranceRoom extends EntranceRoom { for (Room.Door door : connected.values()) { door.set( Room.Door.Type.REGULAR ); - if (door.y == top){ - Painter.set( level, door.x, door.y+1, Terrain.WATER); + if (door.y == top || door.y == top+1){ + Painter.drawInside( level, this, door, 1, Terrain.WATER); } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/sewerboss/SewerBossExitRoom.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/sewerboss/SewerBossExitRoom.java new file mode 100644 index 000000000..4caad71dd --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/sewerboss/SewerBossExitRoom.java @@ -0,0 +1,63 @@ +/* + * Pixel Dungeon + * Copyright (C) 2012-2015 Oleg Dolya + * + * Shattered Pixel Dungeon + * Copyright (C) 2014-2019 Evan Debenham + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +package com.shatteredpixel.shatteredpixeldungeon.levels.rooms.sewerboss; + +import com.shatteredpixel.shatteredpixeldungeon.levels.Level; +import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain; +import com.shatteredpixel.shatteredpixeldungeon.levels.painters.Painter; +import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room; +import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.ExitRoom; +import com.watabou.utils.Point; + +public class SewerBossExitRoom extends ExitRoom { + + @Override + public int minWidth() { + return Math.max(super.minWidth(), 7); + } + + @Override + public int minHeight() { + return Math.max(super.minHeight(), 7); + } + + public void paint(Level level) { + + Painter.fill( level, this, Terrain.WALL ); + Painter.fill( level, this, 1, Terrain.EMPTY ); + + for (Room.Door door : connected.values()) { + door.set( Room.Door.Type.REGULAR ); + } + + Point c = center(); + + Painter.fill( level, c.x-1, c.y-1, 3, 1, Terrain.WALL ); + Painter.fill( level, c.x-1, c.y , 3, 1, Terrain.WALL_DECO ); + Painter.fill( level, c.x-1, c.y+1, 3, 1, Terrain.WATER ); + + level.exit = level.pointToCell(c); + Painter.set( level, level.exit, Terrain.LOCKED_EXIT ); + + } + +}