RocketEntity.disassemble and dupe fixes. Fixed VAF menu not appearing.

This commit is contained in:
XevianLight
2026-02-08 02:01:14 -07:00
parent c0daaf2cfa
commit ff08a51540
12 changed files with 189 additions and 22 deletions

View File

@@ -42,7 +42,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvider, TickableBlockEntity {
public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvider, TickableBlockEntity, IArcFurnaceLike {
private final int SIZE = 4;
private int ENERGY_CAPACITY = 64000;
@@ -309,6 +309,11 @@ public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvide
level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 3);
}
@Override
public IEnergyStorage getTrueEnergyStorage() {
return ENERGY_STORAGE;
}
private final IItemHandler fullHandler = new SidedSlotHandler(inventory, new int[]{INPUT_SLOT, SECONDARY_INPUT_SLOT, OUTPUT_SLOT, ENERGY_SLOT}, true, true);
private final IItemHandler emptyJeiHandler = new SidedSlotHandler(inventory, new int[]{}, false, false);
private final IItemHandler inputHandler = new SidedSlotHandler(inventory, new int[]{0,1}, true, true);
@@ -344,6 +349,11 @@ public class ElectricArcFurnaceEntity extends BlockEntity implements MenuProvide
return inventory;
}
@Override
public ModEnergyStorage getEnergy() {
return ENERGY_STORAGE;
}
public void drops() {
SimpleContainer inv = new SimpleContainer(inventory.getSlots());
for(int i = 0; i < inventory.getSlots(); i++) {

View File

@@ -0,0 +1,20 @@
package net.xevianlight.aphelion.block.entity.custom;
import net.minecraft.core.Direction;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.neoforge.energy.IEnergyStorage;
import net.neoforged.neoforge.items.ItemStackHandler;
import net.xevianlight.aphelion.block.entity.energy.ModEnergyStorage;
public interface IArcFurnaceLike {
ItemStackHandler getInventory();
ModEnergyStorage getEnergy();
void sendUpdate();
BlockState getBlockState();
IEnergyStorage getTrueEnergyStorage();
}

View File

@@ -16,6 +16,7 @@ import net.minecraft.world.level.block.Block;
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.phys.AABB;
import net.xevianlight.aphelion.Aphelion;
import net.xevianlight.aphelion.block.custom.base.TickableBlockEntity;
import net.xevianlight.aphelion.core.init.ModBlockEntities;
@@ -35,6 +36,7 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
Direction facing;
BlockPos padScanStart = BlockPos.ZERO;
private PadInfo padBounds;
RocketEntity lastRocket;
public @Nullable PadInfo getPadBounds() {
return padBounds;
@@ -279,11 +281,14 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
if (level == null) return null;
RocketStructure structure = scan();
RocketEntity rocket = null;
if (lastRocket != null)
lastRocket.disassemble();
if (structure != null && seatPos != null) {
RocketStructure.clearCaptured(level, seatPos, structure);
rocket = RocketEntity.spawnRocket(level, seatPos, structure);
Aphelion.LOGGER.info("Spawn rocket result: {}", rocket);
}
lastRocket = rocket;
return rocket;
}
@@ -394,4 +399,6 @@ public class RocketAssemblerBlockEntity extends BlockEntity implements TickableB
this.loadAdditional(tag, registries);
}
}
}

View File

