mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
Grow speleothems from decorators and keep them supported
Dripstone and sulfur spikes are placed as oriented, thickness-graded columns by the surface and ceiling decorators through a shared IrisSpeleothems helper, replacing the ad hoc dripstone fixups, and the stacking path is unified for both directions. The perfection modifier renormalizes a spike whose support a pool replaced, dropping the unsupported run and re-tipping what remains. Surface sturdiness is asked per face now, so a downward spike tests the ceiling it hangs from.
This commit is contained in:
+3
-3
@@ -33,7 +33,7 @@ import net.minecraft.world.level.EmptyBlockGetter;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.LeavesBlock;
|
||||
import net.minecraft.world.level.block.PointedDripstoneBlock;
|
||||
import net.minecraft.world.level.block.SpeleothemBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.SpeleothemThickness;
|
||||
@@ -476,8 +476,8 @@ public final class ModdedBlockResolution {
|
||||
|
||||
public static boolean isUpdatable(BlockState state) {
|
||||
return isStorage(state)
|
||||
|| (state.getBlock() instanceof PointedDripstoneBlock
|
||||
&& state.getValue(PointedDripstoneBlock.THICKNESS) == SpeleothemThickness.TIP);
|
||||
|| (state.getBlock() instanceof SpeleothemBlock
|
||||
&& state.getValue(SpeleothemBlock.THICKNESS) == SpeleothemThickness.TIP);
|
||||
}
|
||||
|
||||
public static boolean isFoliage(BlockState state) {
|
||||
|
||||
+2
-2
@@ -95,8 +95,8 @@ public final class ModdedDecoratorHooks implements DecoratorPlatformHooks.FaceFi
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGoOn(PlatformBlockState surface) {
|
||||
return isFaceSturdy((BlockState) surface.nativeHandle(), Direction.UP);
|
||||
public boolean canGoOn(PlatformBlockState surface, boolean upward) {
|
||||
return isFaceSturdy((BlockState) surface.nativeHandle(), upward ? Direction.UP : Direction.DOWN);
|
||||
}
|
||||
|
||||
private static boolean isFaceSturdy(BlockState state, Direction face) {
|
||||
|
||||
+43
@@ -1,8 +1,16 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.engine.decorator.DecoratorPlatformHooks;
|
||||
import art.arcane.iris.engine.decorator.IrisSpeleothems;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.SpeleothemBlock;
|
||||
import net.minecraft.world.level.block.state.properties.SpeleothemThickness;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -60,4 +68,39 @@ public class ModdedBlockResolutionContractTest {
|
||||
assertFalse(ModdedBlockResolution.canPlaceOnto(Blocks.CACTUS, Blocks.STONE));
|
||||
assertTrue(ModdedBlockResolution.isDecorant(Blocks.CACTUS.defaultBlockState()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sulfurAndDripstoneTipsReceivePostLoadUpdates() {
|
||||
for (Block block : new Block[]{Blocks.SULFUR_SPIKE, Blocks.POINTED_DRIPSTONE}) {
|
||||
for (SpeleothemThickness thickness : SpeleothemThickness.values()) {
|
||||
assertEquals(thickness == SpeleothemThickness.TIP,
|
||||
ModdedBlockResolution.isUpdatable(block.defaultBlockState()
|
||||
.setValue(SpeleothemBlock.THICKNESS, thickness)));
|
||||
}
|
||||
}
|
||||
assertFalse(ModdedBlockResolution.isUpdatable(Blocks.STONE.defaultBlockState()));
|
||||
}
|
||||
@Test
|
||||
public void nativeSpikesUseModdedSupportFaces() {
|
||||
ModdedDecoratorHooks hooks = new ModdedDecoratorHooks();
|
||||
DecoratorPlatformHooks.Bindings previous = DecoratorPlatformHooks.bind(hooks, hooks);
|
||||
try {
|
||||
for (String material : new String[]{"minecraft:sulfur_spike", "minecraft:pointed_dripstone"}) {
|
||||
for (boolean upward : new boolean[]{true, false}) {
|
||||
Hunk<PlatformBlockState> output = Hunk.newArrayHunk(1, 3, 1);
|
||||
PlatformBlockState spike = ModdedBlockResolution.get(material
|
||||
+ "[vertical_direction=" + (upward ? "up" : "down") + ",thickness=tip]");
|
||||
int supportY = upward ? 0 : 2;
|
||||
output.set(0, 1, 0, spike);
|
||||
output.set(0, supportY, 0, ModdedBlockResolution.get("minecraft:stone_slab[type=top]"));
|
||||
assertEquals(upward, IrisSpeleothems.isSupported(spike, output, 0, 0, 1));
|
||||
output.set(0, supportY, 0, ModdedBlockResolution.get("minecraft:stone_slab[type=bottom]"));
|
||||
assertEquals(!upward, IrisSpeleothems.isSupported(spike, output, 0, 0, 1));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
DecoratorPlatformHooks.restore(previous);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ final class DecoratorCore {
|
||||
PlatformBlockState bdx = data.get(x, height, z);
|
||||
PlatformBlockState bd = decorator.pickBlockData(rng, irisData, realX, realZ);
|
||||
|
||||
if (!underwater && !canGoOn(bd, bdx)
|
||||
if (!IrisSpeleothems.isSpike(bd) && !underwater && !canGoOn(bd, bdx)
|
||||
&& !decorator.isForcePlace() && decorator.getForceBlock() == null) {
|
||||
return;
|
||||
}
|
||||
@@ -137,6 +137,11 @@ final class DecoratorCore {
|
||||
}
|
||||
}
|
||||
|
||||
if (IrisSpeleothems.isSpike(bd)) {
|
||||
placeSingleSpike(bd, data, x, z, height + 1, true, underwater && !caveSkipFluid);
|
||||
return;
|
||||
}
|
||||
|
||||
String half = bd == null ? null : IrisProceduralBlocks.propertyValue(bd, "half");
|
||||
if (half != null) {
|
||||
int lowerY = height + 1;
|
||||
@@ -165,8 +170,11 @@ final class DecoratorCore {
|
||||
}
|
||||
|
||||
private static boolean matchesPalette(PlatformBlockState[] palette, PlatformBlockState surface) {
|
||||
if (surface == null) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < palette.length; i++) {
|
||||
if (palette[i].equals(surface)) {
|
||||
if (palette[i] == surface || surface.matches(palette[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -181,6 +189,12 @@ final class DecoratorCore {
|
||||
if (bd == null) {
|
||||
return;
|
||||
}
|
||||
if (IrisSpeleothems.isSpike(bd)) {
|
||||
if (height + 1 < data.getHeight() && allowsSurface(decorator, data.get(x, height + 1, z), irisData)) {
|
||||
placeSingleSpike(bd, data, x, z, height, false, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (applyFixFaces) {
|
||||
bd = fixFacesForHunk(bd, data, x, z, realX, height, realZ, mantle);
|
||||
}
|
||||
@@ -194,72 +208,48 @@ final class DecoratorCore {
|
||||
return;
|
||||
}
|
||||
|
||||
int effectiveMax = max;
|
||||
if (opts.underwater && height < opts.fluidHeight) {
|
||||
effectiveMax = opts.fluidHeight;
|
||||
}
|
||||
|
||||
int stack = computeStack(decorator, rng, realX, realZ, irisData, effectiveMax);
|
||||
|
||||
if (stack == 1) {
|
||||
int targetY = height + 1;
|
||||
if (targetY >= data.getHeight()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformBlockState existing = data.get(x, targetY, z);
|
||||
if (!canReplaceStackTarget(existing, opts.underwater)
|
||||
|| (opts.caveSkipFluid && B.isFluid(existing))) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformBlockState block = decorator.pickBlockDataTop(rng, irisData, realX, realZ);
|
||||
if (block == null || (!opts.underwater && !canGoOn(block, data.get(x, height, z)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
block = stackedVineBlock(block, stack, 0);
|
||||
data.set(x, targetY, z, block);
|
||||
PlatformBlockState support = data.get(x, height, z);
|
||||
if (!allowsSurface(decorator, support, irisData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformBlockState bdx = data.get(x, height, z);
|
||||
|
||||
int effectiveMax = opts.underwater ? Math.min(max, opts.fluidHeight - height) : max;
|
||||
int stack = computeStack(decorator, rng, realX, realZ, irisData, effectiveMax);
|
||||
int placed = 0;
|
||||
boolean hasSpikes = false;
|
||||
for (int i = 0; i < stack; i++) {
|
||||
int h = height + i;
|
||||
double threshold = ((double) i) / (stack - 1);
|
||||
PlatformBlockState bd = threshold >= decorator.getTopThreshold()
|
||||
? decorator.pickBlockDataTop(rng, irisData, realX, realZ)
|
||||
: decorator.pickBlockData(rng, irisData, realX, realZ);
|
||||
|
||||
if (bd == null) {
|
||||
int y = height + 1 + i;
|
||||
if (y >= data.getHeight()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == 0 && !opts.underwater && !canGoOn(bd, bdx)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (opts.underwater && height + 1 + i > opts.fluidHeight) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (height + 1 + i >= data.getHeight()) {
|
||||
break;
|
||||
}
|
||||
|
||||
PlatformBlockState existing = data.get(x, height + 1 + i, z);
|
||||
PlatformBlockState existing = data.get(x, y, z);
|
||||
if (!canReplaceStackTarget(existing, opts.underwater)
|
||||
|| (opts.caveSkipFluid && B.isFluid(existing))) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (IrisProceduralBlocks.materialKey(bd).equals("minecraft:pointed_dripstone")) {
|
||||
bd = dripstoneBlock(stack, i, "up");
|
||||
double threshold = stack == 1 ? 1.0 : ((double) i) / (stack - 1);
|
||||
PlatformBlockState block = threshold >= decorator.getTopThreshold()
|
||||
? decorator.pickBlockDataTop(rng, irisData, realX, realZ)
|
||||
: decorator.pickBlockData(rng, irisData, realX, realZ);
|
||||
if (block == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
bd = stackedVineBlock(bd, stack, i);
|
||||
data.set(x, height + 1 + i, z, bd);
|
||||
if (IrisSpeleothems.isSpike(block)) {
|
||||
if (!IrisSpeleothems.canPlace(block, data, x, z, y, true, opts.underwater)) {
|
||||
break;
|
||||
}
|
||||
block = IrisSpeleothems.orient(block, existing, true);
|
||||
hasSpikes = true;
|
||||
} else if (i == 0 && !opts.underwater && !canGoOn(block, support)) {
|
||||
break;
|
||||
}
|
||||
data.set(x, y, z, stackedVineBlock(block, stack, i));
|
||||
placed++;
|
||||
}
|
||||
if (placed > 0 && placed < stack) {
|
||||
finishVineTip(data, x, z, height + placed);
|
||||
}
|
||||
if (hasSpikes) {
|
||||
IrisSpeleothems.finishColumn(data, x, z, height + 1, placed, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,46 +259,46 @@ final class DecoratorCore {
|
||||
if (height < 0 || height >= data.getHeight()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int stack = computeStack(decorator, rng, realX, realZ, irisData, max);
|
||||
|
||||
if (stack == 1) {
|
||||
if (opts.caveSkipFluid && B.isFluid(data.get(x, height, z))) {
|
||||
return;
|
||||
}
|
||||
PlatformBlockState block = decorator.pickBlockDataTop(rng, irisData, realX, realZ);
|
||||
if (block == null) {
|
||||
return;
|
||||
}
|
||||
block = stackedVineBlock(block, stack, 0);
|
||||
data.set(x, height, z, fixFacesForHunk(block, data, x, z, realX, height, realZ, mantle));
|
||||
PlatformBlockState support = height + 1 < data.getHeight() ? data.get(x, height + 1, z) : null;
|
||||
if (!allowsSurface(decorator, support, irisData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int stack = computeStack(decorator, rng, realX, realZ, irisData, max);
|
||||
int placed = 0;
|
||||
boolean hasSpikes = false;
|
||||
for (int i = 0; i < stack; i++) {
|
||||
int h = height - i;
|
||||
if (h < 0 || h < minHeight) {
|
||||
int y = height - i;
|
||||
if (y < 0 || y < minHeight) {
|
||||
break;
|
||||
}
|
||||
|
||||
double threshold = ((double) i) / (double) (stack - 1);
|
||||
PlatformBlockState bd = threshold >= decorator.getTopThreshold()
|
||||
PlatformBlockState existing = data.get(x, y, z);
|
||||
if (!canReplaceStackTarget(existing, opts.underwater)
|
||||
|| (opts.caveSkipFluid && B.isFluid(existing))) {
|
||||
break;
|
||||
}
|
||||
double threshold = stack == 1 ? 1.0 : ((double) i) / (stack - 1);
|
||||
PlatformBlockState block = threshold >= decorator.getTopThreshold()
|
||||
? decorator.pickBlockDataTop(rng, irisData, realX, realZ)
|
||||
: decorator.pickBlockData(rng, irisData, realX, realZ);
|
||||
|
||||
if (bd == null) {
|
||||
if (block == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (IrisProceduralBlocks.materialKey(bd).equals("minecraft:pointed_dripstone")) {
|
||||
bd = dripstoneBlock(stack, i, "down");
|
||||
if (IrisSpeleothems.isSpike(block)) {
|
||||
if (!IrisSpeleothems.canPlace(block, data, x, z, y, false, opts.underwater)) {
|
||||
break;
|
||||
}
|
||||
block = IrisSpeleothems.orient(block, existing, false);
|
||||
hasSpikes = true;
|
||||
}
|
||||
|
||||
bd = stackedVineBlock(bd, stack, i);
|
||||
if (opts.caveSkipFluid && B.isFluid(data.get(x, h, z))) {
|
||||
break;
|
||||
}
|
||||
data.set(x, h, z, fixFacesForHunk(bd, data, x, z, realX, h, realZ, mantle));
|
||||
block = stackedVineBlock(block, stack, i);
|
||||
data.set(x, y, z, fixFacesForHunk(block, data, x, z, realX, y, realZ, mantle));
|
||||
placed++;
|
||||
}
|
||||
if (placed > 0 && placed < stack) {
|
||||
finishVineTip(data, x, z, height - placed + 1);
|
||||
}
|
||||
if (hasSpikes) {
|
||||
IrisSpeleothems.finishColumn(data, x, z, height, placed, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +311,13 @@ final class DecoratorCore {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IrisSpeleothems.isSpike(bd)) {
|
||||
if (max > 1 && allowsSurface(decorator, data.get(xf, height, zf), irisData)) {
|
||||
placeSingleSpike(bd, data, xf, zf, height + 1, true, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String half = IrisProceduralBlocks.propertyValue(bd, "half");
|
||||
if (half != null) {
|
||||
int lowerY = height + 1;
|
||||
@@ -357,6 +354,7 @@ final class DecoratorCore {
|
||||
}
|
||||
|
||||
int placed = 0;
|
||||
boolean hasSpikes = false;
|
||||
for (int i = 0; i < stack; i++) {
|
||||
int h = height + 1 + i;
|
||||
if (h >= height + max || h >= data.getHeight()) {
|
||||
@@ -369,10 +367,24 @@ final class DecoratorCore {
|
||||
if (bd == null) {
|
||||
break;
|
||||
}
|
||||
if (IrisSpeleothems.isSpike(bd)) {
|
||||
if (!allowsSurface(decorator, data.get(xf, height, zf), irisData)
|
||||
|| !IrisSpeleothems.canPlace(bd, data, xf, zf, h, true, false)) {
|
||||
break;
|
||||
}
|
||||
bd = IrisSpeleothems.orient(bd, data.get(xf, h, zf), true);
|
||||
hasSpikes = true;
|
||||
}
|
||||
bd = stackedVineBlock(bd, stack, i);
|
||||
data.set(xf, h, zf, bd);
|
||||
placed++;
|
||||
}
|
||||
if (placed > 0 && placed < stack) {
|
||||
finishVineTip(data, xf, zf, height + placed);
|
||||
}
|
||||
if (hasSpikes) {
|
||||
IrisSpeleothems.finishColumn(data, xf, zf, height + 1, placed, true);
|
||||
}
|
||||
return placed;
|
||||
}
|
||||
|
||||
@@ -437,11 +449,7 @@ final class DecoratorCore {
|
||||
if (!B.canPlaceOnto(decorator, surface)) {
|
||||
return false;
|
||||
}
|
||||
DecoratorPlatformHooks.SurfaceSturdiness sturdiness = DecoratorPlatformHooks.surfaceSturdiness();
|
||||
if (sturdiness != null) {
|
||||
return sturdiness.canGoOn(surface);
|
||||
}
|
||||
return ((BlockData) surface.nativeHandle()).isFaceSturdy(BlockFace.UP, BlockSupport.FULL);
|
||||
return IrisSpeleothems.isSturdy(surface, true);
|
||||
}
|
||||
|
||||
static boolean isValidShorelineSupport(IrisDecorator decorator, PlatformBlockState decorant, PlatformBlockState surface) {
|
||||
@@ -477,37 +485,27 @@ final class DecoratorCore {
|
||||
return stack;
|
||||
}
|
||||
|
||||
private static volatile PlatformBlockState[] dripstoneUp;
|
||||
private static volatile PlatformBlockState[] dripstoneDown;
|
||||
|
||||
private static PlatformBlockState[] buildDripstoneArr(String direction) {
|
||||
String[] order = {"tip", "frustum", "base"};
|
||||
PlatformBlockState[] arr = new PlatformBlockState[3];
|
||||
for (int k = 0; k < 3; k++) {
|
||||
arr[k] = B.getState("minecraft:pointed_dripstone[thickness=" + order[k] + ",vertical_direction=" + direction + "]");
|
||||
}
|
||||
return arr;
|
||||
private static boolean allowsSurface(IrisDecorator decorator, PlatformBlockState surface, IrisData data) {
|
||||
return decorator.isForcePlace()
|
||||
|| (decorator.getWhitelist() == null || matchesPalette(decorator.getWhitelistArray(data), surface))
|
||||
&& (decorator.getBlacklist() == null || !matchesPalette(decorator.getBlacklistArray(data), surface));
|
||||
}
|
||||
|
||||
private static PlatformBlockState dripstoneBlock(int stack, int i, String direction) {
|
||||
int thIdx;
|
||||
if (i == stack - 1) {
|
||||
thIdx = 0;
|
||||
} else if (i == stack - 2) {
|
||||
thIdx = 1;
|
||||
} else {
|
||||
thIdx = 2;
|
||||
private static void placeSingleSpike(PlatformBlockState spike, Hunk<PlatformBlockState> data,
|
||||
int x, int z, int y, boolean upward, boolean allowWater) {
|
||||
if (!IrisSpeleothems.canPlace(spike, data, x, z, y, upward, allowWater)) {
|
||||
return;
|
||||
}
|
||||
if (direction.equals("up")) {
|
||||
if (dripstoneUp == null) {
|
||||
dripstoneUp = buildDripstoneArr("up");
|
||||
}
|
||||
return dripstoneUp[thIdx];
|
||||
data.set(x, y, z, IrisSpeleothems.orient(spike, data.get(x, y, z), upward));
|
||||
IrisSpeleothems.finishColumn(data, x, z, y, 1, upward);
|
||||
}
|
||||
|
||||
private static void finishVineTip(Hunk<PlatformBlockState> data, int x, int z, int y) {
|
||||
PlatformBlockState state = data.get(x, y, z);
|
||||
PlatformBlockState tip = stackedVineBlock(state, 1, 0);
|
||||
if (tip != state) {
|
||||
data.set(x, y, z, tip);
|
||||
}
|
||||
if (dripstoneDown == null) {
|
||||
dripstoneDown = buildDripstoneArr("down");
|
||||
}
|
||||
return dripstoneDown[thIdx];
|
||||
}
|
||||
|
||||
static String stackedVineKey(PlatformBlockState state, int stack, int index) {
|
||||
|
||||
@@ -34,7 +34,7 @@ public final class DecoratorPlatformHooks {
|
||||
}
|
||||
|
||||
public interface SurfaceSturdiness {
|
||||
boolean canGoOn(PlatformBlockState surface);
|
||||
boolean canGoOn(PlatformBlockState surface, boolean upward);
|
||||
}
|
||||
|
||||
public static synchronized Bindings bind(FaceFixer faceFixer, SurfaceSturdiness surfaceSturdiness) {
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package art.arcane.iris.engine.decorator;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisProceduralBlocks;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.BlockSupport;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class IrisSpeleothems {
|
||||
private static final String[] SPIKE_THICKNESSES = {"tip", "frustum", "base", "middle", "tip_merge"};
|
||||
private static final ConcurrentHashMap<PlatformBlockState, PlatformBlockState[][]> SPIKE_STATES = new ConcurrentHashMap<>();
|
||||
|
||||
private IrisSpeleothems() {
|
||||
}
|
||||
|
||||
public static boolean isSpike(PlatformBlockState state) {
|
||||
if (state == null || state.key() == null) {
|
||||
return false;
|
||||
}
|
||||
String material = IrisProceduralBlocks.materialKey(state);
|
||||
return material.equals("minecraft:pointed_dripstone") || material.equals("minecraft:sulfur_spike");
|
||||
}
|
||||
|
||||
public static boolean isSupported(PlatformBlockState state, Hunk<PlatformBlockState> data, int x, int z, int y) {
|
||||
return isSupported(state, data, x, z, y,
|
||||
"up".equals(IrisProceduralBlocks.propertyValue(state, "vertical_direction")));
|
||||
}
|
||||
|
||||
public static void finishAtTip(Hunk<PlatformBlockState> data, int x, int z, int y) {
|
||||
PlatformBlockState state = data.get(x, y, z);
|
||||
if (!isSpike(state)) {
|
||||
return;
|
||||
}
|
||||
boolean upward = "up".equals(IrisProceduralBlocks.propertyValue(state, "vertical_direction"));
|
||||
int frontY = y + (upward ? 1 : -1);
|
||||
PlatformBlockState front = frontY >= 0 && frontY < data.getHeight() ? data.get(x, frontY, z) : null;
|
||||
if (!sameSpike(state, front, upward)) {
|
||||
finishColumn(data, x, z, y, 1, upward);
|
||||
}
|
||||
}
|
||||
|
||||
public static void finishColumn(Hunk<PlatformBlockState> data, int x, int z, int start, int placed, boolean upward) {
|
||||
int step = upward ? 1 : -1;
|
||||
for (int i = placed - 1; i >= 0; i--) {
|
||||
int y = start + step * i;
|
||||
PlatformBlockState state = data.get(x, y, z);
|
||||
if (isSpike(state)) {
|
||||
finishSpike(data, x, z, y, state, upward);
|
||||
}
|
||||
}
|
||||
if (placed == 0) {
|
||||
return;
|
||||
}
|
||||
PlatformBlockState state = data.get(x, start, z);
|
||||
if (!isSpike(state)) {
|
||||
return;
|
||||
}
|
||||
for (int y = start - step; y >= 0 && y < data.getHeight(); y -= step) {
|
||||
PlatformBlockState behind = data.get(x, y, z);
|
||||
if (!sameSpike(state, behind, upward)) {
|
||||
break;
|
||||
}
|
||||
finishSpike(data, x, z, y, behind, upward);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean canPlace(PlatformBlockState spike, Hunk<PlatformBlockState> data,
|
||||
int x, int z, int y, boolean upward, boolean allowWater) {
|
||||
int supportY = y + (upward ? -1 : 1);
|
||||
if (y < 0 || y >= data.getHeight() || supportY < 0 || supportY >= data.getHeight()) {
|
||||
return false;
|
||||
}
|
||||
PlatformBlockState existing = data.get(x, y, z);
|
||||
if (!B.isAir(existing) && !(allowWater && existing != null && existing.isWater())) {
|
||||
return false;
|
||||
}
|
||||
return isSupported(spike, data, x, z, y, upward);
|
||||
}
|
||||
|
||||
static PlatformBlockState orient(PlatformBlockState state, PlatformBlockState existing, boolean upward) {
|
||||
if (existing != null && existing.isWater() && !state.isWaterLogged()) {
|
||||
state = state.withProperty("waterlogged", "true");
|
||||
}
|
||||
return spikeBlock(state, upward, 0);
|
||||
}
|
||||
|
||||
static boolean isSturdy(PlatformBlockState surface, boolean upward) {
|
||||
if (surface == null || B.isAir(surface) || B.isFluid(surface)) {
|
||||
return false;
|
||||
}
|
||||
DecoratorPlatformHooks.SurfaceSturdiness sturdiness = DecoratorPlatformHooks.surfaceSturdiness();
|
||||
if (sturdiness != null) {
|
||||
return sturdiness.canGoOn(surface, upward);
|
||||
}
|
||||
return ((BlockData) surface.nativeHandle()).isFaceSturdy(upward ? BlockFace.UP : BlockFace.DOWN, BlockSupport.FULL);
|
||||
}
|
||||
|
||||
private static boolean isSupported(PlatformBlockState state, Hunk<PlatformBlockState> data,
|
||||
int x, int z, int y, boolean upward) {
|
||||
int supportY = y + (upward ? -1 : 1);
|
||||
if (supportY < 0 || supportY >= data.getHeight()) {
|
||||
return false;
|
||||
}
|
||||
PlatformBlockState support = data.get(x, supportY, z);
|
||||
return sameSpike(state, support, upward) || isSturdy(support, upward);
|
||||
}
|
||||
|
||||
private static PlatformBlockState[][] buildSpikeStates(PlatformBlockState state) {
|
||||
PlatformBlockState[][] states = new PlatformBlockState[2][SPIKE_THICKNESSES.length];
|
||||
for (int direction = 0; direction < states.length; direction++) {
|
||||
PlatformBlockState directed = state.withProperty("vertical_direction", direction == 0 ? "up" : "down");
|
||||
for (int thickness = 0; thickness < SPIKE_THICKNESSES.length; thickness++) {
|
||||
states[direction][thickness] = directed.withProperty("thickness", SPIKE_THICKNESSES[thickness]);
|
||||
}
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
private static boolean sameSpike(PlatformBlockState state, PlatformBlockState other, boolean upward) {
|
||||
return isSpike(other)
|
||||
&& IrisProceduralBlocks.materialKey(state).equals(IrisProceduralBlocks.materialKey(other))
|
||||
&& (upward ? "up" : "down").equals(IrisProceduralBlocks.propertyValue(other, "vertical_direction"));
|
||||
}
|
||||
|
||||
private static PlatformBlockState spikeBlock(PlatformBlockState state, boolean upward, int thickness) {
|
||||
return SPIKE_STATES.computeIfAbsent(state, IrisSpeleothems::buildSpikeStates)[upward ? 0 : 1][thickness];
|
||||
}
|
||||
|
||||
private static void finishSpike(Hunk<PlatformBlockState> data, int x, int z, int y, PlatformBlockState state, boolean upward) {
|
||||
int step = upward ? 1 : -1;
|
||||
int frontY = y + step;
|
||||
int backY = y - step;
|
||||
PlatformBlockState front = frontY >= 0 && frontY < data.getHeight() ? data.get(x, frontY, z) : null;
|
||||
PlatformBlockState back = backY >= 0 && backY < data.getHeight() ? data.get(x, backY, z) : null;
|
||||
int thickness = 0;
|
||||
if (sameSpike(state, front, !upward)) {
|
||||
String frontThickness = IrisProceduralBlocks.propertyValue(front, "thickness");
|
||||
if ("tip".equals(frontThickness) || "tip_merge".equals(frontThickness)) {
|
||||
thickness = 4;
|
||||
data.set(x, frontY, z, spikeBlock(front, !upward, 4));
|
||||
}
|
||||
} else if (sameSpike(state, front, upward)) {
|
||||
String frontThickness = IrisProceduralBlocks.propertyValue(front, "thickness");
|
||||
thickness = "tip".equals(frontThickness) || "tip_merge".equals(frontThickness)
|
||||
? 1 : sameSpike(state, back, upward) ? 3 : 2;
|
||||
}
|
||||
data.set(x, y, z, spikeBlock(state, upward, thickness));
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
package art.arcane.iris.engine.modifier;
|
||||
|
||||
import art.arcane.iris.engine.decorator.IrisSpeleothems;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineAssignedModifier;
|
||||
import art.arcane.iris.engine.object.IrisProceduralBlocks;
|
||||
@@ -104,6 +105,9 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlock
|
||||
|
||||
for (int k = top; k >= 0; k--) {
|
||||
PlatformBlockState b = output.get(finalI, k, j);
|
||||
if (IrisSpeleothems.isSpike(b)) {
|
||||
b = normalizeSpike(b, output, finalI, j, k, States.AIR, States.WATER);
|
||||
}
|
||||
boolean now = b != null && !(B.isAir(b) || B.isFluid(b));
|
||||
|
||||
if (now != inside) {
|
||||
@@ -161,6 +165,40 @@ public class IrisPerfectionModifier extends EngineAssignedModifier<PlatformBlock
|
||||
getEngine().getMetrics().getPerfection().put(p.getMilliseconds());
|
||||
}
|
||||
|
||||
static PlatformBlockState normalizeSpike(PlatformBlockState state, Hunk<PlatformBlockState> output,
|
||||
int x, int z, int y, PlatformBlockState air, PlatformBlockState water) {
|
||||
if (IrisSpeleothems.isSupported(state, output, x, z, y)) {
|
||||
IrisSpeleothems.finishAtTip(output, x, z, y);
|
||||
return output.get(x, y, z);
|
||||
}
|
||||
|
||||
String material = IrisProceduralBlocks.materialKey(state);
|
||||
String direction = IrisProceduralBlocks.propertyValue(state, "vertical_direction");
|
||||
boolean upward = "up".equals(direction);
|
||||
int step = upward ? 1 : -1;
|
||||
int nextY = y;
|
||||
while (nextY >= 0 && nextY < output.getHeight()) {
|
||||
PlatformBlockState current = output.get(x, nextY, z);
|
||||
if (!IrisSpeleothems.isSpike(current)
|
||||
|| !material.equals(IrisProceduralBlocks.materialKey(current))
|
||||
|| !direction.equals(IrisProceduralBlocks.propertyValue(current, "vertical_direction"))) {
|
||||
break;
|
||||
}
|
||||
output.set(x, nextY, z, current.isWaterLogged() ? water : air);
|
||||
nextY += step;
|
||||
}
|
||||
|
||||
if (nextY >= 0 && nextY < output.getHeight()) {
|
||||
PlatformBlockState retained = output.get(x, nextY, z);
|
||||
if (IrisSpeleothems.isSpike(retained)
|
||||
&& material.equals(IrisProceduralBlocks.materialKey(retained))
|
||||
&& !direction.equals(IrisProceduralBlocks.propertyValue(retained, "vertical_direction"))) {
|
||||
IrisSpeleothems.finishColumn(output, x, z, nextY, 1, !upward);
|
||||
}
|
||||
}
|
||||
return output.get(x, y, z);
|
||||
}
|
||||
|
||||
private void hideOres(Hunk<PlatformBlockState> output, boolean multicore) {
|
||||
BurstExecutor burst = burst().burst(multicore);
|
||||
int height = output.getHeight();
|
||||
|
||||
@@ -5,12 +5,20 @@ import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisBlockData;
|
||||
import art.arcane.iris.engine.object.IrisDecorationPart;
|
||||
import art.arcane.iris.engine.object.IrisDecorator;
|
||||
import art.arcane.iris.engine.object.IrisProceduralBlocks;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.BlockSupport;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -21,8 +29,11 @@ import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class DecoratorCoreTest {
|
||||
@@ -429,6 +440,211 @@ public class DecoratorCoreTest {
|
||||
assertEquals("minecraft:twisting_vines", DecoratorCore.stackedVineKey(vine, 3, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sulfurStalagmiteKeepsSulfurMaterialAndTapersToOneTip() {
|
||||
Hunk<PlatformBlockState> output = placeSpikeColumn("minecraft:sulfur_spike", 5, true, false);
|
||||
|
||||
assertSpikeColumn(output, "minecraft:sulfur_spike", true, false,
|
||||
"base", "middle", "middle", "frustum", "tip");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sulfurStalactiteKeepsWaterloggingAndPointsDown() {
|
||||
Hunk<PlatformBlockState> output = placeSpikeColumn("minecraft:sulfur_spike", 5, false, true);
|
||||
|
||||
assertSpikeColumn(output, "minecraft:sulfur_spike", false, true,
|
||||
"base", "middle", "middle", "frustum", "tip");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shortSulfurSpikesHaveCorrectTipsInBothDirections() {
|
||||
for (boolean upward : new boolean[]{true, false}) {
|
||||
Hunk<PlatformBlockState> single = placeSpikeColumn("minecraft:sulfur_spike", 1, upward, false);
|
||||
Hunk<PlatformBlockState> pair = placeSpikeColumn("minecraft:sulfur_spike", 2, upward, false);
|
||||
|
||||
assertSpikeColumn(single, "minecraft:sulfur_spike", upward, false, "tip");
|
||||
assertSpikeColumn(pair, "minecraft:sulfur_spike", upward, false, "frustum", "tip");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pointedDripstoneRetainsWaterloggingWhenStacked() {
|
||||
Hunk<PlatformBlockState> output = placeSpikeColumn("minecraft:pointed_dripstone", 4, true, true);
|
||||
|
||||
assertSpikeColumn(output, "minecraft:pointed_dripstone", true, true,
|
||||
"base", "middle", "frustum", "tip");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clippedSpikesRebuildTheirFreeEndWithoutOverwritingObstructions() {
|
||||
for (boolean upward : new boolean[]{true, false}) {
|
||||
SpikeFixture fixture = new SpikeFixture(5, upward);
|
||||
PlatformBlockState obstruction = sturdyState();
|
||||
fixture.output.set(0, upward ? 4 : 2, 0, obstruction);
|
||||
fixture.place();
|
||||
|
||||
assertSame(obstruction, fixture.output.get(0, upward ? 4 : 2, 0));
|
||||
assertEquals("tip", fixture.property(3, "thickness"));
|
||||
assertEquals("frustum", fixture.property(upward ? 2 : 4, "thickness"));
|
||||
assertEquals("base", fixture.property(upward ? 1 : 5, "thickness"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ceilingSpikesRequireTheUndersideOfTheirSupport() {
|
||||
SpikeFixture fixture = new SpikeFixture(3, false);
|
||||
BlockData support = (BlockData) fixture.output.get(0, 4, 0).nativeHandle();
|
||||
when(support.isFaceSturdy(BlockFace.DOWN, BlockSupport.FULL)).thenReturn(false);
|
||||
fixture.place();
|
||||
|
||||
assertTrue(fixture.output.get(0, 3, 0).isAir());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spikeStacksHonorSurfacePalettesInBothDirections() {
|
||||
for (boolean upward : new boolean[]{true, false}) {
|
||||
SpikeFixture fixture = new SpikeFixture(3, upward);
|
||||
PlatformBlockState allowed = sturdyState();
|
||||
when(fixture.decorator.getWhitelist()).thenReturn(new KList<>());
|
||||
when(fixture.decorator.getWhitelistArray(fixture.data)).thenReturn(new PlatformBlockState[]{allowed});
|
||||
fixture.place();
|
||||
assertTrue(fixture.output.get(0, upward ? 1 : 3, 0).isAir());
|
||||
|
||||
PlatformBlockState support = fixture.output.get(0, upward ? 0 : 4, 0);
|
||||
when(support.matches(allowed)).thenReturn(true);
|
||||
fixture.place();
|
||||
assertEquals("tip", fixture.property(upward ? 3 : 1, "thickness"));
|
||||
|
||||
when(fixture.decorator.getBlacklist()).thenReturn(new KList<>());
|
||||
when(fixture.decorator.getBlacklistArray(fixture.data)).thenReturn(new PlatformBlockState[]{allowed});
|
||||
fixture.output.set(0, upward ? 1 : 3, 0, airState());
|
||||
fixture.place();
|
||||
assertTrue(fixture.output.get(0, upward ? 1 : 3, 0).isAir());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submergedSpikesWaterlogAndStopAtLava() {
|
||||
for (boolean upward : new boolean[]{true, false}) {
|
||||
SpikeFixture fixture = new SpikeFixture(4, upward);
|
||||
PlatformBlockState water = mock(PlatformBlockState.class);
|
||||
PlatformBlockState lava = mock(PlatformBlockState.class);
|
||||
when(water.isFluid()).thenReturn(true);
|
||||
when(water.isWater()).thenReturn(true);
|
||||
when(lava.isFluid()).thenReturn(true);
|
||||
int first = upward ? 1 : 4;
|
||||
int second = upward ? 2 : 3;
|
||||
fixture.output.set(0, first, 0, water);
|
||||
fixture.output.set(0, second, 0, lava);
|
||||
fixture.opts.underwater = true;
|
||||
fixture.opts.fluidHeight = 4;
|
||||
fixture.place();
|
||||
|
||||
assertEquals("true", fixture.property(first, "waterlogged"));
|
||||
assertEquals("tip", fixture.property(first, "thickness"));
|
||||
assertSame(lava, fixture.output.get(0, second, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oppositeSulfurTipsMergeWithoutReplacingEitherColumn() {
|
||||
for (boolean upward : new boolean[]{true, false}) {
|
||||
SpikeFixture fixture = new SpikeFixture(5, upward);
|
||||
int oppositeY = 3;
|
||||
PlatformBlockState opposite = spikeState("minecraft:sulfur_spike", Map.of(
|
||||
"thickness", "tip", "vertical_direction", upward ? "down" : "up", "waterlogged", "false"));
|
||||
fixture.output.set(0, oppositeY, 0, opposite);
|
||||
fixture.place();
|
||||
|
||||
assertEquals("tip_merge", fixture.property(oppositeY, "thickness"));
|
||||
assertEquals(upward ? "down" : "up", fixture.property(oppositeY, "vertical_direction"));
|
||||
assertEquals("tip_merge", fixture.property(upward ? 2 : 4, "thickness"));
|
||||
assertEquals("frustum", fixture.property(upward ? 1 : 5, "thickness"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sulfurTipsDoNotMergeWithPointedDripstone() {
|
||||
SpikeFixture fixture = new SpikeFixture(4, true);
|
||||
PlatformBlockState dripstone = spikeState("minecraft:pointed_dripstone", Map.of(
|
||||
"thickness", "tip", "vertical_direction", "down", "waterlogged", "false"));
|
||||
fixture.output.set(0, 3, 0, dripstone);
|
||||
fixture.place();
|
||||
|
||||
assertEquals("tip", fixture.property(2, "thickness"));
|
||||
assertSame(dripstone, fixture.output.get(0, 3, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleDecoratorsNormalizeNativeSpikeDirectionAndThickness() {
|
||||
for (boolean upward : new boolean[]{true, false}) {
|
||||
SpikeFixture fixture = new SpikeFixture(1, upward);
|
||||
if (upward) {
|
||||
DecoratorCore.placeSurfaceSingle(fixture.decorator, 0, 0, 0, 0, 0,
|
||||
fixture.output, new RNG(1L), fixture.data, false, false, null);
|
||||
} else {
|
||||
DecoratorCore.placeSingleAt(fixture.decorator, 0, 0, 0, 1, 0,
|
||||
fixture.output, new RNG(1L), fixture.data, true, null);
|
||||
}
|
||||
assertEquals("tip", fixture.property(1, "thickness"));
|
||||
assertEquals(upward ? "up" : "down", fixture.property(1, "vertical_direction"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extendingSpikeRepairsTheExistingTipBehindIt() {
|
||||
SpikeFixture fixture = new SpikeFixture(3, true);
|
||||
PlatformBlockState spike = spikeState("minecraft:sulfur_spike", Map.of(
|
||||
"thickness", "tip", "vertical_direction", "up", "waterlogged", "false"));
|
||||
fixture.output.set(0, 0, 0, spike);
|
||||
fixture.place();
|
||||
|
||||
assertEquals("base", fixture.property(0, "thickness"));
|
||||
assertEquals("middle", fixture.property(1, "thickness"));
|
||||
assertEquals("frustum", fixture.property(2, "thickness"));
|
||||
assertEquals("tip", fixture.property(3, "thickness"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleSpikeChecksPaletteBeforeReplacingItsSupport() {
|
||||
SpikeFixture fixture = new SpikeFixture(1, true);
|
||||
PlatformBlockState original = fixture.output.get(0, 0, 0);
|
||||
PlatformBlockState replacement = sturdyState();
|
||||
IrisBlockData forced = mock(IrisBlockData.class);
|
||||
when(forced.getBlockData(fixture.data)).thenReturn(replacement);
|
||||
when(fixture.decorator.getForceBlock()).thenReturn(forced);
|
||||
when(fixture.decorator.getWhitelist()).thenReturn(new KList<>());
|
||||
when(fixture.decorator.getWhitelistArray(fixture.data)).thenReturn(new PlatformBlockState[]{original});
|
||||
|
||||
DecoratorCore.placeSurfaceSingle(fixture.decorator, 0, 0, 0, 0, 0,
|
||||
fixture.output, new RNG(1L), fixture.data, false, false, null);
|
||||
|
||||
assertSame(replacement, fixture.output.get(0, 0, 0));
|
||||
assertEquals("tip", fixture.property(1, "thickness"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clippedCeilingVinesRetainTheirGrowingTip() {
|
||||
SpikeFixture fixture = new SpikeFixture(5, false);
|
||||
PlatformBlockState plant = mock(PlatformBlockState.class);
|
||||
PlatformBlockState tip = mock(PlatformBlockState.class);
|
||||
PlatformBlockState obstruction = sturdyState();
|
||||
when(plant.key()).thenReturn("minecraft:weeping_vines_plant");
|
||||
when(tip.key()).thenReturn("minecraft:weeping_vines");
|
||||
when(fixture.decorator.pickBlockData(any(RNG.class), eq(fixture.data), anyDouble(), anyDouble())).thenReturn(tip);
|
||||
when(fixture.decorator.pickBlockDataTop(any(RNG.class), eq(fixture.data), anyDouble(), anyDouble())).thenReturn(tip);
|
||||
fixture.output.set(0, 3, 0, obstruction);
|
||||
try (MockedStatic<B> blocks = mockStatic(B.class, CALLS_REAL_METHODS)) {
|
||||
blocks.when(() -> B.getState("minecraft:weeping_vines_plant")).thenReturn(plant);
|
||||
blocks.when(() -> B.getState("minecraft:weeping_vines")).thenReturn(tip);
|
||||
fixture.place();
|
||||
}
|
||||
|
||||
assertSame(plant, fixture.output.get(0, 5, 0));
|
||||
assertSame(tip, fixture.output.get(0, 4, 0));
|
||||
assertSame(obstruction, fixture.output.get(0, 3, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boundDecoratorHooksWinWhenBukkitClassesArePresent() {
|
||||
PlatformBlockState vine = mock(PlatformBlockState.class);
|
||||
@@ -441,7 +657,7 @@ public class DecoratorCoreTest {
|
||||
when(vine.isVineBlock()).thenReturn(true);
|
||||
when(faceFixer.fixFaces(vine, output, 0, 0, 0, 0, 0, null)).thenReturn(fixed);
|
||||
when(decorator.canPlaceOnto(surface)).thenReturn(true);
|
||||
when(sturdiness.canGoOn(surface)).thenReturn(true);
|
||||
when(sturdiness.canGoOn(surface, true)).thenReturn(true);
|
||||
DecoratorPlatformHooks.Bindings previous = DecoratorPlatformHooks.bind(faceFixer, sturdiness);
|
||||
try {
|
||||
assertSame(fixed, DecoratorCore.fixFacesForHunk(vine, output, 0, 0, 0, 0, 0, null));
|
||||
@@ -457,6 +673,58 @@ public class DecoratorCoreTest {
|
||||
return air;
|
||||
}
|
||||
|
||||
private Hunk<PlatformBlockState> placeSpikeColumn(String material, int height, boolean upward, boolean waterlogged) {
|
||||
IrisDecorator decorator = mock(IrisDecorator.class);
|
||||
IrisData data = mock(IrisData.class);
|
||||
PlatformBlockState spike = spikeState(material, Map.of(
|
||||
"thickness", "tip_merge", "vertical_direction", upward ? "down" : "up",
|
||||
"waterlogged", Boolean.toString(waterlogged)));
|
||||
when(decorator.getHeight(any(RNG.class), anyDouble(), anyDouble(), eq(data))).thenReturn(height);
|
||||
when(decorator.getTopThreshold()).thenReturn(0.75);
|
||||
when(decorator.pickBlockData(any(RNG.class), eq(data), anyDouble(), anyDouble())).thenReturn(spike);
|
||||
when(decorator.pickBlockDataTop(any(RNG.class), eq(data), anyDouble(), anyDouble())).thenReturn(spike);
|
||||
|
||||
Hunk<PlatformBlockState> output = Hunk.newArrayHunk(1, height + 2, 1);
|
||||
for (int y = 1; y <= height; y++) {
|
||||
output.set(0, y, 0, airState());
|
||||
}
|
||||
PlatformBlockState support = sturdyState();
|
||||
output.set(0, upward ? 0 : height + 1, 0, support);
|
||||
DecoratorCore.PlaceOpts opts = new DecoratorCore.PlaceOpts();
|
||||
if (upward) {
|
||||
DecoratorCore.placeStackUp(decorator, 0, 0, 0, 0, 0, height, output, new RNG(1L), data, opts);
|
||||
} else {
|
||||
DecoratorCore.placeStackDown(decorator, 0, 0, 0, 0, height, 1, output, new RNG(1L), data, height, opts, null);
|
||||
}
|
||||
assertSame(support, output.get(0, upward ? 0 : height + 1, 0));
|
||||
return output;
|
||||
}
|
||||
|
||||
private void assertSpikeColumn(Hunk<PlatformBlockState> output, String material,
|
||||
boolean upward, boolean waterlogged, String... thicknesses) {
|
||||
for (int index = 0; index < thicknesses.length; index++) {
|
||||
int y = upward ? index + 1 : thicknesses.length - index;
|
||||
assertEquals(material + "[thickness=" + thicknesses[index]
|
||||
+ ",vertical_direction=" + (upward ? "up" : "down")
|
||||
+ ",waterlogged=" + waterlogged + "]",
|
||||
output.get(0, y, 0).key());
|
||||
}
|
||||
}
|
||||
|
||||
private PlatformBlockState spikeState(String material, Map<String, String> properties) {
|
||||
PlatformBlockState state = mock(PlatformBlockState.class);
|
||||
when(state.key()).thenReturn(material + "[thickness=" + properties.get("thickness")
|
||||
+ ",vertical_direction=" + properties.get("vertical_direction")
|
||||
+ ",waterlogged=" + properties.get("waterlogged") + "]");
|
||||
when(state.canPlaceOnto(any(PlatformBlockState.class))).thenReturn(true);
|
||||
when(state.withProperty(anyString(), anyString())).thenAnswer(invocation -> {
|
||||
Map<String, String> updated = new LinkedHashMap<>(properties);
|
||||
updated.put(invocation.getArgument(0), invocation.getArgument(1));
|
||||
return spikeState(material, updated);
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
private PlatformBlockState tallPlantState() {
|
||||
return tallPlantState(mock(PlatformBlockState.class), mock(PlatformBlockState.class));
|
||||
}
|
||||
@@ -476,4 +744,41 @@ public class DecoratorCoreTest {
|
||||
when(blockData.isFaceSturdy(any(), eq(BlockSupport.FULL))).thenReturn(true);
|
||||
return support;
|
||||
}
|
||||
private class SpikeFixture {
|
||||
private final IrisDecorator decorator = mock(IrisDecorator.class);
|
||||
private final IrisData data = mock(IrisData.class);
|
||||
private final DecoratorCore.PlaceOpts opts = new DecoratorCore.PlaceOpts();
|
||||
private final Hunk<PlatformBlockState> output;
|
||||
private final int height;
|
||||
private final boolean upward;
|
||||
|
||||
private SpikeFixture(int height, boolean upward) {
|
||||
this.height = height;
|
||||
this.upward = upward;
|
||||
output = Hunk.newArrayHunk(1, height + 2, 1);
|
||||
for (int y = 0; y < output.getHeight(); y++) {
|
||||
output.set(0, y, 0, airState());
|
||||
}
|
||||
output.set(0, upward ? 0 : height + 1, 0, sturdyState());
|
||||
PlatformBlockState spike = spikeState("minecraft:sulfur_spike", Map.of(
|
||||
"thickness", "tip_merge", "vertical_direction", upward ? "down" : "up", "waterlogged", "false"));
|
||||
when(decorator.getHeight(any(RNG.class), anyDouble(), anyDouble(), eq(data))).thenReturn(height);
|
||||
when(decorator.getTopThreshold()).thenReturn(0.75);
|
||||
when(decorator.pickBlockData(any(RNG.class), eq(data), anyDouble(), anyDouble())).thenReturn(spike);
|
||||
when(decorator.pickBlockDataTop(any(RNG.class), eq(data), anyDouble(), anyDouble())).thenReturn(spike);
|
||||
}
|
||||
|
||||
private void place() {
|
||||
if (upward) {
|
||||
DecoratorCore.placeStackUp(decorator, 0, 0, 0, 0, 0, height, output, new RNG(1L), data, opts);
|
||||
} else {
|
||||
DecoratorCore.placeStackDown(decorator, 0, 0, 0, 0, height, 1, output, new RNG(1L), data, height, opts, null);
|
||||
}
|
||||
}
|
||||
|
||||
private String property(int y, String name) {
|
||||
return IrisProceduralBlocks.propertyValue(output.get(0, y, 0), name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
package art.arcane.iris.engine.modifier;
|
||||
|
||||
import art.arcane.iris.engine.decorator.IrisSpeleothems;
|
||||
import art.arcane.iris.engine.object.IrisProceduralBlocks;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import org.bukkit.block.BlockSupport;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisPerfectionModifierSpeleothemTest {
|
||||
@Test
|
||||
public void poolReplacementRemovesPreviouslySupportedSpikesInBothDirections() {
|
||||
for (String material : new String[]{"minecraft:sulfur_spike", "minecraft:pointed_dripstone"}) {
|
||||
for (boolean upward : new boolean[]{true, false}) {
|
||||
for (String fluid : new String[]{"minecraft:water", "minecraft:lava"}) {
|
||||
Fixture fixture = new Fixture();
|
||||
fixture.column(material, upward, false);
|
||||
fixture.normalize();
|
||||
for (int y = 2; y <= 4; y++) {
|
||||
assertTrue(IrisSpeleothems.isSpike(fixture.output.get(0, y, 0)));
|
||||
}
|
||||
|
||||
int supportY = upward ? 1 : 5;
|
||||
PlatformBlockState pool = fluid.equals("minecraft:water") ? fixture.water : fluid(fluid);
|
||||
fixture.output.set(0, supportY, 0, pool);
|
||||
fixture.normalize();
|
||||
|
||||
assertSame(pool, fixture.output.get(0, supportY, 0));
|
||||
for (int y = 2; y <= 4; y++) {
|
||||
assertSame(fixture.air, fixture.output.get(0, y, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removedWaterloggedSpikesRestoreWater() {
|
||||
for (boolean upward : new boolean[]{true, false}) {
|
||||
Fixture fixture = new Fixture();
|
||||
fixture.column("minecraft:sulfur_spike", upward, true);
|
||||
fixture.output.set(0, upward ? 1 : 5, 0, fixture.water);
|
||||
fixture.normalize();
|
||||
|
||||
for (int y = 2; y <= 4; y++) {
|
||||
assertSame(fixture.water, fixture.output.get(0, y, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removingOneMergedColumnRepairsTheSupportedOppositeTip() {
|
||||
for (boolean removeUpward : new boolean[]{true, false}) {
|
||||
Fixture fixture = new Fixture();
|
||||
fixture.output.set(0, 0, 0, sturdyState());
|
||||
fixture.output.set(0, 7, 0, sturdyState());
|
||||
fixture.output.set(0, 1, 0, spike("minecraft:sulfur_spike", true, "base", false));
|
||||
fixture.output.set(0, 2, 0, spike("minecraft:sulfur_spike", true, "frustum", false));
|
||||
fixture.output.set(0, 3, 0, spike("minecraft:sulfur_spike", true, "tip_merge", false));
|
||||
fixture.output.set(0, 4, 0, spike("minecraft:sulfur_spike", false, "tip_merge", false));
|
||||
fixture.output.set(0, 5, 0, spike("minecraft:sulfur_spike", false, "frustum", false));
|
||||
fixture.output.set(0, 6, 0, spike("minecraft:sulfur_spike", false, "base", false));
|
||||
fixture.output.set(0, removeUpward ? 0 : 7, 0, fixture.water);
|
||||
fixture.normalize();
|
||||
|
||||
for (int y = removeUpward ? 1 : 4; y <= (removeUpward ? 3 : 6); y++) {
|
||||
assertSame(fixture.air, fixture.output.get(0, y, 0));
|
||||
}
|
||||
assertEquals("tip", fixture.property(removeUpward ? 4 : 3, "thickness"));
|
||||
assertEquals("frustum", fixture.property(removeUpward ? 5 : 2, "thickness"));
|
||||
assertEquals("base", fixture.property(removeUpward ? 6 : 1, "thickness"));
|
||||
assertEquals(removeUpward ? "down" : "up",
|
||||
fixture.property(removeUpward ? 4 : 3, "vertical_direction"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void objectRemovingTheFreeEndRetapersTheSupportedRemainder() {
|
||||
for (boolean upward : new boolean[]{true, false}) {
|
||||
Fixture fixture = new Fixture();
|
||||
fixture.column("minecraft:sulfur_spike", upward, false);
|
||||
fixture.output.set(0, upward ? 4 : 2, 0, fixture.air);
|
||||
fixture.normalize();
|
||||
|
||||
assertEquals("tip", fixture.property(3, "thickness"));
|
||||
assertEquals("frustum", fixture.property(upward ? 2 : 4, "thickness"));
|
||||
assertSame(fixture.air, fixture.output.get(0, upward ? 4 : 2, 0));
|
||||
}
|
||||
}
|
||||
|
||||
private static PlatformBlockState spike(String material, boolean upward, String thickness, boolean waterlogged) {
|
||||
return spike(material, Map.of(
|
||||
"vertical_direction", upward ? "up" : "down",
|
||||
"thickness", thickness,
|
||||
"waterlogged", Boolean.toString(waterlogged)));
|
||||
}
|
||||
|
||||
private static PlatformBlockState spike(String material, Map<String, String> properties) {
|
||||
PlatformBlockState state = mock(PlatformBlockState.class);
|
||||
when(state.key()).thenReturn(material + "[thickness=" + properties.get("thickness")
|
||||
+ ",vertical_direction=" + properties.get("vertical_direction")
|
||||
+ ",waterlogged=" + properties.get("waterlogged") + "]");
|
||||
when(state.isWaterLogged()).thenReturn(Boolean.parseBoolean(properties.get("waterlogged")));
|
||||
when(state.withProperty(anyString(), anyString())).thenAnswer(invocation -> {
|
||||
Map<String, String> updated = new LinkedHashMap<>(properties);
|
||||
updated.put(invocation.getArgument(0), invocation.getArgument(1));
|
||||
return spike(material, updated);
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
private static PlatformBlockState sturdyState() {
|
||||
PlatformBlockState state = mock(PlatformBlockState.class);
|
||||
BlockData nativeState = mock(BlockData.class);
|
||||
when(state.key()).thenReturn("minecraft:sulfur");
|
||||
when(state.nativeHandle()).thenReturn(nativeState);
|
||||
when(nativeState.isFaceSturdy(any(), eq(BlockSupport.FULL))).thenReturn(true);
|
||||
return state;
|
||||
}
|
||||
|
||||
private static PlatformBlockState fluid(String material) {
|
||||
PlatformBlockState state = mock(PlatformBlockState.class);
|
||||
when(state.key()).thenReturn(material);
|
||||
when(state.isFluid()).thenReturn(true);
|
||||
when(state.isWater()).thenReturn(material.equals("minecraft:water"));
|
||||
return state;
|
||||
}
|
||||
|
||||
private static class Fixture {
|
||||
private final PlatformBlockState air = mock(PlatformBlockState.class);
|
||||
private final PlatformBlockState water = fluid("minecraft:water");
|
||||
private final Hunk<PlatformBlockState> output = Hunk.newArrayHunk(1, 9, 1);
|
||||
|
||||
private Fixture() {
|
||||
when(air.key()).thenReturn("minecraft:air");
|
||||
when(air.isAir()).thenReturn(true);
|
||||
for (int y = 0; y < output.getHeight(); y++) {
|
||||
output.set(0, y, 0, air);
|
||||
}
|
||||
}
|
||||
|
||||
private void column(String material, boolean upward, boolean waterlogged) {
|
||||
output.set(0, upward ? 1 : 5, 0, sturdyState());
|
||||
output.set(0, upward ? 2 : 4, 0, spike(material, upward, "base", waterlogged));
|
||||
output.set(0, 3, 0, spike(material, upward, "frustum", waterlogged));
|
||||
output.set(0, upward ? 4 : 2, 0, spike(material, upward, "tip", waterlogged));
|
||||
}
|
||||
|
||||
private void normalize() {
|
||||
for (int y = output.getHeight() - 1; y >= 0; y--) {
|
||||
PlatformBlockState state = output.get(0, y, 0);
|
||||
if (IrisSpeleothems.isSpike(state)) {
|
||||
IrisPerfectionModifier.normalizeSpike(state, output, 0, 0, y, air, water);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String property(int y, String property) {
|
||||
return IrisProceduralBlocks.propertyValue(output.get(0, y, 0), property);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user