diff --git a/src/main/java/de/nicolasklier/custom_structures/CustomStructures.java b/src/main/java/de/nicolasklier/custom_structures/CustomStructures.java index 801662d..6cc0b17 100644 --- a/src/main/java/de/nicolasklier/custom_structures/CustomStructures.java +++ b/src/main/java/de/nicolasklier/custom_structures/CustomStructures.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.nicolasklier.custom_structures.events.PlayerTick; +import de.nicolasklier.custom_structures.items.GameOfLifeSpawner; import de.nicolasklier.custom_structures.items.StructureSpawner; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.item.v1.FabricItemSettings; @@ -22,6 +23,9 @@ public class CustomStructures implements ModInitializer { public static final Item STRUCTURE_SPAWNER = new StructureSpawner(new FabricItemSettings().group(ItemGroup.MISC) .maxCount(1)); + + public static final Item GOL_SPAWNER = new GameOfLifeSpawner(new FabricItemSettings().group(ItemGroup.MISC) + .maxCount(1)); @Override public void onInitialize() { @@ -30,6 +34,7 @@ public class CustomStructures implements ModInitializer { // Proceed with mild caution. Registry.register(Registry.ITEM, new Identifier("customstructures", "structure_spawner"), STRUCTURE_SPAWNER); + Registry.register(Registry.ITEM, new Identifier("customstructures", "gameoflife_spawner"), GOL_SPAWNER); new PlayerTick(); } diff --git a/src/main/java/de/nicolasklier/custom_structures/GameOfLife.java b/src/main/java/de/nicolasklier/custom_structures/GameOfLife.java new file mode 100644 index 0000000..e27c356 --- /dev/null +++ b/src/main/java/de/nicolasklier/custom_structures/GameOfLife.java @@ -0,0 +1,145 @@ +package de.nicolasklier.custom_structures; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.nicolasklier.custom_structures.utils.Vector2; +import net.minecraft.util.math.BlockPos; + +public class GameOfLife { + + public static final Logger LOGGER = LoggerFactory.getLogger("customstructure:gameoflife"); + + private boolean[][] grid; // Finished grid + private boolean[][] wGrid; // Working grid + private int size; + + /** + * Position within Minecraft world. + */ + private BlockPos pos; + + public GameOfLife(int size, BlockPos pos) { + this.size = size; + this.pos = pos; + + long begin = System.currentTimeMillis(); + grid = new boolean[size][size]; + wGrid = new boolean[size][size]; + + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + grid[x][y] = false; + wGrid[x][y] = false; + } + } + + long end = System.currentTimeMillis(); + + LOGGER.info("Initialized grid with " + (size ^ size) + " (" + size + "x" + size + ") cells in " + (end - begin) + "ms."); + } + + public boolean[][] getGrid() { + return grid; + } + + public int getSize() { + return size; + } + + public BlockPos getPosition() { + return pos; + } + + // Look away. + private int booleanToInt(boolean bool ) { + return bool ? 1 : 0; + } + + /** + * Project out of bounds coordinates on finite space. + * @param c Coordinates + * @return Fixed coordinates + */ + private Vector2 project(Vector2 c) { + // Do nothing if coordinate is within bounds. + if ((c.x > 0 && c.y > 0) && (c.x < size && c.y < size)) + return c; + + // If x/y is negative, move them to the end of the array. + if (c.x < 0) c.x = size + c.x; + if (c.y < 0) c.y = size + c.y; + + // If x/y are greater than our size, move them to the beginning of the array. + if (c.x >= size) c.x = c.x % size; + if (c.y >= size) c.y = c.y % size; + + return c; + } + + /** + * 0 0 1 + * 0 C 1 + * 1 0 0 + * + * C = Our target cell. + * Result should be three according to Moore' neighborhood + * + * @param c Coordinate to check + **/ + private int getNeighbours(Vector2 c) { + return booleanToInt(getCell(new Vector2(c.x - 1, c.y + 1))) + // Top left + booleanToInt(getCell(new Vector2(c.x, c.y + 1))) + // Top center + booleanToInt(getCell(new Vector2(c.x + 1, c.y + 1))) + // Top right + booleanToInt(getCell(new Vector2(c.x - 1, c.y))) + // Left + booleanToInt(getCell(new Vector2(c.x + 1, c.y))) + // Right + booleanToInt(getCell(new Vector2(c.x - 1, c.y - 1))) + // Bottom left + booleanToInt(getCell(new Vector2(c.x, c.y - 1))) + // Bottom center + booleanToInt(getCell(new Vector2(c.x + 1, c.y - 1))); // Bottom right + } + + public void createCell(Vector2 c) { + c = project(c); + grid[c.x][c.y] = true; + } + + public void killCell(Vector2 c) { + c = project(c); + grid[c.x][c.y] = false; + } + + public boolean getCell(Vector2 c) { + Vector2 proj = project(c); + + return grid[proj.x][proj.y]; + } + + /** + * Simulate next generation. + */ + public void tick() { + System.out.println("Tick!"); + + // Copy current displayed grid to a working grid. + wGrid = grid; + + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + Vector2 c = new Vector2(x, y); + int neighbours = getNeighbours(c); + + if (wGrid[x][y]) { + if (neighbours < 2) wGrid[x][y] = false; + else if (neighbours == 2 || neighbours == 3) wGrid[x][y] = true; + else if (neighbours > 3) wGrid[x][y] = false; + } else { + // Creates new alive cell because of reproduction. + if (neighbours == 3) wGrid[x][y] = true; + } + } + } + + grid = wGrid; + } + +} diff --git a/src/main/java/de/nicolasklier/custom_structures/events/PlayerTick.java b/src/main/java/de/nicolasklier/custom_structures/events/PlayerTick.java index d92be4f..3cb7321 100644 --- a/src/main/java/de/nicolasklier/custom_structures/events/PlayerTick.java +++ b/src/main/java/de/nicolasklier/custom_structures/events/PlayerTick.java @@ -1,20 +1,22 @@ package de.nicolasklier.custom_structures.events; -import java.util.HashMap; -import java.util.Iterator; +import java.util.ArrayList; +import java.util.List; import de.nicolasklier.custom_structures.CustomStructures; +import de.nicolasklier.custom_structures.GameOfLife; +import de.nicolasklier.custom_structures.utils.BlockStatePosition; +import de.nicolasklier.custom_structures.utils.Vector2; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.minecraft.block.Block; -import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvents; import net.minecraft.util.math.BlockPos; public class PlayerTick { - public static HashMap queue = new HashMap<>(); - public static Iterator queueKeys; + public static List queue = new ArrayList<>(); /** * How many ticks to wait until the next block will be placed. @@ -22,6 +24,9 @@ public class PlayerTick { public static int placeDelay = 3; private static int tickCount = 0; + public static GameOfLife gof; + public static boolean gofStart = false; + public PlayerTick() { registerClientEndTick(); @@ -42,26 +47,56 @@ public class PlayerTick { tickCount = 0; + // Game of Life simulation + if (gof != null && gofStart) { + + // Get current state of the field. + for (int x = 0; x < gof.getSize(); x++) { + for (int z = 0; z < gof.getSize(); z++) { + BlockPos pos = gof.getPosition().add(x, 0, z); + +// System.out.println("Search at " + pos.toShortString() + ": " + instance.world.getBlockState(pos).getBlock().getName()); + + if (instance.world.getBlockState(pos).getBlock() == Blocks.OBSIDIAN) { + gof.createCell(new Vector2(x, z)); + } else { + gof.killCell(new Vector2(x, z)); + } + } + } + + gof.tick(); + + // Set new simulated field + for (int x = 0; x < gof.getSize(); x++) { + for (int z = 0; z < gof.getSize(); z++) { + BlockPos pos = gof.getPosition().add(x, 0, z); + +// System.out.println("Search at " + pos.toShortString() + ": " + instance.world.getBlockState(pos).getBlock().getName()); + + if (gof.getCell(Vector2.fromBlockPos(pos))) { + instance.getServer().getOverworld().setBlockState(pos, Blocks.OBSIDIAN.getDefaultState()); + } else { + instance.getServer().getOverworld().setBlockState(pos, Blocks.AIR.getDefaultState()); + } + } + } + } + if (queue.isEmpty()) return; - for (int i = 0; i < 100; i++) { - if (queueKeys == null || !queueKeys.hasNext()) - return; + for (int i = 0; i < 50; i++) { + if (queue.isEmpty()) + break; - BlockPos pos = queueKeys.next(); - BlockState state = queue.get(pos); + BlockStatePosition block = queue.remove(0); - if (state == null) - return; + //CustomStructures.LOGGER.info("Built at " + pos.toShortString() + "\t Q:" + queue.size()); - //CustomStructures.LOGGER.info("Build at " + pos.toShortString() + "\t Q:" + queue.size()); - - instance.getServer().getOverworld().setBlockState(pos, state, Block.NOTIFY_NEIGHBORS); - instance.world.addBlockBreakParticles(pos, state); - instance.world.playSound(pos, SoundEvents.BLOCK_STONE_PLACE, SoundCategory.BLOCKS, 1f, 1f, true); - - queue.remove(pos); + instance.getServer().getOverworld().setBlockState(block.pos, block.state, Block.NOTIFY_LISTENERS); + instance.world.addBlockBreakParticles(block.pos, block.state); + instance.world.playSound(block.pos, SoundEvents.BLOCK_STONE_PLACE, SoundCategory.BLOCKS, 1f, 1f, true); } }); } diff --git a/src/main/java/de/nicolasklier/custom_structures/guis/SettingsGui.java b/src/main/java/de/nicolasklier/custom_structures/guis/SettingsGui.java index 1f5a16b..2a2b8a9 100644 --- a/src/main/java/de/nicolasklier/custom_structures/guis/SettingsGui.java +++ b/src/main/java/de/nicolasklier/custom_structures/guis/SettingsGui.java @@ -5,12 +5,9 @@ import io.github.cottonmc.cotton.gui.widget.WButton; import io.github.cottonmc.cotton.gui.widget.WGridPanel; import io.github.cottonmc.cotton.gui.widget.WLabel; import io.github.cottonmc.cotton.gui.widget.WSlider; -import io.github.cottonmc.cotton.gui.widget.WSprite; import io.github.cottonmc.cotton.gui.widget.data.Axis; import io.github.cottonmc.cotton.gui.widget.data.Insets; -import net.minecraft.client.gui.hud.MessageIndicator.Icon; import net.minecraft.text.Text; -import net.minecraft.util.Identifier; public class SettingsGui extends LightweightGuiDescription { diff --git a/src/main/java/de/nicolasklier/custom_structures/items/GameOfLifeSpawner.java b/src/main/java/de/nicolasklier/custom_structures/items/GameOfLifeSpawner.java new file mode 100644 index 0000000..6a98c13 --- /dev/null +++ b/src/main/java/de/nicolasklier/custom_structures/items/GameOfLifeSpawner.java @@ -0,0 +1,73 @@ +package de.nicolasklier.custom_structures.items; + +import java.util.ArrayList; +import java.util.List; + +import de.nicolasklier.custom_structures.GameOfLife; +import de.nicolasklier.custom_structures.Helper; +import de.nicolasklier.custom_structures.events.PlayerTick; +import de.nicolasklier.custom_structures.guis.SettingsGui; +import de.nicolasklier.custom_structures.guis.SettingsScreen; +import de.nicolasklier.custom_structures.structures.Generations; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.hit.HitResult.Type; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class GameOfLifeSpawner extends Item { + + public GameOfLifeSpawner(Settings settings) { + super(settings); + } + + @Override + public TypedActionResult use(World world, PlayerEntity user, Hand hand) { + MinecraftClient client = MinecraftClient.getInstance(); + + // Block event on server-side. + if(!world.isClient()) { + return TypedActionResult.success(user.getStackInHand(hand)); + } + + // If the player is sneaking, we open a setting menu. + if (client.player.isSneaking()) { + PlayerTick.gofStart = !PlayerTick.gofStart; + } else { + HitResult hit = Helper.raycastInDirection(client, client.getTickDelta(), client.cameraEntity.getRotationVec(client.getTickDelta())); + + Generations gen = new Generations(); + + if (hit.getType() == Type.BLOCK) { + BlockHitResult result = (BlockHitResult) hit; + + final int SPACE = 100; + BlockPos pos = result.getBlockPos(); + PlayerTick.gof = new GameOfLife(SPACE, pos.add(0, 1, 0)); + + // Build box around simulation + // Ground + for (int x = 0; x < SPACE; x++) { + for (int z = 0; z < SPACE; z++) { + if (x == 0 || x == SPACE - 1) { + if (z == 0 || z == SPACE - 1) { + client.getServer().getOverworld().setBlockState(pos.add(x, 1, z), Blocks.QUARTZ_SLAB.getDefaultState()); + } + } + client.getServer().getOverworld().setBlockState(pos.add(x, 0, z), Blocks.QUARTZ_BLOCK.getDefaultState()); + } + } + } + } + + return TypedActionResult.success(user.getStackInHand(hand)); + } + +} diff --git a/src/main/java/de/nicolasklier/custom_structures/items/StructureSpawner.java b/src/main/java/de/nicolasklier/custom_structures/items/StructureSpawner.java index 6040bb5..55443ff 100644 --- a/src/main/java/de/nicolasklier/custom_structures/items/StructureSpawner.java +++ b/src/main/java/de/nicolasklier/custom_structures/items/StructureSpawner.java @@ -25,10 +25,14 @@ public class StructureSpawner extends Item { @Override public TypedActionResult use(World world, PlayerEntity user, Hand hand) { - System.out.println("Used item!"); - MinecraftClient client = MinecraftClient.getInstance(); + // Block event on server-side. + if(!world.isClient()) { + return TypedActionResult.success(user.getStackInHand(hand)); + } + + System.out.println("Used item!"); // If the player is sneaking, we open a setting menu. if (client.player.isSneaking()) { @@ -47,6 +51,7 @@ public class StructureSpawner extends Item { options.width = 5; options.height = 20; options.noise = new StaircaseNoiseOptions(); + options.noise.shiftZ = true; options.noise.threshold = 0.64f; options.minecartTrack = true; options.mirror = true; diff --git a/src/main/java/de/nicolasklier/custom_structures/structures/Generations.java b/src/main/java/de/nicolasklier/custom_structures/structures/Generations.java index 7b68898..8df64f2 100644 --- a/src/main/java/de/nicolasklier/custom_structures/structures/Generations.java +++ b/src/main/java/de/nicolasklier/custom_structures/structures/Generations.java @@ -1,16 +1,13 @@ package de.nicolasklier.custom_structures.structures; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Random; -import java.util.Set; import org.spongepowered.noise.module.source.Perlin; -import de.nicolasklier.custom_structures.CustomStructures; import de.nicolasklier.custom_structures.events.PlayerTick; +import de.nicolasklier.custom_structures.utils.BlockStatePosition; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; @@ -32,8 +29,9 @@ public class Generations { } public void spawnStaircase(BlockPos at, StaircaseOptions options) { - Map placeQueue = new HashMap<>(); - MinecraftClient instance = MinecraftClient.getInstance(); + List placeQueue = new ArrayList<>(); + + MinecraftClient instance = MinecraftClient.getInstance(); int length = options.height * options.stretch * (options.mirror ? 2 : 1); Random rng = new Random(); @@ -46,6 +44,11 @@ public class Generations { : 0; int minecartRedstoneTorchStep = 0; + /** + * Since how many blocks are we going straight? + */ + int straightBlocks = 0; + int y = -(options.weight); int step = 0; @@ -66,6 +69,32 @@ public class Generations { while (true) { x++; + BlockPos currentShift = null; + BlockPos nextShift = null; + + // Calculate z-shift + if (noise != null) { + double currentShiftNoise = noise.get(x + 1_000, -5_000, 0); + double nextShiftNoise = noise.get(x + 1_000 + 1, -5_000, 0); + + currentShift = currentShiftNoise < options.noise.shiftRightThreshold ? new BlockPos(0, 0, 1) : + (currentShiftNoise > options.noise.shiftRightThreshold && currentShiftNoise < options.noise.shiftLeftThreshold ? new BlockPos(0, 0, -1) : BlockPos.ORIGIN); + + nextShift = nextShiftNoise < options.noise.shiftRightThreshold ? new BlockPos(0, 0, 1) : + (nextShiftNoise > options.noise.shiftRightThreshold && nextShiftNoise < options.noise.shiftLeftThreshold ? new BlockPos(0, 0, -1) : BlockPos.ORIGIN); + + if (currentShift.getZ() == 0) { + straightBlocks++; + } else { + // Block shift if we shifted one block before. The reason is that rails are buggy. + if (straightBlocks < 1) { + currentShift = BlockPos.ORIGIN; + nextShift = BlockPos.ORIGIN; + } + straightBlocks = 0; + } + } + boolean isMirroring = y > options.height && options.mirror || reachedTop; if (isMirroring) { @@ -88,6 +117,11 @@ public class Generations { else y++; } + // Shift staircase if enabled. + if (options.noise.shiftZ) { + at = at.add(currentShift); + } + System.out.println("Noise: " + noise.get(x, 0, 0) + " | X: " + x + " Y: " + y); } else { if (step >= options.stretch) { @@ -113,7 +147,7 @@ public class Generations { } else { if (noise.get(x + 1, 0, 0) < options.noise.threshold) { isNextHeight = true; - } + } } } else { if (options.stretch > 1) { @@ -132,25 +166,21 @@ public class Generations { // From left to right for (int z2 = 0; z2 <= options.width; z2 += (options.width - 1) / options.fenceHorizontalDensity) { // Place fence manually on ground because the loop below offsets by one. - placeQueue.put(at.add(x + 1, 0, z2), getRandomFance(rng)); + placeQueue.add(new BlockStatePosition(new BlockPos(x + 1, 0, z2), getRandomFance(rng))); - int y2 = y + 2; - while (true) { - break; - Material mat = instance.getServer().getOverworld().getBlockState(new BlockPos(x, y2, z2)).getMaterial(); - if (mat == Material.AIR || mat == Material.WATER) { - placeQueue.put(at.add(x, y2, z2), getRandomFance(rng)); - placeQueue.put(at.add(x + 1, y2 + 1, z2), getRandomFance(rng)); + for (int y2 = y + options.weight; y > -64; y2--) { + BlockPos pos = new BlockPos(x, y2, z2); + Block mat = instance.getServer().getOverworld().getBlockState(pos).getBlock(); + + System.out.println("Fence " + pos.toShortString() + "\t(" + mat.toString() + ")"); + + if (mat == Blocks.AIR || mat == Blocks.WATER) { + System.out.println("Set fence at " + pos.toShortString()); + placeQueue.add(new BlockStatePosition(new BlockPos(x, y2, z2), getRandomFance(rng))); + placeQueue.add(new BlockStatePosition(new BlockPos(x + 1, y2 + 1, z2), getRandomFance(rng))); } else { break; } - - System.out.println("Fence stuff " + y2 + "(" + mat.toString() + ")"); - - if (y2 <= -64) - break; - - y2--; } } }*/ @@ -171,7 +201,7 @@ public class Generations { // Building imperfection if (rng.nextFloat() < 0.001) continue; - placeQueue.put(at.add(x, y + j, z), block.getDefaultState()); + placeQueue.add(new BlockStatePosition(at.add(x, y + j, z), block.getDefaultState())); } Direction facing = isMirroring ? Direction.WEST : Direction.EAST; @@ -184,7 +214,7 @@ public class Generations { stairState = stairState.with(Properties.HORIZONTAL_FACING, facing); if (rng.nextFloat() < options.chanceSlap / 100f) { - placeQueue.put(at.add(x, y + options.weight, z), Blocks.SMOOTH_STONE_SLAB.getDefaultState()); + placeQueue.add(new BlockStatePosition(at.add(x, y + options.weight, z), Blocks.SMOOTH_STONE_SLAB.getDefaultState())); } else { //CustomStructures.LOGGER.info("Noise [" + x + ", " + y + ", " + z + "]: " + noise.get(x, y + options.weight, z)); if (noise.get(x, y + options.weight, 0) < options.chanceWoodStair / 100f) { @@ -197,40 +227,52 @@ public class Generations { .with(Properties.HORIZONTAL_FACING, stairState.get(Properties.HORIZONTAL_FACING)); } - placeQueue.put(at.add(x, y + options.weight, z), stairState); + placeQueue.add(new BlockStatePosition(at.add(x, y + options.weight, z), stairState)); } } else { - if (rng.nextFloat() < 0.04) { - placeQueue.put(at.add(x, y + options.weight - 1, z), Blocks.GLOWSTONE.getDefaultState()); + if (rng.nextFloat() < options.chanceGlowstone / 100) { + placeQueue.add(new BlockStatePosition(at.add(x, y + options.weight - 1, z), Blocks.GLOWSTONE.getDefaultState())); } } // Build wall if ((z == 0 || z == options.width - 1) && options.wallHeight > 0) { for (int y2 = 0; y2 < options.wallHeight; y2++) { - placeQueue.put(at.add(x, y + y2 + options.weight, z), Blocks.OAK_PLANKS.getDefaultState()); + placeQueue.add(new BlockStatePosition(at.add(x, y + y2 + options.weight, z), Blocks.OAK_PLANKS.getDefaultState())); } - placeQueue.put(at.add(x, y + options.wallHeight + options.weight, z), Blocks.OAK_FENCE.getDefaultState()); - placeQueue.put(at.add(x, y + options.wallHeight + options.weight + 1, z), Blocks.OAK_FENCE.getDefaultState()); - placeQueue.put(at.add(x, y + options.wallHeight + options.weight + 2, z), Blocks.OAK_PLANKS.getDefaultState()); + placeQueue.add(new BlockStatePosition(at.add(x, y + options.wallHeight + options.weight, z), Blocks.OAK_FENCE.getDefaultState())); + placeQueue.add(new BlockStatePosition(at.add(x, y + options.wallHeight + options.weight + 1, z), Blocks.OAK_FENCE.getDefaultState())); + placeQueue.add(new BlockStatePosition(at.add(x, y + options.wallHeight + options.weight + 2, z), Blocks.OAK_PLANKS.getDefaultState())); if (isNextHeight) { - placeQueue.put(at.add(x, y + options.wallHeight + options.weight + 3, z), Blocks.OAK_STAIRS.getDefaultState() - .with(Properties.HORIZONTAL_FACING, facing)); + placeQueue.add(new BlockStatePosition(at.add(x, y + options.wallHeight + options.weight + 3, z), Blocks.OAK_STAIRS.getDefaultState() + .with(Properties.HORIZONTAL_FACING, facing))); } } // Minecart rails if (minecartTrackPosition > 0 && z == minecartTrackPosition) { - System.array + // Place additional rail if we shift staircase to left or right. + if (noise != null && options.noise.shiftZ) { + placeQueue.add(new BlockStatePosition(at.add(x, y + options.weight, z).add(nextShift), Blocks.RAIL.getDefaultState())); + } + if (minecartRedstoneTorchStep >= 4) { - placeQueue.put(at.add(x, y + options.weight - 2, z), Blocks.REDSTONE_TORCH.getDefaultState()); - placeQueue.put(at.add(x, y + options.weight, z), Blocks.POWERED_RAIL.getDefaultState()); - minecartRedstoneTorchStep = 0; + + // Don't place powered rail because they can only go straight. + if (noise != null && options.noise.shiftZ) { + if (nextShift.getZ() != 0) { + placeQueue.add(new BlockStatePosition(at.add(x, y + options.weight, z).add(nextShift), Blocks.RAIL.getDefaultState())); + continue; + } + } + + placeQueue.add(new BlockStatePosition(at.add(x, y + options.weight - 2, z), Blocks.REDSTONE_TORCH.getDefaultState())); + placeQueue.add(new BlockStatePosition(at.add(x, y + options.weight, z), Blocks.POWERED_RAIL.getDefaultState())); } else { - placeQueue.put(at.add(x, y + options.weight, z), Blocks.RAIL.getDefaultState()); + placeQueue.add(new BlockStatePosition(at.add(x, y + options.weight, z), Blocks.RAIL.getDefaultState())); } minecartRedstoneTorchStep++; @@ -240,12 +282,8 @@ public class Generations { step++; } - PlayerTick.queue.putAll(placeQueue); - - Set set = placeQueue.keySet(); - List list = new ArrayList<>(set); //Collections.shuffle(list); - PlayerTick.queueKeys = list.iterator(); + PlayerTick.queue.addAll(placeQueue); } diff --git a/src/main/java/de/nicolasklier/custom_structures/structures/StaircaseNoiseOptions.java b/src/main/java/de/nicolasklier/custom_structures/structures/StaircaseNoiseOptions.java index 3201264..16c4a18 100644 --- a/src/main/java/de/nicolasklier/custom_structures/structures/StaircaseNoiseOptions.java +++ b/src/main/java/de/nicolasklier/custom_structures/structures/StaircaseNoiseOptions.java @@ -11,6 +11,15 @@ public class StaircaseNoiseOptions { */ public float threshold = 0.7f; + /** + * If set to true, the staircase will change its z-coordinate based on the noise. + */ + public boolean shiftZ = false; + + public double shiftLeftThreshold = 0.55; + + public double shiftRightThreshold = 0.4; + public Random rng = new Random(); } diff --git a/src/main/java/de/nicolasklier/custom_structures/structures/StaircaseOptions.java b/src/main/java/de/nicolasklier/custom_structures/structures/StaircaseOptions.java index 390abd9..c77cc9c 100644 --- a/src/main/java/de/nicolasklier/custom_structures/structures/StaircaseOptions.java +++ b/src/main/java/de/nicolasklier/custom_structures/structures/StaircaseOptions.java @@ -49,7 +49,7 @@ public class StaircaseOptions { /** * Value between 0% and 100% being the chance to spawn glowstone. */ - public short chanceGlowstone = 4; + public short chanceGlowstone = 0; /** * Value between 0% and 100% being the chance to spawn mossy cobblestone. diff --git a/src/main/java/de/nicolasklier/custom_structures/utils/BlockStatePosition.java b/src/main/java/de/nicolasklier/custom_structures/utils/BlockStatePosition.java new file mode 100644 index 0000000..04b23af --- /dev/null +++ b/src/main/java/de/nicolasklier/custom_structures/utils/BlockStatePosition.java @@ -0,0 +1,20 @@ +package de.nicolasklier.custom_structures.utils; + +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; + +/** + * Tiny struct like class to store position and block state in one object. + * + */ +public class BlockStatePosition { + + public BlockPos pos; + public BlockState state; + + public BlockStatePosition(BlockPos pos, BlockState state) { + this.pos = pos; + this.state = state; + } + +} diff --git a/src/main/java/de/nicolasklier/custom_structures/utils/Vector2.java b/src/main/java/de/nicolasklier/custom_structures/utils/Vector2.java new file mode 100644 index 0000000..9562b13 --- /dev/null +++ b/src/main/java/de/nicolasklier/custom_structures/utils/Vector2.java @@ -0,0 +1,24 @@ +package de.nicolasklier.custom_structures.utils; + +import net.minecraft.util.math.BlockPos; + +public class Vector2 { + + public int x, y; + + public Vector2(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public String toString() { + return this.x + " " + this.y; + } + + public static Vector2 fromBlockPos(BlockPos pos) { + // Minecraft's coordinates are flipped. + return new Vector2(pos.getX(), pos.getZ()); + } + +} diff --git a/src/main/resources/assets/customstructures/models/item/gameoflife_spawner.json b/src/main/resources/assets/customstructures/models/item/gameoflife_spawner.json new file mode 100644 index 0000000..ef9a3b0 --- /dev/null +++ b/src/main/resources/assets/customstructures/models/item/gameoflife_spawner.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "customstructures:item/gameoflife_spawner" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/customstructures/textures/item/gameoflife_spawner.png b/src/main/resources/assets/customstructures/textures/item/gameoflife_spawner.png new file mode 100644 index 0000000..de39746 Binary files /dev/null and b/src/main/resources/assets/customstructures/textures/item/gameoflife_spawner.png differ