@@ -47,7 +47,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Optional;
public class VacuumArcFurnaceControllerEntity extends BlockEntity implements MenuProvider, IMultiblockController, TickableBlockEntity {
public class VacuumArcFurnaceControllerEntity extends BlockEntity implements MenuProvider, IMultiblockController, TickableBlockEntity, IArcFurnaceLike {
private final int SIZE = 4;
private final int ENERGY_CAPACITY = 64000;
@@ -357,7 +357,8 @@ public class VacuumArcFurnaceControllerEntity extends BlockEntity implements Men
return isFormed() ? ENERGY_STORAGE : null;
}
public IEnergyStorage getTrueEnergyStorage(@Nullable Direction direction) {
@Override
public IEnergyStorage getTrueEnergyStorage() {
return this.ENERGY_STORAGE;
}

View File

@@ -19,6 +19,24 @@ public class RocketAssemblerBlockEntityRenderer implements BlockEntityRenderer<R
}
@Override
public AABB getRenderBoundingBox(RocketAssemblerBlockEntity blockEntity) {
// If we don't know bounds yet, fall back to default BE culling.
RocketAssemblerBlockEntity.PadInfo pad = blockEntity.getPadBounds();
if (pad == null) {
return BlockEntityRenderer.super.getRenderBoundingBox(blockEntity);
}
BlockPos min = pad.min();
BlockPos max = pad.max();
// Expand slightly to avoid edge precision culling
return new AABB(
min.getX(), min.getY(), min.getZ(),
max.getX() + 1, max.getY() + 1, max.getZ() + 1
).inflate(0.5);
}
@Override
public void render(@NotNull RocketAssemblerBlockEntity be, float v, @NotNull PoseStack poseStack, @NotNull MultiBufferSource multiBufferSource, int i, int i1) {
// if (!Minecraft.getInstance().gui.getDebugOverlay().showDebugScreen()) return;

View File

@@ -474,6 +474,22 @@ public class AphelionCommand {
)
)
)
.then(Commands.literal("disassemble")
.executes(context -> {
Entity entity = EntityArgument.getEntity(context, "entity");
if (entity instanceof RocketEntity rocket) {
if (rocket.disassemble()) {
context.getSource().sendSuccess(() -> Component.translatable("command.aphelion.rocket.disassemble.success"), true);
} else {
context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.disassemble.failure"));
}
} else {
context.getSource().sendFailure(Component.translatable("command.aphelion.rocket.entity_invalid"));
}
return 1;
})
)
)
)

View File

@@ -506,4 +506,54 @@ public class RocketEntity extends VehicleEntity implements IEntityWithComplexSpa
return InteractionResult.sidedSuccess(level().isClientSide);
}
public boolean disassemble() {
if (level().isClientSide) return false;
if (!(level() instanceof ServerLevel server)) return false;
// Optional: only allow when not in flight
if (getPhase() == FlightPhase.ASCEND || getPhase() == FlightPhase.TRANSIT || getPhase() == FlightPhase.DESCEND) {
return false;
}
// Kick riders off first (so we don't place blocks inside them)
ejectPassengers();
// Anchor: blocks were captured relative to the scan origin.
// Your rocket is spawned at seatPos + (0.5, 0, 0.5), so we convert back to the block origin.
BlockPos origin = BlockPos.containing(getX(), getY(), getZ());
// Place blocks
for (int i = 0; i < structure.size(); i++) {
int packed = structure.packedPosAt(i);
int dx = RocketStructure.unpackX(packed);
int dy = RocketStructure.unpackY(packed);
int dz = RocketStructure.unpackZ(packed);
BlockPos wp = origin.offset(dx, dy, dz);
var stateToPlace = structure.stateAt(i);
// Don't place air (shouldn't exist in structure anyway, but safe)
if (stateToPlace.isAir()) continue;
// Safety: don't overwrite existing blocks
if (!server.getBlockState(wp).isAir()) {
// If you want strict behavior, abort entirely:
// return false;
// Otherwise just skip conflicts:
continue;
}
// Extra safety: avoid accidentally placing into portal/void/etc if desired
// if (!server.isInWorldBounds(wp)) continue;
server.setBlock(wp, stateToPlace, 3);
}
// Remove the rocket entity
discard(); // preferred over kill() for "just remove this entity"
return true;
}
}

View File

