diff --git a/gradle.properties b/gradle.properties index 05d2bf6..7eb0809 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.jvmargs = -Xmx1G org.gradle.parallel = true -version = 11.0.0 +version = b12.0.0 maven_group = net.ludocrypt archives_base_name = limlib \ No newline at end of file diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/CombineMaze.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/CombineMaze.java deleted file mode 100644 index fb60b9a..0000000 --- a/src/main/java/net/ludocrypt/limlib/api/world/maze/CombineMaze.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.ludocrypt.limlib.api.world.maze; - -public class CombineMaze extends MazeComponent { - - MazeComponent[] components; - - public CombineMaze(MazeComponent... components) { - super(components[0].width, components[0].height); - this.components = components; - } - - @Override - public void create() { - - for (MazeComponent maze : components) { - - for (int x = 0; x < width; x++) { - - for (int y = 0; y < height; y++) { - CellState reference = maze.cellState(x, y); - - for (Face face : Face.values()) { - - if (reference.goes(face)) { - this.cellState(x, y).go(face); - } - - } - - this.cellState(x, y).appendAll(reference.getExtra()); - } - - } - - } - - } - -} diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/DepthFirstMaze.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/DepthFirstMaze.java deleted file mode 100644 index 72b6fcc..0000000 --- a/src/main/java/net/ludocrypt/limlib/api/world/maze/DepthFirstMaze.java +++ /dev/null @@ -1,53 +0,0 @@ -package net.ludocrypt.limlib.api.world.maze; - -import java.util.List; - -import com.google.common.collect.Lists; - -import net.minecraft.util.random.RandomGenerator; - -public class DepthFirstMaze extends DepthLikeMaze { - - public RandomGenerator random; - - public DepthFirstMaze(int width, int height, RandomGenerator RandomGenerator) { - super(width, height); - this.random = RandomGenerator; - } - - @Override - public void create() { - visit(new Vec2i(0, 0)); - this.visitedCells++; - this.stack.push(new Vec2i(0, 0)); - - while (visitedCells < this.width * this.height) { - List neighbours = Lists.newArrayList(); - - for (Face face : Face.values()) { - - if (this.hasNeighbour(this.stack.peek(), face)) { - neighbours.add(face); - } - - } - - if (!neighbours.isEmpty()) { - Face nextFace = neighbours.get(random.nextInt(neighbours.size())); - - this.cellState(this.stack.peek()).go(nextFace); - this.cellState(this.stack.peek().go(nextFace)).go(nextFace.mirror()); - this.visit(this.stack.peek().go(nextFace)); - this.stack.push(this.stack.peek().go(nextFace)); - - this.visitedCells++; - - } else { - this.stack.pop(); - } - - } - - } - -} diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/DepthFirstMazeSolver.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/DepthFirstMazeSolver.java deleted file mode 100644 index 96d4152..0000000 --- a/src/main/java/net/ludocrypt/limlib/api/world/maze/DepthFirstMazeSolver.java +++ /dev/null @@ -1,145 +0,0 @@ -package net.ludocrypt.limlib.api.world.maze; - -import java.util.List; -import java.util.Stack; - -import com.google.common.collect.Lists; - -import net.minecraft.util.random.RandomGenerator; - -/** - * Solves a maze using the Depth First Search algorithm. - **/ -public class DepthFirstMazeSolver extends DepthLikeMaze { - - private final MazeComponent mazeToSolve; - private final Vec2i end; - private final List beginnings; - public final RandomGenerator random; - - /** - * Creates a depth first maze solver. - *

- * - * @param mazeToSolve is the maze to solve - * @param random is the random - * @param end is the position for the depth first algorithm to find - * @param beginnings are the positions to start from - **/ - public DepthFirstMazeSolver(MazeComponent mazeToSolve, RandomGenerator random, Vec2i end, Vec2i... beginnings) { - super(mazeToSolve.width, mazeToSolve.height); - this.mazeToSolve = mazeToSolve; - this.end = end; - this.beginnings = Lists.newArrayList(beginnings); - this.random = random; - } - - @Override - public void create() { - List> paths = Lists.newArrayList(); - this.beginnings.forEach((beginning) -> { - Stack stack = new Stack(); - stack.push(new Vec2i(beginning.getX(), beginning.getY())); - Vec2i peek = stack.peek(); - visit(peek); - - while (!peek.equals(end)) { - List neighbours = Lists.newArrayList(); - - for (Face face : Face.values()) { - - if (this.hasNeighbour(peek, face)) { - neighbours.add(face); - } - - } - - if (!neighbours.isEmpty()) { - Face nextFace = neighbours.get(random.nextInt(neighbours.size())); - - visit(peek.go(nextFace)); - stack.push(peek.go(nextFace)); - - } else { - stack.pop(); - } - - peek = stack.peek(); - } - - for (int x = 0; x < width; x++) { - - for (int y = 0; y < height; y++) { - visit(new Vec2i(x, y), false); - } - - } - - paths.add(stack); - }); - paths.forEach((path) -> { - - for (int i = 0; i < path.size(); i++) { - Vec2i pos = path.get(i); - - if (i + 1 != path.size()) { - Vec2i nextPos = path.get(i + 1); - - Face face = pos.normal(nextPos); - this.cellState(pos).go(face); - this.cellState(pos.go(face)).go(face.mirror()); - - this.cellState(pos).appendAll(mazeToSolve.cellState(pos).getExtra()); - - } - - if (this.beginnings.contains(pos) || pos.equals(this.end)) { - - if (pos.getX() == 0) { - this.cellState(pos).down(); - } - - if (pos.getY() == 0) { - this.cellState(pos).left(); - } - - if (pos.getX() == width - 1) { - this.cellState(pos).up(); - } - - if (pos.getY() == height - 1) { - this.cellState(pos).right(); - } - - } - - } - - }); - } - - public MazeComponent getMazeToSolve() { - return mazeToSolve; - } - - @Override - public boolean hasNeighbourUp(Vec2i vec) { - return super.hasNeighbourUp(vec) && this.mazeToSolve.cellState(vec).goesUp(); - } - - @Override - public boolean hasNeighbourRight(Vec2i vec) { - return super.hasNeighbourRight(vec) && this.mazeToSolve.cellState(vec).goesRight(); - } - - @Override - public boolean hasNeighbourDown(Vec2i vec) { - return super.hasNeighbourDown(vec) && this.mazeToSolve.cellState(vec).goesDown(); - } - - @Override - public boolean hasNeighbourLeft(Vec2i vec) { - return super.hasNeighbourLeft(vec) && this.mazeToSolve.cellState(vec).goesLeft(); - } - -} diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/DilateMaze.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/DilateMaze.java deleted file mode 100644 index 681274a..0000000 --- a/src/main/java/net/ludocrypt/limlib/api/world/maze/DilateMaze.java +++ /dev/null @@ -1,100 +0,0 @@ -package net.ludocrypt.limlib.api.world.maze; -/** - * Dilates or scales a maze to be dilation times bigger. - **/ -public class DilateMaze extends MazeComponent { - - private MazeComponent mazeIn; - private int dilationX; - private int dilationY; - - public DilateMaze(MazeComponent mazeIn, int dilation) { - this(mazeIn, dilation, dilation); - } - - public DilateMaze(MazeComponent mazeIn, int dilationX, int dilationY) { - super(mazeIn.width * dilationX, mazeIn.height * dilationY); - this.mazeIn = mazeIn; - this.dilationX = dilationX; - this.dilationY = dilationY; - } - - @Override - public void create() { - - for (int x = 0; x < mazeIn.width; x++) { - - for (int y = 0; y < mazeIn.height; y++) { - - for (int dx = 0; dx < dilationX; dx++) { - - for (int dy = 0; dy < dilationY; dy++) { - int mazeX = x * dilationX + dx; - int mazeY = y * dilationY + dy; - Vec2i position = new Vec2i(mazeX, mazeY); - CellState reference = mazeIn.cellState(x, y); - - if (dx % dilationX == 0) { - - if (dy % dilationY == 0) { - CellState copy = reference.copy(); - copy.setPosition(position); - this.maze[mazeY * this.width + mazeX] = copy; - } else { - - if (mazeIn.cellState(x, y).goesRight()) { - CellState copy = new CellState(); - copy.right(); - copy.left(); - copy.setPosition(position); - copy.appendAll(reference.getExtra()); - this.maze[mazeY * this.width + mazeX] = copy; - } - - } - - } else { - - if (dy % dilationY == 0) { - - if (mazeIn.cellState(x, y).goesUp()) { - CellState copy = new CellState(); - copy.up(); - copy.down(); - copy.setPosition(position); - copy.appendAll(reference.getExtra()); - this.maze[mazeY * this.width + mazeX] = copy; - } - - } else { - CellState copy = new CellState(); - copy.setPosition(position); - copy.appendAll(reference.getExtra()); - this.maze[mazeY * this.width + mazeX] = copy; - } - - } - - } - - } - - } - - } - - } - - public MazeComponent getMazeIn() { - return mazeIn; - } - - public int getDilationX() { - return dilationX; - } - - public int getDilationY() { - return dilationY; - } - -} diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/MazeComponent.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/MazeComponent.java index 855ace3..d5051b5 100644 --- a/src/main/java/net/ludocrypt/limlib/api/world/maze/MazeComponent.java +++ b/src/main/java/net/ludocrypt/limlib/api/world/maze/MazeComponent.java @@ -1,20 +1,21 @@ package net.ludocrypt.limlib.api.world.maze; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import com.google.common.collect.Maps; +import net.ludocrypt.limlib.api.world.maze.storage.NbtSerializer; import net.minecraft.nbt.NbtCompound; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3i; -public abstract class MazeComponent { +public final class MazeComponent implements NbtSerializer { public final int width; public final int height; public final CellState[] maze; - public boolean generated = false; public MazeComponent(int width, int height) { this.width = width; @@ -33,33 +34,6 @@ public MazeComponent(int width, int height) { } - /** - * Attempt to generate the maze - **/ - public void generateMaze() { - this.generateMaze(false); - } - - /** - * Attempt to generate the maze, throw if this has already been generated. - **/ - public void generateMaze(boolean doesThrow) { - - if (generated) { - - if (doesThrow) { - throw new UnsupportedOperationException("This maze has already been created"); - } - - } else { - create(); - generated = true; - } - - } - - public abstract void create(); - public CellState cellState(int x, int y) { return this.maze[y * this.width + x]; } @@ -91,6 +65,34 @@ public String toString() { return row.toString(); } + @Override + public NbtCompound write(NbtCompound nbt) { + + for (int x = 0; x < width; x++) { + + for (int y = 0; y < height; y++) { + nbt.put(x + "." + y, this.cellState(x, y).write(new NbtCompound())); + } + + } + + return nbt; + } + + @Override + public MazeComponent read(NbtCompound nbt) { + + for (int x = 0; x < width; x++) { + + for (int y = 0; y < height; y++) { + this.cellState(x, y).read(nbt.getCompound(x + "." + y)); + } + + } + + return this; + } + /** * Describes the state of a particular room or 'cell' in a maze *

@@ -102,7 +104,7 @@ public String toString() { * @param extra information appended to this state * @param position inside the maze **/ - public static class CellState { + public static final class CellState implements NbtSerializer { private Vec2i position = new Vec2i(0, 0); private boolean up = false; @@ -261,9 +263,45 @@ public String toString() { } + @Override + public NbtCompound write(NbtCompound nbt) { + nbt.putBoolean("up", up); + nbt.putBoolean("down", down); + nbt.putBoolean("left", left); + nbt.putBoolean("right", right); + nbt.put("pos", this.position.write(new NbtCompound())); + + NbtCompound extraData = new NbtCompound(); + + for (Entry entry : extra.entrySet()) { + extraData.put(entry.getKey(), entry.getValue()); + } + + nbt.put("extra", extraData); + + return nbt; + } + + @Override + public CellState read(NbtCompound nbt) { + this.up = nbt.getBoolean("up"); + this.down = nbt.getBoolean("down"); + this.left = nbt.getBoolean("left"); + this.right = nbt.getBoolean("right"); + this.position.read(nbt.getCompound("pos")); + + NbtCompound extraData = nbt.getCompound("extra"); + + for (String key : extraData.getKeys()) { + this.extra.put(key, extraData.getCompound(key)); + } + + return this; + } + } - public static class Vec2i { + public static final class Vec2i implements NbtSerializer { private int x; private int y; @@ -374,6 +412,22 @@ public String toString() { return "(" + this.x + ", " + this.y + ")"; } + @Override + public NbtCompound write(NbtCompound nbt) { + nbt.putInt("x", x); + nbt.putInt("y", y); + + return nbt; + } + + @Override + public Vec2i read(NbtCompound nbt) { + this.x = nbt.getInt("x"); + this.y = nbt.getInt("y"); + + return this; + } + } public static enum Face { diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/MazeGenerator.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/MazeGenerator.java index b30184b..b592d92 100644 --- a/src/main/java/net/ludocrypt/limlib/api/world/maze/MazeGenerator.java +++ b/src/main/java/net/ludocrypt/limlib/api/world/maze/MazeGenerator.java @@ -1,6 +1,7 @@ package net.ludocrypt.limlib.api.world.maze; import java.util.HashMap; +import java.util.function.BiConsumer; import net.ludocrypt.limlib.api.world.LimlibHelper; import net.ludocrypt.limlib.api.world.maze.MazeComponent.CellState; @@ -8,15 +9,17 @@ import net.minecraft.util.random.RandomGenerator; import net.minecraft.world.ChunkRegion; -public class MazeGenerator { +public class MazeGenerator { - private final HashMap mazes = new HashMap(30); + private final HashMap mazes = new HashMap(30); public final int width; public final int height; public final int thicknessX; public final int thicknessY; public final long seedModifier; + private BiConsumer whenNewMaze; + /** * Creates a rectangular maze generator. *

@@ -46,7 +49,7 @@ public MazeGenerator(int width, int height, int thicknessX, int thicknessY, long * @param cellDecorator funcional interface to generate a single maze block, or * 'cell' */ - public void generateMaze(Vec2i pos, ChunkRegion region, MazeCreator mazeCreator, CellDecorator cellDecorator) { + public void generateMaze(Vec2i pos, ChunkRegion region, MazeCreator mazeCreator, CellDecorator cellDecorator) { for (int x = 0; x < 16; x++) { @@ -54,29 +57,37 @@ public void generateMaze(Vec2i pos, ChunkRegion region, MazeCreator mazeCreat Vec2i inPos = pos.add(x, y); if (Math.floorMod(inPos.getX(), thicknessX) == 0 && Math.floorMod(inPos.getY(), thicknessY) == 0) { - Vec2i mazePos = new Vec2i(inPos.getX() - Math.floorMod(inPos.getX(), (width * thicknessX)), + Vec2i realPos = new Vec2i(inPos.getX() - Math.floorMod(inPos.getX(), (width * thicknessX)), inPos.getY() - Math.floorMod(inPos.getY(), (height * thicknessY))); - M maze; + + Vec2i mazePos = new Vec2i(realPos.getX() / (width * thicknessX), realPos.getY() / (height * thicknessY)); + + MazeComponent maze; if (this.mazes.containsKey(mazePos)) { maze = this.mazes.get(mazePos); } else { maze = mazeCreator - .newMaze(region, mazePos, width, height, + .newMaze(region, realPos, new Vec2i(width, height), RandomGenerator .createLegacy(LimlibHelper .blockSeed(mazePos.getX(), mazePos.getY(), region.getSeed() + seedModifier))); this.mazes.put(mazePos, maze); + + if (this.whenNewMaze != null) { + this.whenNewMaze.accept(mazePos, maze); + } + } - int mazeX = (inPos.getX() - mazePos.getX()) / thicknessX; - int mazeY = (inPos.getY() - mazePos.getY()) / thicknessY; - CellState originCell = maze.cellState(mazeX, mazeY); + int cellX = (inPos.getX() - realPos.getX()) / thicknessX; + int cellY = (inPos.getY() - realPos.getY()) / thicknessY; + CellState originCell = maze.cellState(cellX, cellY); cellDecorator - .generate(region, inPos, mazePos, maze, originCell, new Vec2i(this.thicknessX, this.thicknessY), + .generate(region, inPos, realPos, maze, originCell, new Vec2i(this.thicknessX, this.thicknessY), RandomGenerator .createLegacy(LimlibHelper - .blockSeed(mazePos.getX(), mazePos.getY(), region.getSeed() + seedModifier))); + .blockSeed(realPos.getX(), realPos.getY(), region.getSeed() + seedModifier))); } } @@ -85,22 +96,26 @@ public void generateMaze(Vec2i pos, ChunkRegion region, MazeCreator mazeCreat } - public HashMap getMazes() { + public HashMap getMazes() { return mazes; } + public void connect(BiConsumer whenNewMaze) { + this.whenNewMaze = whenNewMaze; + } + @FunctionalInterface - public static interface CellDecorator { + public static interface CellDecorator { - void generate(ChunkRegion region, Vec2i pos, Vec2i mazePos, M maze, CellState state, Vec2i thickness, + void generate(ChunkRegion region, Vec2i pos, Vec2i mazePos, MazeComponent maze, CellState state, Vec2i thickness, RandomGenerator random); } @FunctionalInterface - public static interface MazeCreator { + public static interface MazeCreator { - M newMaze(ChunkRegion region, Vec2i mazePos, int width, int height, RandomGenerator random); + MazeComponent newMaze(ChunkRegion region, Vec2i mazePos, Vec2i size, RandomGenerator random); } diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/MazeStorage.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/MazeStorage.java new file mode 100644 index 0000000..f4e1103 --- /dev/null +++ b/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/MazeStorage.java @@ -0,0 +1,86 @@ +package net.ludocrypt.limlib.api.world.maze.storage; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; + +import org.apache.commons.io.FilenameUtils; +import org.slf4j.Logger; + +import com.mojang.logging.LogUtils; + +import net.ludocrypt.limlib.api.world.maze.MazeComponent; +import net.ludocrypt.limlib.api.world.maze.MazeComponent.Vec2i; +import net.ludocrypt.limlib.api.world.maze.MazeGenerator; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtIo; + +public class MazeStorage { + + private static final Logger LOGGER = LogUtils.getLogger(); + + private final Map generators; + + private Queue dirt; + + private File dir; + + public MazeStorage(Map generators, File dir) { + this.generators = generators; + this.dir = dir; + dir.mkdirs(); + + for (Entry entry : generators.entrySet()) { + entry.getValue().connect((pos, maze) -> dirt.add(() -> serialize(entry.getKey(), pos, maze))); + } + + } + + public void read() { + + for (String id : generators.keySet()) { + File mazeDir = new File(dir, id); + + for (File data : mazeDir.listFiles((file) -> FilenameUtils.getExtension(file.getAbsolutePath()).equals("nbt"))) { + + try { + NbtCompound readMaze = NbtIo.read(data); + + String[] sizeRaw = FilenameUtils.getBaseName(data.getAbsolutePath()).substring(2).split("\\."); + Vec2i size = new Vec2i(Integer.valueOf(sizeRaw[0]), Integer.valueOf(sizeRaw[1])); + + this.generators.get(id).getMazes().put(size, new MazeComponent(size.getX(), size.getY()).read(readMaze)); + } catch (IOException | NullPointerException e) { + LOGGER.error("Could not read data {}", this, e); + } + + } + + } + + } + + public void save() { + Runnable serializer; + + while ((serializer = dirt.poll()) != null) { + serializer.run(); + } + + } + + public void serialize(String mazeId, Vec2i pos, MazeComponent maze) { + + try { + NbtIo + .writeCompressed(maze.write(new NbtCompound()), + new File(new File(dir, mazeId), "m." + pos.getX() + "." + pos.getY() + ".nbt")); + } catch (IOException e) { + LOGGER.error("Could not save data {}", this, e); + } + + } + +} diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/MazeStorageProvider.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/MazeStorageProvider.java new file mode 100644 index 0000000..34fdfab --- /dev/null +++ b/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/MazeStorageProvider.java @@ -0,0 +1,11 @@ +package net.ludocrypt.limlib.api.world.maze.storage; + +import java.util.Map; + +import net.ludocrypt.limlib.api.world.maze.MazeGenerator; + +public interface MazeStorageProvider { + + public Map generators(); + +} diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/NbtSerializer.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/NbtSerializer.java new file mode 100644 index 0000000..0cd952e --- /dev/null +++ b/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/NbtSerializer.java @@ -0,0 +1,11 @@ +package net.ludocrypt.limlib.api.world.maze.storage; + +import net.minecraft.nbt.NbtCompound; + +public interface NbtSerializer { + + public NbtCompound write(NbtCompound nbt); + + public T read(NbtCompound nbt); + +} diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/ServerWorldMazeAccess.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/ServerWorldMazeAccess.java new file mode 100644 index 0000000..51a755a --- /dev/null +++ b/src/main/java/net/ludocrypt/limlib/api/world/maze/storage/ServerWorldMazeAccess.java @@ -0,0 +1,7 @@ +package net.ludocrypt.limlib.api.world.maze.storage; + +public interface ServerWorldMazeAccess { + + public MazeStorage getMazeStorage(); + +} diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/DepthLikeMaze.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/types/DepthLikeMaze.java similarity index 56% rename from src/main/java/net/ludocrypt/limlib/api/world/maze/DepthLikeMaze.java rename to src/main/java/net/ludocrypt/limlib/api/world/maze/types/DepthLikeMaze.java index 1f5f69a..c3dfc9a 100644 --- a/src/main/java/net/ludocrypt/limlib/api/world/maze/DepthLikeMaze.java +++ b/src/main/java/net/ludocrypt/limlib/api/world/maze/types/DepthLikeMaze.java @@ -1,33 +1,37 @@ -package net.ludocrypt.limlib.api.world.maze; +package net.ludocrypt.limlib.api.world.maze.types; import java.util.Stack; +import net.ludocrypt.limlib.api.world.maze.MazeComponent; +import net.ludocrypt.limlib.api.world.maze.MazeComponent.Face; +import net.ludocrypt.limlib.api.world.maze.MazeComponent.Vec2i; import net.minecraft.nbt.NbtCompound; -public abstract class DepthLikeMaze extends MazeComponent { +public final class DepthLikeMaze { + public MazeComponent maze; public Stack stack = new Stack(); public int visitedCells = 0; - public DepthLikeMaze(int width, int height) { - super(width, height); + public DepthLikeMaze(MazeComponent maze) { + this.maze = maze; } public boolean hasNeighbourUp(Vec2i vec) { - return fits(vec.up()) && !visited(vec.up()); + return this.maze.fits(vec.up()) && !visited(vec.up()); } public boolean hasNeighbourRight(Vec2i vec) { - return fits(vec.right()) && !visited(vec.right()); + return this.maze.fits(vec.right()) && !visited(vec.right()); } public boolean hasNeighbourDown(Vec2i vec) { - return fits(vec.down()) && !visited(vec.down()); + return this.maze.fits(vec.down()) && !visited(vec.down()); } public boolean hasNeighbourLeft(Vec2i vec) { - return fits(vec.left()) && !visited(vec.left()); + return this.maze.fits(vec.left()) && !visited(vec.left()); } public boolean hasNeighbours(Vec2i vec) { @@ -51,13 +55,13 @@ public NbtCompound visit(Vec2i vec) { public NbtCompound visit(Vec2i vec, boolean visited) { NbtCompound appendage = new NbtCompound(); appendage.putBoolean("visited", visited); - cellState(vec).getExtra().put("visited", appendage); + this.maze.cellState(vec).getExtra().put("visited", appendage); return appendage; } public boolean visited(Vec2i vec) { - return cellState(vec).getExtra().containsKey("visited") - ? cellState(vec).getExtra().get("visited").getBoolean("visited") + return this.maze.cellState(vec).getExtra().containsKey("visited") + ? this.maze.cellState(vec).getExtra().get("visited").getBoolean("visited") : false; } diff --git a/src/main/java/net/ludocrypt/limlib/api/world/maze/types/MazeCreator.java b/src/main/java/net/ludocrypt/limlib/api/world/maze/types/MazeCreator.java new file mode 100644 index 0000000..f6fe44f --- /dev/null +++ b/src/main/java/net/ludocrypt/limlib/api/world/maze/types/MazeCreator.java @@ -0,0 +1,244 @@ +package net.ludocrypt.limlib.api.world.maze.types; + +import java.util.List; +import java.util.Stack; + +import com.google.common.collect.Lists; + +import net.ludocrypt.limlib.api.world.maze.MazeComponent; +import net.ludocrypt.limlib.api.world.maze.MazeComponent.CellState; +import net.ludocrypt.limlib.api.world.maze.MazeComponent.Face; +import net.ludocrypt.limlib.api.world.maze.MazeComponent.Vec2i; +import net.minecraft.util.random.RandomGenerator; + +public class MazeCreator { + + public static MazeComponent combine(MazeComponent... components) { + MazeComponent combined = new MazeComponent(components[0].width, components[0].height); + + for (MazeComponent maze : components) { + + for (int x = 0; x < components[0].width; x++) { + + for (int y = 0; y < components[0].height; y++) { + CellState reference = maze.cellState(x, y); + + for (Face face : Face.values()) { + + if (reference.goes(face)) { + combined.cellState(x, y).go(face); + } + + } + + combined.cellState(x, y).appendAll(reference.getExtra()); + } + + } + + } + + return combined; + } + + public static MazeComponent dilate(MazeComponent mazeIn, int dilationX, int dilationY) { + MazeComponent dilate = new MazeComponent(mazeIn.width * dilationX, mazeIn.height * dilationY); + + for (int x = 0; x < mazeIn.width; x++) { + + for (int y = 0; y < mazeIn.height; y++) { + + for (int dx = 0; dx < dilationX; dx++) { + + for (int dy = 0; dy < dilationY; dy++) { + int mazeX = x * dilationX + dx; + int mazeY = y * dilationY + dy; + Vec2i position = new Vec2i(mazeX, mazeY); + CellState reference = mazeIn.cellState(x, y); + + if (dx % dilationX == 0) { + + if (dy % dilationY == 0) { + CellState copy = reference.copy(); + copy.setPosition(position); + dilate.maze[mazeY * dilate.width + mazeX] = copy; + } else { + + if (reference.goesRight()) { + CellState copy = new CellState(); + copy.right(); + copy.left(); + copy.setPosition(position); + copy.appendAll(reference.getExtra()); + dilate.maze[mazeY * dilate.width + mazeX] = copy; + } + + } + + } else { + + if (dy % dilationY == 0) { + + if (reference.goesUp()) { + CellState copy = new CellState(); + copy.up(); + copy.down(); + copy.setPosition(position); + copy.appendAll(reference.getExtra()); + dilate.maze[mazeY * dilate.width + mazeX] = copy; + } + + } else { + CellState copy = new CellState(); + copy.setPosition(position); + copy.appendAll(reference.getExtra()); + dilate.maze[mazeY * dilate.width + mazeX] = copy; + } + + } + + } + + } + + } + + } + + return dilate; + } + + public static MazeComponent depthFirst(int width, int height, RandomGenerator random) { + DepthLikeMaze depthLike = new DepthLikeMaze(new MazeComponent(width, height)); + MazeComponent maze = depthLike.maze; + + Vec2i start = new Vec2i(random.nextInt(width), random.nextInt(height)); + + depthLike.visit(start); + depthLike.stack.push(start); + + depthLike.visitedCells++; + + while (depthLike.visitedCells < maze.width * maze.height) { + List neighbours = Lists.newArrayList(); + + for (Face face : Face.values()) { + + if (depthLike.hasNeighbour(depthLike.stack.peek(), face)) { + neighbours.add(face); + } + + } + + if (!neighbours.isEmpty()) { + Face nextFace = neighbours.get(random.nextInt(neighbours.size())); + + maze.cellState(depthLike.stack.peek()).go(nextFace); + maze.cellState(depthLike.stack.peek().go(nextFace)).go(nextFace.mirror()); + depthLike.visit(depthLike.stack.peek().go(nextFace)); + depthLike.stack.push(depthLike.stack.peek().go(nextFace)); + + depthLike.visitedCells++; + + } else { + depthLike.stack.pop(); + } + + } + + return maze; + } + + public static MazeComponent solve(MazeComponent mazeToSolve, RandomGenerator random, Vec2i end, Vec2i... roots) { + DepthLikeMaze depthLike = new DepthLikeMaze(new MazeComponent(mazeToSolve.width, mazeToSolve.height)); + MazeComponent maze = depthLike.maze; + + List beginnings = Lists.newArrayList(roots); + + List> paths = Lists.newArrayList(); + + for (Vec2i beginning : beginnings) { + Stack stack = new Stack(); + stack.push(new Vec2i(beginning.getX(), beginning.getY())); + Vec2i peek = stack.peek(); + depthLike.visit(peek); + + while (!peek.equals(end)) { + List neighbours = Lists.newArrayList(); + + for (Face face : Face.values()) { + + if (depthLike.hasNeighbour(peek, face)) { + neighbours.add(face); + } + + } + + if (!neighbours.isEmpty()) { + Face nextFace = neighbours.get(random.nextInt(neighbours.size())); + + depthLike.visit(peek.go(nextFace)); + stack.push(peek.go(nextFace)); + + } else { + stack.pop(); + } + + peek = stack.peek(); + } + + for (int x = 0; x < mazeToSolve.width; x++) { + + for (int y = 0; y < mazeToSolve.height; y++) { + depthLike.visit(new Vec2i(x, y), false); + } + + } + + paths.add(stack); + } + + for (Stack path : paths) { + + for (int i = 0; i < path.size(); i++) { + Vec2i pos = path.get(i); + + if (i + 1 != path.size()) { + Vec2i nextPos = path.get(i + 1); + + Face face = pos.normal(nextPos); + maze.cellState(pos).go(face); + maze.cellState(pos.go(face)).go(face.mirror()); + + maze.cellState(pos).appendAll(mazeToSolve.cellState(pos).getExtra()); + + } + + if (beginnings.contains(pos) || pos.equals(end)) { + + if (pos.getX() == 0) { + maze.cellState(pos).down(); + } + + if (pos.getY() == 0) { + maze.cellState(pos).left(); + } + + if (pos.getX() == maze.width - 1) { + maze.cellState(pos).up(); + } + + if (pos.getY() == maze.height - 1) { + maze.cellState(pos).right(); + } + + } + + } + + } + + return maze; + } + +} diff --git a/src/main/java/net/ludocrypt/limlib/impl/debug/DebugNbtChunkGenerator.java b/src/main/java/net/ludocrypt/limlib/impl/debug/DebugNbtChunkGenerator.java index 6b737d3..9f63bc9 100644 --- a/src/main/java/net/ludocrypt/limlib/impl/debug/DebugNbtChunkGenerator.java +++ b/src/main/java/net/ludocrypt/limlib/impl/debug/DebugNbtChunkGenerator.java @@ -1,7 +1,6 @@ package net.ludocrypt.limlib.impl.debug; import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -10,6 +9,8 @@ import java.util.concurrent.Executor; import java.util.function.Function; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.mojang.datafixers.util.Either; @@ -54,7 +55,7 @@ public class DebugNbtChunkGenerator extends AbstractNbtChunkGenerator { .group(RegistryOps.retrieveElement(Biomes.THE_VOID)) .apply(instance, instance.stable(DebugNbtChunkGenerator::new)); }); - BidirectionalMap positions = new BidirectionalMap(); + BiMap positions = HashBiMap.create(); public DebugNbtChunkGenerator(Holder.Reference reference) { super(new FixedBiomeSource(reference), new DebugNbtGroup()); @@ -135,8 +136,8 @@ public CompletableFuture populateNoise(ChunkRegion chunkRegion, ChunkStat for (int z = 0; z < 16; z++) { BlockPos pos = chunk.getPos().getStartPos().add(x, 10, z); - if (positions.reverseMap.containsKey(pos.add(0, -10, 0))) { - Identifier id = positions.reverseMap.get(pos.add(0, -10, 0)); + if (positions.inverse().containsKey(pos.add(0, -10, 0))) { + Identifier id = positions.inverse().get(pos.add(0, -10, 0)); this.generateNbt(chunkRegion, pos, id); chunkRegion .setBlockState(pos.add(-1, -1, -1), @@ -198,28 +199,4 @@ public Identifier nbtId(String group, String nbt) { } - public static class BidirectionalMap { - - private Map forwardMap = new HashMap<>(); - private Map reverseMap = new HashMap<>(); - - public void put(K key, V value) { - forwardMap.put(key, value); - reverseMap.put(value, key); - } - - public V get(K key) { - return forwardMap.get(key); - } - - public K invertGet(V value) { - return reverseMap.get(value); - } - - public boolean isEmpty() { - return forwardMap.isEmpty() || reverseMap.isEmpty(); - } - - } - } diff --git a/src/main/java/net/ludocrypt/limlib/impl/mixin/ServerWorldMixin.java b/src/main/java/net/ludocrypt/limlib/impl/mixin/ServerWorldMixin.java new file mode 100644 index 0000000..cdcd10f --- /dev/null +++ b/src/main/java/net/ludocrypt/limlib/impl/mixin/ServerWorldMixin.java @@ -0,0 +1,60 @@ +package net.ludocrypt.limlib.impl.mixin; + +import java.util.List; +import java.util.concurrent.Executor; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.ludocrypt.limlib.api.world.maze.storage.MazeStorage; +import net.ludocrypt.limlib.api.world.maze.storage.MazeStorageProvider; +import net.ludocrypt.limlib.api.world.maze.storage.ServerWorldMazeAccess; +import net.minecraft.registry.RegistryKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldGenerationProgressListener; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.unmapped.C_xmjhbbku; +import net.minecraft.world.ServerWorldProperties; +import net.minecraft.world.World; +import net.minecraft.world.dimension.DimensionOptions; +import net.minecraft.world.gen.Spawner; +import net.minecraft.world.storage.WorldSaveStorage; + +@Mixin(ServerWorld.class) +public class ServerWorldMixin implements ServerWorldMazeAccess { + + @Unique + MazeStorage mazeStorage; + + @Inject(method = "", at = @At("TAIL")) + private void limlib$init(MinecraftServer server, Executor executor, WorldSaveStorage.Session session, + ServerWorldProperties worldProperties, RegistryKey registryKey, DimensionOptions dimensionOptions, + WorldGenerationProgressListener worldGenerationProgressListener, boolean bl, long l, List spawners, + boolean shouldTickTime, @Nullable C_xmjhbbku c_xmjhbbku, CallbackInfo ci) { + + if (dimensionOptions.getChunkGenerator() instanceof MazeStorageProvider provider) { + this.mazeStorage = new MazeStorage(provider.generators(), + session.getWorldDirectory(registryKey).resolve("maze_region").toFile()); + } + + } + + @Inject(method = "saveWorld", at = @At("TAIL")) + private void limlib$saveWorld(CallbackInfo ci) { + + if (mazeStorage != null) { + this.mazeStorage.save(); + } + + } + + @Override + public MazeStorage getMazeStorage() { + return this.mazeStorage; + } + +} diff --git a/src/main/resources/limlib.mixins.json b/src/main/resources/limlib.mixins.json index c74188e..e4d388d 100644 --- a/src/main/resources/limlib.mixins.json +++ b/src/main/resources/limlib.mixins.json @@ -11,6 +11,7 @@ "RegistriesAccessor", "RegistryLoaderMixin", "ServerPlayerEntityMixin", + "ServerWorldMixin", "WorldDimensionsMixin", "WorldSaveStorageMixin" ],