From 73aa475247edc35fa2aecb51c7dfe922099f0d38 Mon Sep 17 00:00:00 2001 From: paulevsGitch Date: Fri, 6 Nov 2020 14:25:27 +0300 Subject: [PATCH] Shadow forest structures & fixes --- .../ru/betterend/blocks/BlockTerrain.java | 19 +- .../ru/betterend/blocks/InfusionPedestal.java | 1 - .../entities/render/PedestalItemRenderer.java | 2 - .../java/ru/betterend/registry/EndBiomes.java | 11 +- .../ru/betterend/registry/EndFeatures.java | 7 + .../java/ru/betterend/util/JsonFactory.java | 190 ++++++++++-------- .../ru/betterend/util/StructureHelper.java | 9 +- .../ru/betterend/world/biome/EndBiome.java | 46 ++++- .../betterend/world/features/ListFeature.java | 63 ++++++ .../world/features/NBTStructureFeature.java | 106 ++++++---- .../biome/shadow_forest/fallen_log_1.nbt | Bin 0 -> 373 bytes .../biome/shadow_forest/fallen_log_2.nbt | Bin 0 -> 420 bytes .../biome/shadow_forest/ruins_1.nbt | Bin 0 -> 494 bytes .../biome/shadow_forest/ruins_2.nbt | Bin 0 -> 647 bytes .../biome/shadow_forest/ruins_3.nbt | Bin 0 -> 466 bytes .../biome/shadow_forest/ruins_4.nbt | Bin 0 -> 581 bytes .../biome/shadow_forest/ruins_5.nbt | Bin 0 -> 405 bytes .../biome/shadow_forest/ruins_6.nbt | Bin 0 -> 452 bytes .../biome/shadow_forest/ruins_7.nbt | Bin 0 -> 1048 bytes .../biome/shadow_forest/ruins_8.nbt | Bin 0 -> 2951 bytes .../biome/shadow_forest/small_mansion.nbt | Bin 0 -> 10067 bytes .../biome/shadow_forest/structures.json | 17 ++ .../biome/shadow_forest/stump_1.nbt | Bin 0 -> 276 bytes .../biome/shadow_forest/stump_2.nbt | Bin 0 -> 307 bytes 24 files changed, 327 insertions(+), 144 deletions(-) create mode 100644 src/main/java/ru/betterend/world/features/ListFeature.java create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/fallen_log_1.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/fallen_log_2.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_1.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_2.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_3.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_4.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_5.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_6.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_7.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_8.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/small_mansion.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/structures.json create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/stump_1.nbt create mode 100644 src/main/resources/data/betterend/structures/biome/shadow_forest/stump_2.nbt diff --git a/src/main/java/ru/betterend/blocks/BlockTerrain.java b/src/main/java/ru/betterend/blocks/BlockTerrain.java index d97c8938..e47e1905 100644 --- a/src/main/java/ru/betterend/blocks/BlockTerrain.java +++ b/src/main/java/ru/betterend/blocks/BlockTerrain.java @@ -10,6 +10,7 @@ import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.block.MaterialColor; +import net.minecraft.block.SnowBlock; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.enchantment.Enchantments; import net.minecraft.entity.player.PlayerEntity; @@ -24,7 +25,10 @@ import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; import net.minecraft.world.World; +import net.minecraft.world.WorldView; +import net.minecraft.world.chunk.light.ChunkLightProvider; import ru.betterend.blocks.basis.BlockBase; public class BlockTerrain extends BlockBase { @@ -64,8 +68,21 @@ public class BlockTerrain extends BlockBase { @Override public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { - if (random.nextInt(16) == 0 && world.getBlockState(pos.up()).getMaterial().blocksLight()) { + if (random.nextInt(16) == 0 && !canSurvive(state, world, pos)) { world.setBlockState(pos, Blocks.END_STONE.getDefaultState()); } } + + public static boolean canSurvive(BlockState state, WorldView worldView, BlockPos pos) { + BlockPos blockPos = pos.up(); + BlockState blockState = worldView.getBlockState(blockPos); + if (blockState.isOf(Blocks.SNOW) && (Integer)blockState.get(SnowBlock.LAYERS) == 1) { + return true; + } else if (blockState.getFluidState().getLevel() == 8) { + return false; + } else { + int i = ChunkLightProvider.getRealisticOpacity(worldView, state, pos, blockState, blockPos, Direction.UP, blockState.getOpacity(worldView, blockPos)); + return i < 5; + } + } } diff --git a/src/main/java/ru/betterend/blocks/InfusionPedestal.java b/src/main/java/ru/betterend/blocks/InfusionPedestal.java index 8301b4be..cc65f2fd 100644 --- a/src/main/java/ru/betterend/blocks/InfusionPedestal.java +++ b/src/main/java/ru/betterend/blocks/InfusionPedestal.java @@ -3,7 +3,6 @@ package ru.betterend.blocks; import net.minecraft.block.Blocks; import net.minecraft.block.entity.BlockEntity; import net.minecraft.world.BlockView; - import ru.betterend.blocks.basis.BlockPedestal; import ru.betterend.blocks.entities.InfusionPedestalEntity; diff --git a/src/main/java/ru/betterend/blocks/entities/render/PedestalItemRenderer.java b/src/main/java/ru/betterend/blocks/entities/render/PedestalItemRenderer.java index bc2632d3..67f78c17 100644 --- a/src/main/java/ru/betterend/blocks/entities/render/PedestalItemRenderer.java +++ b/src/main/java/ru/betterend/blocks/entities/render/PedestalItemRenderer.java @@ -2,7 +2,6 @@ package ru.betterend.blocks.entities.render; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; - import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.RenderLayer; @@ -20,7 +19,6 @@ import net.minecraft.item.Items; import net.minecraft.util.DyeColor; import net.minecraft.util.Identifier; import net.minecraft.util.math.MathHelper; - import ru.betterend.blocks.EternalPedestal; import ru.betterend.blocks.basis.BlockPedestal; import ru.betterend.blocks.entities.PedestalBlockEntity; diff --git a/src/main/java/ru/betterend/registry/EndBiomes.java b/src/main/java/ru/betterend/registry/EndBiomes.java index ceacfb58..bded0d14 100644 --- a/src/main/java/ru/betterend/registry/EndBiomes.java +++ b/src/main/java/ru/betterend/registry/EndBiomes.java @@ -77,8 +77,9 @@ public class EndBiomes { biomeRegistry.forEach((biome) -> { if (biome.getCategory() == Category.THEEND) { - if (!MUTABLE.containsKey(biome) && !biomeRegistry.getId(biome).getNamespace().equals("minecraft")) { - EndBiome endBiome = new EndBiome(biome, 1, 1); + Identifier id = biomeRegistry.getId(biome); + if (!MUTABLE.containsKey(biome) && !ID_MAP.containsKey(id)) { + EndBiome endBiome = new EndBiome(id, biome, 1, 1); LAND_BIOMES.addBiomeMutable(endBiome); KEYS.put(endBiome, biomeRegistry.getKey(biome).get()); } @@ -118,7 +119,7 @@ public class EndBiomes { * @return registered {@link EndBiome} */ public static EndBiome registerBiome(Biome biome, BiomeType type, float fogDensity, float genChance) { - EndBiome endBiome = new EndBiome(biome, fogDensity, genChance); + EndBiome endBiome = new EndBiome(BuiltinRegistries.BIOME.getId(biome), biome, fogDensity, genChance); addToPicker(endBiome, type); makeLink(endBiome); return endBiome; @@ -144,10 +145,11 @@ public class EndBiomes { * @return registered {@link EndBiome} */ public static EndBiome registerSubBiome(Biome biome, EndBiome parent, float fogDensity, float genChance) { - EndBiome endBiome = new EndBiome(biome, fogDensity, genChance); + EndBiome endBiome = new EndBiome(BuiltinRegistries.BIOME.getId(biome), biome, fogDensity, genChance); parent.addSubBiome(endBiome); makeLink(endBiome); SUBBIOMES.add(endBiome); + ID_MAP.put(endBiome.getID(), endBiome); return endBiome; } @@ -162,6 +164,7 @@ public class EndBiomes { parent.addSubBiome(biome); makeLink(biome); SUBBIOMES.add(biome); + ID_MAP.put(biome.getID(), biome); return biome; } diff --git a/src/main/java/ru/betterend/registry/EndFeatures.java b/src/main/java/ru/betterend/registry/EndFeatures.java index f1bf93ca..874d0fb4 100644 --- a/src/main/java/ru/betterend/registry/EndFeatures.java +++ b/src/main/java/ru/betterend/registry/EndFeatures.java @@ -9,6 +9,7 @@ import net.minecraft.util.Identifier; import net.minecraft.world.biome.Biome; import net.minecraft.world.gen.GenerationStep; import net.minecraft.world.gen.feature.ConfiguredFeature; +import ru.betterend.world.biome.EndBiome; import ru.betterend.world.features.BlueVineFeature; import ru.betterend.world.features.CavePlantFeature; import ru.betterend.world.features.DoublePlantFeature; @@ -103,6 +104,12 @@ public class EndFeatures { addFeature(ENDER_ORE, features); addFeature(ROUND_CAVE_RARE, features); addFeature(CAVE_GRASS, features); + + EndBiome endBiome = EndBiomes.getBiome(id); + EndFeature feature = endBiome.getStructuresFeature(); + if (feature != null) { + addFeature(feature, features); + } } private static void addFeature(EndFeature feature, List>>> features) { diff --git a/src/main/java/ru/betterend/util/JsonFactory.java b/src/main/java/ru/betterend/util/JsonFactory.java index 94c80e98..2b28a5c3 100644 --- a/src/main/java/ru/betterend/util/JsonFactory.java +++ b/src/main/java/ru/betterend/util/JsonFactory.java @@ -1,88 +1,102 @@ -package ru.betterend.util; - -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import net.minecraft.resource.Resource; -import ru.betterend.BetterEnd; - -public class JsonFactory { - - public final static Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - - public static JsonObject getJsonObject(String path) throws IOException { - try (InputStream is = JsonFactory.class.getResourceAsStream(path)) { - Reader reader = new InputStreamReader(is); - JsonObject jsonObject = loadJson(reader).getAsJsonObject(); - if (jsonObject == null) { - return new JsonObject(); - } - return jsonObject; - } - } - - public static JsonObject getJsonObject(Resource jsonSource) { - if (jsonSource != null) { - try (InputStream is = jsonSource.getInputStream()) { - Reader reader = new InputStreamReader(is); - JsonObject jsonObject = loadJson(reader).getAsJsonObject(); - if (jsonObject == null) { - return new JsonObject(); - } - return jsonObject; - } catch (IOException ex) { - BetterEnd.LOGGER.catching(ex); - return new JsonObject(); - } - } - - return new JsonObject(); - } - - public static JsonObject getJsonObject(File jsonFile) { - if (jsonFile.exists()) { - JsonObject jsonObject = loadJson(jsonFile).getAsJsonObject(); - if (jsonObject == null) { - return new JsonObject(); - } - return jsonObject; - } - - return new JsonObject(); - } - - public static JsonElement loadJson(File jsonFile) { - if (jsonFile.exists()) { - try (Reader reader = new FileReader(jsonFile)) { - return loadJson(reader); - } catch (Exception ex) { - BetterEnd.LOGGER.catching(ex); - } - } - return null; - } - - public static JsonElement loadJson(Reader reader) { - return GSON.fromJson(reader, JsonElement.class); - } - - public static void storeJson(File jsonFile, JsonElement jsonObject) { - try(FileWriter writer = new FileWriter(jsonFile)) { - String json = GSON.toJson(jsonObject); - writer.write(json); - writer.flush(); - } catch (IOException ex) { - BetterEnd.LOGGER.catching(ex); - } - } -} +package ru.betterend.util; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import net.minecraft.resource.Resource; +import ru.betterend.BetterEnd; + +public class JsonFactory { + + public final static Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + public static JsonObject getJsonObject(String path) throws IOException { + try (InputStream is = JsonFactory.class.getResourceAsStream(path)) { + Reader reader = new InputStreamReader(is); + JsonObject jsonObject = loadJson(reader).getAsJsonObject(); + if (jsonObject == null) { + return new JsonObject(); + } + return jsonObject; + } + } + + public static JsonObject getJsonObject(Resource jsonSource) { + if (jsonSource != null) { + try (InputStream is = jsonSource.getInputStream()) { + Reader reader = new InputStreamReader(is); + JsonObject jsonObject = loadJson(reader).getAsJsonObject(); + if (jsonObject == null) { + return new JsonObject(); + } + return jsonObject; + } catch (IOException ex) { + BetterEnd.LOGGER.catching(ex); + return new JsonObject(); + } + } + + return new JsonObject(); + } + + public static JsonObject getJsonObject(InputStream stream) { + try { + Reader reader = new InputStreamReader(stream); + JsonObject jsonObject = loadJson(reader).getAsJsonObject(); + if (jsonObject == null) { + return new JsonObject(); + } + return jsonObject; + } catch (Exception ex) { + BetterEnd.LOGGER.catching(ex); + return new JsonObject(); + } + } + + public static JsonObject getJsonObject(File jsonFile) { + if (jsonFile.exists()) { + JsonObject jsonObject = loadJson(jsonFile).getAsJsonObject(); + if (jsonObject == null) { + return new JsonObject(); + } + return jsonObject; + } + + return new JsonObject(); + } + + public static JsonElement loadJson(File jsonFile) { + if (jsonFile.exists()) { + try (Reader reader = new FileReader(jsonFile)) { + return loadJson(reader); + } catch (Exception ex) { + BetterEnd.LOGGER.catching(ex); + } + } + return null; + } + + public static JsonElement loadJson(Reader reader) { + return GSON.fromJson(reader, JsonElement.class); + } + + public static void storeJson(File jsonFile, JsonElement jsonObject) { + try(FileWriter writer = new FileWriter(jsonFile)) { + String json = GSON.toJson(jsonObject); + writer.write(json); + writer.flush(); + } catch (IOException ex) { + BetterEnd.LOGGER.catching(ex); + } + } +} diff --git a/src/main/java/ru/betterend/util/StructureHelper.java b/src/main/java/ru/betterend/util/StructureHelper.java index d669b843..a9bf2b40 100644 --- a/src/main/java/ru/betterend/util/StructureHelper.java +++ b/src/main/java/ru/betterend/util/StructureHelper.java @@ -9,7 +9,6 @@ import net.minecraft.block.Blocks; import net.minecraft.block.Material; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; -import net.minecraft.server.MinecraftServer; import net.minecraft.structure.Structure; import net.minecraft.structure.StructurePlacementData; import net.minecraft.tag.BlockTags; @@ -30,15 +29,17 @@ public class StructureHelper { public static Structure readStructure(Identifier resource) { String ns = resource.getNamespace(); String nm = resource.getPath(); - + return readStructure("/data/" + ns + "/structures/" + nm + ".nbt"); + } + + public static Structure readStructure(String path) { try { - InputStream inputstream = MinecraftServer.class.getResourceAsStream("/data/" + ns + "/structures/" + nm + ".nbt"); + InputStream inputstream = StructureHelper.class.getResourceAsStream(path); return readStructureFromStream(inputstream); } catch (IOException e) { e.printStackTrace(); } - return null; } diff --git a/src/main/java/ru/betterend/world/biome/EndBiome.java b/src/main/java/ru/betterend/world/biome/EndBiome.java index 0177e532..fb1eef89 100644 --- a/src/main/java/ru/betterend/world/biome/EndBiome.java +++ b/src/main/java/ru/betterend/world/biome/EndBiome.java @@ -1,15 +1,23 @@ package ru.betterend.world.biome; +import java.io.InputStream; import java.util.List; import java.util.Random; import com.google.common.collect.Lists; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import net.minecraft.structure.Structure; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.registry.BuiltinRegistries; import net.minecraft.world.WorldAccess; import net.minecraft.world.biome.Biome; +import ru.betterend.util.JsonFactory; +import ru.betterend.util.StructureHelper; +import ru.betterend.world.features.EndFeature; +import ru.betterend.world.features.ListFeature; +import ru.betterend.world.features.ListFeature.StructureInfo; public class EndBiome { protected List subbiomes = Lists.newArrayList(); @@ -25,19 +33,22 @@ public class EndBiome { protected float genChance = 1; private final float fogDensity; + private EndFeature structuresFeature; public EndBiome(BiomeDefinition definition) { biome = definition.build(); mcID = definition.getID(); fogDensity = definition.getFodDensity(); genChanceUnmutable = definition.getGenChance(); + readStructureList(); } - public EndBiome(Biome biome, float fogDensity, float genChance) { + public EndBiome(Identifier id, Biome biome, float fogDensity, float genChance) { this.biome = biome; - this.mcID = BuiltinRegistries.BIOME.getId(biome); + this.mcID = id; this.fogDensity = fogDensity; this.genChanceUnmutable = genChance; + readStructureList(); } public void genSurfColumn(WorldAccess world, BlockPos pos, Random random) { @@ -116,4 +127,33 @@ public class EndBiome { public float getFogDensity() { return fogDensity; } + + protected void readStructureList() { + String ns = mcID.getNamespace(); + String nm = mcID.getPath(); + + String path = "/data/" + ns + "/structures/biome/" + nm + "/"; + InputStream inputstream = StructureHelper.class.getResourceAsStream(path + "structures.json"); + if (inputstream != null) { + JsonObject obj = JsonFactory.getJsonObject(inputstream); + JsonArray enties = obj.getAsJsonArray("structures"); + if (enties != null) { + List list = Lists.newArrayList(); + enties.forEach((entry) -> { + JsonObject e = entry.getAsJsonObject(); + Structure structure = StructureHelper.readStructure(path + e.get("nbt").getAsString() + ".nbt"); + boolean adjustTerrain = e.get("adjustTerrain").getAsBoolean(); + int offsetY = e.get("offsetY").getAsInt(); + list.add(new StructureInfo(structure, offsetY, adjustTerrain)); + }); + if (!list.isEmpty()) { + structuresFeature = EndFeature.makeChansedFeature(nm + "_structures", new ListFeature(list), 50); + } + } + } + } + + public EndFeature getStructuresFeature() { + return structuresFeature; + } } diff --git a/src/main/java/ru/betterend/world/features/ListFeature.java b/src/main/java/ru/betterend/world/features/ListFeature.java new file mode 100644 index 00000000..e6da5d8b --- /dev/null +++ b/src/main/java/ru/betterend/world/features/ListFeature.java @@ -0,0 +1,63 @@ +package ru.betterend.world.features; + +import java.util.List; +import java.util.Random; + +import net.minecraft.structure.Structure; +import net.minecraft.util.BlockMirror; +import net.minecraft.util.BlockRotation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.StructureWorldAccess; +import ru.betterend.registry.EndTags; + +public class ListFeature extends NBTStructureFeature { + private final List list; + private StructureInfo selected; + + public ListFeature(List list) { + this.list = list; + } + + @Override + protected Structure getStructure(StructureWorldAccess world, BlockPos pos, Random random) { + selected = list.get(random.nextInt(list.size())); + return selected.structure; + } + + @Override + protected boolean canSpawn(StructureWorldAccess world, BlockPos pos, Random random) { + return pos.getY() > 58 && world.getBlockState(pos.down()).isIn(EndTags.GEN_TERRAIN); + } + + @Override + protected BlockRotation getRotation(StructureWorldAccess world, BlockPos pos, Random random) { + return BlockRotation.random(random); + } + + @Override + protected BlockMirror getMirror(StructureWorldAccess world, BlockPos pos, Random random) { + return BlockMirror.values()[random.nextInt(3)]; + } + + @Override + protected int getYOffset(Structure structure, StructureWorldAccess world, BlockPos pos, Random random) { + return selected.offsetY; + } + + @Override + protected boolean adjustSurface(StructureWorldAccess world, BlockPos pos, Random random) { + return selected.adjustTerrain; + } + + public static final class StructureInfo { + public final boolean adjustTerrain; + public final Structure structure; + public final int offsetY; + + public StructureInfo(Structure structure, int offsetY, boolean adjustTerrain) { + this.adjustTerrain = adjustTerrain; + this.structure = structure; + this.offsetY = offsetY; + } + } +} diff --git a/src/main/java/ru/betterend/world/features/NBTStructureFeature.java b/src/main/java/ru/betterend/world/features/NBTStructureFeature.java index 4fee674b..e1f9ff4d 100644 --- a/src/main/java/ru/betterend/world/features/NBTStructureFeature.java +++ b/src/main/java/ru/betterend/world/features/NBTStructureFeature.java @@ -4,8 +4,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.Random; +import net.minecraft.block.Block; import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; import net.minecraft.server.MinecraftServer; @@ -16,19 +16,24 @@ import net.minecraft.util.BlockRotation; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockBox; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; import net.minecraft.util.math.BlockPos.Mutable; import net.minecraft.world.StructureWorldAccess; +import net.minecraft.world.biome.Biome; import net.minecraft.world.gen.chunk.ChunkGenerator; import net.minecraft.world.gen.feature.DefaultFeatureConfig; +import net.minecraft.world.gen.surfacebuilder.SurfaceConfig; +import ru.betterend.registry.EndBiomes; +import ru.betterend.registry.EndTags; import ru.betterend.util.BlocksHelper; import ru.betterend.world.processors.DestructionStructureProcessor; public abstract class NBTStructureFeature extends DefaultFeature { protected static final DestructionStructureProcessor DESTRUCTION = new DestructionStructureProcessor(); - protected abstract Structure getStructure(); + protected abstract Structure getStructure(StructureWorldAccess world, BlockPos pos, Random random); - protected abstract boolean canSpawn(StructureWorldAccess world, BlockPos pos); + protected abstract boolean canSpawn(StructureWorldAccess world, BlockPos pos, Random random); protected abstract BlockRotation getRotation(StructureWorldAccess world, BlockPos pos, Random random); @@ -36,14 +41,37 @@ public abstract class NBTStructureFeature extends DefaultFeature { protected abstract int getYOffset(Structure structure, StructureWorldAccess world, BlockPos pos, Random random); - protected abstract boolean hasErosion(); - - protected abstract boolean hasTerrainOverlay(); - - protected abstract void addProcessors(StructurePlacementData placementData, Random random); + protected abstract boolean adjustSurface(StructureWorldAccess world, BlockPos pos, Random random); protected BlockPos getGround(StructureWorldAccess world, BlockPos center) { - return getPosOnSurface(world, center); + Biome biome = world.getBiome(center); + Identifier id = EndBiomes.getBiomeID(biome); + if (id.getNamespace().contains("moutain") || id.getNamespace().contains("lake")) { + int y = getAverageY(world, center); + return new BlockPos(center.getX(), y, center.getZ()); + } + else { + int y = getAverageYWG(world, center); + return new BlockPos(center.getX(), y, center.getZ()); + } + } + + protected int getAverageY(StructureWorldAccess world, BlockPos center) { + int y = getYOnSurface(world, center.getX(), center.getZ()); + y += getYOnSurface(world, center.getX() - 2, center.getZ() - 2); + y += getYOnSurface(world, center.getX() + 2, center.getZ() - 2); + y += getYOnSurface(world, center.getX() - 2, center.getZ() + 2); + y += getYOnSurface(world, center.getX() + 2, center.getZ() + 2); + return y / 5; + } + + protected int getAverageYWG(StructureWorldAccess world, BlockPos center) { + int y = getYOnSurfaceWG(world, center.getX(), center.getZ()); + y += getYOnSurfaceWG(world, center.getX() - 2, center.getZ() - 2); + y += getYOnSurfaceWG(world, center.getX() + 2, center.getZ() - 2); + y += getYOnSurfaceWG(world, center.getX() - 2, center.getZ() + 2); + y += getYOnSurfaceWG(world, center.getX() + 2, center.getZ() + 2); + return y / 5; } @Override @@ -51,11 +79,12 @@ public abstract class NBTStructureFeature extends DefaultFeature { center = new BlockPos(((center.getX() >> 4) << 4) | 8, 128, ((center.getZ() >> 4) << 4) | 8); center = getGround(world, center); - if (!canSpawn(world, center)) { + if (!canSpawn(world, center, random)) { return false; } - Structure structure = getStructure(); + int posY = center.getY() + 1; + Structure structure = getStructure(world, center, random); BlockRotation rotation = getRotation(world, center, random); BlockMirror mirror = getMirror(world, center, random); BlockPos offset = Structure.transformAround(structure.getSize(), mirror, rotation, BlockPos.ORIGIN); @@ -63,44 +92,39 @@ public abstract class NBTStructureFeature extends DefaultFeature { BlockBox bounds = makeBox(center); StructurePlacementData placementData = new StructurePlacementData().setRotation(rotation).setMirror(mirror).setBoundingBox(bounds); - addProcessors(placementData, random); - structure.place(world, center.add(-offset.getX() * 0.5, 0, -offset.getZ() * 0.5), placementData, random); + center = center.add(-offset.getX() * 0.5, 1, -offset.getZ() * 0.5); + structure.place(world, center, placementData, random); - int x1 = center.getX(); - int y1 = center.getX(); - int z1 = center.getX(); - int x2 = x1 + offset.getX(); - int y2 = y1 + offset.getY(); - int z2 = z1 + offset.getZ(); - - boolean erosion = hasErosion(); - boolean overlay = hasTerrainOverlay(); - - if (erosion || overlay) { + if (adjustSurface(world, center, random)) { Mutable mut = new Mutable(); + int x1 = center.getX(); + int z1 = center.getZ(); + int x2 = x1 + offset.getX(); + int z2 = z1 + offset.getZ(); + + int surfMax = posY - 1; for (int x = x1; x <= x2; x++) { mut.setX(x); for (int z = z1; z <= z2; z++) { mut.setZ(z); - for (int y = y1; y <= y2; y++) { - mut.setY(y); - if (bounds.contains(mut)) { - if (overlay) { - if (world.getBlockState(mut).isOf(Blocks.END_STONE) && world.isAir(mut.up())) { - BlockState terrain = world.getBiome(mut).getGenerationSettings().getSurfaceConfig().getTopMaterial(); - BlocksHelper.setWithoutUpdate(world, mut, terrain); - } + mut.setY(posY); + BlockState state = world.getBlockState(mut); + if (!state.isIn(EndTags.GEN_TERRAIN) && Block.sideCoversSmallSquare(world, mut, Direction.DOWN)) { + for (int i = 0; i < 10; i--) { + mut.setY(mut.getY() - 1); + BlockState stateSt = world.getBlockState(mut); + if (!stateSt.isIn(EndTags.GEN_TERRAIN) && stateSt.getMaterial().isReplaceable()) { + SurfaceConfig config = world.getBiome(mut).getGenerationSettings().getSurfaceConfig(); + boolean isTop = mut.getY() == surfMax && state.getMaterial().blocksLight(); + BlockState top = isTop ? config.getTopMaterial() : config.getUnderMaterial(); + BlocksHelper.setWithoutUpdate(world, mut, top); } - if (erosion) { - mut.setY(mut.getY() - 1); - int down = BlocksHelper.downRay(world, mut, 32); - if (down > 0) { - mut.setY(mut.getY() + 1); - BlockState state = world.getBlockState(mut); - BlocksHelper.setWithoutUpdate(world, mut, AIR); - mut.setY(mut.getY() - down); - BlocksHelper.setWithoutUpdate(world, mut, state); + else { + if (stateSt.isIn(EndTags.END_GROUND) && state.getMaterial().blocksLight()) { + SurfaceConfig config = world.getBiome(mut).getGenerationSettings().getSurfaceConfig(); + BlocksHelper.setWithoutUpdate(world, mut, config.getUnderMaterial()); } + break; } } } diff --git a/src/main/resources/data/betterend/structures/biome/shadow_forest/fallen_log_1.nbt b/src/main/resources/data/betterend/structures/biome/shadow_forest/fallen_log_1.nbt new file mode 100644 index 0000000000000000000000000000000000000000..aab1b13a4e01f180d87d67a72508e2d8640a7617 GIT binary patch literal 373 zcmV-*0gC<~iwFP!000000IikLPQxG+h7X0dYrXVFAHi4f+H2#Lw+-8lldLNQSkyhe ztr?S$7MPKQq$J-D|M?H0EkFazh!au(m?j%%G0wJu6%JNdfd;10K(+4WW^dYY@z4NO z(l`-;^Po@~%PpQ2a}3E~iVTKkFf4;9GnguasWX^W2Gc~CA|pe`#4>bDEJMe{GIUHV zL&wB2bWAEklBnG=sry4RYX9j+29xSV%1?2HpHweMq8FFpF=T=9>nau)ma$Kju}>Y@ z$8mmLbb;~DVl!ujy_oYH_9FD`?vI~gxxmEoasBc=S2J~mp66;0J%_c^89x(KE8e5E zI6dig3+;=R3ib5luYmDSVSt=q{CwsIg!}b`Y8|td3gs*eTh6DXDQ$kNh9?*ElZ+sqOpUq(&Z7QVKUu-11k{ TM#y3OP2a&cHDG&##0LNXfZ4Up literal 0 HcmV?d00001 diff --git a/src/main/resources/data/betterend/structures/biome/shadow_forest/fallen_log_2.nbt b/src/main/resources/data/betterend/structures/biome/shadow_forest/fallen_log_2.nbt new file mode 100644 index 0000000000000000000000000000000000000000..40c21aceb241ba0e4262176732204f8c3f21beb7 GIT binary patch literal 420 zcmV;V0bBkbiwFP!000000G*ayPlGTNh7WDo240#N{TbeR?X~gBTheXE*~nWCZwhOy{}=to(>3Old7LI=ZD!D8L&&8w^XhuaFc z(JmA*_`V7brSsC`sUpTO0>cT6NH82iqdb#x4ou29Fe&FE3<8tN3JfJHwLXI2BM3f% z=)*F55t!WiFv9w7?J41B0hYE8E1(JQ%3rfGYm^)m65DzAAsLyH8!s)Gj$HkIBOBv(_9}O z=`-DnGQv#fUWEB2`tS%dT^|uShXb=TvPJK4_~b2Xs2{C1XopYY3WWSn4vHi6w-0iU zaI+pzi{18aYvoRNjkkzRt9LLdT|CQ;>UJOw=={0<@~7TQ)i!(W9IbMcY~HMCu`#;+ zG}_{9J<5O8m+nOx?EeI`_j-SBa5YjtDCcq8ysM6wPMP;9mwRopp&TymrI$}=ozfjZ OeGOk@Ctm!fiyYfni6tMFede9lGlppZYM_e0A_0JTS7uYQcQsTvYOXmdm{$$ueh`QM zsxd!))IcVoY)}TrMW9fcX*@>wm@I+GBTVkm$sC4w{Nw>^*w9?Oe|O7$rWNq#82+2v+wN0xnpOB*-dOft#KYPUm9rsN-?3FM^FWnJ#z)w zt1(6YuzQ5>`x%uucGsO`y%<|95nH7tpC;_uLG<8gR2?_z#sfn3vx3GvSLT5kb3nCx zz(x9-UwT0HXr^IU^!LX_E%o(8J#sPNfop8{lAFQ1Fc&9A-LXzQwY_BO^#7GlL-XOi z_hV6CK2y~4mL2%m1ARiJ7oqc$kY;>n?}d7o-9{ElJm|Z4X!z{Yz)Y$B%#skaiE7G;Pve_D^h5U$^IJPkTj9xCE;X zMz)hk`|D@gUhAgddQ;0RAr$!B-S>W2F%>`wnGwG+1%TmNx+@#*B^20MVFel(Rtfo9 zs?D{j+>b9Mq`fj^l)&vuQeeh%i+2`d%p!tGBbY3LVIGs5F^dSz#Q2&Q6HFSxWDyJt z=t(0q6BE)*Oh_{^AY#@C!qFd@yvgfugtVAJzMOeim6LU|Dr>IE^O zyokvIOdg?`nCJFja;9f&e?7-!#f46Q zeN%_dCUE=q?Ha8`ja_rqV}s72oR~Ae_tgyX{QOq07%!bUKZ?g@ipTy#{}yNQE?&8w z=kYFQh{tA%$7Zf)LiZW-dm68VWzXecI~;Z26p;PkdxTF9?v6r9+&CR|ucR<7Hp5u| z)T&249GD9z8sQvv8-(O*0hN1h+yi%@hRo^%!idvG^PTEys}XC7{OHczM133!ZH4rZ z_Ya6^d81DCPIF_&;W5#0{5ldH$Q@w*3O%Z04>>ZxVca=CTi0LohB%D+0fa%rh{ zJC{7$a%r$|MQnNRk~3Cwv1_dt54jib^H@G>E!@{d?aC(fq;6)^!M1-T{NA)v`X5an zs~@$bm)_9r5BmCqBeT(Rb2G$V-EM~OuDD~v;Y}Qka;(>7gIj)(_J!T%y^xaY7xsEV h7PVDK%oEt literal 0 HcmV?d00001 diff --git a/src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_3.nbt b/src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_3.nbt new file mode 100644 index 0000000000000000000000000000000000000000..56514f7c5c15d98c8bf4fa55976082eaf4f8efcb GIT binary patch literal 466 zcmV;@0WJO?iwFP!000000KJq!kDD+Mg$E1?kXEYNODpx#Ur}kV?YZiyS2TeEtU5Nb zCt0b#zQgXO>#$IzU1^aJ!hD|hp7FQ>C?N~_7byUk?)>~Lp&-keH3gWi5;l9|nxCPP zmp3J-(S`G*1n#dyK^_>H9%d{t>Mu`%Sf~i2+Jy)>9hV_YR&2%Ur)h)a}XiJU9 zj&D%=1oAKLXgPuVuW$FnOrC6eGg3=Z&@ALnX1;gAY)3TH+-YmRDj@rY2ZE1}fYXfym`u^KM(}@Mw6cb)uF%p9KnbUyBwz+I!JIQ%JD3!U{CdUMVcrQWd{U zMlZK1utu4FQvk;+U=SP2Ep|J`FwqQ5>|lZcChRbwg9$wtHi2O?c-Rb{Xh!WlW~%lc zGgW&$!ZXAK-93egW?*1Mi+S>Ldl-2zYy#8g<@PZ4U|c^7PhL|zYz9xC1vr?c2jkYB zcra`N(`V{>PDg4F23o@`9a|TF{$_D^90&=7qJ+0=v=%ki z<=vWVjnZS?rCVK2$3{Bw>Uz2{cNwgBVZ{M+L99oxX=N_U(~*~L8tca zq}E`GL^#yzo9l*jbDjNgVE;8?mJOHf&URG4Ng(>pcL*O(WCx)n7F1Tg)`CVlImmu! z)u3+2eF8}-h^{UWg3k$Lv~FmDjsu9S-XrvspH7se=378RpzLx+^IR#9B5d+LXlYP%(oc*CW^gt}Dm zhWs0=x!6|L(L?U&-THf#Q)h&JVfEOky{-PAe693V@*bjp5Zl>ja=G#I_~-ccME5sa z!+k^BXua+#eeTEVhQMU=UxG2IxOtd*P67|(Wm)2e@1=d_-|mOsRI@Mg$zb)BTmG{h TNUEm8>jV4&Tj9eE!wLWZ>9Hh$ literal 0 HcmV?d00001 diff --git a/src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_5.nbt b/src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_5.nbt new file mode 100644 index 0000000000000000000000000000000000000000..83ccf5a13cb37b338fb473d94eb673c01f59dcdb GIT binary patch literal 405 zcmV;G0c!pqiwFP!000000L7F|PlPZKhKCk*`8ay<>*C+=?AdtoO1kSTOzcw9R#*M? zmMAM?yI?d~lBS_N^Um8&nH(U4*w7nt0O&^S#-f`uNU^bG2@P~l28)$c>qnEjmx~N| zt4tph!N(JENQ@O0KO=k$8!*v`VIG|j!zOaEkq!$m>;S`N=&%_&Q9sXMru@v&iDxoX zo|6fNd+NnKI?;%U1I#d2;@SIHXAxkAI?E9Qo|>kCy8#0x3ov}Z{&!i--$zo|qx_;0jmp-Ibl1z;c zZm0R3RxRqThqw^&LI$GOee}6EnxqtZ9J|ESXlTG%*=?1wQ|cfv2f$aJ}Bz1 z#}ODza1sj;mOLbS&iB4|7iS4jL1x4eQvm39W_MzDRY8d@2@+_aUlkM^skV=1X&SaK0S$e`pM1s>7jnH<=38XeaJhq&R9C|4U@dE@ z>e?E}cU&4QZ6O-oA(VzR7j;8kGUQ&e%aGWO7WVJ7wzU*`RCL}dP0hcl{!*f!tN!Xf uUUachL~8%<)&6DnKdM@8)YDwAQ&`<_;`gYHP_6xvPvI4mf$wY-2><{vfYlfP literal 0 HcmV?d00001 diff --git a/src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_7.nbt b/src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_7.nbt new file mode 100644 index 0000000000000000000000000000000000000000..25a72062d3e97b887780343fee60140f445c45c4 GIT binary patch literal 1048 zcmV+z1n2u7iwFP!000000KJ$^Z`(K!hAD}%Wjkq`cH2$c1@<>=F9mwu^P;C-4aTM& zA&Mwabkp>_KYYp9Oyu#9oY;#75DEn^=Y8g*93fN2CXDy>PgyX=+%vY%YxnPjRkAg; zsb$aH&xDPx8nw9Tr}pLF2`f6)`$PUGFId?dVdU<_P?PO689_6QlLa(IBu?&u2%0S7 zE$=GINAE#ls0aC8EJ*qafs75N4xD6dGO$lm_2Gp2z8H2naPlI5~eWu$plv8flDlz5F<3)2~`}QoBE2RjgXpR@yEk z%RW_XYJcC`U&z)lZuCZ4R{oE;mF$lz7eUK*`DLlaTD5bdrJOfXe3v~72mR}R3(Cch zx@Xz_cE2^h%Jzi+#V1}Lr#AOkX84_jy literal 0 HcmV?d00001 diff --git a/src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_8.nbt b/src/main/resources/data/betterend/structures/biome/shadow_forest/ruins_8.nbt new file mode 100644 index 0000000000000000000000000000000000000000..b4791e3a3ba9455daba2c1b7c061c85fdd496265 GIT binary patch literal 2951 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 aH(KH9L9>3Qn2dKoeTz?ThS%cN0{{TRI)b_Y literal 0 HcmV?d00001 diff --git a/src/main/resources/data/betterend/structures/biome/shadow_forest/stump_2.nbt b/src/main/resources/data/betterend/structures/biome/shadow_forest/stump_2.nbt new file mode 100644 index 0000000000000000000000000000000000000000..2b2b93f939567e7c0e6b7bb2cce840b8cbbc519d GIT binary patch literal 307 zcmV-30nGj%iwFP!000000Hu;qPlGTNg%7l4fRB68zv8pc#wTA%w_UNZYn!%<&R;J? zHjw}$Ta%`ceCM8fZd(Eccu?QO0JJ-6cG{i=axzgxMFH(9kZrWxeuT35Sr=f=hLb44 z?@Ad&h%!>A7-QHBlXgt9fZ;O?TTq8BsKXcJO1o^AdQL8QVZDO6JFk*%|`wjY8IjJc|YM*XiqI~hzk#C=N4lch` zKypO$`7HN@hfRxmGP}o}m$fxj^hA}mH3rh`O>UK`!9OX)vwr=jk5cJsZ$nt7>^ZxG zcbY0^_0d^RQ+i>nkMCDmi(n5|N;%1!y8f@fq3IcGhPx*j