Add perlin noise to staircase generation.
This commit is contained in:
@@ -16,6 +16,11 @@ repositories {
|
||||
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
|
||||
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
|
||||
// for more information about repositories.
|
||||
|
||||
maven {
|
||||
name = "CottonMC"
|
||||
url = "https://server.bbkr.space/artifactory/libs-release"
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
// modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}"
|
||||
modImplementation include("io.github.cottonmc:LibGui:6.3.0+1.19")
|
||||
}
|
||||
|
||||
processResources {
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
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.LoggerFactory;
|
||||
|
||||
import de.nicolasklier.custom_structures.events.PlayerTick;
|
||||
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 {
|
||||
// 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.
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Iterator;
|
||||
|
||||
import de.nicolasklier.custom_structures.CustomStructures;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.sound.SoundCategory;
|
||||
import net.minecraft.sound.SoundEvents;
|
||||
@@ -56,7 +57,7 @@ public class PlayerTick {
|
||||
|
||||
//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.playSound(pos, SoundEvents.BLOCK_STONE_PLACE, SoundCategory.BLOCKS, 1f, 1f, true);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package de.nicolasklier.custom_structures.items;
|
||||
|
||||
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.StaircaseNoiseOptions;
|
||||
import de.nicolasklier.custom_structures.structures.StaircaseOptions;
|
||||
@@ -23,23 +25,34 @@ public class StructureSpawner extends Item {
|
||||
|
||||
@Override
|
||||
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
|
||||
System.out.println("Used item!");
|
||||
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
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;
|
||||
client.player.sendChatMessage("Coords: " + result.getBlockPos().toShortString(), null);
|
||||
// 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()));
|
||||
|
||||
// Spawn structure at raycast hit
|
||||
StaircaseOptions options = new StaircaseOptions();
|
||||
options.stretch = 2;
|
||||
options.height = 10;
|
||||
options.noise = new StaircaseNoiseOptions();
|
||||
options.mirror = true;
|
||||
Generations gen = new Generations();
|
||||
|
||||
gen.spawnStaircase(result.getBlockPos().add(0, 1, 0), options);
|
||||
if (hit.getType() == Type.BLOCK) {
|
||||
BlockHitResult result = (BlockHitResult) hit;
|
||||
client.player.sendChatMessage("Coords: " + result.getBlockPos().toShortString(), null);
|
||||
// Spawn structure at raycast hit
|
||||
StaircaseOptions options = new StaircaseOptions();
|
||||
//options.stretch = 2;
|
||||
options.width = 5;
|
||||
options.height = 20;
|
||||
options.noise = new StaircaseNoiseOptions();
|
||||
options.noise.threshold = 0.64f;
|
||||
options.minecartTrack = true;
|
||||
options.mirror = true;
|
||||
|
||||
gen.spawnStaircase(result.getBlockPos().add(0, 1, 0), options);
|
||||
}
|
||||
}
|
||||
|
||||
return TypedActionResult.success(user.getStackInHand(hand));
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package de.nicolasklier.custom_structures.structures;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
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.PerlinNoise;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
@@ -19,7 +19,6 @@ import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.state.property.Properties;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.Direction.Axis;
|
||||
|
||||
public class Generations {
|
||||
|
||||
@@ -38,53 +37,106 @@ public class Generations {
|
||||
int length = options.height * options.stretch * (options.mirror ? 2 : 1);
|
||||
|
||||
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 step = 0;
|
||||
|
||||
if (options.noise != null) {
|
||||
noise = new PerlinNoise(rng, options.noise.roughness, length, length);
|
||||
noise.initialise();
|
||||
noise = new Perlin();
|
||||
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.
|
||||
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++;
|
||||
|
||||
if (step >= options.stretch) {
|
||||
step = 0;
|
||||
|
||||
// Check if we already reached our final height. If yes, go downwards
|
||||
if (isMirroring) {
|
||||
y -= 1;
|
||||
} else {
|
||||
y++;
|
||||
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) {
|
||||
step = 0;
|
||||
|
||||
// Check if we already reached our final height. If yes, go downwards
|
||||
if (isMirroring) {
|
||||
y--;
|
||||
} else {
|
||||
y++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int z = 0; z < options.width; z++) {
|
||||
boolean isNextHeight = true;
|
||||
boolean isNextHeight = false;
|
||||
|
||||
if (options.stretch > 1) {
|
||||
if (noise != null) {
|
||||
if (isMirroring) {
|
||||
isNextHeight = step == 0;
|
||||
if (noise.get(x, 0, 0) < options.noise.threshold) {
|
||||
isNextHeight = true;
|
||||
}
|
||||
} else {
|
||||
isNextHeight = step + 1 == options.stretch;
|
||||
if (noise.get(x + 1, 0, 0) < options.noise.threshold) {
|
||||
isNextHeight = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (options.stretch > 1) {
|
||||
if (isMirroring) {
|
||||
isNextHeight = step == 0;
|
||||
} else {
|
||||
isNextHeight = step + 1 == options.stretch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Block block = Blocks.COBBLESTONE;
|
||||
|
||||
// Build supporting fence
|
||||
if (x % options.fenceVerticalDensity == 0 && y > 0) {
|
||||
/*if (x % options.fenceVerticalDensity == 0 && y > 0) {
|
||||
// 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));
|
||||
|
||||
int y2 = y - options.weight;
|
||||
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));
|
||||
@@ -93,18 +145,23 @@ public class Generations {
|
||||
break;
|
||||
}
|
||||
|
||||
System.out.println("Fence stuff " + y2 + "(" + mat.toString() + ")");
|
||||
|
||||
if (y2 <= -64)
|
||||
break;
|
||||
|
||||
y2--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
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) {
|
||||
block = Blocks.MOSSY_COBBLESTONE;
|
||||
} else if (rng.nextFloat() < options.chanceGlass / 100f) {
|
||||
block = Blocks.GLASS;
|
||||
} else if (rng.nextFloat() < options.chanceStoneBricks / 100f) {
|
||||
block = Blocks.STONE_BRICKS;
|
||||
} else {
|
||||
putStair = true;
|
||||
}
|
||||
@@ -129,8 +186,8 @@ public class Generations {
|
||||
if (rng.nextFloat() < options.chanceSlap / 100f) {
|
||||
placeQueue.put(at.add(x, y + options.weight, z), Blocks.SMOOTH_STONE_SLAB.getDefaultState());
|
||||
} else {
|
||||
CustomStructures.LOGGER.info("Noise [" + x + ", " + y + "]: " + noise.getValueAt(x, y + options.weight));
|
||||
if (noise.getValueAt(x, y + options.weight) < options.chanceWoodStair / 100f) {
|
||||
//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) {
|
||||
|
||||
// Override stair as wood stair, copying the facing.
|
||||
stairState = Blocks.OAK_STAIRS.getDefaultState()
|
||||
@@ -146,7 +203,7 @@ public class Generations {
|
||||
if (rng.nextFloat() < 0.04) {
|
||||
placeQueue.put(at.add(x, y + options.weight - 1, z), Blocks.GLOWSTONE.getDefaultState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build wall
|
||||
if ((z == 0 || z == options.width - 1) && options.wallHeight > 0) {
|
||||
@@ -163,6 +220,21 @@ public class Generations {
|
||||
.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++;
|
||||
|
||||
@@ -6,6 +6,11 @@ public class StaircaseNoiseOptions {
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
@@ -42,9 +42,9 @@ public class StaircaseOptions {
|
||||
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.
|
||||
@@ -71,4 +71,6 @@ public class StaircaseOptions {
|
||||
* To disable this feature set it to null.
|
||||
*/
|
||||
public StaircaseNoiseOptions noise = null;
|
||||
|
||||
public boolean minecartTrack = false;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
accessWidener v1 named
|
||||
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
|
||||
Reference in New Issue
Block a user