Add perlin noise to staircase generation.

This commit is contained in:
2022-10-27 17:26:55 +02:00
parent e7f9022dfb
commit eb5a3e1a76
11 changed files with 205 additions and 161 deletions

View File

@@ -16,6 +16,11 @@ repositories {
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically. // Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html // See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories. // for more information about repositories.
maven {
name = "CottonMC"
url = "https://server.bbkr.space/artifactory/libs-release"
}
} }
dependencies { dependencies {
@@ -31,6 +36,7 @@ dependencies {
// These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time. // These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time.
// modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}" // modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}"
modImplementation include("io.github.cottonmc:LibGui:6.3.0+1.19")
} }
processResources { processResources {

View File

@@ -1,18 +1,19 @@
package de.nicolasklier.custom_structures; package de.nicolasklier.custom_structures;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import de.nicolasklier.custom_structures.events.PlayerTick; import de.nicolasklier.custom_structures.events.PlayerTick;
import de.nicolasklier.custom_structures.items.StructureSpawner; import de.nicolasklier.custom_structures.items.StructureSpawner;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
@SuppressWarnings("deprecation")
public class CustomStructures implements ModInitializer { public class CustomStructures implements ModInitializer {
// This logger is used to write text to the console and the log file. // This logger is used to write text to the console and the log file.
// It is considered best practice to use your mod id as the logger's name. // It is considered best practice to use your mod id as the logger's name.

View File

@@ -5,6 +5,7 @@ import java.util.Iterator;
import de.nicolasklier.custom_structures.CustomStructures; import de.nicolasklier.custom_structures.CustomStructures;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents; import net.minecraft.sound.SoundEvents;
@@ -56,7 +57,7 @@ public class PlayerTick {
//CustomStructures.LOGGER.info("Build at " + pos.toShortString() + "\t Q:" + queue.size()); //CustomStructures.LOGGER.info("Build at " + pos.toShortString() + "\t Q:" + queue.size());
instance.getServer().getOverworld().setBlockState(pos, state); instance.getServer().getOverworld().setBlockState(pos, state, Block.NOTIFY_NEIGHBORS);
instance.world.addBlockBreakParticles(pos, state); instance.world.addBlockBreakParticles(pos, state);
instance.world.playSound(pos, SoundEvents.BLOCK_STONE_PLACE, SoundCategory.BLOCKS, 1f, 1f, true); instance.world.playSound(pos, SoundEvents.BLOCK_STONE_PLACE, SoundCategory.BLOCKS, 1f, 1f, true);

View File

@@ -0,0 +1,41 @@
package de.nicolasklier.custom_structures.guis;
import io.github.cottonmc.cotton.gui.client.LightweightGuiDescription;
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 {
public SettingsGui() {
WGridPanel root = new WGridPanel();
setRootPanel(root);
root.setSize(256, 240);
root.setInsets(Insets.ROOT_PANEL);
WLabel title = new WLabel(Text.of("Custom Structures"), 0x000000);
root.add(title, 0, 0, 2, 1);
//WSprite icon = new WSprite(new Identifier("minecraft:textures/item/redstone.png"));
//root.add(icon, 0, 2, 1, 1);
WLabel label_width = new WLabel(Text.of("Width"), 0x000000);
root.add(label_width , 0, 2, 2, 1);
WSlider slider = new WSlider(0, 100, Axis.HORIZONTAL);
root.add(slider, 0, 4, 5, 1);
WButton button = new WButton(Text.of("Save"));
root.add(button, 0, 10, 4, 1);
root.validate(this);
}
}

View File

@@ -0,0 +1,12 @@
package de.nicolasklier.custom_structures.guis;
import io.github.cottonmc.cotton.gui.GuiDescription;
import io.github.cottonmc.cotton.gui.client.CottonClientScreen;
public class SettingsScreen extends CottonClientScreen {
public SettingsScreen(GuiDescription description) {
super(description);
}
}

View File

@@ -1,6 +1,8 @@
package de.nicolasklier.custom_structures.items; package de.nicolasklier.custom_structures.items;
import de.nicolasklier.custom_structures.Helper; import de.nicolasklier.custom_structures.Helper;
import de.nicolasklier.custom_structures.guis.SettingsGui;
import de.nicolasklier.custom_structures.guis.SettingsScreen;
import de.nicolasklier.custom_structures.structures.Generations; import de.nicolasklier.custom_structures.structures.Generations;
import de.nicolasklier.custom_structures.structures.StaircaseNoiseOptions; import de.nicolasklier.custom_structures.structures.StaircaseNoiseOptions;
import de.nicolasklier.custom_structures.structures.StaircaseOptions; import de.nicolasklier.custom_structures.structures.StaircaseOptions;
@@ -23,7 +25,15 @@ public class StructureSpawner extends Item {
@Override @Override
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) { public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
System.out.println("Used item!");
MinecraftClient client = MinecraftClient.getInstance(); MinecraftClient client = MinecraftClient.getInstance();
// If the player is sneaking, we open a setting menu.
if (client.player.isSneaking()) {
client.setScreen(new SettingsScreen(new SettingsGui()));
} else {
HitResult hit = Helper.raycastInDirection(client, client.getTickDelta(), client.cameraEntity.getRotationVec(client.getTickDelta())); HitResult hit = Helper.raycastInDirection(client, client.getTickDelta(), client.cameraEntity.getRotationVec(client.getTickDelta()));
Generations gen = new Generations(); Generations gen = new Generations();
@@ -31,16 +41,19 @@ public class StructureSpawner extends Item {
if (hit.getType() == Type.BLOCK) { if (hit.getType() == Type.BLOCK) {
BlockHitResult result = (BlockHitResult) hit; BlockHitResult result = (BlockHitResult) hit;
client.player.sendChatMessage("Coords: " + result.getBlockPos().toShortString(), null); client.player.sendChatMessage("Coords: " + result.getBlockPos().toShortString(), null);
// Spawn structure at raycast hit // Spawn structure at raycast hit
StaircaseOptions options = new StaircaseOptions(); StaircaseOptions options = new StaircaseOptions();
options.stretch = 2; //options.stretch = 2;
options.height = 10; options.width = 5;
options.height = 20;
options.noise = new StaircaseNoiseOptions(); options.noise = new StaircaseNoiseOptions();
options.noise.threshold = 0.64f;
options.minecartTrack = true;
options.mirror = true; options.mirror = true;
gen.spawnStaircase(result.getBlockPos().add(0, 1, 0), options); gen.spawnStaircase(result.getBlockPos().add(0, 1, 0), options);
} }
}
return TypedActionResult.success(user.getStackInHand(hand)); return TypedActionResult.success(user.getStackInHand(hand));
} }

View File

@@ -1,16 +1,16 @@
package de.nicolasklier.custom_structures.structures; package de.nicolasklier.custom_structures.structures;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import org.spongepowered.noise.module.source.Perlin;
import de.nicolasklier.custom_structures.CustomStructures; import de.nicolasklier.custom_structures.CustomStructures;
import de.nicolasklier.custom_structures.events.PlayerTick; import de.nicolasklier.custom_structures.events.PlayerTick;
import de.nicolasklier.custom_structures.utils.PerlinNoise;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
@@ -19,7 +19,6 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.state.property.Properties; import net.minecraft.state.property.Properties;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Direction.Axis;
public class Generations { public class Generations {
@@ -38,34 +37,85 @@ public class Generations {
int length = options.height * options.stretch * (options.mirror ? 2 : 1); int length = options.height * options.stretch * (options.mirror ? 2 : 1);
Random rng = new Random(); Random rng = new Random();
PerlinNoise noise = null; Perlin noise = null;
int minecartTrackPosition = options.minecartTrack
&& options.width > 2
&& options.weight > 2
? options.width / 2
: 0;
int minecartRedstoneTorchStep = 0;
int y = -(options.weight); int y = -(options.weight);
int step = 0; int step = 0;
if (options.noise != null) { if (options.noise != null) {
noise = new PerlinNoise(rng, options.noise.roughness, length, length); noise = new Perlin();
noise.initialise(); noise.setSeed(rng.nextInt());
noise.setOctaveCount(3);
noise.setFrequency(0.3);
noise.setPersistence(0.3);
} }
// Final length is height times stretch factor. If mirroring is enabled, then it will be twice as long. // Final length is height times stretch factor. If mirroring is enabled, then it will be twice as long.
int x = 0; int x = 0;
boolean isMirroring = y > options.height * options.stretch;
boolean reachedTop = false;
// Very dangerous. But since we use noise to control the stretch of the staircase we cannot know how long it will be at the end.
while (true) {
x++;
boolean isMirroring = y > options.height && options.mirror || reachedTop;
if (isMirroring) {
reachedTop = true;
}
// Break conditions
if (isMirroring && y <= -options.weight) {
break;
}
if (!options.mirror && y >= options.height) {
break;
}
if (noise != null) {
// In 20% of all cases the y-level shouldn't change.
if (noise.get(x, 0, 0) < options.noise.threshold) {
if (isMirroring) y--;
else y++;
}
System.out.println("Noise: " + noise.get(x, 0, 0) + " | X: " + x + " Y: " + y);
} else {
if (step >= options.stretch) { if (step >= options.stretch) {
step = 0; step = 0;
// Check if we already reached our final height. If yes, go downwards // Check if we already reached our final height. If yes, go downwards
if (isMirroring) { if (isMirroring) {
y -= 1; y--;
} else { } else {
y++; y++;
} }
} }
}
for (int z = 0; z < options.width; z++) { for (int z = 0; z < options.width; z++) {
boolean isNextHeight = true; boolean isNextHeight = false;
if (noise != null) {
if (isMirroring) {
if (noise.get(x, 0, 0) < options.noise.threshold) {
isNextHeight = true;
}
} else {
if (noise.get(x + 1, 0, 0) < options.noise.threshold) {
isNextHeight = true;
}
}
} else {
if (options.stretch > 1) { if (options.stretch > 1) {
if (isMirroring) { if (isMirroring) {
isNextHeight = step == 0; isNextHeight = step == 0;
@@ -73,18 +123,20 @@ public class Generations {
isNextHeight = step + 1 == options.stretch; isNextHeight = step + 1 == options.stretch;
} }
} }
}
Block block = Blocks.COBBLESTONE; Block block = Blocks.COBBLESTONE;
// Build supporting fence // Build supporting fence
if (x % options.fenceVerticalDensity == 0 && y > 0) { /*if (x % options.fenceVerticalDensity == 0 && y > 0) {
// From left to right // From left to right
for (int z2 = 0; z2 <= options.width; z2 += (options.width - 1) / options.fenceHorizontalDensity) { 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. // Place fence manually on ground because the loop below offsets by one.
placeQueue.put(at.add(x + 1, 0, z2), getRandomFance(rng)); placeQueue.put(at.add(x + 1, 0, z2), getRandomFance(rng));
int y2 = y - options.weight; int y2 = y + 2;
while (true) { while (true) {
break;
Material mat = instance.getServer().getOverworld().getBlockState(new BlockPos(x, y2, z2)).getMaterial(); Material mat = instance.getServer().getOverworld().getBlockState(new BlockPos(x, y2, z2)).getMaterial();
if (mat == Material.AIR || mat == Material.WATER) { if (mat == Material.AIR || mat == Material.WATER) {
placeQueue.put(at.add(x, y2, z2), getRandomFance(rng)); placeQueue.put(at.add(x, y2, z2), getRandomFance(rng));
@@ -93,18 +145,23 @@ public class Generations {
break; break;
} }
System.out.println("Fence stuff " + y2 + "(" + mat.toString() + ")");
if (y2 <= -64)
break;
y2--; y2--;
} }
} }
} }*/
boolean putStair = false; boolean putStair = false;
// Decide if there should be mossy cobblestone or glass. If yes, then don't put a stair there. // Decide if there should be mossy cobblestone or cracked stone. If yes, then don't put a stair there.
if (rng.nextFloat() < options.chanceMossyCobblestone / 100f) { if (rng.nextFloat() < options.chanceMossyCobblestone / 100f) {
block = Blocks.MOSSY_COBBLESTONE; block = Blocks.MOSSY_COBBLESTONE;
} else if (rng.nextFloat() < options.chanceGlass / 100f) { } else if (rng.nextFloat() < options.chanceStoneBricks / 100f) {
block = Blocks.GLASS; block = Blocks.STONE_BRICKS;
} else { } else {
putStair = true; putStair = true;
} }
@@ -129,8 +186,8 @@ public class Generations {
if (rng.nextFloat() < options.chanceSlap / 100f) { if (rng.nextFloat() < options.chanceSlap / 100f) {
placeQueue.put(at.add(x, y + options.weight, z), Blocks.SMOOTH_STONE_SLAB.getDefaultState()); placeQueue.put(at.add(x, y + options.weight, z), Blocks.SMOOTH_STONE_SLAB.getDefaultState());
} else { } else {
CustomStructures.LOGGER.info("Noise [" + x + ", " + y + "]: " + noise.getValueAt(x, y + options.weight)); //CustomStructures.LOGGER.info("Noise [" + x + ", " + y + ", " + z + "]: " + noise.get(x, y + options.weight, z));
if (noise.getValueAt(x, y + options.weight) < options.chanceWoodStair / 100f) { if (noise.get(x, y + options.weight, 0) < options.chanceWoodStair / 100f) {
// Override stair as wood stair, copying the facing. // Override stair as wood stair, copying the facing.
stairState = Blocks.OAK_STAIRS.getDefaultState() stairState = Blocks.OAK_STAIRS.getDefaultState()
@@ -163,6 +220,21 @@ public class Generations {
.with(Properties.HORIZONTAL_FACING, facing)); .with(Properties.HORIZONTAL_FACING, facing));
} }
} }
// Minecart rails
if (minecartTrackPosition > 0 && z == minecartTrackPosition) {
System.array
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;
} else {
placeQueue.put(at.add(x, y + options.weight, z), Blocks.RAIL.getDefaultState());
}
minecartRedstoneTorchStep++;
}
} }
step++; step++;

View File

@@ -6,6 +6,11 @@ public class StaircaseNoiseOptions {
public float roughness = 0.1f; public float roughness = 0.1f;
/**
* Noise value that will change the y-level of the staircase.
*/
public float threshold = 0.7f;
public Random rng = new Random(); public Random rng = new Random();
} }

View File

@@ -42,9 +42,9 @@ public class StaircaseOptions {
public boolean mirror = false; public boolean mirror = false;
/** /**
* Value between 0% and 100% being the chance to spawn glass. * Value between 0% and 100% being the chance to spawn stone bricks.
*/ */
public short chanceGlass = 10; public short chanceStoneBricks = 10;
/** /**
* Value between 0% and 100% being the chance to spawn glowstone. * Value between 0% and 100% being the chance to spawn glowstone.
@@ -71,4 +71,6 @@ public class StaircaseOptions {
* To disable this feature set it to null. * To disable this feature set it to null.
*/ */
public StaircaseNoiseOptions noise = null; public StaircaseNoiseOptions noise = null;
public boolean minecartTrack = false;
} }

View File

@@ -1,110 +0,0 @@
package de.nicolasklier.custom_structures.utils;
import java.util.Random;
public class PerlinNoise {
/** Source of entropy */
private Random rand_;
/** Amount of roughness */
float roughness_;
/** Plasma fractal grid */
private float[][] grid_;
/** Generate a noise source based upon the midpoint displacement fractal.
*
* @param rand The random number generator
* @param roughness a roughness parameter
* @param width the width of the grid
* @param height the height of the grid
*/
public PerlinNoise(Random rand, float roughness, int width, int height) {
roughness_ = roughness / width;
grid_ = new float[width][height];
rand_ = (rand == null) ? new Random() : rand;
}
public void initialise() {
int xh = grid_.length - 1;
int yh = grid_[0].length - 1;
// set the corner points
grid_[0][0] = rand_.nextFloat() - 0.5f;
grid_[0][yh] = rand_.nextFloat() - 0.5f;
grid_[xh][0] = rand_.nextFloat() - 0.5f;
grid_[xh][yh] = rand_.nextFloat() - 0.5f;
// generate the fractal
generate(0, 0, xh, yh);
}
// Add a suitable amount of random displacement to a point
private float roughen(float v, int l, int h) {
return v + roughness_ * (float) (rand_.nextGaussian() * (h - l));
}
// generate the fractal
private void generate(int xl, int yl, int xh, int yh) {
int xm = (xl + xh) / 2;
int ym = (yl + yh) / 2;
if ((xl == xm) && (yl == ym)) return;
grid_[xm][yl] = 0.5f * (grid_[xl][yl] + grid_[xh][yl]);
grid_[xm][yh] = 0.5f * (grid_[xl][yh] + grid_[xh][yh]);
grid_[xl][ym] = 0.5f * (grid_[xl][yl] + grid_[xl][yh]);
grid_[xh][ym] = 0.5f * (grid_[xh][yl] + grid_[xh][yh]);
float v = roughen(0.5f * (grid_[xm][yl] + grid_[xm][yh]), xl + yl, yh
+ xh);
grid_[xm][ym] = v;
grid_[xm][yl] = roughen(grid_[xm][yl], xl, xh);
grid_[xm][yh] = roughen(grid_[xm][yh], xl, xh);
grid_[xl][ym] = roughen(grid_[xl][ym], yl, yh);
grid_[xh][ym] = roughen(grid_[xh][ym], yl, yh);
generate(xl, yl, xm, ym);
generate(xm, yl, xh, ym);
generate(xl, ym, xm, yh);
generate(xm, ym, xh, yh);
}
public float getValueAt(int x, int y) {
return grid_[x][y];
}
/**
* Dump out as a CSV
*/
public void printAsCSV() {
for(int i = 0;i < grid_.length;i++) {
for(int j = 0;j < grid_[0].length;j++) {
System.out.print(grid_[i][j]);
System.out.print(",");
}
System.out.println();
}
}
/**
* Convert to a Boolean array
* @return the boolean array
*/
public boolean[][] toBooleans() {
int w = grid_.length;
int h = grid_[0].length;
boolean[][] ret = new boolean[w][h];
for(int i = 0;i < w;i++) {
for(int j = 0;j < h;j++) {
ret[i][j] = grid_[i][j] < 0;
}
}
return ret;
}
}

View File

@@ -1,3 +1,4 @@
accessWidener v1 named accessWidener v1 named
extendable class net/minecraft/world/gen/chunk/NoiseChunkGenerator extendable class net/minecraft/world/gen/chunk/NoiseChunkGenerator
accessible class net/minecraft/world/gen/WorldPresets$Registrar accessible class net/minecraft/world/gen/WorldPresets$Registrar
accessible class net/minecraft/inventory/Inventory