diff --git a/src/main/java/com/github/elenterius/biomancy/block/cradle/PrimordialCradleBlockEntity.java b/src/main/java/com/github/elenterius/biomancy/block/cradle/PrimordialCradleBlockEntity.java index 5bdac9fff..2d0a45b2b 100644 --- a/src/main/java/com/github/elenterius/biomancy/block/cradle/PrimordialCradleBlockEntity.java +++ b/src/main/java/com/github/elenterius/biomancy/block/cradle/PrimordialCradleBlockEntity.java @@ -197,7 +197,7 @@ public void onSacrifice(ServerLevel level) { SoundUtil.broadcastBlockSound(level, pos, ModSoundEvents.CREATOR_SPAWN_MOB); } - if (level.random.nextFloat() >= sacrificeHandler.getTumorFactor() / 2) { + if (level.random.nextFloat() >= sacrificeHandler.getTumorFactor() / 3) { PrimordialEcosystem.tryToReplaceBlock(level, pos.below(), ModBlocks.PRIMAL_FLESH.get().defaultBlockState()); } diff --git a/src/main/java/com/github/elenterius/biomancy/block/veins/FleshVeinsBlock.java b/src/main/java/com/github/elenterius/biomancy/block/veins/FleshVeinsBlock.java index b62bc4cd2..8617256b4 100644 --- a/src/main/java/com/github/elenterius/biomancy/block/veins/FleshVeinsBlock.java +++ b/src/main/java/com/github/elenterius/biomancy/block/veins/FleshVeinsBlock.java @@ -15,6 +15,8 @@ import com.github.elenterius.biomancy.world.PrimordialEcosystem; import com.github.elenterius.biomancy.world.mound.MoundChamber; import com.github.elenterius.biomancy.world.mound.MoundShape; +import com.github.elenterius.biomancy.world.mound.decorator.ChamberDecorator; +import com.github.elenterius.biomancy.world.mound.decorator.ChamberSpecialDecorator; import com.github.elenterius.biomancy.world.spatial.SpatialShapeManager; import com.github.elenterius.biomancy.world.spatial.geometry.HasRadius; import com.github.elenterius.biomancy.world.spatial.geometry.Shape; @@ -54,9 +56,9 @@ public class FleshVeinsBlock extends MultifaceBlock implements SimpleWaterloggedBlock { + public static final Predicate BLOCKS_TO_AVOID_PREDICATE = blockState -> blockState.is(ModBlocks.MALIGNANT_BLOOM.get()); protected static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; protected static final EnhancedIntegerProperty CHARGE = ModBlockProperties.CHARGE; - public static final Predicate BLOCKS_TO_AVOID_PREDICATE = blockState -> blockState.is(ModBlocks.MALIGNANT_BLOOM.get()); private final MultifaceSpreader spreader = new MultifaceSpreader(new MalignantFleshSpreaderConfig(this)); public FleshVeinsBlock(Properties properties) { @@ -75,8 +77,8 @@ public static boolean convert(BlockState state, ServerLevel level, BlockPos pos, facesSet.set(0, hasFace(state, Direction.EAST)); if (mound != null) { - MoundChamber chamber = mound.getChamberAt(pos.getX(), pos.getY(), pos.getZ()); - if (chamber != null) { + MoundChamber chamber = mound.getChamberAt(pos); + if (chamber != null && chamber.contains(pos)) { return convertInsideChamber(level, pos, directNeighbors, mound, chamber, nearBoundingCenterPct, facesSet, energyHandler); } } @@ -124,37 +126,51 @@ protected static boolean convertInsideChamber(ServerLevel level, BlockPos pos, i ArrayUtil.shuffle(axisDirections, level.random); for (Direction axisDirection : axisDirections) { - BlockPos posRelative = pos.relative(axisDirection); - BlockState stateRelative = level.getBlockState(posRelative); + BlockPos closeOffsetPos = pos.relative(axisDirection); + BlockState closeOffsetState = level.getBlockState(closeOffsetPos); - if (PrimordialEcosystem.isReplaceable(stateRelative)) { - return destroyBlockAndConvertIntoEnergy(level, posRelative, energyHandler, 15); + if (PrimordialEcosystem.isReplaceable(closeOffsetState)) { + return destroyBlockAndConvertIntoEnergy(level, closeOffsetPos, energyHandler, 15); } - if (PrimordialEcosystem.FULL_FLESH_BLOCKS.contains(stateRelative.getBlock())) { - if (chamber.contains(posRelative.getX(), posRelative.getY(), posRelative.getZ())) { - return destroyBlockAndConvertIntoEnergy(level, posRelative, energyHandler, 30); //TODO: this might interfere with future room content generation - } + if (PrimordialEcosystem.FULL_FLESH_BLOCKS.contains(closeOffsetState.getBlock())) { + ChamberDecorator chamberDecorator = chamber.getDecorator(); - BlockPos posRelative2 = pos.relative(axisDirection, 2); - BlockState stateRelative2 = level.getBlockState(posRelative2); + boolean chamberContainsCloseOffsetPos = chamber.contains(closeOffsetPos); - // create "Door" between two adjacent chambers - MoundChamber neighborChamber = mound.getChamberAt(posRelative2.getX(), posRelative2.getY(), posRelative2.getZ()); - if (neighborChamber != null && neighborChamber != chamber) { - return level.setBlock(posRelative, ModBlocks.PRIMAL_PERMEABLE_MEMBRANE.get().defaultBlockState(), Block.UPDATE_CLIENTS); + if (chamberContainsCloseOffsetPos) { + ChamberDecorator.PartOfDecorationResult result = chamberDecorator.isBlockPartOfDecoration(chamber, level, closeOffsetPos, closeOffsetState); + if (result.positionIsValid && !result.materialIsValid && !closeOffsetState.is(ModBlocks.BLOOMLIGHT.get())) { + return destroyBlockAndConvertIntoEnergy(level, closeOffsetPos, energyHandler, 30); + } } - boolean isFleshBlock = PrimordialEcosystem.FULL_FLESH_BLOCKS.contains(stateRelative2.getBlock()); + if (chamberDecorator.canPlace(chamber, level, pos, axisDirection)) { + return chamberDecorator.place(chamber, level, pos, axisDirection); + } + + BlockPos farOffsetPos = pos.relative(axisDirection, 2); + BlockState farOffsetState = level.getBlockState(farOffsetPos); + + if (!chamberContainsCloseOffsetPos) { + // create "Door" between two adjacent chambers that are separated by a one block thick wall + boolean hasNoChamberAtPos = mound.getChamberAt(closeOffsetPos) == null; + if (hasNoChamberAtPos) { + MoundChamber farChamber = mound.getChamberAt(farOffsetPos); + if (farChamber != null && farChamber != chamber) { + return level.setBlock(closeOffsetPos, ModBlocks.PRIMAL_PERMEABLE_MEMBRANE.get().defaultBlockState(), Block.UPDATE_CLIENTS); + } + } + } - // create light source in dark corners - if (isFleshBlock && axisDirection == Direction.UP && LevelUtil.getMaxBrightness(level, pos) < 5) { - return level.setBlock(posRelative, ModBlocks.BLOOMLIGHT.get().defaultBlockState(), Block.UPDATE_CLIENTS); + // create light source in dark areas + if (ChamberSpecialDecorator.BLOOMLIGHT.canDecorate(chamber, level, pos, axisDirection, closeOffsetPos, closeOffsetState, farOffsetPos, farOffsetState)) { + return ChamberSpecialDecorator.BLOOMLIGHT.decorate(chamber, level, pos, axisDirection, closeOffsetPos, closeOffsetState, farOffsetPos, farOffsetState); } - if (PrimordialEcosystem.isReplaceable(stateRelative2) && stateRelative2.isCollisionShapeFullBlock(level, posRelative2)) { + if (PrimordialEcosystem.isReplaceable(farOffsetState) && farOffsetState.isCollisionShapeFullBlock(level, farOffsetPos)) { BlockState replacementState = level.random.nextFloat() < nearBoundingCenterPct ? ModBlocks.PRIMAL_FLESH.get().defaultBlockState() : ModBlocks.MALIGNANT_FLESH.get().defaultBlockState(); - return level.setBlock(posRelative2, replacementState, Block.UPDATE_CLIENTS); + return level.setBlock(farOffsetPos, replacementState, Block.UPDATE_CLIENTS); } } } @@ -502,11 +518,11 @@ public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource energyHandler = peh; } - Shape boundingShape = mound.getBoundingShapeAt(pos.getX(), pos.getY(), pos.getZ()); + Shape boundingShape = mound.getBoundingShapeAt(pos); if (boundingShape != null) { double radius = boundingShape instanceof HasRadius sphere ? sphere.getRadius() : boundingShape.getAABB().getSize() / 2; double radiusSqr = radius * radius; - double distSqr = boundingShape.distanceToSqr(pos.getX(), pos.getY(), pos.getZ()); + double distSqr = boundingShape.distanceToSqr(pos.getX() + 0.5d, pos.getY() + 0.5d, pos.getZ() + 0.5d); nearBoundingCenterPct = Mth.clamp((float) (1 - distSqr / radiusSqr), 0f, 1f); } } diff --git a/src/main/java/com/github/elenterius/biomancy/world/MobSpawnFilterShape.java b/src/main/java/com/github/elenterius/biomancy/world/MobSpawnFilterShape.java index 2176fb158..db0a33aab 100644 --- a/src/main/java/com/github/elenterius/biomancy/world/MobSpawnFilterShape.java +++ b/src/main/java/com/github/elenterius/biomancy/world/MobSpawnFilterShape.java @@ -34,8 +34,8 @@ public boolean intersectsCuboid(double minX, double minY, double minZ, double ma } @Override - public Vec3 getCenter() { - return shape.getCenter(); + public Vec3 center() { + return shape.center(); } @Override diff --git a/src/main/java/com/github/elenterius/biomancy/world/mound/Chamber.java b/src/main/java/com/github/elenterius/biomancy/world/mound/Chamber.java index 036e0b476..eb6e8e28d 100644 --- a/src/main/java/com/github/elenterius/biomancy/world/mound/Chamber.java +++ b/src/main/java/com/github/elenterius/biomancy/world/mound/Chamber.java @@ -1,7 +1,26 @@ package com.github.elenterius.biomancy.world.mound; +import com.github.elenterius.biomancy.world.mound.decorator.ChamberDecorator; +import com.github.elenterius.biomancy.world.spatial.geometry.Shape; +import net.minecraft.core.BlockPos; import net.minecraft.world.phys.Vec3; -public interface Chamber { - Vec3 getOrigin(); +public interface Chamber extends Shape { + + int seed(); + + ChamberDecorator getDecorator(); + + Vec3 origin(); + + // default boolean containsValidBlock(Level level, BlockPos pos, BlockState blockState) { + // if (!contains(pos)) return false; + // ChamberDecorator.Result result = getDecorator().isBlockPartOfDecoration(this, level, pos, blockState); + // return result == ChamberDecorator.Result.POSITION_IS_VALID_AND_MATERIAL_IS_INVALID; + // } + + default boolean contains(BlockPos pos) { + return contains(pos.getX() + 0.5d, pos.getY() + 0.5d, pos.getZ() + 0.5d); + } + } diff --git a/src/main/java/com/github/elenterius/biomancy/world/mound/ChambersGenerator.java b/src/main/java/com/github/elenterius/biomancy/world/mound/ChamberFactory.java similarity index 59% rename from src/main/java/com/github/elenterius/biomancy/world/mound/ChambersGenerator.java rename to src/main/java/com/github/elenterius/biomancy/world/mound/ChamberFactory.java index 5e931d33e..6ecf511ac 100644 --- a/src/main/java/com/github/elenterius/biomancy/world/mound/ChambersGenerator.java +++ b/src/main/java/com/github/elenterius/biomancy/world/mound/ChamberFactory.java @@ -1,19 +1,19 @@ package com.github.elenterius.biomancy.world.mound; +import com.github.elenterius.biomancy.world.mound.decorator.ChamberDecorators; import com.github.elenterius.biomancy.world.spatial.geometry.OctantEllipsoidShape; import com.github.elenterius.biomancy.world.spatial.geometry.SphereShape; +import net.minecraft.util.RandomSource; import net.minecraft.util.random.SimpleWeightedRandomList; import java.util.function.Consumer; -public interface ChambersGenerator { - void generate(double x, double y, double z, float chamberRadius, Consumer consumer); +public interface ChamberFactory { + void create(double x, double y, double z, float chamberRadius, RandomSource random, Consumer consumer); - ChambersGenerator ONE_SPHERE = (double x, double y, double z, float chamberRadius, Consumer consumer) -> { - consumer.accept(new MoundChamber(new SphereShape(x, y, z, chamberRadius))); - }; + ChamberFactory ONE_SPHERE = (double x, double y, double z, float chamberRadius, RandomSource random, Consumer consumer) -> consumer.accept(new MoundChamber(new SphereShape(x, y, z, chamberRadius))); - ChambersGenerator EIGHT_SMALL_ELLIPSOIDS = (double x, double y, double z, float chamberRadius, Consumer consumer) -> { + ChamberFactory EIGHT_SMALL_ELLIPSOIDS = (double x, double y, double z, float chamberRadius, RandomSource random, Consumer consumer) -> { float halfR = chamberRadius / 2; float quarterR = halfR / 2; float p = chamberRadius / 3.8f; // radius / 4.25f @@ -28,20 +28,7 @@ public interface ChambersGenerator { consumer.accept(new MoundChamber(new OctantEllipsoidShape(x - p, y - p, z - p, quarterR, quarterR, quarterR, halfR, halfR, halfR))); }; - ChambersGenerator SPECIAL_CRADLE = (double x, double y, double z, float chamberRadius, Consumer consumer) -> { - float halfR = chamberRadius / 2; - float quarterR = halfR / 2; - float p = chamberRadius / 3.8f; // radius / 4.25f - - consumer.accept(new MoundChamber(new OctantEllipsoidShape(x, y + p - 1, z, halfR, halfR, halfR, halfR, quarterR, halfR))); - - consumer.accept(new MoundChamber(new OctantEllipsoidShape(x + p, y - p, z + p, halfR, quarterR, halfR, quarterR, halfR, quarterR))); - consumer.accept(new MoundChamber(new OctantEllipsoidShape(x - p, y - p, z + p, quarterR, quarterR, halfR, halfR, halfR, quarterR))); - consumer.accept(new MoundChamber(new OctantEllipsoidShape(x + p, y - p, z - p, halfR, quarterR, quarterR, quarterR, halfR, halfR))); - consumer.accept(new MoundChamber(new OctantEllipsoidShape(x - p, y - p, z - p, quarterR, quarterR, quarterR, halfR, halfR, halfR))); - }; - - ChambersGenerator ONE_BIG_FOUR_SMALL_ELLIPSOIDS = (double x, double y, double z, float chamberRadius, Consumer consumer) -> { + ChamberFactory ONE_BIG_FOUR_SMALL_ELLIPSOIDS = (double x, double y, double z, float chamberRadius, RandomSource random, Consumer consumer) -> { float halfR = chamberRadius / 2; float quarterR = halfR / 2; float p = chamberRadius / 3.8f; // radius / 4.25f @@ -54,7 +41,7 @@ public interface ChambersGenerator { consumer.accept(new MoundChamber(new OctantEllipsoidShape(x - p, y - p, z - p, quarterR, quarterR, quarterR, halfR, halfR, halfR))); }; - ChambersGenerator FOUR_SMALL_ONE_BIG_ELLIPSOIDS = (double x, double y, double z, float chamberRadius, Consumer consumer) -> { + ChamberFactory FOUR_SMALL_ONE_BIG_ELLIPSOIDS = (double x, double y, double z, float chamberRadius, RandomSource random, Consumer consumer) -> { float halfR = chamberRadius / 2; float quarterR = halfR / 2; float p = chamberRadius / 3.8f; // radius / 4.25f @@ -67,7 +54,7 @@ public interface ChambersGenerator { consumer.accept(new MoundChamber(new OctantEllipsoidShape(x, y - p, z, halfR, quarterR, halfR, halfR, halfR, halfR))); }; - ChambersGenerator TWO_BIG_ELLIPSOIDS = (double x, double y, double z, float chamberRadius, Consumer consumer) -> { + ChamberFactory TWO_BIG_ELLIPSOIDS = (double x, double y, double z, float chamberRadius, RandomSource random, Consumer consumer) -> { float halfR = chamberRadius / 2; float quarterR = halfR / 2; float p = chamberRadius / 3.8f; // radius / 4.25f @@ -76,7 +63,25 @@ public interface ChambersGenerator { consumer.accept(new MoundChamber(new OctantEllipsoidShape(x, y - p, z, halfR, quarterR, halfR, halfR, halfR, halfR))); }; - SimpleWeightedRandomList RANDOM_DEFAULT = SimpleWeightedRandomList.builder() + ChamberFactory SPECIAL_CRADLE = (double x, double y, double z, float chamberRadius, RandomSource random, Consumer consumer) -> { + float halfR = chamberRadius / 2; + float quarterR = halfR / 2; + float p = chamberRadius / 3.8f; // radius / 4.25f + + consumer.accept(new MoundChamber(new OctantEllipsoidShape(x, y + p - 1, z, halfR, halfR, halfR, halfR, quarterR, halfR))); + + Consumer wrappedConsumer = chamber -> { + chamber.setDecorator(ChamberDecorators.PRIMAL_ORIFICE_COMBS, random.nextInt()); + consumer.accept(chamber); + }; + + wrappedConsumer.accept(new MoundChamber(new OctantEllipsoidShape(x + p, y - p, z + p, halfR, quarterR, halfR, quarterR, halfR, quarterR))); + wrappedConsumer.accept(new MoundChamber(new OctantEllipsoidShape(x - p, y - p, z + p, quarterR, quarterR, halfR, halfR, halfR, quarterR))); + wrappedConsumer.accept(new MoundChamber(new OctantEllipsoidShape(x + p, y - p, z - p, halfR, quarterR, quarterR, quarterR, halfR, halfR))); + wrappedConsumer.accept(new MoundChamber(new OctantEllipsoidShape(x - p, y - p, z - p, quarterR, quarterR, quarterR, halfR, halfR, halfR))); + }; + + SimpleWeightedRandomList RANDOM_DEFAULTS_LIST = SimpleWeightedRandomList.builder() .add(ONE_SPHERE, 5) .add(ONE_BIG_FOUR_SMALL_ELLIPSOIDS, 20) .add(FOUR_SMALL_ONE_BIG_ELLIPSOIDS, 20) @@ -84,4 +89,15 @@ public interface ChambersGenerator { .add(EIGHT_SMALL_ELLIPSOIDS, 60) .build(); + ChamberFactory RANDOM_DEFAULT = (double x, double y, double z, float chamberRadius, RandomSource random, Consumer consumer) -> { + ChamberFactory factory = RANDOM_DEFAULTS_LIST.getRandomValue(random).orElse(ChamberFactory.EIGHT_SMALL_ELLIPSOIDS); + + Consumer wrappedConsumer = chamber -> { + chamber.setDecorator(ChamberDecorators.getRandomDefault(random), random.nextInt()); + consumer.accept(chamber); + }; + + factory.create(x, y, z, chamberRadius, random, wrappedConsumer); + }; + } diff --git a/src/main/java/com/github/elenterius/biomancy/world/mound/ChamberFactoryType.java b/src/main/java/com/github/elenterius/biomancy/world/mound/ChamberFactoryType.java new file mode 100644 index 000000000..fb5ee61ff --- /dev/null +++ b/src/main/java/com/github/elenterius/biomancy/world/mound/ChamberFactoryType.java @@ -0,0 +1,5 @@ +package com.github.elenterius.biomancy.world.mound; + +enum ChamberFactoryType { + DEFAULT, CRADLE, END_CAP_MAIN_SPIRE, END_CAP_SUB_SPIRE +} diff --git a/src/main/java/com/github/elenterius/biomancy/world/mound/MoundChamber.java b/src/main/java/com/github/elenterius/biomancy/world/mound/MoundChamber.java index f14f0490f..4cbb9ba04 100644 --- a/src/main/java/com/github/elenterius/biomancy/world/mound/MoundChamber.java +++ b/src/main/java/com/github/elenterius/biomancy/world/mound/MoundChamber.java @@ -1,13 +1,17 @@ package com.github.elenterius.biomancy.world.mound; import com.github.elenterius.biomancy.util.serialization.NBTSerializer; +import com.github.elenterius.biomancy.world.mound.decorator.ChamberDecorator; +import com.github.elenterius.biomancy.world.mound.decorator.ChamberDecorators; import com.github.elenterius.biomancy.world.spatial.geometry.Shape; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; -public class MoundChamber implements Chamber, Shape { +public class MoundChamber implements Chamber { private final Shape shape; private final Vec3 origin; + private int seed = 1337; + private ChamberDecorator chamberDecorator = ChamberDecorators.EMPTY; public MoundChamber(Vec3 origin, Shape shape) { this.shape = shape; @@ -16,7 +20,7 @@ public MoundChamber(Vec3 origin, Shape shape) { public MoundChamber(Shape shape) { this.shape = shape; - origin = shape.getCenter(); + origin = shape.center(); } @Override @@ -30,12 +34,27 @@ public boolean intersectsCuboid(double minX, double minY, double minZ, double ma } @Override - public Vec3 getCenter() { - return shape.getCenter(); + public Vec3 center() { + return shape.center(); } @Override - public Vec3 getOrigin() { + public int seed() { + return seed; + } + + public void setDecorator(ChamberDecorator chamberDecorator, int seed) { + this.chamberDecorator = chamberDecorator; + this.seed = seed; + } + + @Override + public ChamberDecorator getDecorator() { + return chamberDecorator; + } + + @Override + public Vec3 origin() { return origin; } diff --git a/src/main/java/com/github/elenterius/biomancy/world/mound/MoundGenerator.java b/src/main/java/com/github/elenterius/biomancy/world/mound/MoundGenerator.java index 6aab39aaf..e79c83836 100644 --- a/src/main/java/com/github/elenterius/biomancy/world/mound/MoundGenerator.java +++ b/src/main/java/com/github/elenterius/biomancy/world/mound/MoundGenerator.java @@ -99,7 +99,7 @@ private static void genSpire(double x, double y, double z, float maxHeight, floa float prevRadius = baseRadius + context.relativeWallThickness; float totalHeight = 0; - genSphereWithChambers(x, y, z, prevRadius, prevRadius - context.relativeWallThickness / 2f, context, isMainSpire ? ChamberType.CRADLE : ChamberType.DEFAULT); + genSphereWithChambers(x, y, z, prevRadius, prevRadius - context.relativeWallThickness / 2f, context, isMainSpire ? ChamberFactoryType.CRADLE : ChamberFactoryType.DEFAULT); while (totalHeight < maxHeight) { float t = totalHeight / maxHeight; @@ -122,7 +122,7 @@ private static void genSpire(double x, double y, double z, float maxHeight, floa leanZ = prevLean.z - leanOffset.z; } - genSphereWithChambers(x + leanX, y + height, z + leanZ, radius, radius - context.relativeWallThickness / 2f, context, ChamberType.DEFAULT); + genSphereWithChambers(x + leanX, y + height, z + leanZ, radius, radius - context.relativeWallThickness / 2f, context, ChamberFactoryType.DEFAULT); prevLean.set(leanX, 0, leanZ); prevRadius = radius; @@ -130,11 +130,11 @@ private static void genSpire(double x, double y, double z, float maxHeight, floa } //end cap shape - ChamberType chamberType = isMainSpire ? ChamberType.END_CAP_MAIN_SPIRE : ChamberType.END_CAP_SUB_SPIRE; + ChamberFactoryType chamberType = isMainSpire ? ChamberFactoryType.END_CAP_MAIN_SPIRE : ChamberFactoryType.END_CAP_SUB_SPIRE; genSphereWithChambers(x + prevLean.x, y + totalHeight + (prevRadius / 2) * 1.5f, z + prevLean.z, prevRadius / 2f, prevRadius / 2f, context, chamberType); } - private static void genSphereWithChambers(double x, double y, double z, float radius, float chamberRadius, Context context, ChamberType type) { + private static void genSphereWithChambers(double x, double y, double z, float radius, float chamberRadius, Context context, ChamberFactoryType type) { Vec3 pos = new Vec3(x, y, z); context.boundingShapes.add(new SphereShape(pos, radius)); @@ -144,16 +144,15 @@ private static void genSphereWithChambers(double x, double y, double z, float ra } switch (type) { - case DEFAULT -> ChambersGenerator.RANDOM_DEFAULT.getRandomValue(context.random).orElse(ChambersGenerator.EIGHT_SMALL_ELLIPSOIDS) - .generate(x, y, z, chamberRadius, context.chambers::add); - case CRADLE -> ChambersGenerator.SPECIAL_CRADLE.generate(x, y, z, chamberRadius, context.chambers::add); + case DEFAULT -> ChamberFactory.RANDOM_DEFAULT.create(x, y, z, chamberRadius, context.random, context.chambers::add); + case CRADLE -> ChamberFactory.SPECIAL_CRADLE.create(x, y, z, chamberRadius, context.random, context.chambers::add); case END_CAP_MAIN_SPIRE -> { - ChambersGenerator generator = context.random.nextFloat() < 0.8f ? ChambersGenerator.ONE_SPHERE : ChambersGenerator.ONE_BIG_FOUR_SMALL_ELLIPSOIDS; - generator.generate(x, y, z, chamberRadius, context.chambers::add); + ChamberFactory generator = context.random.nextFloat() < 0.8f ? ChamberFactory.ONE_SPHERE : ChamberFactory.ONE_BIG_FOUR_SMALL_ELLIPSOIDS; + generator.create(x, y, z, chamberRadius, context.random, context.chambers::add); } case END_CAP_SUB_SPIRE -> { - ChambersGenerator generator = context.random.nextFloat() < 0.8f ? ChambersGenerator.ONE_BIG_FOUR_SMALL_ELLIPSOIDS : ChambersGenerator.ONE_SPHERE; - generator.generate(x, y, z, chamberRadius, context.chambers::add); + ChamberFactory generator = context.random.nextFloat() < 0.8f ? ChamberFactory.ONE_BIG_FOUR_SMALL_ELLIPSOIDS : ChamberFactory.ONE_SPHERE; + generator.create(x, y, z, chamberRadius, context.random, context.chambers::add); } } } @@ -172,10 +171,6 @@ private static float easeOutQuad(float x) { return 1f - (1f - x) * (1f - x); } - private enum ChamberType { - DEFAULT, CRADLE, END_CAP_MAIN_SPIRE, END_CAP_SUB_SPIRE - } - private static class Context { List boundingShapes = new ArrayList<>(); List chambers = new ArrayList<>(); diff --git a/src/main/java/com/github/elenterius/biomancy/world/mound/MoundShape.java b/src/main/java/com/github/elenterius/biomancy/world/mound/MoundShape.java index 5962213fa..aba92026e 100644 --- a/src/main/java/com/github/elenterius/biomancy/world/mound/MoundShape.java +++ b/src/main/java/com/github/elenterius/biomancy/world/mound/MoundShape.java @@ -44,7 +44,7 @@ public boolean intersectsCuboid(double minX, double minY, double minZ, double ma } @Override - public Vec3 getCenter() { + public Vec3 center() { return boundingShapes.getCenter(); } @@ -59,12 +59,22 @@ public AABB getAABB() { } @Nullable - public MoundChamber getChamberAt(int x, int y, int z) { + public MoundChamber getChamberAt(BlockPos pos) { + return chamberShapes.getClosestShapeContaining(pos.getX() + 0.5d, pos.getY() + 0.5d, pos.getZ() + 0.5d); + } + + @Nullable + public MoundChamber getChamberAt(double x, double y, double z) { return chamberShapes.getClosestShapeContaining(x, y, z); } @Nullable - public Shape getBoundingShapeAt(int x, int y, int z) { + public Shape getBoundingShapeAt(BlockPos pos) { + return boundingShapes.getClosestShapeContaining(pos.getX() + 0.5d, pos.getY() + 0.5d, pos.getZ() + 0.5d); + } + + @Nullable + public Shape getBoundingShapeAt(double x, double y, double z) { return boundingShapes.getClosestShapeContaining(x, y, z); } diff --git a/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/ChamberDecorator.java b/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/ChamberDecorator.java new file mode 100644 index 000000000..9ff018fcb --- /dev/null +++ b/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/ChamberDecorator.java @@ -0,0 +1,53 @@ +package com.github.elenterius.biomancy.world.mound.decorator; + +import com.github.elenterius.biomancy.world.mound.Chamber; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; + +public interface ChamberDecorator { + PartOfDecorationResult isBlockPartOfDecoration(Chamber chamber, Level level, BlockPos pos, BlockState state); + + boolean canPlace(Chamber chamber, Level level, BlockPos pos, Direction axisDirection); + + boolean place(Chamber chamber, Level level, BlockPos pos, Direction axisDirection); + + public enum PartOfDecorationResult { + POSITION_AND_MATERIAL_ARE_VALID(true, true), + POSITION_AND_MATERIAL_ARE_INVALID(false, false), + POSITION_IS_VALID_AND_MATERIAL_IS_INVALID(true, false), + POSITION_IS_INVALID_AND_MATERIAL_IS_VALID(false, true); + + static final PartOfDecorationResult[] sortedValues; + + static { + sortedValues = new PartOfDecorationResult[values().length]; + for (PartOfDecorationResult result : values()) { + int index = getIndex(result.positionIsValid, result.materialIsValid); + sortedValues[index] = result; + } + } + + public final boolean positionIsValid; + public final boolean materialIsValid; + + PartOfDecorationResult(boolean positionIsValid, boolean materialIsValid) { + this.positionIsValid = positionIsValid; + this.materialIsValid = materialIsValid; + } + + public int index() { + return getIndex(positionIsValid, materialIsValid); + } + + public static PartOfDecorationResult of(boolean positionValid, boolean materialValid) { + int index = getIndex(positionValid, materialValid); + return sortedValues[index]; + } + + private static int getIndex(boolean positionValid, boolean materialValid) { + return (positionValid ? 1 : 0) << 1 | (materialValid ? 1 : 0); + } + } +} diff --git a/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/ChamberDecorators.java b/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/ChamberDecorators.java new file mode 100644 index 000000000..97d15d681 --- /dev/null +++ b/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/ChamberDecorators.java @@ -0,0 +1,52 @@ +package com.github.elenterius.biomancy.world.mound.decorator; + +import com.github.elenterius.biomancy.init.ModBlocks; +import com.github.elenterius.biomancy.world.mound.Chamber; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.util.random.SimpleWeightedRandomList; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; + +public final class ChamberDecorators { + + public static final ChamberDecorator EMPTY = new ChamberDecorator() { + @Override + public PartOfDecorationResult isBlockPartOfDecoration(Chamber chamber, Level level, BlockPos pos, BlockState state) { + //consider everything as a decoration except full primal and malignant flesh blocks + //this allows all blocks placed inside the room to survive and not be destroyed + boolean materialValid = !state.is(ModBlocks.PRIMAL_FLESH.get()) && !state.is(ModBlocks.MALIGNANT_FLESH.get()); + return PartOfDecorationResult.of(true, materialValid); + } + + @Override + public boolean canPlace(Chamber chamber, Level level, BlockPos pos, Direction axisDirection) { + return false; + } + + @Override + public boolean place(Chamber chamber, Level level, BlockPos pos, Direction axisDirection) { + return false; + } + }; + + public static final ChamberDecorator PRIMAL_FLESH_PILLARS = new PillarsDecorator(ModBlocks.PRIMAL_FLESH.get().defaultBlockState()); + public static final ChamberDecorator MALIGNANT_FLESH_PILLARS = new PillarsDecorator(ModBlocks.MALIGNANT_FLESH.get().defaultBlockState()); + public static final ChamberDecorator PRIMAL_ORIFICE_PILLARS = new PillarsDecorator(ModBlocks.PRIMAL_ORIFICE.get().defaultBlockState()); + public static final ChamberDecorator PRIMAL_ORIFICE_COMBS = new HangingCombsDecorator(ModBlocks.PRIMAL_ORIFICE.get().defaultBlockState()); + + public static final SimpleWeightedRandomList RANDOM_DEFAULTS = SimpleWeightedRandomList.builder() + .add(EMPTY, 10) + .add(PRIMAL_FLESH_PILLARS, 30) + .add(MALIGNANT_FLESH_PILLARS, 15) + .add(PRIMAL_ORIFICE_PILLARS, 5) + .build(); + + private ChamberDecorators() {} + + public static ChamberDecorator getRandomDefault(RandomSource random) { + return RANDOM_DEFAULTS.getRandomValue(random).orElse(PRIMAL_FLESH_PILLARS); + } + +} diff --git a/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/ChamberSpecialDecorator.java b/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/ChamberSpecialDecorator.java new file mode 100644 index 000000000..038736eab --- /dev/null +++ b/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/ChamberSpecialDecorator.java @@ -0,0 +1,31 @@ +package com.github.elenterius.biomancy.world.mound.decorator; + +import com.github.elenterius.biomancy.init.ModBlocks; +import com.github.elenterius.biomancy.util.LevelUtil; +import com.github.elenterius.biomancy.world.PrimordialEcosystem; +import com.github.elenterius.biomancy.world.mound.Chamber; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +public interface ChamberSpecialDecorator { + + boolean canDecorate(Chamber chamber, Level level, BlockPos pos, Direction axisDirection, BlockPos closeOffsetPos, BlockState closeOffsetState, BlockPos farOffsetPos, BlockState farOffsetState); + + boolean decorate(Chamber chamber, Level level, BlockPos pos, Direction axisDirection, BlockPos closeOffsetPos, BlockState closeOffsetState, BlockPos farOffsetPos, BlockState farOffsetState); + + ChamberSpecialDecorator BLOOMLIGHT = new ChamberSpecialDecorator() { + @Override + public boolean canDecorate(Chamber chamber, Level level, BlockPos pos, Direction axisDirection, BlockPos closeOffsetPos, BlockState closeOffsetState, BlockPos farOffsetPos, BlockState farOffsetState) { + boolean isFleshBlock = PrimordialEcosystem.FULL_FLESH_BLOCKS.contains(farOffsetState.getBlock()); + return isFleshBlock && axisDirection != Direction.DOWN && LevelUtil.getMaxBrightness(level, pos) < 5; + } + + @Override + public boolean decorate(Chamber chamber, Level level, BlockPos pos, Direction axisDirection, BlockPos closeOffsetPos, BlockState closeOffsetState, BlockPos farOffsetPos, BlockState farOffsetState) { + return level.setBlock(closeOffsetPos, ModBlocks.BLOOMLIGHT.get().defaultBlockState(), Block.UPDATE_CLIENTS); + } + }; +} diff --git a/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/HangingCombsDecorator.java b/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/HangingCombsDecorator.java new file mode 100644 index 000000000..7eb783a90 --- /dev/null +++ b/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/HangingCombsDecorator.java @@ -0,0 +1,73 @@ +package com.github.elenterius.biomancy.world.mound.decorator; + +import com.github.elenterius.biomancy.util.random.FastNoiseLite; +import com.github.elenterius.biomancy.world.mound.Chamber; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +public class HangingCombsDecorator implements ChamberDecorator { + + private final FastNoiseLite simplexNoise; + private final BlockState material; + + public HangingCombsDecorator(BlockState material) { + this.material = material; + this.simplexNoise = initNoise(); + } + + protected FastNoiseLite initNoise() { + FastNoiseLite noise = new FastNoiseLite(); + noise.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2S); + noise.SetFrequency(0.1f); + return noise; + } + + @Override + public PartOfDecorationResult isBlockPartOfDecoration(Chamber chamber, Level level, BlockPos pos, BlockState state) { + return PartOfDecorationResult.of(isPosInsideAnyComb(chamber, pos), state == material); + } + + @Override + public boolean canPlace(Chamber chamber, Level level, BlockPos pos, Direction axisDirection) { + return isPosInsideAnyComb(chamber, pos); + } + + @Override + public boolean place(Chamber chamber, Level level, BlockPos pos, Direction axisDirection) { + return level.setBlock(pos, material, Block.UPDATE_CLIENTS); + } + + protected float combThreshold(float y) { + return 0.5f + easeInExpo(1 - y) * 10; + } + + protected boolean isPosInsideAnyComb(Chamber chamber, BlockPos pos) { + Vec3 center = chamber.center(); + float x = (float) (center.x - (pos.getX() + 0.5d)); + float z = (float) (center.z - (pos.getZ() + 0.5d)); + + AABB aabb = chamber.getAABB(); + float yNormalized = normalize(pos.getY(), aabb.minY, aabb.maxY); + float threshold = combThreshold(yNormalized); + + simplexNoise.SetSeed(chamber.seed()); + + return simplexNoise.GetNoise(x, 0, z) >= threshold; + } + + private static float normalize(double value, double min, double max) { + float rescaled = (float) ((value - min) / (max - min)); //min-max rescale + return Mth.clamp(rescaled, 0f, 1f); + } + + private static float easeInExpo(float x) { + if (x == 0) return 0; + return (float) Math.pow(2, 10 * x - 10); + } +} diff --git a/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/PillarsDecorator.java b/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/PillarsDecorator.java new file mode 100644 index 000000000..43eb48e6d --- /dev/null +++ b/src/main/java/com/github/elenterius/biomancy/world/mound/decorator/PillarsDecorator.java @@ -0,0 +1,69 @@ +package com.github.elenterius.biomancy.world.mound.decorator; + +import com.github.elenterius.biomancy.util.random.FastNoiseLite; +import com.github.elenterius.biomancy.world.mound.Chamber; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +public class PillarsDecorator implements ChamberDecorator { + + private final FastNoiseLite simplexNoise; + private final BlockState material; + + public PillarsDecorator(BlockState material) { + this.material = material; + this.simplexNoise = initNoise(); + } + + protected FastNoiseLite initNoise() { + FastNoiseLite noise = new FastNoiseLite(); + noise.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2S); + noise.SetFrequency(0.1f); + return noise; + } + + @Override + public PartOfDecorationResult isBlockPartOfDecoration(Chamber chamber, Level level, BlockPos pos, BlockState state) { + return PartOfDecorationResult.of(isPosInsideAnyPillar(chamber, pos), state == material); + } + + @Override + public boolean canPlace(Chamber chamber, Level level, BlockPos pos, Direction axisDirection) { + return isPosInsideAnyPillar(chamber, pos); + } + + @Override + public boolean place(Chamber chamber, Level level, BlockPos pos, Direction axisDirection) { + return level.setBlock(pos, material, Block.UPDATE_CLIENTS); + } + + protected float pillarThreshold(float y) { + float fx = (((y - 0.5f) * (y - 0.5f)) * 4f) * -1f + 1f; + return 0.42f + fx * 0.2f; + } + + protected boolean isPosInsideAnyPillar(Chamber chamber, BlockPos pos) { + Vec3 center = chamber.center(); + float x = (float) (center.x - (pos.getX() + 0.5d)); + float z = (float) (center.z - (pos.getZ() + 0.5d)); + + AABB aabb = chamber.getAABB(); + float yNormalized = normalize(pos.getY(), aabb.minY, aabb.maxY); + float threshold = pillarThreshold(yNormalized); + + simplexNoise.SetSeed(chamber.seed()); + + return simplexNoise.GetNoise(x, 0, z) >= threshold; + } + + private static float normalize(double value, double min, double max) { + float rescaled = (float) ((value - min) / (max - min)); //min-max rescale + return Mth.clamp(rescaled, 0f, 1f); + } +} diff --git a/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/CuboidShape.java b/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/CuboidShape.java index 64b012f88..6e4b1131e 100644 --- a/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/CuboidShape.java +++ b/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/CuboidShape.java @@ -36,7 +36,7 @@ public boolean contains(double x, double y, double z) { } @Override - public Vec3 getCenter() { + public Vec3 center() { return origin; } diff --git a/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/OctantEllipsoidShape.java b/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/OctantEllipsoidShape.java index 5bd1e49fc..01e58212e 100644 --- a/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/OctantEllipsoidShape.java +++ b/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/OctantEllipsoidShape.java @@ -63,7 +63,7 @@ public boolean intersectsCuboid(double minX, double minY, double minZ, double ma } @Override - public Vec3 getCenter() { + public Vec3 center() { return center; } diff --git a/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/Shape.java b/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/Shape.java index 7eb9adedc..e45cf9811 100644 --- a/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/Shape.java +++ b/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/Shape.java @@ -14,7 +14,7 @@ public interface Shape extends NBTSerializable { boolean intersectsCuboid(double minX, double minY, double minZ, double maxX, double maxY, double maxZ); - Vec3 getCenter(); + Vec3 center(); double distanceToSqr(double x, double y, double z); @@ -38,7 +38,7 @@ public boolean intersectsCuboid(double minX, double minY, double minZ, double ma } @Override - public Vec3 getCenter() { + public Vec3 center() { return Vec3.ZERO; } diff --git a/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/SphereShape.java b/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/SphereShape.java index 2efa0a3ef..9913efdce 100644 --- a/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/SphereShape.java +++ b/src/main/java/com/github/elenterius/biomancy/world/spatial/geometry/SphereShape.java @@ -35,7 +35,7 @@ public boolean intersectsCuboid(double minX, double minY, double minZ, double ma } @Override - public Vec3 getCenter() { + public Vec3 center() { return origin; }