From 0f55d3874f84fe78df4bc507b044260c2daa8801 Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Fri, 19 Dec 2025 16:56:20 -0500 Subject: [PATCH] v3.3.2: implemented a simple grid builder and tied it into vault level --- .../levels/VaultLevel.java | 250 +++++++----------- .../levels/builders/GridBuilder.java | 125 +++++++++ .../levels/features/HighGrass.java | 6 + 3 files changed, 229 insertions(+), 152 deletions(-) create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/GridBuilder.java diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/VaultLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/VaultLevel.java index c64d407f2..9353525f4 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/VaultLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/VaultLevel.java @@ -21,10 +21,7 @@ package com.shatteredpixel.shatteredpixeldungeon.levels; -import com.shatteredpixel.shatteredpixeldungeon.Assets; -import com.shatteredpixel.shatteredpixeldungeon.Bones; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; -import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; @@ -33,186 +30,115 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Generator; import com.shatteredpixel.shatteredpixeldungeon.items.Heap; import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.armor.Armor; -import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfRegrowth; +import com.shatteredpixel.shatteredpixeldungeon.items.keys.GoldenKey; +import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.TrinketCatalyst; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon; +import com.shatteredpixel.shatteredpixeldungeon.levels.builders.Builder; +import com.shatteredpixel.shatteredpixeldungeon.levels.builders.GridBuilder; import com.shatteredpixel.shatteredpixeldungeon.levels.features.LevelTransition; -import com.shatteredpixel.shatteredpixeldungeon.levels.painters.Painter; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room; -import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.EmptyRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.RegionDecoLineRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.SegmentedRoom; -import com.watabou.noosa.audio.Music; +import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.standard.entrance.EntranceRoom; import com.watabou.utils.Point; import com.watabou.utils.Random; -import com.watabou.utils.Rect; import java.util.ArrayList; -public class VaultLevel extends Level { //for now +public class VaultLevel extends CityLevel { - { - color1 = 0x4b6636; - color2 = 0xf2f2f2; + @Override + protected ArrayList initRooms() { + ArrayList initRooms = new ArrayList<>(); + + initRooms.add(roomEntrance = new EntranceRoom(){ + @Override + public int maxConnections(int direction) { + return 1; + } + + @Override + public boolean canMerge(Level l, Room other, Point p, int mergeTerrain) { + return false; + } + }); + + for (int i = 0; i < 23; i++){ + initRooms.add(new SegmentedRoom(){ + @Override + public float[] sizeCatProbs() { + return new float[]{1, 0, 0}; + } + }); + } + + initRooms.add(new RegionDecoLineRoom(){ + @Override + public int maxConnections(int direction) { + return 1; + } + + @Override + public boolean canMerge(Level l, Room other, Point p, int mergeTerrain) { + return false; + } + }); + + return initRooms; } @Override - public void playLevelMusic() { - Music.INSTANCE.playTracks(CityLevel.CITY_TRACK_LIST, CityLevel.CITY_TRACK_CHANCES, false); + protected Builder builder() { + return new GridBuilder(); } @Override - public String tilesTex() { - return Assets.Environment.TILES_CITY; - } - - @Override - public String waterTex() { - return Assets.Environment.WATER_CITY; + protected int nTraps() { + return 0; } @Override protected boolean build() { - setSize(34, 34); - - ArrayList rooms = new ArrayList<>(); - - Room finalRoom = null; - Room entryRoom = null; - - for (int x = 0; x < 4; x++){ - for (int y = 0; y < 4; y++){ - - if (x == 3 && y <= 1){ - if (y == 1) { - continue; - } else { - Room r = new RegionDecoLineRoom(); - r.set(1+8*x, 1+8*y, 9+8*x, 17); - rooms.add(r); - finalRoom = r; - continue; - } + for (int i = 0; i < 20; i++){ + Item item = Generator.randomUsingDefaults(Random.oneOf( + Generator.Category.WEAPON, Generator.Category.WEAPON, Generator.Category.WEAPON, + Generator.Category.ARMOR, + Generator.Category.WAND, + Generator.Category.RING)); + if (item.cursed){ + item.cursed = false; + if (item instanceof MeleeWeapon && ((MeleeWeapon) item).hasCurseEnchant()){ + ((MeleeWeapon) item).enchant(null); + } else if (item instanceof Armor && ((Armor) item).hasCurseGlyph()){ + ((Armor) item).inscribe(null); } + } + item.identify(); + addItemToSpawn(item); + } - if (x == 0 && y == 3){ - Room r = new EmptyRoom(); - r.set(1+8*x, 1+8*y, 9+8*x, 9+8*y); - rooms.add(r); - entryRoom = r; - } else { - Room r = new SegmentedRoom(); - r.set(1+8*x, 1+8*y, 9+8*x, 9+8*y); - rooms.add(r); - } + if (!super.build()){ + return false; + } + + Room finalRoom = room(RegionDecoLineRoom.class); + for (Point p : finalRoom.getPoints()){ + int cell = pointToCell(p); + if (map[cell] == Terrain.REGION_DECO){ + set(cell, Terrain.REGION_DECO_ALT, this); + } else if (map[cell] == Terrain.EMPTY || map[cell] == Terrain.EMPTY_DECO || map[cell] == Terrain.WATER || map[cell] == Terrain.HIGH_GRASS || map[cell] == Terrain.GRASS){ + set(cell, Terrain.EMPTY_SP, this); } } - //builder.findneighbnours - 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] ); - } - } - - for (Room n : rooms){ - for (Room p : n.neigbours){ - if (p.height() > 10){ - continue; - } - if (n.height() > 10){ - if (n.canConnect(p)){ - if (n.bottom == p.top){ - n.connect(p); - } - } - } else if (n.canConnect(p)) { - n.connect(p); - } - } - } - - //Painter.placedoors - for (Room r : rooms){ - for (Room n : r.connected.keySet()) { - Room.Door door = r.connected.get( n ); - if (door == null) { - - Rect i = r.intersect( n ); - ArrayList doorSpots = new ArrayList<>(); - for (Point p : i.getPoints()){ - if (r.canConnect(p) && n.canConnect(p)) - doorSpots.add(p); - } - if (doorSpots.isEmpty()){ - ShatteredPixelDungeon.reportException( - new RuntimeException("Could not place a door! " + - "r=" + r.getClass().getSimpleName() + - " n=" + n.getClass().getSimpleName())); - continue; - } - door = new Room.Door(Random.element(doorSpots)); - - r.connected.put( n, door ); - n.connected.put( r, door ); - } - } - } - - for (Room n : rooms){ - n.paint(this); - if (n instanceof RegionDecoLineRoom){ - Painter.fill(this, n, 1, Terrain.EMPTY_SP); - Painter.fill(this, n.left+1, n.top+1, 7, 1, Terrain.REGION_DECO_ALT); - Painter.fill(this, n.left+1, n.top+1, 1, 14, Terrain.REGION_DECO_ALT); - Painter.fill(this, n.right-1, n.top+1, 1, 14, Terrain.REGION_DECO_ALT); - } - for (Point door : n.connected.values()){ - Painter.set(this, door, Terrain.DOOR); - } - } - - entrance = pointToCell(entryRoom.random()); + set(entrance(), Terrain.EMPTY, this); transitions.add(new LevelTransition(this, - entrance, + entrance(), LevelTransition.Type.BRANCH_ENTRANCE, Dungeon.depth, 0, LevelTransition.Type.BRANCH_EXIT)); - rooms.remove(entryRoom); - rooms.remove(finalRoom); - - for (Room n : rooms){ - if (Random.Int(5) != 0){ - Item item = Generator.randomUsingDefaults(Random.oneOf( - Generator.Category.WEAPON, Generator.Category.WEAPON, - Generator.Category.ARMOR, - Generator.Category.WAND, - Generator.Category.RING)); - - //regrowth is disallowed as it can be used to farm HP regen - if (item instanceof WandOfRegrowth){ - continue; - } - - int pos; - do { - pos = pointToCell(n.random()); - } while (map[pos] != Terrain.EMPTY); - if (item.cursed){ - item.cursed = false; - if (item instanceof MeleeWeapon && ((MeleeWeapon) item).hasCurseEnchant()){ - ((MeleeWeapon) item).enchant(null); - } else if (item instanceof Armor && ((Armor) item).hasCurseGlyph()){ - ((Armor) item).inscribe(null); - } - } - item.identify(); - drop(item, pos); - } - } - return true; } @@ -237,9 +163,29 @@ public class VaultLevel extends Level { //for now @Override protected void createItems() { - //do nothing for now + //copypasta from super.createItems + for (Item item : itemsToSpawn) { + int cell = randomDropCell(); + if (item instanceof TrinketCatalyst){ + drop( item, cell ).type = Heap.Type.LOCKED_CHEST; + int keyCell = randomDropCell(); + drop( new GoldenKey(Dungeon.depth), keyCell ).type = Heap.Type.HEAP; + if (map[keyCell] == Terrain.HIGH_GRASS || map[keyCell] == Terrain.FURROWED_GRASS) { + map[keyCell] = Terrain.GRASS; + losBlocking[keyCell] = false; + } + } else { + drop( item, cell ).type = Heap.Type.HEAP; + } + if (map[cell] == Terrain.HIGH_GRASS || map[cell] == Terrain.FURROWED_GRASS) { + map[cell] = Terrain.GRASS; + losBlocking[cell] = false; + } + } } + //TODO createItems will generate normal ones too =S + @Override public int randomRespawnCell( Char ch ) { return entrance()-width(); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/GridBuilder.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/GridBuilder.java new file mode 100644 index 000000000..808883ed2 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/GridBuilder.java @@ -0,0 +1,125 @@ +/* + * Pixel Dungeon + * Copyright (C) 2012-2015 Oleg Dolya + * + * Shattered Pixel Dungeon + * Copyright (C) 2014-2025 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.watabou.utils.Random; +import com.watabou.utils.SparseArray; + +import java.util.ArrayList; + +//a builder with static room sizes aligned to a grid +//TODO extend regular builder? +public class GridBuilder extends Builder { + + public static int ROOM_SIZE = 9; + + //each adjacency is processed twice, so this gives a ~50% chance to connect two adjacent rooms + protected float extraConnectionChance = 0.30f; + + @Override + public ArrayList build(ArrayList rooms) { + for(Room r : rooms){ + r.setEmpty(); + } + + Room entrance = null; + Room exit = null; + + for (Room r : rooms){ + if (r.isEntrance()){ + entrance = r; + } else if (r.isExit()){ + exit = r; + } + } + + if (!entrance.forceSize(ROOM_SIZE, ROOM_SIZE)){ + throw new RuntimeException("rigid room sizes for now!"); + } + entrance.setPos(0, 0); + + ArrayList toPlace = new ArrayList<>(rooms); + toPlace.remove(entrance); + + ArrayList placed = new ArrayList<>(); + placed.add(entrance); + + //use a sparse array to track room positions, with a mapping of x + 1000*y = cell + //this assumes that levels won't more than 1000 rooms wide + SparseArray gridCells = new SparseArray<>(); + gridCells.put(0, entrance); + + for (Room r : toPlace){ + if (!r.forceSize(ROOM_SIZE, ROOM_SIZE)){ + throw new RuntimeException("rigid room sizes for now!"); + } + do { + Room n = Random.element(placed); + int nIdx = gridCells.findKey(n, true, Integer.MIN_VALUE); + int rIdx = nIdx; + switch (Random.Int(4)){ + case 0: + rIdx += 1; + break; + case 1: + rIdx += 1000; + break; + case 2: + rIdx -= 1; + break; + case 3: + rIdx -= 1000; + break; + } + //TODO negatives + int x = rIdx%1000; + int y = rIdx/1000; + r.setPos(x*(ROOM_SIZE-1), y*(ROOM_SIZE-1)); + //TODO want to manually limit size probably + if (x >= 0 && y >= 0 && !gridCells.containsKey(rIdx)){ + if (r.connect(n)) { + placed.add(r); + gridCells.put(rIdx, r); + } + } + } while (!placed.contains(r)); + } + + //need a buffer room? + //contains an internal room, fills with + + findNeighbours(rooms); + + for (Room r : rooms){ + for (Room n : r.neigbours){ + if (!n.connected.containsKey(r) + && Random.Float() < extraConnectionChance){ + r.connect(n); + } + } + } + + return rooms; + } + +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/features/HighGrass.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/features/HighGrass.java index 1e983b313..f960f100a 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/features/HighGrass.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/features/HighGrass.java @@ -41,6 +41,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.PetrifiedSeed; import com.shatteredpixel.shatteredpixeldungeon.levels.Level; import com.shatteredpixel.shatteredpixeldungeon.levels.MiningLevel; import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain; +import com.shatteredpixel.shatteredpixeldungeon.levels.VaultLevel; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.watabou.utils.Random; @@ -118,6 +119,11 @@ public class HighGrass { && Random.Int(3) != 0){ naturalismLevel = -1; } + + //grass gives no loot in vault tester area + if (Dungeon.level instanceof VaultLevel){ + naturalismLevel = -1; + } if (naturalismLevel >= 0) { // Seed, scales from 1/25 to 1/9