package com.gtnewhorizon.structurelib.structure; import java.util.function.Consumer; import javax.annotation.Nullable; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.util.IChatComponent; import net.minecraft.util.StatCollector; import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.IFluidBlock; import com.gtnewhorizon.gtnhlib.chat.customcomponents.ChatComponentFluid; import com.gtnewhorizon.gtnhlib.chat.customcomponents.ChatComponentFluidName; import com.gtnewhorizon.gtnhlib.util.numberformatting.NumberFormatUtil; import com.gtnewhorizon.structurelib.ConfigurationHandler; import com.gtnewhorizon.structurelib.StructureLib; import com.gtnewhorizon.structurelib.StructureLibAPI; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; import com.gtnewhorizon.structurelib.fluid.FluidAmounts; import com.gtnewhorizon.structurelib.fluid.FluidBlockRequirement; import com.gtnewhorizon.structurelib.fluid.FluidFillPolicy; import com.gtnewhorizon.structurelib.fluid.IFluidSource; import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult; /** * Autoplace of fluid blocks. *
* Placing a fluid is not the same as placing a block. A block stays where it was put, while a fluid flows out of an * unfinished structure, floods the surroundings, and turns a partially filled position into something that no longer * counts as a source block. This class handles that: *
* Nothing is consumed when this returns anything other than {@link PlaceResult#ACCEPT}, and the block that was
* there before is put back when the fluid cannot be taken after the block has been placed.
*
* @param world world to place in
* @param x x coord
* @param y y coord
* @param z z coord
* @param env autoplace environment, providing the fluid source, the actor and the chatter
* @param requirement which block the structure wants there, what it costs, and how strict the check is
* @return {@link PlaceResult#SKIP} when the position is already good enough, {@link PlaceResult#ACCEPT} when the
* fluid block has been placed, {@link PlaceResult#REJECT_CONTINUE} when the structure is not ready for its
* fluid yet or fluid autoplace is off, and {@link PlaceResult#REJECT} when the fluid is missing
*/
public static PlaceResult tryPlace(World world, int x, int y, int z, AutoPlaceEnvironment env,
FluidBlockRequirement requirement) {
if (world.isRemote || !ConfigurationHandler.INSTANCE.isFluidAutoplaceEnabled()) {
return PlaceResult.REJECT_CONTINUE;
}
if (isSatisfied(world, x, y, z, requirement)) return PlaceResult.SKIP;
if (!canReplace(world, x, y, z, env.getActor())) return PlaceResult.REJECT;
if (!isGateOpen(world, x, y, z, env)) {
env.markFluidPlacementDeferred();
return PlaceResult.REJECT_CONTINUE;
}
IFluidSource source = env.getEffectiveFluidSource();
if (source == null) {
reportMissingFluid(env, requirement, requirement.cost().amount);
return PlaceResult.REJECT;
}
Block existingBlock = world.getBlock(x, y, z);
int existingMeta = world.getBlockMetadata(x, y, z);
int target = requirement.cost().amount;
int existing = existingBlock == requirement.block() ? FluidAmounts.getStoredAmount(world, x, y, z, target) : 0;
int missing = Math.max(0, target - existing);
if (missing > 0 && !source.takeAll(new FluidStack(requirement.cost(), missing), true)) {
reportMissingFluid(
env,
requirement,
missing - source.getAvailable(new FluidStack(requirement.cost(), missing)));
return PlaceResult.REJECT;
}
if (!placeFluidBlock(world, x, y, z, requirement)) return PlaceResult.REJECT;
if (missing > 0 && !source.takeAll(new FluidStack(requirement.cost(), missing), false)) {
restore(world, x, y, z, existingBlock, existingMeta);
return PlaceResult.REJECT;
}
return PlaceResult.ACCEPT;
}
/**
* Try to place one of several fluid blocks, in order, and stop at the first one that is already there or that could
* be placed.
*
* @return the result of the first attempt that was more than a {@link PlaceResult#REJECT_CONTINUE}, or
* {@link PlaceResult#REJECT} when the fluid of an alternative was missing, or
* {@link PlaceResult#REJECT_CONTINUE} when every alternative had to wait
* @see #tryPlace(World, int, int, int, AutoPlaceEnvironment, FluidBlockRequirement)
*/
public static PlaceResult tryPlace(World world, int x, int y, int z, AutoPlaceEnvironment env,
Iterable
* This is what a fluid structure element uses for its check, so that a check and an autoplace always agree.
*
* @param world world to look at
* @param x x coord
* @param y y coord
* @param z z coord
* @param requirement which block the structure wants there, and how strict the check is
*/
public static boolean isSatisfied(World world, int x, int y, int z, FluidBlockRequirement requirement) {
Block worldBlock = world.getBlock(x, y, z);
if (worldBlock != requirement.block()) return false;
if (world.getBlockMetadata(x, y, z) == requirement.meta()) return true;
if (requirement.policy() == FluidFillPolicy.EXACT_STATE) return false;
int target = requirement.cost().amount;
return requirement.policy().isSatisfied(FluidAmounts.getStoredAmount(world, x, y, z, target), target);
}
/**
* Put the fluid block into the world without paying for it, which is what a creative mode build does.
*
* Unlike {@link #tryPlace(World, int, int, int, AutoPlaceEnvironment, FluidBlockRequirement)} this does not check
* whether the structure is ready to hold the fluid. A creative mode build places everything that isn't a fluid
* first for that reason.
*
* @return true if the block has been placed
*/
public static boolean place(World world, int x, int y, int z, FluidBlockRequirement requirement) {
return placeFluidBlock(world, x, y, z, requirement);
}
/**
* Returns a human-readable description of the structure element requirement.
*/
public static String describe(FluidBlockRequirement requirement) {
FluidStack stack = requirement.cost();
return StatCollector.translateToLocalFormatted(
"structurelib.autoplace.requirement_fluid",
stack.getLocalizedName(),
NumberFormatUtil.formatFluid(stack.amount));
}
/**
* 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.
*
* @param world world to place in
* @param x x coord
* @param y y coord
* @param z z coord
* @param env autoplace environment
*/
public static boolean isGateOpen(World world, int x, int y, int z, AutoPlaceEnvironment env) {
GateMode mode = ConfigurationHandler.INSTANCE.getFluidGateMode();
if (mode == GateMode.NONE) return true;
if (mode == GateMode.LENIENT) return isLocallyContained(world, x, y, z, env);
Boolean cached = env.getFluidGateResult();
if (cached != null) return cached;
boolean ready = isStructureReady(world, env);
env.setFluidGateResult(ready);
return ready;
}
/**
* Whether every non fluid element of the structure is satisfied, i.e. whether the structure is built far enough to
* hold the fluid.
*
* An autoplace environment that doesn't know its structure, e.g. one handed to a legacy caller, always answers
* true, as there is nothing to be checked against.
*/
private static boolean isStructureReady(World world, AutoPlaceEnvironment env) {
IStructureDefinition> definition = env.getDefinition();
int[] base = env.getBasePositionXYZ();
if (definition == null || base == null) return true;
Object context = env.getContextObject();
int[] baseOffset = env.getBaseOffsetABC();
ExtendedFacing facing = env.getFacing();
IStructureElement