@@ -12,13 +12,14 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.neoforged.neoforge.items.SlotItemHandler;
import net.xevianlight.aphelion.block.entity.custom.ElectricArcFurnaceEntity;
import net.xevianlight.aphelion.block.entity.custom.IArcFurnaceLike;
import net.xevianlight.aphelion.util.EnergyItemSlot;
import net.xevianlight.aphelion.util.ExtractOnlySlot;
import org.jetbrains.annotations.NotNull;
public class ElectricArcFurnaceMenu extends AbstractContainerMenu {
public final ElectricArcFurnaceEntity blockEntity;
public final IArcFurnaceLike blockEntity;
private final Level level;
private final ContainerData data;
@@ -28,17 +29,17 @@ public class ElectricArcFurnaceMenu extends AbstractContainerMenu {
public ElectricArcFurnaceMenu(int i, Inventory inventory, BlockEntity blockEntity, ContainerData data) {
super(ModMenuTypes.ELECTRIC_ARC_FURNACE_MENU.get(), i);
this.blockEntity = ((ElectricArcFurnaceEntity) blockEntity);
this.blockEntity = ((IArcFurnaceLike) blockEntity);
this.level = inventory.player.level();
this.data = data;
addPlayerInventory(inventory);
addPlayerHotbar(inventory);
this.addSlot(new EnergyItemSlot(this.blockEntity.inventory, ElectricArcFurnaceEntity.ENERGY_SLOT, 8, 54));
this.addSlot(new SlotItemHandler(this.blockEntity.inventory, ElectricArcFurnaceEntity.INPUT_SLOT, 63, 35));
this.addSlot(new SlotItemHandler(this.blockEntity.inventory, ElectricArcFurnaceEntity.SECONDARY_INPUT_SLOT, 40, 35));
this.addSlot(new ExtractOnlySlot(this.blockEntity.inventory, ElectricArcFurnaceEntity.OUTPUT_SLOT, 125, 35));
this.addSlot(new EnergyItemSlot(this.blockEntity.getInventory(), ElectricArcFurnaceEntity.ENERGY_SLOT, 8, 54));
this.addSlot(new SlotItemHandler(this.blockEntity.getInventory(), ElectricArcFurnaceEntity.INPUT_SLOT, 63, 35));
this.addSlot(new SlotItemHandler(this.blockEntity.getInventory(), ElectricArcFurnaceEntity.SECONDARY_INPUT_SLOT, 40, 35));
this.addSlot(new ExtractOnlySlot(this.blockEntity.getInventory(), ElectricArcFurnaceEntity.OUTPUT_SLOT, 125, 35));
addDataSlots(data);
}

View File

@@ -62,8 +62,8 @@ public class ElectricArcFurnaceScreen extends AbstractContainerScreen<ElectricAr
}
private void renderEnergyBar(GuiGraphics guiGraphics, int x, int y) {
int stored = menu.blockEntity.getTrueEnergyStorage(null).getEnergyStored();
int max = menu.blockEntity.getTrueEnergyStorage(null).getMaxEnergyStored();
int stored = menu.blockEntity.getTrueEnergyStorage().getEnergyStored();
int max = menu.blockEntity.getTrueEnergyStorage().getMaxEnergyStored();
int h = (max <= 0) ? 0 : (int) Math.round((stored / (double) max) * 42.0);
h = Math.max(0, Math.min(42, h));
@@ -79,7 +79,7 @@ public class ElectricArcFurnaceScreen extends AbstractContainerScreen<ElectricAr
private void assignEnergyInfoArea() {
energyInfoArea = new EnergyDisplayTooltipArea(((width - imageWidth) / 2) + 9,
((height - imageHeight) / 2 ) + 9, menu.blockEntity.getTrueEnergyStorage(null), 14, 42);
((height - imageHeight) / 2 ) + 9, menu.blockEntity.getTrueEnergyStorage(), 14, 42);
}
private void renderEnergyAreaTooltip(GuiGraphics guiGraphics, int pMouseX, int pMouseY, int x, int y) {

View File

@@ -62,8 +62,8 @@ public class VacuumArcFurnaceScreen extends AbstractContainerScreen<VacuumArcFur
}
private void renderEnergyBar(GuiGraphics guiGraphics, int x, int y) {
int stored = menu.blockEntity.getTrueEnergyStorage(null).getEnergyStored();
int max = menu.blockEntity.getTrueEnergyStorage(null).getMaxEnergyStored();
int stored = menu.blockEntity.getTrueEnergyStorage().getEnergyStored();
int max = menu.blockEntity.getTrueEnergyStorage().getMaxEnergyStored();
int h = (max <= 0) ? 0 : (int) Math.round((stored / (double) max) * 42.0);
h = Math.max(0, Math.min(42, h));
@@ -79,7 +79,7 @@ public class VacuumArcFurnaceScreen extends AbstractContainerScreen<VacuumArcFur
private void assignEnergyInfoArea() {
energyInfoArea = new EnergyDisplayTooltipArea(((width - imageWidth) / 2) + 9,
((height - imageHeight) / 2 ) + 9, menu.blockEntity.getTrueEnergyStorage(null), 14, 42);
((height - imageHeight) / 2 ) + 9, menu.blockEntity.getTrueEnergyStorage(), 14, 42);
}
private void renderEnergyAreaTooltip(GuiGraphics guiGraphics, int pMouseX, int pMouseY, int x, int y) {

View File

@@ -5,8 +5,11 @@ import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.*;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
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.DoubleBlockHalf;
import net.minecraft.world.phys.AABB;
import java.util.ArrayList;
@@ -170,15 +173,54 @@ public final class RocketStructure {
}
public static void clearCaptured(Level level, BlockPos origin, RocketStructure struct) {
final int flags = Block.UPDATE_CLIENTS;
// Pass 1: remove blocks which implement DOUBLE_BLOCK_HALF like doors to try and prevent duplication.
for (int i = 0; i < struct.size(); i++) {
int packed = struct.packedPosAt(i);
int dx = RocketStructure.unpackX(packed);
int dy = RocketStructure.unpackY(packed);
int dz = RocketStructure.unpackZ(packed);
BlockPos wp = origin.offset(
RocketStructure.unpackX(packed),
RocketStructure.unpackY(packed),
RocketStructure.unpackZ(packed)
);
BlockPos wp = origin.offset(dx, dy, dz);
if (level.getBlockState(wp).is(struct.stateAt(i).getBlock()))
level.setBlock(wp, Blocks.AIR.defaultBlockState(), 3);
BlockState st = level.getBlockState(wp);
if (st.isAir()) continue;
if (st.hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF)) {
DoubleBlockHalf half = st.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF);
BlockPos bottom = (half == DoubleBlockHalf.LOWER) ? wp : null;
// Break the BOTTOM block to stop potential dupes, as it seems that is the "master" block for doors.
// If you set the top block to air, the bottom one breaks a moment later and drops.
// If this doesn't work I declare it NOT MY FAULT!
// DoubleBlockHalf blocks should have a way to delete the entire thing at once god damnit
if (bottom != null && !level.getBlockState(bottom).isAir()) {
level.setBlock(bottom, Blocks.AIR.defaultBlockState(), flags);
}
}
}
// Pass 2: remove likely-attached blocks first. This should stop duplication of torches/buttons/whatever else may break due to its supporting block being broken
for (int i = 0; i < struct.size(); i++) {
int packed = struct.packedPosAt(i);
BlockPos wp = origin.offset(unpackX(packed), unpackY(packed), unpackZ(packed));
BlockState st = level.getBlockState(wp);
if (st.isAir()) continue;
// Heuristic: if it isn't a full collision cube, it's often "attached" (buttons, torches, etc.)
if (!st.isCollisionShapeFullBlock(level, wp)) {
level.setBlock(wp, Blocks.AIR.defaultBlockState(), flags);
}
}
// Pass 3: remove the rest
for (int i = 0; i < struct.size(); i++) {
int packed = struct.packedPosAt(i);
BlockPos wp = origin.offset(unpackX(packed), unpackY(packed), unpackZ(packed));
if (!level.getBlockState(wp).isAir()) {
level.setBlock(wp, Blocks.AIR.defaultBlockState(), flags);
}
}
}