CreateMod/src/main/java/com/simibubi/create/foundation/ponder/SceneBuilder.java
simibubi 66797f233d PR tidy-up
- backport of #4998
- Rename localisation readme so github displays it in the folder view
2023-07-03 18:34:47 +02:00

925 lines
36 KiB
Java

package com.simibubi.create.foundation.ponder;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import com.mojang.math.Vector3f;
import com.simibubi.create.content.contraptions.actors.trainControls.ControlsBlock;
import com.simibubi.create.content.contraptions.glue.SuperGlueItem;
import com.simibubi.create.content.fluids.pump.PumpBlockEntity;
import com.simibubi.create.content.kinetics.base.IRotate.SpeedLevel;
import com.simibubi.create.content.kinetics.base.KineticBlock;
import com.simibubi.create.content.kinetics.base.KineticBlockEntity;
import com.simibubi.create.content.kinetics.base.RotationIndicatorParticleData;
import com.simibubi.create.content.kinetics.belt.BeltBlockEntity;
import com.simibubi.create.content.kinetics.belt.behaviour.DirectBeltInputBehaviour;
import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour;
import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour.TransportedResult;
import com.simibubi.create.content.kinetics.crafter.ConnectedInputHandler;
import com.simibubi.create.content.kinetics.crafter.MechanicalCrafterBlockEntity;
import com.simibubi.create.content.kinetics.gauge.SpeedGaugeBlockEntity;
import com.simibubi.create.content.kinetics.mechanicalArm.ArmBlockEntity;
import com.simibubi.create.content.logistics.funnel.FunnelBlockEntity;
import com.simibubi.create.content.processing.burner.BlazeBurnerBlockEntity;
import com.simibubi.create.content.redstone.displayLink.DisplayLinkBlockEntity;
import com.simibubi.create.content.trains.display.FlapDisplayBlockEntity;
import com.simibubi.create.content.trains.signal.SignalBlockEntity;
import com.simibubi.create.content.trains.signal.SignalBlockEntity.SignalState;
import com.simibubi.create.content.trains.station.StationBlockEntity;
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
import com.simibubi.create.foundation.ponder.element.AnimatedSceneElement;
import com.simibubi.create.foundation.ponder.element.BeltItemElement;
import com.simibubi.create.foundation.ponder.element.EntityElement;
import com.simibubi.create.foundation.ponder.element.InputWindowElement;
import com.simibubi.create.foundation.ponder.element.MinecartElement;
import com.simibubi.create.foundation.ponder.element.MinecartElement.MinecartConstructor;
import com.simibubi.create.foundation.ponder.element.ParrotElement;
import com.simibubi.create.foundation.ponder.element.ParrotElement.ParrotPose;
import com.simibubi.create.foundation.ponder.element.ParrotElement.SpinOnComponentPose;
import com.simibubi.create.foundation.ponder.element.TextWindowElement;
import com.simibubi.create.foundation.ponder.element.WorldSectionElement;
import com.simibubi.create.foundation.ponder.instruction.AnimateBlockEntityInstruction;
import com.simibubi.create.foundation.ponder.instruction.AnimateMinecartInstruction;
import com.simibubi.create.foundation.ponder.instruction.AnimateParrotInstruction;
import com.simibubi.create.foundation.ponder.instruction.AnimateWorldSectionInstruction;
import com.simibubi.create.foundation.ponder.instruction.BlockEntityDataInstruction;
import com.simibubi.create.foundation.ponder.instruction.ChaseAABBInstruction;
import com.simibubi.create.foundation.ponder.instruction.CreateMinecartInstruction;
import com.simibubi.create.foundation.ponder.instruction.CreateParrotInstruction;
import com.simibubi.create.foundation.ponder.instruction.DelayInstruction;
import com.simibubi.create.foundation.ponder.instruction.DisplayWorldSectionInstruction;
import com.simibubi.create.foundation.ponder.instruction.EmitParticlesInstruction;
import com.simibubi.create.foundation.ponder.instruction.EmitParticlesInstruction.Emitter;
import com.simibubi.create.foundation.ponder.instruction.FadeOutOfSceneInstruction;
import com.simibubi.create.foundation.ponder.instruction.HighlightValueBoxInstruction;
import com.simibubi.create.foundation.ponder.instruction.KeyframeInstruction;
import com.simibubi.create.foundation.ponder.instruction.LineInstruction;
import com.simibubi.create.foundation.ponder.instruction.MarkAsFinishedInstruction;
import com.simibubi.create.foundation.ponder.instruction.MovePoiInstruction;
import com.simibubi.create.foundation.ponder.instruction.OutlineSelectionInstruction;
import com.simibubi.create.foundation.ponder.instruction.PonderInstruction;
import com.simibubi.create.foundation.ponder.instruction.ReplaceBlocksInstruction;
import com.simibubi.create.foundation.ponder.instruction.RotateSceneInstruction;
import com.simibubi.create.foundation.ponder.instruction.ShowInputInstruction;
import com.simibubi.create.foundation.ponder.instruction.TextInstruction;
import com.simibubi.create.foundation.utility.Color;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Direction.Axis;
import net.minecraft.core.Vec3i;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RedstoneTorchBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
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.Property;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
/**
* Enqueue instructions to the schedule via this object's methods.
*/
public class SceneBuilder {
/**
* Ponder's toolkit for showing information on top of the scene world, such as
* highlighted bounding boxes, texts, icons and keybindings.
*/
public final OverlayInstructions overlay;
/**
* Instructions for manipulating the schematic and its currently visible areas.
* Allows to show, hide and modify blocks as the scene plays out.
*/
public final WorldInstructions world;
/**
* Additional tools for debugging ponder and bypassing the facade
*/
public final DebugInstructions debug;
/**
* Special effects to embellish and communicate with
*/
public final EffectInstructions effects;
/**
* Random other instructions that might come in handy
*/
public final SpecialInstructions special;
private final PonderScene scene;
public SceneBuilder(PonderScene ponderScene) {
scene = ponderScene;
overlay = new OverlayInstructions();
special = new SpecialInstructions();
world = new WorldInstructions();
debug = new DebugInstructions();
effects = new EffectInstructions();
}
// General
/**
* Assign a unique translation key, as well as the standard english translation
* for this scene's title using this method, anywhere inside the program
* function.
*
* @param sceneId
* @param title
*/
public void title(String sceneId, String title) {
scene.sceneId = new ResourceLocation(scene.getNamespace(), sceneId);
PonderLocalization.registerSpecific(scene.sceneId, PonderScene.TITLE_KEY, title);
}
/**
* Communicates to the ponder UI which parts of the schematic make up the base
* horizontally. Use of this is encouraged whenever there are components outside
* the the base plate. <br>
* As a result, showBasePlate() will only show the configured size, and the
* scene's scaling inside the UI will be consistent with its base size.
*
* @param xOffset Block spaces between the base plate and the schematic
* boundary on the Western side.
* @param zOffset Block spaces between the base plate and the schematic
* boundary on the Northern side.
* @param basePlateSize Length in blocks of the base plate itself. Ponder
* assumes it to be square
*/
public void configureBasePlate(int xOffset, int zOffset, int basePlateSize) {
scene.basePlateOffsetX = xOffset;
scene.basePlateOffsetZ = zOffset;
scene.basePlateSize = basePlateSize;
}
/**
* Use this in case you are not happy with the scale of the scene relative to
* the overlay
*
* @param factor {@literal >}1 will make the scene appear larger, smaller
* otherwise
*/
public void scaleSceneView(float factor) {
scene.scaleFactor = factor;
}
/**
* Use this to disable the base plates' shadow for this scene
*/
public void removeShadow() {
scene.hidePlatformShadow = true;
}
/**
* Use this in case you are not happy with the vertical alignment of the scene
* relative to the overlay
*
* @param yOffset {@literal >}0 moves the scene up, down otherwise
*/
public void setSceneOffsetY(float yOffset) {
scene.yOffset = yOffset;
}
/**
* Fade the layer of blocks into the scene ponder assumes to be the base plate
* of the schematic's structure. Makes for a nice opener
*/
public void showBasePlate() {
world.showSection(scene.getSceneBuildingUtil().select.cuboid(
new BlockPos(scene.getBasePlateOffsetX(), 0, scene.getBasePlateOffsetZ()),
new Vec3i(scene.getBasePlateSize() - 1, 0, scene.getBasePlateSize() - 1)), Direction.UP);
}
/**
* Adds an instruction to the scene. It is recommended to only use this method
* if another method in this class or its subclasses does not already allow
* adding a certain instruction.
*/
public void addInstruction(PonderInstruction instruction) {
scene.schedule.add(instruction);
}
/**
* Adds a simple instruction to the scene. It is recommended to only use this
* method if another method in this class or its subclasses does not already
* allow adding a certain instruction.
*/
public void addInstruction(Consumer<PonderScene> callback) {
addInstruction(PonderInstruction.simple(callback));
}
/**
* Before running the upcoming instructions, wait for a duration to let previous
* actions play out. <br>
* Idle does not stall any animations, only schedules a time gap between
* instructions.
*
* @param ticks Duration to wait for
*/
public void idle(int ticks) {
addInstruction(new DelayInstruction(ticks));
}
/**
* Before running the upcoming instructions, wait for a duration to let previous
* actions play out. <br>
* Idle does not stall any animations, only schedules a time gap between
* instructions.
*
* @param seconds Duration to wait for
*/
public void idleSeconds(int seconds) {
idle(seconds * 20);
}
/**
* Once the scene reaches this instruction in the timeline, mark it as
* "finished". This happens automatically when the end of a storyboard is
* reached, but can be desirable to do earlier, in order to bypass the wait for
* any residual text windows to time out. <br>
* So far this event only affects the "next scene" button in the UI to flash.
*/
public void markAsFinished() {
addInstruction(new MarkAsFinishedInstruction());
}
/**
* Pans the scene's camera view around the vertical axis by the given amount
*
* @param degrees
*/
public void rotateCameraY(float degrees) {
addInstruction(new RotateSceneInstruction(0, degrees, true));
}
/**
* Adds a Key Frame at the end of the last delay() instruction for the users to
* skip to
*/
public void addKeyframe() {
addInstruction(KeyframeInstruction.IMMEDIATE);
}
/**
* Adds a Key Frame a couple ticks after the last delay() instruction for the
* users to skip to
*/
public void addLazyKeyframe() {
addInstruction(KeyframeInstruction.DELAYED);
}
public class EffectInstructions {
public void emitParticles(Vec3 location, Emitter emitter, float amountPerCycle, int cycles) {
addInstruction(new EmitParticlesInstruction(location, emitter, amountPerCycle, cycles));
}
public void superGlue(BlockPos pos, Direction side, boolean fullBlock) {
addInstruction(scene -> SuperGlueItem.spawnParticles(scene.getWorld(), pos, side, fullBlock));
}
private void rotationIndicator(BlockPos pos, boolean direction, BlockPos displayPos) {
addInstruction(scene -> {
BlockState blockState = scene.getWorld()
.getBlockState(pos);
BlockEntity blockEntity = scene.getWorld()
.getBlockEntity(pos);
if (!(blockState.getBlock() instanceof KineticBlock))
return;
if (!(blockEntity instanceof KineticBlockEntity))
return;
KineticBlockEntity kbe = (KineticBlockEntity) blockEntity;
KineticBlock kb = (KineticBlock) blockState.getBlock();
Axis rotationAxis = kb.getRotationAxis(blockState);
float speed = kbe.getTheoreticalSpeed();
SpeedLevel speedLevel = SpeedLevel.of(speed);
int color = direction ? speed > 0 ? 0xeb5e0b : 0x1687a7 : speedLevel.getColor();
int particleSpeed = speedLevel.getParticleSpeed();
particleSpeed *= Math.signum(speed);
Vec3 location = VecHelper.getCenterOf(displayPos);
RotationIndicatorParticleData particleData = new RotationIndicatorParticleData(color, particleSpeed,
kb.getParticleInitialRadius(), kb.getParticleTargetRadius(), 20, rotationAxis.name()
.charAt(0));
for (int i = 0; i < 20; i++)
scene.getWorld()
.addParticle(particleData, location.x, location.y, location.z, 0, 0, 0);
});
}
public void rotationSpeedIndicator(BlockPos pos) {
rotationIndicator(pos, false, pos);
}
public void rotationDirectionIndicator(BlockPos pos) {
rotationIndicator(pos, true, pos);
}
public void rotationSpeedIndicator(BlockPos pos, BlockPos displayPos) {
rotationIndicator(pos, false, displayPos);
}
public void rotationDirectionIndicator(BlockPos pos, BlockPos displayPos) {
rotationIndicator(pos, true, displayPos);
}
public void indicateRedstone(BlockPos pos) {
createRedstoneParticles(pos, 0xFF0000, 10);
}
public void indicateSuccess(BlockPos pos) {
createRedstoneParticles(pos, 0x80FFaa, 10);
}
public void createRedstoneParticles(BlockPos pos, int color, int amount) {
Vector3f rgb = new Color(color).asVectorF();
addInstruction(new EmitParticlesInstruction(VecHelper.getCenterOf(pos),
Emitter.withinBlockSpace(new DustParticleOptions(rgb, 1), Vec3.ZERO), amount, 2));
}
}
public class OverlayInstructions {
public TextWindowElement.Builder showText(int duration) {
TextWindowElement textWindowElement = new TextWindowElement();
addInstruction(new TextInstruction(textWindowElement, duration));
return textWindowElement.new Builder(scene);
}
public TextWindowElement.Builder showSelectionWithText(Selection selection, int duration) {
TextWindowElement textWindowElement = new TextWindowElement();
addInstruction(new TextInstruction(textWindowElement, duration, selection));
return textWindowElement.new Builder(scene).pointAt(selection.getCenter());
}
public void showControls(InputWindowElement element, int duration) {
addInstruction(new ShowInputInstruction(element.clone(), duration));
}
public void chaseBoundingBoxOutline(PonderPalette color, Object slot, AABB boundingBox, int duration) {
addInstruction(new ChaseAABBInstruction(color, slot, boundingBox, duration));
}
public void showCenteredScrollInput(BlockPos pos, Direction side, int duration) {
showFilterSlotInput(scene.getSceneBuildingUtil().vector.blockSurface(pos, side), side, duration);
}
public void showScrollInput(Vec3 location, Direction side, int duration) {
Axis axis = side.getAxis();
float s = 1 / 16f;
float q = 1 / 4f;
Vec3 expands = new Vec3(axis == Axis.X ? s : q, axis == Axis.Y ? s : q, axis == Axis.Z ? s : q);
addInstruction(new HighlightValueBoxInstruction(location, expands, duration));
}
public void showRepeaterScrollInput(BlockPos pos, int duration) {
showFilterSlotInput(scene.getSceneBuildingUtil().vector.blockSurface(pos, Direction.DOWN)
.add(0, 3 / 16f, 0), Direction.UP, duration);
}
public void showFilterSlotInput(Vec3 location, Direction side, int duration) {
location = location.add(Vec3.atLowerCornerOf(side.getNormal())
.scale(-3 / 128f));
Vec3 expands = VecHelper.axisAlingedPlaneOf(side)
.scale(11 / 128f);
addInstruction(new HighlightValueBoxInstruction(location, expands, duration));
}
public void showLine(PonderPalette color, Vec3 start, Vec3 end, int duration) {
addInstruction(new LineInstruction(color, start, end, duration, false));
}
public void showBigLine(PonderPalette color, Vec3 start, Vec3 end, int duration) {
addInstruction(new LineInstruction(color, start, end, duration, true));
}
public void showOutline(PonderPalette color, Object slot, Selection selection, int duration) {
addInstruction(new OutlineSelectionInstruction(color, slot, selection, duration));
}
}
public class SpecialInstructions {
public ElementLink<ParrotElement> birbOnTurntable(BlockPos pos) {
return createBirb(VecHelper.getCenterOf(pos), () -> new SpinOnComponentPose(pos));
}
public ElementLink<ParrotElement> birbOnSpinnyShaft(BlockPos pos) {
return createBirb(VecHelper.getCenterOf(pos)
.add(0, 0.5, 0), () -> new SpinOnComponentPose(pos));
}
public ElementLink<ParrotElement> createBirb(Vec3 location, Supplier<? extends ParrotPose> pose) {
ElementLink<ParrotElement> link = new ElementLink<>(ParrotElement.class);
ParrotElement parrot = ParrotElement.create(location, pose);
addInstruction(new CreateParrotInstruction(10, Direction.DOWN, parrot));
addInstruction(scene -> scene.linkElement(parrot, link));
return link;
}
public void changeBirbPose(ElementLink<ParrotElement> birb, Supplier<? extends ParrotPose> pose) {
addInstruction(scene -> scene.resolve(birb)
.setPose(pose.get()));
}
public void conductorBirb(ElementLink<ParrotElement> birb, boolean conductor) {
addInstruction(scene -> scene.resolve(birb)
.setConductor(conductor));
}
public void movePointOfInterest(Vec3 location) {
addInstruction(new MovePoiInstruction(location));
}
public void movePointOfInterest(BlockPos location) {
movePointOfInterest(VecHelper.getCenterOf(location));
}
public void rotateParrot(ElementLink<ParrotElement> link, double xRotation, double yRotation, double zRotation,
int duration) {
addInstruction(AnimateParrotInstruction.rotate(link, new Vec3(xRotation, yRotation, zRotation), duration));
}
public void moveParrot(ElementLink<ParrotElement> link, Vec3 offset, int duration) {
addInstruction(AnimateParrotInstruction.move(link, offset, duration));
}
public ElementLink<MinecartElement> createCart(Vec3 location, float angle, MinecartConstructor type) {
ElementLink<MinecartElement> link = new ElementLink<>(MinecartElement.class);
MinecartElement cart = new MinecartElement(location, angle, type);
addInstruction(new CreateMinecartInstruction(10, Direction.DOWN, cart));
addInstruction(scene -> scene.linkElement(cart, link));
return link;
}
public void rotateCart(ElementLink<MinecartElement> link, float yRotation, int duration) {
addInstruction(AnimateMinecartInstruction.rotate(link, yRotation, duration));
}
public void moveCart(ElementLink<MinecartElement> link, Vec3 offset, int duration) {
addInstruction(AnimateMinecartInstruction.move(link, offset, duration));
}
public <T extends AnimatedSceneElement> void hideElement(ElementLink<T> link, Direction direction) {
addInstruction(new FadeOutOfSceneInstruction<>(15, direction, link));
}
}
public class WorldInstructions {
public void incrementBlockBreakingProgress(BlockPos pos) {
addInstruction(scene -> {
PonderWorld world = scene.getWorld();
int progress = world.getBlockBreakingProgressions()
.getOrDefault(pos, -1) + 1;
if (progress == 9) {
world.addBlockDestroyEffects(pos, world.getBlockState(pos));
world.destroyBlock(pos, false);
world.setBlockBreakingProgress(pos, 0);
scene.forEach(WorldSectionElement.class, WorldSectionElement::queueRedraw);
} else
world.setBlockBreakingProgress(pos, progress + 1);
});
}
public void showSection(Selection selection, Direction fadeInDirection) {
addInstruction(new DisplayWorldSectionInstruction(15, fadeInDirection, selection,
Optional.of(scene::getBaseWorldSection)));
}
public void showSectionAndMerge(Selection selection, Direction fadeInDirection,
ElementLink<WorldSectionElement> link) {
addInstruction(new DisplayWorldSectionInstruction(15, fadeInDirection, selection,
Optional.of(() -> scene.resolve(link))));
}
public void glueBlockOnto(BlockPos position, Direction fadeInDirection, ElementLink<WorldSectionElement> link) {
addInstruction(new DisplayWorldSectionInstruction(15, fadeInDirection,
scene.getSceneBuildingUtil().select.position(position), Optional.of(() -> scene.resolve(link)),
position));
}
public ElementLink<WorldSectionElement> showIndependentSection(Selection selection, Direction fadeInDirection) {
DisplayWorldSectionInstruction instruction =
new DisplayWorldSectionInstruction(15, fadeInDirection, selection, Optional.empty());
addInstruction(instruction);
return instruction.createLink(scene);
}
public ElementLink<WorldSectionElement> showIndependentSection(Selection selection, Direction fadeInDirection,
int fadeInDuration) {
DisplayWorldSectionInstruction instruction =
new DisplayWorldSectionInstruction(fadeInDuration, fadeInDirection, selection, Optional.empty());
addInstruction(instruction);
return instruction.createLink(scene);
}
public ElementLink<WorldSectionElement> showIndependentSectionImmediately(Selection selection) {
DisplayWorldSectionInstruction instruction =
new DisplayWorldSectionInstruction(0, Direction.DOWN, selection, Optional.empty());
addInstruction(instruction);
return instruction.createLink(scene);
}
public void hideSection(Selection selection, Direction fadeOutDirection) {
WorldSectionElement worldSectionElement = new WorldSectionElement(selection);
ElementLink<WorldSectionElement> elementLink = new ElementLink<>(WorldSectionElement.class);
addInstruction(scene -> {
scene.getBaseWorldSection()
.erase(selection);
scene.linkElement(worldSectionElement, elementLink);
scene.addElement(worldSectionElement);
worldSectionElement.queueRedraw();
});
hideIndependentSection(elementLink, fadeOutDirection);
}
public void hideIndependentSection(ElementLink<WorldSectionElement> link, Direction fadeOutDirection) {
addInstruction(new FadeOutOfSceneInstruction<>(15, fadeOutDirection, link));
}
public void hideIndependentSection(ElementLink<WorldSectionElement> link, Direction fadeOutDirection,
int fadeOutDuration) {
addInstruction(new FadeOutOfSceneInstruction<>(fadeOutDuration, fadeOutDirection, link));
}
public void hideIndependentSectionImmediately(ElementLink<WorldSectionElement> link) {
addInstruction(new FadeOutOfSceneInstruction<>(0, Direction.DOWN, link));
}
public void restoreBlocks(Selection selection) {
addInstruction(scene -> scene.getWorld()
.restoreBlocks(selection));
}
public ElementLink<WorldSectionElement> makeSectionIndependent(Selection selection) {
WorldSectionElement worldSectionElement = new WorldSectionElement(selection);
ElementLink<WorldSectionElement> elementLink = new ElementLink<>(WorldSectionElement.class);
addInstruction(scene -> {
scene.getBaseWorldSection()
.erase(selection);
scene.linkElement(worldSectionElement, elementLink);
scene.addElement(worldSectionElement);
worldSectionElement.queueRedraw();
worldSectionElement.resetAnimatedTransform();
worldSectionElement.setVisible(true);
worldSectionElement.forceApplyFade(1);
});
return elementLink;
}
public void rotateSection(ElementLink<WorldSectionElement> link, double xRotation, double yRotation,
double zRotation, int duration) {
addInstruction(
AnimateWorldSectionInstruction.rotate(link, new Vec3(xRotation, yRotation, zRotation), duration));
}
public void configureCenterOfRotation(ElementLink<WorldSectionElement> link, Vec3 anchor) {
addInstruction(scene -> scene.resolve(link)
.setCenterOfRotation(anchor));
}
public void configureStabilization(ElementLink<WorldSectionElement> link, Vec3 anchor) {
addInstruction(scene -> scene.resolve(link)
.stabilizeRotation(anchor));
}
public void moveSection(ElementLink<WorldSectionElement> link, Vec3 offset, int duration) {
addInstruction(AnimateWorldSectionInstruction.move(link, offset, duration));
}
public void rotateBearing(BlockPos pos, float angle, int duration) {
addInstruction(AnimateBlockEntityInstruction.bearing(pos, angle, duration));
}
public void movePulley(BlockPos pos, float distance, int duration) {
addInstruction(AnimateBlockEntityInstruction.pulley(pos, distance, duration));
}
public void animateBogey(BlockPos pos, float distance, int duration) {
addInstruction(AnimateBlockEntityInstruction.bogey(pos, distance, duration + 1));
}
public void moveDeployer(BlockPos pos, float distance, int duration) {
addInstruction(AnimateBlockEntityInstruction.deployer(pos, distance, duration));
}
public void setBlocks(Selection selection, BlockState state, boolean spawnParticles) {
addInstruction(new ReplaceBlocksInstruction(selection, $ -> state, true, spawnParticles));
}
public void destroyBlock(BlockPos pos) {
setBlock(pos, Blocks.AIR.defaultBlockState(), true);
}
public void setBlock(BlockPos pos, BlockState state, boolean spawnParticles) {
setBlocks(scene.getSceneBuildingUtil().select.position(pos), state, spawnParticles);
}
public void replaceBlocks(Selection selection, BlockState state, boolean spawnParticles) {
modifyBlocks(selection, $ -> state, spawnParticles);
}
public void modifyBlock(BlockPos pos, UnaryOperator<BlockState> stateFunc, boolean spawnParticles) {
modifyBlocks(scene.getSceneBuildingUtil().select.position(pos), stateFunc, spawnParticles);
}
public void cycleBlockProperty(BlockPos pos, Property<?> property) {
modifyBlocks(scene.getSceneBuildingUtil().select.position(pos),
s -> s.hasProperty(property) ? s.cycle(property) : s, false);
}
public void modifyBlocks(Selection selection, UnaryOperator<BlockState> stateFunc, boolean spawnParticles) {
addInstruction(new ReplaceBlocksInstruction(selection, stateFunc, false, spawnParticles));
}
public void toggleRedstonePower(Selection selection) {
modifyBlocks(selection, s -> {
if (s.hasProperty(BlockStateProperties.POWER))
s = s.setValue(BlockStateProperties.POWER, s.getValue(BlockStateProperties.POWER) == 0 ? 15 : 0);
if (s.hasProperty(BlockStateProperties.POWERED))
s = s.cycle(BlockStateProperties.POWERED);
if (s.hasProperty(RedstoneTorchBlock.LIT))
s = s.cycle(RedstoneTorchBlock.LIT);
return s;
}, false);
}
public <T extends Entity> void modifyEntities(Class<T> entityClass, Consumer<T> entityCallBack) {
addInstruction(scene -> scene.forEachWorldEntity(entityClass, entityCallBack));
}
public <T extends Entity> void modifyEntitiesInside(Class<T> entityClass, Selection area,
Consumer<T> entityCallBack) {
addInstruction(scene -> scene.forEachWorldEntity(entityClass, e -> {
if (area.test(e.blockPosition()))
entityCallBack.accept(e);
}));
}
public void modifyEntity(ElementLink<EntityElement> link, Consumer<Entity> entityCallBack) {
addInstruction(scene -> {
EntityElement resolve = scene.resolve(link);
if (resolve != null)
resolve.ifPresent(entityCallBack::accept);
});
}
public ElementLink<EntityElement> createEntity(Function<Level, Entity> factory) {
ElementLink<EntityElement> link = new ElementLink<>(EntityElement.class, UUID.randomUUID());
addInstruction(scene -> {
PonderWorld world = scene.getWorld();
Entity entity = factory.apply(world);
EntityElement handle = new EntityElement(entity);
scene.addElement(handle);
scene.linkElement(handle, link);
world.addFreshEntity(entity);
});
return link;
}
public ElementLink<EntityElement> createItemEntity(Vec3 location, Vec3 motion, ItemStack stack) {
return createEntity(world -> {
ItemEntity itemEntity = new ItemEntity(world, location.x, location.y, location.z, stack);
itemEntity.setDeltaMovement(motion);
return itemEntity;
});
}
public void createItemOnBeltLike(BlockPos location, Direction insertionSide, ItemStack stack) {
addInstruction(scene -> {
PonderWorld world = scene.getWorld();
BlockEntity blockEntity = world.getBlockEntity(location);
if (!(blockEntity instanceof SmartBlockEntity))
return;
SmartBlockEntity beltBlockEntity = (SmartBlockEntity) blockEntity;
DirectBeltInputBehaviour behaviour = beltBlockEntity.getBehaviour(DirectBeltInputBehaviour.TYPE);
if (behaviour == null)
return;
behaviour.handleInsertion(stack, insertionSide.getOpposite(), false);
});
flapFunnel(location.above(), true);
}
public ElementLink<BeltItemElement> createItemOnBelt(BlockPos beltLocation, Direction insertionSide,
ItemStack stack) {
ElementLink<BeltItemElement> link = new ElementLink<>(BeltItemElement.class);
addInstruction(scene -> {
PonderWorld world = scene.getWorld();
BlockEntity blockEntity = world.getBlockEntity(beltLocation);
if (!(blockEntity instanceof BeltBlockEntity))
return;
BeltBlockEntity beltBlockEntity = (BeltBlockEntity) blockEntity;
DirectBeltInputBehaviour behaviour = beltBlockEntity.getBehaviour(DirectBeltInputBehaviour.TYPE);
behaviour.handleInsertion(stack, insertionSide.getOpposite(), false);
BeltBlockEntity controllerBE = beltBlockEntity.getControllerBE();
if (controllerBE != null)
controllerBE.tick();
TransportedItemStackHandlerBehaviour transporter =
beltBlockEntity.getBehaviour(TransportedItemStackHandlerBehaviour.TYPE);
transporter.handleProcessingOnAllItems(tis -> {
BeltItemElement tracker = new BeltItemElement(tis);
scene.addElement(tracker);
scene.linkElement(tracker, link);
return TransportedResult.doNothing();
});
});
flapFunnel(beltLocation.above(), true);
return link;
}
public void removeItemsFromBelt(BlockPos beltLocation) {
addInstruction(scene -> {
PonderWorld world = scene.getWorld();
BlockEntity blockEntity = world.getBlockEntity(beltLocation);
if (!(blockEntity instanceof SmartBlockEntity))
return;
SmartBlockEntity beltBlockEntity = (SmartBlockEntity) blockEntity;
TransportedItemStackHandlerBehaviour transporter =
beltBlockEntity.getBehaviour(TransportedItemStackHandlerBehaviour.TYPE);
if (transporter == null)
return;
transporter.handleCenteredProcessingOnAllItems(.52f, tis -> TransportedResult.removeItem());
});
}
public void stallBeltItem(ElementLink<BeltItemElement> link, boolean stalled) {
addInstruction(scene -> {
BeltItemElement resolve = scene.resolve(link);
if (resolve != null)
resolve.ifPresent(tis -> tis.locked = stalled);
});
}
public void changeBeltItemTo(ElementLink<BeltItemElement> link, ItemStack newStack) {
addInstruction(scene -> {
BeltItemElement resolve = scene.resolve(link);
if (resolve != null)
resolve.ifPresent(tis -> tis.stack = newStack);
});
}
public void setKineticSpeed(Selection selection, float speed) {
modifyKineticSpeed(selection, f -> speed);
}
public void multiplyKineticSpeed(Selection selection, float modifier) {
modifyKineticSpeed(selection, f -> f * modifier);
}
public void modifyKineticSpeed(Selection selection, UnaryOperator<Float> speedFunc) {
modifyBlockEntityNBT(selection, SpeedGaugeBlockEntity.class, nbt -> {
float newSpeed = speedFunc.apply(nbt.getFloat("Speed"));
nbt.putFloat("Value", SpeedGaugeBlockEntity.getDialTarget(newSpeed));
});
modifyBlockEntityNBT(selection, KineticBlockEntity.class, nbt -> {
nbt.putFloat("Speed", speedFunc.apply(nbt.getFloat("Speed")));
});
}
public void propagatePipeChange(BlockPos pos) {
modifyBlockEntity(pos, PumpBlockEntity.class, be -> be.onSpeedChanged(0));
}
public void setFilterData(Selection selection, Class<? extends BlockEntity> teType, ItemStack filter) {
modifyBlockEntityNBT(selection, teType, nbt -> {
nbt.put("Filter", filter.serializeNBT());
});
}
public void modifyBlockEntityNBT(Selection selection, Class<? extends BlockEntity> beType,
Consumer<CompoundTag> consumer) {
modifyBlockEntityNBT(selection, beType, consumer, false);
}
public <T extends BlockEntity> void modifyBlockEntity(BlockPos position, Class<T> beType,
Consumer<T> consumer) {
addInstruction(scene -> {
BlockEntity blockEntity = scene.getWorld()
.getBlockEntity(position);
if (beType.isInstance(blockEntity))
consumer.accept(beType.cast(blockEntity));
});
}
public void modifyBlockEntityNBT(Selection selection, Class<? extends BlockEntity> teType,
Consumer<CompoundTag> consumer, boolean reDrawBlocks) {
addInstruction(new BlockEntityDataInstruction(selection, teType, nbt -> {
consumer.accept(nbt);
return nbt;
}, reDrawBlocks));
}
public void instructArm(BlockPos armLocation, ArmBlockEntity.Phase phase, ItemStack heldItem,
int targetedPoint) {
modifyBlockEntityNBT(scene.getSceneBuildingUtil().select.position(armLocation), ArmBlockEntity.class,
compound -> {
NBTHelper.writeEnum(compound, "Phase", phase);
compound.put("HeldItem", heldItem.serializeNBT());
compound.putInt("TargetPointIndex", targetedPoint);
compound.putFloat("MovementProgress", 0);
});
}
public void flapFunnel(BlockPos position, boolean outward) {
modifyBlockEntity(position, FunnelBlockEntity.class, funnel -> funnel.flap(!outward));
}
public void setCraftingResult(BlockPos crafter, ItemStack output) {
modifyBlockEntity(crafter, MechanicalCrafterBlockEntity.class, mct -> mct.setScriptedResult(output));
}
public void connectCrafterInvs(BlockPos position1, BlockPos position2) {
addInstruction(s -> {
ConnectedInputHandler.toggleConnection(s.getWorld(), position1, position2);
s.forEach(WorldSectionElement.class, WorldSectionElement::queueRedraw);
});
}
public void toggleControls(BlockPos position) {
cycleBlockProperty(position, ControlsBlock.VIRTUAL);
}
public void animateTrainStation(BlockPos position, boolean trainPresent) {
modifyBlockEntityNBT(scene.getSceneBuildingUtil().select.position(position), StationBlockEntity.class,
c -> c.putBoolean("ForceFlag", trainPresent));
}
public void conductorBlaze(BlockPos position, boolean conductor) {
modifyBlockEntityNBT(scene.getSceneBuildingUtil().select.position(position), BlazeBurnerBlockEntity.class,
c -> c.putBoolean("TrainHat", conductor));
}
public void changeSignalState(BlockPos position, SignalState state) {
modifyBlockEntityNBT(scene.getSceneBuildingUtil().select.position(position), SignalBlockEntity.class,
c -> NBTHelper.writeEnum(c, "State", state));
}
public void setDisplayBoardText(BlockPos position, int line, Component text) {
modifyBlockEntity(position, FlapDisplayBlockEntity.class,
t -> t.applyTextManually(line, Component.Serializer.toJson(text)));
}
public void dyeDisplayBoard(BlockPos position, int line, DyeColor color) {
modifyBlockEntity(position, FlapDisplayBlockEntity.class, t -> t.setColour(line, color));
}
public void flashDisplayLink(BlockPos position) {
modifyBlockEntity(position, DisplayLinkBlockEntity.class,
linkBlockEntity -> linkBlockEntity.glow.setValue(2));
}
}
public class DebugInstructions {
public void debugSchematic() {
addInstruction(
scene -> scene.addElement(new WorldSectionElement(scene.getSceneBuildingUtil().select.everywhere())));
}
public void addInstructionInstance(PonderInstruction instruction) {
addInstruction(instruction);
}
public void enqueueCallback(Consumer<PonderScene> callback) {
addInstruction(callback);
}
}
}