diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/AutoPlaceEnvironment.java b/src/main/java/com/gtnewhorizon/structurelib/structure/AutoPlaceEnvironment.java index 0e839f1b..525e93cb 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/AutoPlaceEnvironment.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/AutoPlaceEnvironment.java @@ -340,6 +340,10 @@ public class AutoPlaceEnvironment { if (fluidRoundState != null) fluidRoundState.setGateResult(ready); } + void clearFluidGateResult() { + if (fluidRoundState != null) fluidRoundState.clearGateResult(); + } + /** * Flag that at least one fluid element wanted to place its fluid, but had to wait for the structure to be built * first. An autoplace round that did nothing but wait reports zero placed elements instead of reporting that the @@ -407,6 +411,15 @@ public class AutoPlaceEnvironment { */ void setGateResult(boolean ready); + /** + * Forget the answer of the fluid placement gate, so that it is asked again. + *

+ * The answer describes the world as it was before this round built anything, so it has to be dropped once the + * round has placed something and the fluids that had to wait are retried. An implementation that keeps no + * answer at all can leave this alone. + */ + default void clearGateResult() {} + /** * Remember that a fluid element had to wait for the structure to be built first. */ diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/FluidAutoplace.java b/src/main/java/com/gtnewhorizon/structurelib/structure/FluidAutoplace.java index c9d7468c..ce48cb3d 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/FluidAutoplace.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/FluidAutoplace.java @@ -63,9 +63,11 @@ public class FluidAutoplace { */ LENIENT, /** - * Only place the fluid once every non fluid element of the structure is satisfied. This is the default, and - * together with the fact that an autoplace round places a limited number of elements it effectively builds the - * structure first and fills it with fluid afterwards. + * Only place the fluid once every non fluid element of the structure is satisfied. This is the default. + *

+ * The fluid is placed as soon as the structure can hold it, which is normally the round that finishes the + * structure: the fluids that round could not place yet are tried once more when it is done building, so they do + * not have to wait for another round. */ STRICT } @@ -195,9 +197,9 @@ public class FluidAutoplace { /** * Whether a fluid may be placed at this position right now. *

- * With {@link GateMode#STRICT} the answer is computed once per autoplace round and cached on the environment, as it - * costs a walk over the whole structure. That also means a fluid is at worst one round late, which is what keeps - * the walk cheap on big multiblocks. + * With {@link GateMode#STRICT} the answer is computed by a walk over the whole structure and cached for the rest of + * the round, as that walk is expensive. The cache is dropped as soon as the round has built something, so that the + * fluids which waited for it are not held back by an answer that predates those placements. * * @param world world to place in * @param x x coord @@ -302,15 +304,23 @@ public class FluidAutoplace { private static boolean placeFluidBlock(World world, int x, int y, int z, FluidBlockRequirement requirement) { FluidStack fluid = requirement.cost(); + Block block = requirement.block(); if (requirement.placer() != null) { - return requirement.placer().place(world, x, y, z, requirement.block(), requirement.meta(), fluid); + if (!requirement.placer().place(world, x, y, z, block, requirement.meta(), fluid)) return false; + } else { + // The notification flags matter here. A fluid that is put into the world silently neither flows nor tells + // its neighbours about itself, so it would sit where it was placed until something else happens to touch + // it, which for water inside a multiblock can be never. + if (!world.setBlock(x, y, z, block, requirement.meta(), 3)) return false; } - return world.setBlock(x, y, z, requirement.block(), requirement.meta(), 2); + // A fluid that is never ticked does not spread, and a fluid placed as a flowing one would never settle. + world.scheduleBlockUpdate(x, y, z, block, block.tickRate(world)); + return true; } private static void restore(World world, int x, int y, int z, Block block, int meta) { if (block == null || block.isAir(world, x, y, z)) world.setBlockToAir(x, y, z); - else world.setBlock(x, y, z, block, meta, 2); + else world.setBlock(x, y, z, block, meta, 3); } private static void reportMissingFluid(AutoPlaceEnvironment env, FluidBlockRequirement requirement, int missing) { diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java index 7158942e..a1eda53f 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java @@ -318,6 +318,9 @@ public interface IStructureDefinition { basePositionC, walker, "survivalBuild"); + // A fluid position is often visited before the blocks that hold it, so give the fluids that had to wait for + // those blocks a chance to be placed in this round rather than the next one. + walker.finishRound(world); return walker.getBuilt(); } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/SurvivalBuildStructureWalker.java b/src/main/java/com/gtnewhorizon/structurelib/structure/SurvivalBuildStructureWalker.java index c8769db1..ca56c5e2 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/SurvivalBuildStructureWalker.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/SurvivalBuildStructureWalker.java @@ -1,10 +1,14 @@ package com.gtnewhorizon.structurelib.structure; +import java.util.ArrayList; +import java.util.List; + import javax.annotation.Nullable; import net.minecraft.item.ItemStack; import net.minecraft.world.World; +import com.github.bsideup.jabel.Desugar; import com.gtnewhorizon.structurelib.StructureLibAPI; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult; @@ -20,6 +24,7 @@ class SurvivalBuildStructureWalker implements IStructureWalker, AutoPlaceE private Boolean fluidGateResult; private boolean fluidDeferred; + private final List> deferredFluids = new ArrayList<>(); private final AutoPlaceEnvironment env; @@ -52,6 +57,11 @@ class SurvivalBuildStructureWalker implements IStructureWalker, AutoPlaceE env.offsetABC[2] = c; env.setSource(params.getSource()); PlaceResult placeResult = element.survivalPlaceBlock(object, world, x, y, z, trigger, env); + if (placeResult == PlaceResult.REJECT_CONTINUE && element.isFluidElement(object)) { + // The fluid has to wait. Remember where it is, as this round may still build what it is waiting for, and + // then it can be placed before the round is over instead of one round later. + deferredFluids.add(new DeferredFluid<>(element, x, y, z, a, b, c)); + } if (placeResult != PlaceResult.SKIP && placeResult != PlaceResult.REJECT_CONTINUE && built == -1) built = 0; switch (placeResult) { case SKIP: @@ -74,6 +84,44 @@ class SurvivalBuildStructureWalker implements IStructureWalker, AutoPlaceE } } + /** + * Place the fluids that had to wait during this round, now that the round has built everything it was going to. + *

+ * A fluid position is often visited before the blocks that are meant to hold it, so a round that finishes the + * structure would otherwise leave the fluid to the next round even though there is nothing left to wait for. The + * gate is asked again, as the answer it gave was about the world as it was before this round built anything. + *

+ * Like the walk itself, this places at most as many elements as the round still has budget left, and whatever it + * cannot reach is left to the next round. + * + * @param world the world this round was run in + */ + void finishRound(World world) { + if (deferredFluids.isEmpty()) return; + // A round that built nothing has not changed the answer of the gate either. + if (built <= 0) return; + + int budget = elementBudget - built; + env.clearFluidGateResult(); + for (DeferredFluid deferred : deferredFluids) { + if (budget <= 0) break; + IStructureElement element = deferred.element(); + // The element has to see the same offsets and item source it saw during the walk, as elements may read + // channel data or take items through them. + env.offsetABC[0] = deferred.a(); + env.offsetABC[1] = deferred.b(); + env.offsetABC[2] = deferred.c(); + env.setSource(params.getSource()); + PlaceResult placeResult = element + .survivalPlaceBlock(object, world, deferred.x(), deferred.y(), deferred.z(), trigger, env); + if (placeResult != PlaceResult.ACCEPT) continue; + if (check) element.check(object, world, deferred.x(), deferred.y(), deferred.z()); + built += 1; + budget -= 1; + } + deferredFluids.clear(); + } + /** * Number of elements this round has placed, or -1 when the structure is done. *

@@ -95,6 +143,11 @@ class SurvivalBuildStructureWalker implements IStructureWalker, AutoPlaceE fluidGateResult = ready; } + @Override + public void clearGateResult() { + fluidGateResult = null; + } + @Override public void markDeferred() { fluidDeferred = true; @@ -104,4 +157,10 @@ class SurvivalBuildStructureWalker implements IStructureWalker, AutoPlaceE public boolean isDeferred() { return fluidDeferred; } + + /** + * A fluid position a round had to leave alone, kept so that it can be tried once more when the round is over. + */ + @Desugar + private record DeferredFluid (IStructureElement element, int x, int y, int z, int a, int b, int c) {} } diff --git a/src/main/resources/assets/structurelib/lang/zh_CN.lang b/src/main/resources/assets/structurelib/lang/zh_CN.lang index e59ddd51..3c8cae6b 100644 --- a/src/main/resources/assets/structurelib/lang/zh_CN.lang +++ b/src/main/resources/assets/structurelib/lang/zh_CN.lang @@ -51,6 +51,7 @@ structurelib.autoplace.error.not_enabled=§c这个多方块结构不支持在生 structurelib.autoplace.error.no_simple_block=§c建议放置 §r%s§c ,但没有找到它 structurelib.autoplace.error.no_item_stack=§c建议使用 §r%s§c 放置方块,但没有找到它 structurelib.autoplace.warning.no_explicit_channel=§6这个结构依赖于不存在的子信道 §r%s§6 。已使用主信道值替代。 +structurelib.autoplace.missing_block=§c缺少方块:%s structurelib.autoplace.missing_fluid=§c缺少流体:%s(%s) structurelib.autoplace.requirement_fluid=%s(%s)